Serverless Image Generation Application Using Generative AI on AWS

abhirockzz

Abhishek Gupta

Posted on November 2, 2023

Serverless Image Generation Application Using Generative AI on AWS

Use Amazon Bedrock to build an image generation solution in Go and deploy it using AWS CDK.

Whether it's crafting personalized content or tailoring images to user preferences, the ability to generate visual assets based on a description is quite powerful. But text-to-image conversion typically involves deploying an end-to-end machine learning solution, which is quite resource intensive. What if this capability was an API call away, thereby making the process simpler and more accessible for developers?

This tutorial will walk you through how to use AWS CDK to deploy a Serverless image generation application implemented using AWS Lambda and Amazon Bedrock, which is a fully managed service that makes base models from Amazon and third-party model providers (such as Anthropic, Cohere, and more) accessible through an API. Developers can leverage leading foundation models through a single API, while maintaining the flexibility to adopt new models in the future.

The solution is deployed as a static website hosted on Amazon S3 accessible via an Amazon CloudFront domain. Users can enter the image description which will be passed on to a Lambda function (via Amazon API Gateway) which in turn will invoke Stable Diffusion model on Amazon Bedrock to generate the image.

Image description

The entire solution is built using Go - this includes the Lambda function (using aws-lambda-go library) as well as the complete solution deployment using AWS CDK.

The code is available on GitHub.

Prerequisites

Before starting this tutorial, you will need the following:

Clone this GitHub repository and change to the right directory:



git clone https://github.com/build-on-aws/amazon-bedrock-lambda-image-generation-golang

cd amazon-bedrock-lambda-image-generation-golang


Enter fullscreen mode Exit fullscreen mode

Deploy the Solution Using AWS CDK

To start the deployment, simply invoke cdk deploy.



cd cdk

export DOCKER_DEFAULT_PLATFORM=linux/amd64
cdk deploy


Enter fullscreen mode Exit fullscreen mode

You will see a list of resources that will be created and will need to provide your confirmation to proceed (output shortened for brevity).



Bundling asset BedrockLambdaImgeGenWebsiteStack/bedrock-imagegen-s3/Code/Stage...

✨  Synthesis time: 7.84s

//.... omitted

This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:

//.... omitted

Do you wish to deploy these changes (y/n)? y


Enter fullscreen mode Exit fullscreen mode

This will start creating the AWS resources required for the application.

If you want to see the AWS CloudFormation template which will be used behind the scenes, run cdk synth and check the cdk.out folder.

You can keep track of the progress in the terminal or navigate to AWS console: CloudFormation > Stacks > BedrockLambdaImgeGenWebsiteStack

Image description

Once all the resources are created, you can try out the application. You should have:

  • The image generation Lambda function and API Gateway.
  • An S3 bucket to host the website HTML page.
  • CloudFront distribution.
  • And a few other components (like IAM roles, permissions, S3 Bucket policy etc.)

The deployment can take a bit of time since creating the CloudFront distribution is a time-consuming process. Once complete, you should get a confirmation along with the values for the S3 bucket name, API Gateway URL, and the CloudFront domain name.

Image description

Update the HTML Page and Copy It to S3 Bucket

Open the index.html file in the GitHub repo, and locate the following text ENTER_API_GATEWAY_URL. Replace this with the API Gateway URL that you received as the CDK deployment output above.

To copy the file to S3, I used the AWS CLI:



aws s3 cp index.html s3://<name of the S3 bucket from CDK output>


Enter fullscreen mode Exit fullscreen mode

Verify that the file was uploaded:



aws s3 ls s3://<name of the S3 bucket from CDK output>


Enter fullscreen mode Exit fullscreen mode

Now you are ready to access the website!

Verify the Solution

Enter the CloudFront domain name in your web browser to navigate to the website. You should see the website with a pre-populated description that can be used as a prompt.

Click Generate Image to start the process. After a few seconds, you should see the generated image.

Image description

Modify the Model Parameters

The Stability Diffusion model allows us to refine the generation parameters as per our requirements.

The Stability.ai Diffusion models support the following controls:

  • Prompt strength (cfg_scale) controls the image's fidelity to the prompt, with lower values increasing randomness.
  • Generation step (steps) determines the accuracy of the result, with more steps producing more precise images.
  • Seed (seed) sets the initial noise level, allowing for reproducible results when using the same seed and settings.

Click Show Configuration to edit these.

Image description

Max values for cfg_steps and steps are 30 and 150 respectively.

Don’t Forget To Clean Up

Once you're done, to delete all the services, simply use:



cdk destroy

#output prompt (choose 'y' to continue)

Are you sure you want to delete: BedrockLambdaImgeGenWebsiteStack (y/n)?


Enter fullscreen mode Exit fullscreen mode

You were able to set up and try the complete solution. Before we wrap up, let's quickly walk through some of important parts of the code to get a better understanding of what's going the behind the scenes.

Code Walkthrough

Since we will only focus on the important bits, a lot of the code (print statements, error handling etc.) has been omitted for brevity.

CDK

You can refer to the CDK code here.

We start by creating the API Gateway and the S3 bucket.



    apigw := awscdkapigatewayv2alpha.NewHttpApi(stack, jsii.String("image-gen-http-api"), nil)

    bucket := awss3.NewBucket(stack, jsii.String("website-s3-bucket"), &awss3.BucketProps{
        BlockPublicAccess: awss3.BlockPublicAccess_BLOCK_ALL(),
        RemovalPolicy:     awscdk.RemovalPolicy_DESTROY,
        AutoDeleteObjects: jsii.Bool(true),
    })


Enter fullscreen mode Exit fullscreen mode

Then we create the CloudFront Origin Access Identity and grant S3 bucket read permissions to the CloudFront Origin Access Identity principal. Then we create the CloudFront Distribution:

  • Specify the S3 bucket as the origin.
  • Specify the Origin Access Identity that we created before.


    oai := awscloudfront.NewOriginAccessIdentity(stack, jsii.String("OAI"), nil)

    bucket.GrantRead(oai.GrantPrincipal(), "*")

    distribution := awscloudfront.NewDistribution(stack, jsii.String("MyDistribution"), &awscloudfront.DistributionProps{
        DefaultBehavior: &awscloudfront.BehaviorOptions{
            Origin: awscloudfrontorigins.NewS3Origin(bucket, &awscloudfrontorigins.S3OriginProps{
                OriginAccessIdentity: oai,
            }),
        },
        DefaultRootObject: jsii.String("index.html"), //name of the file in S3
    })


Enter fullscreen mode Exit fullscreen mode

Then, we create the image generation Lambda function along with IAM permissions (to the function execution IAM role) to allow it to invoke Bedrock operations.



    function := awscdklambdagoalpha.NewGoFunction(stack, jsii.String("bedrock-imagegen-s3"),
        &awscdklambdagoalpha.GoFunctionProps{
            Runtime: awslambda.Runtime_GO_1_X(),
            Entry:   jsii.String(functionDir),
            Timeout: awscdk.Duration_Seconds(jsii.Number(30)),
        })

    function.AddToRolePolicy(awsiam.NewPolicyStatement(&awsiam.PolicyStatementProps{
        Actions:   jsii.Strings("bedrock:*"),
        Effect:    awsiam.Effect_ALLOW,
        Resources: jsii.Strings("*"),
    }))


Enter fullscreen mode Exit fullscreen mode

Finally, we configure Lambda function integration with API Gateway, add the HTTP routes and specify API Gateway endpoint, S3 bucket name and CloudFront domain name as CloudFormation outputs.


<span class="n">functionIntg</span> <span class="o">:=</span> <span class="n">awscdkapigatewayv2integrationsalpha</span><span class="o">.</span><span class="n">NewHttpLambdaIntegration</span><span class="p">(</span><span class="n">jsii</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"function-integration"</span><span class="p">),</span> <span class="n">function</span><span class="p">,</span> <span class="no">nil</span><span class="p">)</span>

<span class="n">apigw</span><span class="o">.</span><span class="n">AddRoutes</span><span class="p">(</span><span class="o">&amp;</span><span class="n">awscdkapigatewayv2alpha</span><span class="o">.</span><span class="n">AddRoutesOptions</span><span class="p">{</span>
    <span class="n">Path</span><span class="o">:</span>        <span class="n">jsii</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"/"</span><span class="p">),</span>
    <span class="n">Methods</span><span class="o">:</span>     <span class="o">&amp;</span><span class="p">[]</span><span class="n">awscdkapigatewayv2alpha</span><span class="o">.</span><span class="n">HttpMethod</span><span class="p">{</span><span class="n">awscdkapigatewayv2alpha</span><span class="o">.</span><span class="n">HttpMethod_POST</span><span class="p">},</span>
    <span class="n">Integration</span><span class="o">:</span> <span class="n">functionIntg</span><span class="p">})</span>

<span class="n">awscdk</span><span class="o">.</span><span class="n">NewCfnOutput</span><span class="p">(</span><span class="n">stack</span><span class="p">,</span> <span class="n">jsii</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"apigw URL"</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">awscdk</span><span class="o">.</span><span class="n">CfnOutputProps</span><span class="p">{</span><span class="n">Value</span><span class="o">:</span> <span class="n">apigw</span><span class="o">.</span><span class="n">Url</span><span class="p">(),</span> <span class="n">Description</span><span class="o">:</span> <span class="n">jsii</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"API Gateway endpoint"</span><span class="p">)})</span>

<span class="n">awscdk</span><span class="o">.</span><span class="n">NewCfnOutput</span><span class="p">(</span><span class="n">stack</span><span class="p">,</span> <span class="n">jsii</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"cloud front domain name"</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">awscdk</span><span class="o">.</span><span class="n">CfnOutputProps</span><span class="p">{</span><span class="n">Value</span><span class="o">:</span> <span class="n">distribution</span><span class="o">.</span><span class="n">DomainName</span><span class="p">(),</span> <span class="n">Description</span><span class="o">:</span> <span class="n">jsii</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"cloud front domain name"</span><span class="p">)})</span>

<span class="n">awscdk</span><span class="o">.</span><span class="n">NewCfnOutput</span><span class="p">(</span><span class="n">stack</span><span class="p">,</span> <span class="n">jsii</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"s3 bucket name"</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">awscdk</span><span class="o">.</span><span class="n">CfnOutputProps</span><span class="p">{</span><span class="n">Value</span><span class="o">:</span> <span class="n">bucket</span><span class="o">.</span><span class="n">BucketName</span><span class="p">(),</span> <span class="n">Description</span><span class="o">:</span> <span class="n">jsii</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"s3 bucket name"</span><span class="p">)})</span>
Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode




Lambda Function

You can refer to the Lambda Function code here.

In the function handler, we extract the prompt from the HTTP request body, and the configuration from the query parameters. Then it's used to call the model using bedrockruntime.InvokeModel function. Note the JSON payload sent to Amazon Bedrock is represented by an instance of the Request struct.

The output body returned from Amazon Bedrock Stability Diffusion model is a JSON payload which is converted into a Response struct which contains the generated image as a base64 string. This is returned as an events.APIGatewayV2HTTPResponse object along with CORS headers.



func handler(ctx context.Context, req events.APIGatewayV2HTTPRequest) (events.APIGatewayV2HTTPResponse, error) {

<span class="n">prompt</span> <span class="o">:=</span> <span class="n">req</span><span class="o">.</span><span class="n">Body</span>

<span class="n">cfgScaleF</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">strconv</span><span class="o">.</span><span class="n">ParseFloat</span><span class="p">(</span><span class="n">req</span><span class="o">.</span><span class="n">QueryStringParameters</span><span class="p">[</span><span class="s">"cfg_scale"</span><span class="p">],</span> <span class="m">64</span><span class="p">)</span>
<span class="n">seed</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">strconv</span><span class="o">.</span><span class="n">Atoi</span><span class="p">(</span><span class="n">req</span><span class="o">.</span><span class="n">QueryStringParameters</span><span class="p">[</span><span class="s">"seed"</span><span class="p">])</span>
<span class="n">steps</span><span class="p">,</span> <span class="n">_</span> <span class="o">:=</span> <span class="n">strconv</span><span class="o">.</span><span class="n">Atoi</span><span class="p">(</span><span class="n">req</span><span class="o">.</span><span class="n">QueryStringParameters</span><span class="p">[</span><span class="s">"steps"</span><span class="p">])</span>

<span class="n">payload</span> <span class="o">:=</span> <span class="n">Request</span><span class="p">{</span>
    <span class="n">TextPrompts</span><span class="o">:</span> <span class="p">[]</span><span class="n">TextPrompt</span><span class="p">{{</span><span class="n">Text</span><span class="o">:</span> <span class="n">prompt</span><span class="p">}},</span>
    <span class="n">CfgScale</span><span class="o">:</span>    <span class="n">cfgScaleF</span><span class="p">,</span>
    <span class="n">Steps</span><span class="o">:</span> <span class="n">steps</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">if</span> <span class="n">seed</span> <span class="o">&gt;</span> <span class="m">0</span> <span class="p">{</span>
    <span class="n">payload</span><span class="o">.</span><span class="n">Seed</span> <span class="o">=</span> <span class="n">seed</span>
<span class="p">}</span>

<span class="n">payloadBytes</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">json</span><span class="o">.</span><span class="n">Marshal</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>

<span class="n">output</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">brc</span><span class="o">.</span><span class="n">InvokeModel</span><span class="p">(</span><span class="n">context</span><span class="o">.</span><span class="n">Background</span><span class="p">(),</span> <span class="o">&amp;</span><span class="n">bedrockruntime</span><span class="o">.</span><span class="n">InvokeModelInput</span><span class="p">{</span>
    <span class="n">Body</span><span class="o">:</span>        <span class="n">payloadBytes</span><span class="p">,</span>
    <span class="n">ModelId</span><span class="o">:</span>     <span class="n">aws</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">stableDiffusionXLModelID</span><span class="p">),</span>
    <span class="n">ContentType</span><span class="o">:</span> <span class="n">aws</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"application/json"</span><span class="p">),</span>
<span class="p">})</span>

<span class="k">var</span> <span class="n">resp</span> <span class="n">Response</span>

<span class="n">err</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">Unmarshal</span><span class="p">(</span><span class="n">output</span><span class="o">.</span><span class="n">Body</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">resp</span><span class="p">)</span>

<span class="n">image</span> <span class="o">:=</span> <span class="n">resp</span><span class="o">.</span><span class="n">Artifacts</span><span class="p">[</span><span class="m">0</span><span class="p">]</span><span class="o">.</span><span class="n">Base64</span>

<span class="k">return</span> <span class="n">events</span><span class="o">.</span><span class="n">APIGatewayV2HTTPResponse</span><span class="p">{</span>
    <span class="n">StatusCode</span><span class="o">:</span>      <span class="n">http</span><span class="o">.</span><span class="n">StatusOK</span><span class="p">,</span>
    <span class="n">Body</span><span class="o">:</span>            <span class="n">image</span><span class="p">,</span>
    <span class="n">IsBase64Encoded</span><span class="o">:</span> <span class="no">false</span><span class="p">,</span>
    <span class="n">Headers</span><span class="o">:</span> <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span>
        <span class="s">"Access-Control-Allow-Origin"</span><span class="o">:</span>  <span class="s">"*"</span><span class="p">,</span>
        <span class="s">"Access-Control-Allow-Methods"</span><span class="o">:</span> <span class="s">"POST,OPTIONS"</span><span class="p">,</span>
    <span class="p">},</span>
<span class="p">},</span> <span class="no">nil</span>
Enter fullscreen mode Exit fullscreen mode

}

//request/response model

type Request struct {
TextPrompts []TextPrompt json:"text_prompts"
CfgScale float64 json:"cfg_scale"
Steps int json:"steps"
Seed int json:"seed"
}

type TextPrompt struct {
Text string json:"text"
}

type Response struct {
Result string json:"result"
Artifacts []Artifact json:"artifacts"
}

type Artifact struct {
Base64 string json:"base64"
FinishReason string json:"finishReason"
}

Enter fullscreen mode Exit fullscreen mode




Conclusion

In this tutorial, you used AWS CDK to deploy a serverless image generation solution that was implemented using Amazon Bedrock and AWS Lambda and was accessed using a static website on S3 via a CloudFront domain.

If you are interested in an introductory guide to using the AWS Go SDK and Amazon Bedrock Foundation Models (FMs), check out this blog post.

Happy building!

💖 💪 🙅 🚩
abhirockzz
Abhishek Gupta

Posted on November 2, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related