44 "cell_type" : " markdown" ,
55 "metadata" : {},
66 "source" : [
7- " # Deploy a Nova Model to Amazon Bedrock\n " ,
7+ " # Deploy a Fine-Tuned Nova Model to Amazon Bedrock\n " ,
88 " \n " ,
9- " This notebook demonstrates how to fine-tune an Amazon Nova model using the SageMaker SDK \n " ,
10- " and deploy it to Amazon Bedrock using `BedrockModelBuilder`.\n " ,
9+ " This notebook demonstrates how to deploy a fine-tuned Amazon Nova model to \n " ,
10+ " Amazon Bedrock using `BedrockModelBuilder`.\n " ,
1111 " \n " ,
1212 " The workflow:\n " ,
13- " 1. Fine-tune Nova Micro using `SFTTrainer` \n " ,
14- " 2. Create a `BedrockModelBuilder` from the completed training job\n " ,
13+ " 1. Retrieve a completed Nova SFT training job \n " ,
14+ " 2. Create a `BedrockModelBuilder` from the training job\n " ,
1515 " 3. Deploy to Bedrock — the builder automatically:\n " ,
1616 " - Detects the model as Nova\n " ,
1717 " - Reads the checkpoint URI from the training job manifest\n " ,
18- " - Calls `CreateCustomModel`\n " ,
19- " - Polls until the model is Active\n " ,
20- " - Calls `CreateCustomModelDeployment`\n " ,
21- " - Polls until the deployment is Active\n " ,
22- " 4. Clean up resources\n " ,
18+ " - Calls `CreateCustomModel` and polls until Active\n " ,
19+ " - Calls `CreateCustomModelDeployment` and polls until Active\n " ,
20+ " 4. Test inference\n " ,
21+ " 5. Clean up resources\n " ,
2322 " \n " ,
2423 " **Prerequisites:**\n " ,
2524 " - AWS credentials with SageMaker and Bedrock access\n " ,
26- " - `sagemaker-serve` and `sagemaker-train` packages installed\n " ,
25+ " - `sagemaker-serve` package installed\n " ,
26+ " - A completed Nova SFT training job\n " ,
2727 " - An IAM role with Bedrock and SageMaker permissions"
2828 ]
2929 },
4040 "metadata" : {},
4141 "outputs" : [],
4242 "source" : [
43- " import os\n " ,
44- " import json\n " ,
45- " import time\n " ,
46- " import random\n " ,
47- " import boto3\n " ,
43+ " import os, json, time, random, boto3\n " ,
4844 " \n " ,
49- " # Set your region — Nova fine-tuning is available in us-east-1\n " ,
5045 " REGION = \" us-east-1\"\n " ,
5146 " os.environ[\" AWS_DEFAULT_REGION\" ] = REGION\n " ,
5247 " os.environ[\" SAGEMAKER_REGION\" ] = REGION\n " ,
5348 " \n " ,
5449 " from sagemaker.core.helper.session_helper import get_execution_role\n " ,
55- " \n " ,
5650 " role_arn = get_execution_role()\n " ,
57- " account_id = boto3.client(\" sts\" ).get_caller_identity()[\" Account\" ]\n " ,
58- " bucket = f\" sagemaker-{REGION}-{account_id}\"\n " ,
59- " \n " ,
60- " print(f\" Region: {REGION}\" )\n " ,
61- " print(f\" Account: {account_id}\" )\n " ,
62- " print(f\" Role: {role_arn}\" )\n " ,
63- " print(f\" Bucket: {bucket}\" )"
64- ]
65- },
66- {
67- "cell_type" : " markdown" ,
68- "metadata" : {},
69- "source" : [
70- " ## Step 1: Prepare training data\n " ,
71- " \n " ,
72- " Upload a small JSONL dataset in the chat-messages format that Nova expects."
73- ]
74- },
75- {
76- "cell_type" : " code" ,
77- "execution_count" : null ,
78- "metadata" : {},
79- "outputs" : [],
80- "source" : [
81- " s3 = boto3.client(\" s3\" , region_name=REGION)\n " ,
82- " \n " ,
83- " # Ensure the bucket exists\n " ,
84- " try:\n " ,
85- " s3.head_bucket(Bucket=bucket)\n " ,
86- " except Exception:\n " ,
87- " s3.create_bucket(\n " ,
88- " Bucket=bucket,\n " ,
89- " CreateBucketConfiguration={\" LocationConstraint\" : REGION},\n " ,
90- " )\n " ,
91- " print(f\" Created bucket: {bucket}\" )\n " ,
92- " \n " ,
93- " train_key = \" nova-example/train.jsonl\"\n " ,
94- " train_uri = f\" s3://{bucket}/{train_key}\"\n " ,
95- " \n " ,
96- " rows = []\n " ,
97- " for i in range(50):\n " ,
98- " rows.append(json.dumps({\n " ,
99- " \" messages\" : [\n " ,
100- " {\" role\" : \" user\" , \" content\" : f\" What is {i+1} + {i+1}?\" },\n " ,
101- " {\" role\" : \" assistant\" , \" content\" : f\" The answer is {(i+1)*2}.\" }\n " ,
102- " ]\n " ,
103- " }))\n " ,
104- " \n " ,
105- " s3.put_object(Bucket=bucket, Key=train_key, Body=\"\\ n\" .join(rows).encode())\n " ,
106- " print(f\" Uploaded {len(rows)} examples to {train_uri}\" )"
107- ]
108- },
109- {
110- "cell_type" : " markdown" ,
111- "metadata" : {},
112- "source" : [
113- " ## Step 2: Create model package group\n " ,
114- " \n " ,
115- " SFTTrainer requires a model package group to register the fine-tuned model.\n " ,
116- " We create one if it doesn't already exist."
117- ]
118- },
119- {
120- "cell_type" : " code" ,
121- "execution_count" : null ,
122- "metadata" : {},
123- "outputs" : [],
124- "source" : [
125- " sm = boto3.client(\" sagemaker\" , region_name=REGION)\n " ,
126- " \n " ,
127- " MODEL_PACKAGE_GROUP = f\" nova-example-{account_id}\"\n " ,
128- " \n " ,
129- " try:\n " ,
130- " sm.describe_model_package_group(ModelPackageGroupName=MODEL_PACKAGE_GROUP)\n " ,
131- " print(f\" Model package group already exists: {MODEL_PACKAGE_GROUP}\" )\n " ,
132- " except sm.exceptions.ClientError:\n " ,
133- " sm.create_model_package_group(\n " ,
134- " ModelPackageGroupName=MODEL_PACKAGE_GROUP,\n " ,
135- " ModelPackageGroupDescription=\" Nova fine-tuning example models\" ,\n " ,
136- " )\n " ,
137- " print(f\" Created model package group: {MODEL_PACKAGE_GROUP}\" )"
51+ " print(f\" Role: {role_arn}\" )"
13852 ]
13953 },
14054 {
14155 "cell_type" : " markdown" ,
14256 "metadata" : {},
14357 "source" : [
144- " ## Step 3: Fine-tune Nova Micro with SFTTrainer \n " ,
58+ " ## Step 1: Retrieve the completed training job \n " ,
14559 " \n " ,
146- " This launches a SageMaker training job. It typically takes 15-30 minutes to complete ."
60+ " Use an existing completed Nova SFT training job. Replace the job name with your own ."
14761 ]
14862 },
14963 {
15266 "metadata" : {},
15367 "outputs" : [],
15468 "source" : [
155- " from sagemaker.train.sft_trainer import SFTTrainer\n " ,
156- " \n " ,
157- " trainer = SFTTrainer(\n " ,
158- " model=\" nova-textgeneration-micro\" ,\n " ,
159- " training_dataset=train_uri,\n " ,
160- " accept_eula=True,\n " ,
161- " model_package_group=MODEL_PACKAGE_GROUP,\n " ,
162- " )\n " ,
163- " \n " ,
164- " # Set wait=True to block until training completes\n " ,
165- " trainer.train(wait=True)\n " ,
69+ " from sagemaker.core.resources import TrainingJob\n " ,
16670 " \n " ,
167- " training_job = trainer._latest_training_job \n " ,
71+ " training_job = TrainingJob.get(training_job_name= \" nova-textgeneration-micro-sft-20251208154822 \" ) \n " ,
16872 " print(f\" Training job: {training_job.training_job_name}\" )\n " ,
16973 " print(f\" Status: {training_job.training_job_status}\" )"
17074 ]
17377 "cell_type" : " markdown" ,
17478 "metadata" : {},
17579 "source" : [
176- " ## Step 4 : Deploy to Bedrock with BedrockModelBuilder\n " ,
80+ " ## Step 2 : Deploy to Bedrock with BedrockModelBuilder\n " ,
17781 " \n " ,
17882 " The builder handles the full deployment flow:\n " ,
17983 " - Fetches the model package from the training job\n " ,
19296 " from sagemaker.serve.bedrock_model_builder import BedrockModelBuilder\n " ,
19397 " \n " ,
19498 " builder = BedrockModelBuilder(model=training_job)\n " ,
195- " \n " ,
196- " print(f\" Model package: {builder.model_package}\" )\n " ,
197- " print(f\" S3 artifacts: {builder.s3_model_artifacts}\" )"
99+ " print(f\" Model package: {builder.model_package}\" )\n " ,
100+ " print(f\" S3 artifacts: {builder.s3_model_artifacts}\" )"
198101 ]
199102 },
200103 {
204107 "outputs" : [],
205108 "source" : [
206109 " rand = random.randint(1000, 9999)\n " ,
207- " custom_model_name = f\" nova-example -{rand}-{int(time.time())}\"\n " ,
110+ " custom_model_name = f\" nova-e2e -{rand}-{int(time.time())}\"\n " ,
208111 " deployment_name = f\" {custom_model_name}-dep\"\n " ,
209112 " \n " ,
210113 " print(f\" Deploying as: {custom_model_name}\" )\n " ,
225128 "cell_type" : " markdown" ,
226129 "metadata" : {},
227130 "source" : [
228- " ## Step 5 : Test inference (optional) \n " ,
131+ " ## Step 3 : Test inference\n " ,
229132 " \n " ,
230- " Once the deployment is Active, you can invoke it via the Bedrock Runtime API."
133+ " Once the deployment is Active, invoke it via the Bedrock Runtime API.\n " ,
134+ " Nova expects `content` as an array of objects with a `text` key."
231135 ]
232136 },
233137 {
238142 "source" : [
239143 " bedrock_runtime = boto3.client(\" bedrock-runtime\" , region_name=REGION)\n " ,
240144 " \n " ,
241- " # Get the model ARN from the deployment\n " ,
242- " bedrock = boto3.client(\" bedrock\" , region_name=REGION)\n " ,
243- " dep_info = bedrock.get_custom_model_deployment(\n " ,
244- " customModelDeploymentIdentifier=deployment_arn\n " ,
245- " )\n " ,
246- " model_arn = dep_info.get(\" modelArn\" )\n " ,
247- " print(f\" Model ARN: {model_arn}\" )\n " ,
248- " \n " ,
249- " # Invoke\n " ,
250- " invoke_response = bedrock_runtime.invoke_model(\n " ,
145+ " resp = bedrock_runtime.invoke_model(\n " ,
251146 " modelId=deployment_arn,\n " ,
252147 " contentType=\" application/json\" ,\n " ,
253148 " body=json.dumps({\n " ,
254- " \" messages\" : [{\" role\" : \" user\" , \" content\" : \" What is 7 + 7?\" }]\n " ,
149+ " \" messages\" : [{\" role\" : \" user\" , \" content\" : [{ \" text \" : \" What is 7 + 7?\" }] }]\n " ,
255150 " }),\n " ,
256151 " )\n " ,
257152 " \n " ,
258- " result = json.loads(invoke_response [\" body\" ].read())\n " ,
259- " print(f\" Response: {result}\" )"
153+ " result = json.loads(resp [\" body\" ].read())\n " ,
154+ " print(f\" Response: {json.dumps( result, indent=2) }\" )"
260155 ]
261156 },
262157 {
263158 "cell_type" : " markdown" ,
264159 "metadata" : {},
265160 "source" : [
266- " ## Step 6 : Cleanup\n " ,
161+ " ## Step 4 : Cleanup\n " ,
267162 " \n " ,
268163 " Delete the deployment and custom model to avoid ongoing charges."
269164 ]
276171 "source" : [
277172 " bedrock = boto3.client(\" bedrock\" , region_name=REGION)\n " ,
278173 " \n " ,
174+ " dep_info = bedrock.get_custom_model_deployment(\n " ,
175+ " customModelDeploymentIdentifier=deployment_arn\n " ,
176+ " )\n " ,
177+ " model_arn = dep_info.get(\" modelArn\" )\n " ,
178+ " \n " ,
279179 " # Delete deployment first\n " ,
280- " if deployment_arn:\n " ,
281- " try:\n " ,
282- " bedrock.delete_custom_model_deployment(\n " ,
283- " customModelDeploymentIdentifier=deployment_arn\n " ,
284- " )\n " ,
285- " print(f\" Deleted deployment: {deployment_arn}\" )\n " ,
286- " except Exception as e:\n " ,
287- " print(f\" Failed to delete deployment: {e}\" )\n " ,
180+ " try:\n " ,
181+ " bedrock.delete_custom_model_deployment(\n " ,
182+ " customModelDeploymentIdentifier=deployment_arn\n " ,
183+ " )\n " ,
184+ " print(f\" Deleted deployment: {deployment_arn}\" )\n " ,
185+ " except Exception as e:\n " ,
186+ " print(f\" Failed to delete deployment: {e}\" )\n " ,
288187 " \n " ,
289188 " # Then delete the custom model\n " ,
290- " if model_arn:\n " ,
291- " try:\n " ,
292- " bedrock.delete_custom_model(modelIdentifier=model_arn)\n " ,
293- " print(f\" Deleted custom model: {model_arn}\" )\n " ,
294- " except Exception as e:\n " ,
295- " print(f\" Failed to delete custom model: {e}\" )"
189+ " try:\n " ,
190+ " bedrock.delete_custom_model(modelIdentifier=model_arn)\n " ,
191+ " print(f\" Deleted custom model: {model_arn}\" )\n " ,
192+ " except Exception as e:\n " ,
193+ " print(f\" Failed to delete custom model: {e}\" )"
296194 ]
297195 }
298196 ],
309207 },
310208 "nbformat" : 4 ,
311209 "nbformat_minor" : 4
312- }
210+ }
0 commit comments