AWS 기술 블로그

Amazon SageMaker JumpStart를 이용하여 Falcon Foundation Model기반의 Chatbot 만들기

2023년 6월부터 AWS 서울 리전에서 EC2 G5인스턴스를 사용할 수 있게 되었습니다. 여기서는 Falcon Foundation Model을 Amazon SageMaker JumpStart를 이용해 AWS 서울 리전의 EC2 G5에 설치하고, 웹 브라우저 기반의 Chatbot을 생성하는 방법에 대해 설명합니다. Falcon FM은 HuggingFace의 Open LLM Leaderboard에서 상위권(2023년 7월 기준)에 위치할 만큼 우수한 성능을 가지고 있으면서도, 아파치 2.0 라이선스 정책에 따라 상용을 포함하여 누구나 자유롭게 사용할 수 있습니다.

Falcon FM은 SageMaker JumpStart를 이용하여 편리하게 설치하고, EC2 G5 인스턴스와 같은 우수한 인스턴스를 통해 AWS 서울 리전에서 개발 및 상용이 가능합니다. EC2 G5인스턴스는 EC2 G4dn 인스턴스보다 그래픽 집약적 애플리케이션 및 기계 학습 추론에 대해 최대 3배 더 높은 성능을 제공할 수 있어서, 고성능을 요구하는 자연어 처리, 컴퓨터 비전 및 추천 엔진과 같은 분야에 적합합니다. 또한, LLM (Large Language Models) 기반의 Chatbot은 기존 Rule-based의 Chatbot에 비하여 훨씬 우수한 대화능력을 보여주며, AWS Lambda, Amazon CloudFront, AWS API Gateway, Amazon S3와 같은 AWS 인프라를 이용하여 쉽게 구현할 수 있습니다.

본 게시글에서는 SageMaker JumpStart를 통해 Falcon FM 기반의 Endpoint를 설치하고, AWS CDK를 이용하여 Chatbot를 위한 제공하기 위한 인프라를 준비합니다. 생성된 Chatbot은 REST API를 통하여 텍스트로 요청을 하고 결과를 화면에 표시할 수 있습니다. 상세한 Architecture는 아래와 같습니다.

  1. 사용자는 CloudFront를 통해 Chatbot 웹페이지에 접속합니다. 이때 HTML, CSS, JS와 같은 리소스는 S3에서 읽어옵니다.
  2. 채팅 화면에서 사용자가 메시지를 입력하면, CloudFront로 chat API를 호출하는, 요청을 보냅니다.
  3. CloudFront는 API Gateway로 요청을 전달합니다.
  4. API Gateway는 Chat을 담당하는 Lambda로 요청을 이벤트로 전달하게 되고, Lambda는 이벤트에서 메시지를 parsing하여 SageMaker Endpoint로 전달합니다.
  5. Lambda가 SageMaker Endpoint로 요청을 전달하면 Falcon FM을 통해 텍스트에 대한 답변을 생성합니다.

Architecture 구현하기

메시지 전송 및 PDF 파일 요약을 구성하기 위한 방법에 대해 설명합니다.

메시지 전송

사용자가 Chatbot UI에서 메시지를 입력하면, ‘/chat’ API를 이용해 메시지를 전송합니다. 이때의 메시지 body의 포맷은 아래와 같습니다.

{
    "text": "Building a website can be done in 10 simple steps"
}

사용자가 입력한 메시지는 CloudFront – API Gateway를 통해 Lambda로 전달됩니다. Lambda는 이벤트(event)에서 메시지(text)를 분리한 후에 payload를 생성합니다. 상세한 내용은 lambda-chat/lambda_function.py 을 참조합니다. 여기서 payload의 parameters는 Falcon Parameters을 참조합니다.

text = event['text']

payload = {
    "inputs": text,
    "parameters": {
        "max_new_tokens": 512,
        "return_full_text": False,
        "do_sample": False,
        "temperature": 0.5,
        "repetition_penalty": 1.03,
        "top_p": 0.9,
        "top_k": 1,
        "stop": ["<|endoftext|>", "</s>"]
    }
}

아래와 같이 boto3를 이용해 endpoint로 payload를 전달하고 응답에서 generated_text를 추출합니다.

client = boto3.client('runtime.sagemaker')
response = client.invoke_endpoint(
    EndpointName=endpoint_name, 
    ContentType='application/json', 
    Body=json.dumps(payload).encode('utf-8'))                
    
response_payload = json.loads(response['Body'].read())
generated_text = response_payload[0]['generated_text']

전달된 payload에 대한 SageMaker Endpoint의 Response 예는 아래와 같습니다.

{
   "ResponseMetadata":{
      "RequestId":"80e8d6c5-0362-44a0-ab6d-bf11b2f2963e",
      "HTTPStatusCode":200,
      "HTTPHeaders":{
         "x-amzn-requestid":"80e8d6c5-0362-44a0-ab6d-bf11b2f2963e",
         "x-amzn-invoked-production-variant":"AllTraffic",
         "date":"Mon, 10 Jul 2023 07:27:42 GMT",
         "content-type":"application/json",
         "content-length":"185",
         "connection":"keep-alive"
      },
      "RetryAttempts":0
   },
   "ContentType":"application/json",
   "InvokedProductionVariant":"AllTraffic",
   "Body":<botocore.response.StreamingBody object at 0x7f0379091400>
}

여기서 Body는 json 포맷으로 decoding하면 아래와 같습니다.

[
   {
      "generated_text":" Hello, Daniel! I've been practicing my super-power, which is to be a super-duper-super-hero of super-duper-super-duperness, that can do super-duper-heroey things"
   }
]

PDF 파일 요약

Chatbot 대화창 하단의 파일 업로드 버튼을 클릭하여 PDF 파일을 업로드하면, 중복을 피하기 위하여 UUID(Universally Unique IDentifier)를 이름으로 가지는 Object로 S3에 저장합니다. 이후 ‘/pdf’ API를 이용해 Falcon FM에 파일 요약(Summary)을 요청합니다. 이때 요청하는 메시지의 형태는 아래와 같습니다.

{
    "object": "3efe99b5-1a85-4f01-b268-10bdc7a673e7.pdf"
}

lambda-pdf-summay/lambda_function.py와 같이 S3에서 PDF Object를 로드하여 text를 분리 합니다.

s3r = boto3.resource("s3")
doc = s3r.Object(s3_bucket, s3_prefix + '/' + s3_file_name)

contents = doc.get()['Body'].read()
reader = PyPDF2.PdfReader(BytesIO(contents))

raw_text = []
for page in reader.pages:
    raw_text.append(page.extract_text())
contents = '\n'.join(raw_text)

여기서는 LangChain Summation을 이용하여 PDF 파일의 내용을 요약합니다. LangChain이 SageMaker Endpoint의 입력 포맷을 읽어올 수 있도록 아래와 같이 ContentHandler Class를 정의합니다.

class ContentHandler(LLMContentHandler):
    content_type = "application/json"
    accepts = "application/json"

    def transform_input(self, prompt: str, model_kwargs: dict) -> bytes:
        input_str = json.dumps({'inputs': prompt, 'parameters': model_kwargs})
        return input_str.encode('utf-8')
      
    def transform_output(self, output: bytes) -> str:
        response_json = json.loads(output.read().decode("utf-8"))
        return response_json[0]["generated_text"]

content_handler = ContentHandler()

이제 SageMakerEndpoint()를 이용하여 llm을 정의합니다.

aws_region = boto3.Session().region_name
parameters = {
    "max_new_tokens": 300,
}
content_handler = ContentHandler()

llm = SagemakerEndpoint(
    endpoint_name = endpoint_name,
    region_name = aws_region,
    model_kwargs = parameters,
    content_handler = content_handler
)

문서의 크기에 따라 RecursiveCharacterTextSplitter를 이용해 chunk 단위로 분리하고 Document에 저장합니다. 이후 load_summarize_chain를 이용해 요약합니다.

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains.summarize import load_summarize_chain
from langchain import PromptTemplate, SagemakerEndpoint

new_contents = str(contents).replace("\n", " ")
text_splitter = RecursiveCharacterTextSplitter(chunk_size = 1000, chunk_overlap = 0)
texts = text_splitter.split_text(new_contents)
docs = [
    Document(
        page_content = t
    ) for t in texts[: 3]
]
prompt_template = """Write a concise summary of the following:
{ text }
        CONCISE SUMMARY """
PROMPT = PromptTemplate(template = prompt_template, input_variables = ["text"])
chain = load_summarize_chain(llm, chain_type = "stuff", prompt = PROMPT)
summary = chain.run(docs)

인프라 구현

cdk-chatbot-falcon-stack.ts에 대해 설명합니다. Chatbot에서 chat을 처리하는 lambda를 python3.9로 아래와 같이 구현합니다. 이때, environment의 endpoint는 Falcon FM 에서 생성된 endpoint 이름을 입력합니다.

const lambdaChatApi = new lambda.Function(this, 'lambda-chat', {
    description: 'lambda for chat api',
    functionName: 'lambda-chat-api',
    handler: 'lambda_function.lambda_handler',
    runtime: lambda.Runtime.PYTHON_3_9,
    code: lambda.Code.fromAsset(path.join(__dirname, '../../lambda-chat')),
    timeout: cdk.Duration.seconds(120),
    logRetention: logs.RetentionDays.ONE_DAY,
    environment: {
        endpoint: endpoint,
    }
});

lambda-chat의 퍼미션은 아래와 같이 SageMaker를 사용할 수 있는 권한과 API Gateway를 invoke 할 수 있도록 설정합니다.

const SageMakerPolicy = new iam.PolicyStatement({  
    actions: ['sagemaker:*'],
    resources: ['*'],
});
lambdaChatApi.role?.attachInlinePolicy(
    new iam.Policy(this, 'sagemaker-policy', {
        statements: [SageMakerPolicy],
    }),
);
lambdaChatApi.grantInvoke(new iam.ServicePrincipal('apigateway.amazonaws.com'));  

마찬가지로 PDF에서 요약(Summary)를 수행하는 lambda-pdf-summay를 Docker Conatiner를 이용하여 정의합니다. 아래와 같이 파일 저장할 bucket의 이름과 폴더를 지정하고, SageMaker, S3 읽기, API Gateway Invoke에 대한 권한을 설정합니다.

const lambdaPdfApi = new lambda.DockerImageFunction(this, "lambda-pdf-summay", {
    description: 'lambda for pdf api',
    functionName: 'lambda-pdf-api',
    code: lambda.DockerImageCode.fromImageAsset(path.join(__dirname, '../../lambda-pdf-summary')),
    timeout: cdk.Duration.seconds(60),
    environment: {
        endpoint: endpoint,
        s3_bucket: s3Bucket.bucketName,
        s3_prefix: s3_prefix
    }
});
const version = lambdaPdfApi.currentVersion;
const alias = new lambda.Alias(this, 'LambdaAlias', {
    aliasName: 'Dev',
    version,
});

lambdaPdfApi.role?.attachInlinePolicy( // add sagemaker policy
    new iam.Policy(this, 'sagemaker-policy-for-lambda-pdf', {
        statements: [SageMakerPolicy],
    }),
);
s3Bucket.grantRead(lambdaPdfApi); // permission for s3
lambdaPdfApi.grantInvoke(new iam.ServicePrincipal('apigateway.amazonaws.com'));

API Gateway에 대한 권한 및 POST 방식의 ‘/chat’ API를 생성합니다.

// role
const role = new iam.Role(this, "api-role-chatbot", {
    roleName: "api-role-chatbot",
    assumedBy: new iam.ServicePrincipal("apigateway.amazonaws.com")
});
role.addToPolicy(new iam.PolicyStatement({
    resources: ['*'],
    actions: ['lambda:InvokeFunction']
}));
role.addManagedPolicy({
    managedPolicyArn: 'arn:aws:iam::aws:policy/AWSLambdaExecute',
});

// API Gateway
const api = new apiGateway.RestApi(this, 'api-chatbot', {
    description: 'API Gateway for chatbot',
    endpointTypes: [apiGateway.EndpointType.REGIONAL],
    deployOptions: {
        stageName: stage,

        // logging for debug
        loggingLevel: apiGateway.MethodLoggingLevel.INFO,
        dataTraceEnabled: true,
    },
});

// POST method
const chat = api.root.addResource('chat');
chat.addMethod('POST', new apiGateway.LambdaIntegration(lambdaChatApi, {
    passthroughBehavior: apiGateway.PassthroughBehavior.WHEN_NO_TEMPLATES,
    credentialsRole: role,
    integrationResponses: [{
        statusCode: '200',
    }],
    proxy: false,
}), {
    methodResponses: [   // API Gateway sends to the client that called a method.
        {
            statusCode: '200',
            responseModels: {
                'application/json': apiGateway.Model.EMPTY_MODEL,
            },
        }
    ]
});

CloudFront와 API Gateway를 연결하도록 아래와 같이 설정합니다.

const distribution = new cloudFront.Distribution(this, 'cloudfront', {
    defaultBehavior: {
        origin: new origins.S3Origin(s3Bucket),
        allowedMethods: cloudFront.AllowedMethods.ALLOW_ALL,
        cachePolicy: cloudFront.CachePolicy.CACHING_DISABLED,
        viewerProtocolPolicy: cloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
    },
    priceClass: cloudFront.PriceClass.PRICE_CLASS_200,
});
new cdk.CfnOutput(this, 'distributionDomainName', {
    value: distribution.domainName,
    description: 'The domain name of the Distribution',
});

distribution.addBehavior("/chat", new origins.RestApiOrigin(api), {
    cachePolicy: cloudFront.CachePolicy.CACHING_DISABLED,
    allowedMethods: cloudFront.AllowedMethods.ALLOW_ALL,
    viewerProtocolPolicy: cloudFront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
});

마찬가지로 cdk-chatbot-falcon-stack.ts에서는 ‘/pdf’, ‘/upload’ API를 설정하고 있습니다.

직접 실습 해보기

사전 준비 사항

이 솔루션을 사용하기 위해서는 사전에 아래와 같은 준비가 되어야 합니다.

Falcon FM 설치

Amazon SageMaker JumpStart를 이용하여 Falcon Foundation Model을 설치합니다. 편의상 인프라 설치와 관련된 설명은 서울 리전(Region)을 기준으로 구성합니다.

  1. SageMaker Studio Console에서Open Studio를 선택합니다. Studio를 처음 사용한다면 Launch Amazon SageMaker Studio에 따라 Studio를 생성합니다.
  2. SageMaker Jumpstart에서 “Falcon 7B Instruct BF16″을 검색한 후에 아래와 같이 기본값에서 [Deploy]를 선택하여 모델을 설치합니다.

3. Deploy가 끝나면, 아래와 같이 Endpoint를 확인합니다. 여기서 생성된 Endpoint의 이름은 “jumpstart-dft-hf-llm-falcon-7b-instruct-bf16″입니다.

AWS CDK를 이용한 인프라 설치

여기서는 Cloud9에서 AWS CDK를 이용하여 인프라를 설치합니다.

  1. Cloud9 Console에 접속하여 [Create environment]-[Name]에서 “chatbot”로 이름을 입력하고, EC2 instance는 “m5.large”를 선택합니다. 나머지는 기본값을 유지하고, 하단으로 스크롤하여 [Create]를 선택합니다.
  2. Environment에서 “chatbot”를 [Open]한 후에 아래와 같이 터미널을 실행합니다.
  3. 소스를 다운로드합니다.
    curl https://raw.githubusercontent.com/aws-samples/generative-ai-demo-using-amazon-sagemaker-jumpstart-kr/main/blogs/chatbot-based-on-Falcon-FM/chatbot-based-on-Falcon-FM.zip -o chatbot-based-on-Falcon-FM.zip && unzip chatbot-based-on-Falcon-FM.zip
  4. cdk 폴더로 이동하여 필요한 라이브러리를 설치합니다.
    cd chatbot-based-on-Falcon-FM/cdk-chatbot-falcon/ && npm install
  5. CDK를 위해 Boostraping을 수행합니다.
    아래 명령어로 Account ID를 확인합니다.

    aws sts get-caller-identity --query Account --output text

    아래와 같이 bootstrap을 수행합니다. 여기서 “account-id”는 상기 명령어로 확인한 12자리의 Account ID입니다. bootstrap 1회만 수행하면 되므로, 기존에 cdk를 사용하고 있었다면 bootstrap은 건너뛰어도 됩니다.

    cdk bootstrap aws://account-id/ap-northeast-2
  6. Endpoint주소를 업데이트 합니다.
    Endpoint 주소는 [Falcon FM 생성]에서 얻은 Endpoint의 이름입니다. 아래와 같이 “chatbot-based-on-Falcon-FM/cdk-chatbot-falcon/lib/cdk-chatbot-falcon-stack.ts”를 열어서 “endpoint”의 값을 업데이트 합니다.
  7. 인프라를 설치합니다.
    cdk deploy
  8. 설치가 완료되면 브라우저에서 아래와 같이 WebUrl을 확인하여 브라우저로 접속합니다.

실행 결과 확인

  1. “Building a website can be done in 10 simple steps”로 질문하면 각 단계별로 아래와 같이 답변하는 것을 확인할 수 있습니다.
  2. “Guide me how to travel from New York to LA.”로 질문하면 여행방법을 설명합니다.
  3. 파일 아이콘을 선택하여 pdf파일을 선택하면 파일 요약 내용을 아래와 같이 확인할 수 있습니다. 여기서는 gen-ai-wiki.pdf를 업로드 하였고 아래와 같은 요약 결과를 얻을 수 있습니다. 현재 Chatbot UI은 간단한 동작테스트용으로 업로드하는 파일은 5MB 이하로 제한됩니다.

리소스 정리하기

더이상 인프라를 사용하지 않는 경우에 아래처럼 모든 리소스를 삭제할 수 있습니다. 리소스 삭제는 Chatbot을 제공하기 위한 인프라를 먼저 삭제하고 이후로 SageMaker Endpoint롤 포함한 Falcon FM을 삭제합니다. 먼저, Cloud9에 접속하여 아래와 같이 API Gateway, Lambda, CloudFront S3에 대해 삭제를 요청합니다.

cdk destroy

본 실습에서는 Falcon FM을 위해 “ml.g5.2xlarge”를 사용하고 있으므로, 더이상 사용하지 않을 경우에 반드시 삭제하여야 합니다. 특히 Chatbot 인프라만 삭제할 경우에 SageMaker Endpoint가 유지되어 지속적으로 비용이 발생될 수 있습니다. 이를 위해 Inference Console에 접속해서 Endpoint를 삭제합니다. 마찬가지로 Model console과 Endpoint configuration에서 설치한 Falcon을 삭제합니다.

결론

AWS 서울 리전에서 Falcon FM 기반의 Chatbot을 생성하여 텍스트에 대한 요청 및 PDF 파일의 요약(Summary)를 수행하였습니다. Falcon FM은 다른 LLM에 비교할 때에 최고 수준의 성능 뿐 아니라, 상용시에 추가적인 라이선스 비용을 지불하지 않아도 됩니다. SageMaker JumpStart를 이용해 Falcon FM을 EC2 G5인스턴스에 손쉽게 설치하고 활용하면, Chatbot과 같은 유용한 LLM 어플리케이션들을 빠르게 개발 및 상용화할 수 있습니다. 근래에 다양하고 성능 좋은 LLM들이 많이 나오고, 일부는 한글도 지원되고 있어서 향후 다양한 용도로 많은 활용이 기대됩니다.

Kyoung-Su Park

Kyoung-Su Park

박경수 솔루션즈 아키텍트는 다양한 워크로드에 대한 개발 경험을 바탕으로 고객이 최적의 솔루션을 선택하여 비즈니스 성과를 달성할 수 있도록 고객과 함께 효율적인 아키텍처를 구성하는 역할을 수행하고 있습니다. 현재 AWS의 Machine Learning, IoT, Analytics TFC에서 활동하고 있습니다.

Youngjin Kim

Youngjin Kim

김영진 솔루션 아키텍트는 소프트웨어 개발자, DevOps 엔지니어 및 소프트웨어 아키텍트의 경험을 통해 엔터프라이즈 고객이 높은 수준의 아키텍처 설계 선택을 하고 AWS 서비스를 사용하여 사용 사례를 구축할 수 있도록 지원하고 있으며 현재는 AWS 기술 블로그 리딩과 삼성전자 무선사업부 서비스의 AWS 클라우드 전환을 지원하는 업무를 담당하고 있습니다.