Skip to content

Commit 041f6b5

Browse files
authored
Doc update and added SFT integ test (#6018)
* Doc update and added SFT integ test - Moved Nova-only CPT notebook from open-weight section to Nova section. - Renamed "Customizing Models" → "Model Customization (Foundation Models)". - Consolidated deployment docs under "Deploy Models for Inference" (including Bedrock). - Added SFT SMTJ serverful integ test * Update integ test * Update instance type and model
1 parent 13ffe0c commit 041f6b5

8 files changed

Lines changed: 486 additions & 16 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Deploying Fine-Tuned Models
2+
============================
3+
4+
Deploy models that have been fine-tuned through the :doc:`Model Customization <../model_customization/index>` workflow to either a SageMaker real-time endpoint or Amazon Bedrock.
5+
6+
.. toctree::
7+
:maxdepth: 1
8+
9+
Deploy to SageMaker Endpoint <../model_customization/deploy_sagemaker_endpoint>
10+
Deploy to Amazon Bedrock <../../v3-examples/model-customization-examples/bedrock-modelbuilder-deployment>

‎docs/inference/index.rst‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,12 @@ V3 Package Structure
508508
- Pipelines, processing, model registry, monitoring, Clarify
509509

510510

511+
.. toctree::
512+
:maxdepth: 2
513+
514+
deploy_finetuned
515+
516+
511517
Inference Examples
512518
-----------------
513519

‎docs/model_customization/deployment.rst‎

Lines changed: 0 additions & 10 deletions
This file was deleted.

‎docs/model_customization/index.rst‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
Customizing Models
2-
===================
1+
Model Customization (Foundation Models)
2+
=========================================
33

44
.. raw:: html
55

@@ -23,4 +23,3 @@ Key Benefits
2323
open_weight_model_customization
2424
nova
2525
evaluation
26-
deployment

‎docs/model_customization/model_customization.rst‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ Trainer Classes
2424
**SFTTrainer (Supervised Fine-Tuning)**
2525
Traditional fine-tuning with labeled datasets for task-specific adaptation
2626

27-
**CPTTrainer (Continued Pre-Training)**
28-
Continue pre-training on a raw corpus to extend model knowledge in a specific domain
27+
**CPTTrainer (Continued Pre-Training)** *(Nova models only)*
28+
Continue pre-training on a raw corpus to extend model knowledge in a specific domain.
29+
See the :doc:`Nova section <nova>` for CPT examples.
2930

3031
**DPOTrainer (Direct Preference Optimization)**
3132
Fine-tune models using human preference data without reinforcement learning complexity
@@ -62,6 +63,5 @@ Key Features
6263
DPOTrainer Finetuning <../../v3-examples/model-customization-examples/dpo_trainer_example_notebook_v3_prod>
6364
RLAIF Finetuning <../../v3-examples/model-customization-examples/rlaif_finetuning_example_notebook_v3_prod>
6465
RLVR Finetuning <../../v3-examples/model-customization-examples/rlvr_finetuning_example_notebook_v3_prod>
65-
CPT Training on HyperPod <../../v3-examples/model-customization-examples/cpt_data_mixing_hyperpod>
6666
Fine-Tuning with Serverful Training Jobs <finetuning_serverful>
6767
Fine-Tuning with HyperPod <finetuning_hyperpod>

‎docs/model_customization/nova.rst‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ Adapt Amazon Nova foundation models to your specific use cases through fine-tuni
1313
:caption: Nova Customization Guides
1414

1515
nova_data_mixing
16+
CPT Training on HyperPod <../../v3-examples/model-customization-examples/cpt_data_mixing_hyperpod>
1617
../../v3-examples/model-customization-examples/sm-studio-nova-training-job-sample-notebook

‎sagemaker-train/tests/data/train/sft_smtj_sample_data.jsonl‎

Lines changed: 300 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
"""Integration test for SFT fine-tuning with serverful Training Jobs (SMTJ).
14+
15+
Based on: v3-examples/model-customization-examples/sft_finetuning_serverful_smtj.ipynb
16+
17+
Prerequisites:
18+
- Active AWS credentials in us-east-1 with permissions to launch SageMaker
19+
Training Jobs and access the default SageMaker bucket.
20+
21+
The test is self-sufficient: it uses the account's default SageMaker bucket
22+
(``sagemaker-{region}-{account_id}``, auto-created by ``Session.default_bucket()``).
23+
The sample training data in ``tests/data/train/sft_smtj_sample_data.jsonl`` is
24+
uploaded under the ``sft-smtj-integ/`` prefix if not already present.
25+
26+
Run with:
27+
export AWS_DEFAULT_REGION=us-east-1
28+
pytest tests/integ/train/test_sft_trainer_serverful_smtj.py -v -s
29+
"""
30+
from __future__ import absolute_import
31+
32+
import logging
33+
import os
34+
import time
35+
import random
36+
37+
import pytest
38+
39+
from sagemaker.train import SFTTrainer
40+
from sagemaker.train.common import TrainingType
41+
from sagemaker.core.training.configs import TrainingJobCompute
42+
43+
logger = logging.getLogger(__name__)
44+
45+
# Test configuration
46+
DATA_PREFIX = "sft-smtj-integ"
47+
DATA_S3_KEY = f"{DATA_PREFIX}/sft_smtj_sample_data.jsonl"
48+
49+
# Local sample data file
50+
DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "data", "train")
51+
LOCAL_TRAINING_DATA = os.path.join(DATA_DIR, "sft_smtj_sample_data.jsonl")
52+
53+
54+
def _ensure_training_data(s3_client, bucket_name: str) -> str:
55+
"""Upload local training data to S3 if not already present.
56+
57+
Returns the S3 URI for the training data file.
58+
"""
59+
s3_uri = f"s3://{bucket_name}/{DATA_S3_KEY}"
60+
61+
# Check if already exists
62+
try:
63+
s3_client.head_object(Bucket=bucket_name, Key=DATA_S3_KEY)
64+
logger.info(f"Training data already exists at {s3_uri}")
65+
return s3_uri
66+
except s3_client.exceptions.ClientError:
67+
pass
68+
69+
logger.info(f"Uploading training data from {LOCAL_TRAINING_DATA} to {s3_uri}")
70+
s3_client.upload_file(LOCAL_TRAINING_DATA, bucket_name, DATA_S3_KEY)
71+
logger.info("Training data uploaded successfully.")
72+
return s3_uri
73+
74+
75+
@pytest.fixture(scope="module")
76+
def training_resources(sagemaker_session_us_east_1):
77+
"""Ensure training data exists in the account's default SageMaker bucket.
78+
79+
Uses ``Session.default_bucket()`` (creates ``sagemaker-{region}-{account_id}``
80+
if needed), then uploads the local sample data file under the
81+
``sft-smtj-integ/`` prefix if not already present.
82+
83+
Returns a dict with:
84+
- training_dataset: S3 URI to the training data file
85+
- s3_output_path: S3 URI for training output
86+
- bucket_name: the default bucket name
87+
"""
88+
bucket_name = sagemaker_session_us_east_1.default_bucket()
89+
s3_client = sagemaker_session_us_east_1.boto_session.client("s3")
90+
training_dataset = _ensure_training_data(s3_client, bucket_name)
91+
92+
return {
93+
"training_dataset": training_dataset,
94+
"s3_output_path": f"s3://{bucket_name}/{DATA_PREFIX}/output/",
95+
"bucket_name": bucket_name,
96+
}
97+
98+
99+
@pytest.mark.gpu_intensive
100+
@pytest.mark.us_east_1
101+
def test_sft_trainer_serverful_smtj(sagemaker_session_us_east_1, training_resources):
102+
"""Test SFT fine-tuning on serverful compute with recipe overrides.
103+
104+
Exercises the full notebook flow:
105+
1. Create SFTTrainer with TrainingJobCompute (dedicated instances)
106+
2. Apply recipe overrides and verify via get_resolved_recipe()
107+
3. Set hyperparameters and submit training job
108+
4. Poll until completion
109+
"""
110+
unique_id = f"{int(time.time())}-{random.randint(1000, 9999)}"
111+
112+
sft_trainer = SFTTrainer(
113+
model="amazon.nova-micro-v1",
114+
training_type=TrainingType.LORA,
115+
training_dataset=training_resources["training_dataset"],
116+
s3_output_path=training_resources["s3_output_path"],
117+
compute=TrainingJobCompute(
118+
instance_type="ml.g6.48xlarge",
119+
instance_count=1,
120+
),
121+
sagemaker_session=sagemaker_session_us_east_1,
122+
overrides={"training_config": {"max_epochs": 1}},
123+
base_job_name=f"sft-smtj-integ-{unique_id}",
124+
)
125+
126+
# Verify recipe overrides are resolved correctly
127+
resolved = sft_trainer.get_resolved_recipe()
128+
training_config = resolved["training_config"]
129+
logger.info(f"Resolved training_config: {training_config}")
130+
131+
# Nova Micro uses trainer.max_epochs for step control
132+
assert training_config["trainer"]["max_epochs"] == 1, (
133+
f"Expected max_epochs=1, got: {training_config.get('trainer')}"
134+
)
135+
136+
# Submit (non-blocking)
137+
training_job = sft_trainer.train(wait=False)
138+
assert training_job is not None
139+
assert training_job.training_job_name is not None
140+
logger.info(f"Training job submitted: {training_job.training_job_name}")
141+
142+
# Poll for completion
143+
max_wait_time = 10800 # 3 hour timeout (Nova training can be slow)
144+
poll_interval = 60
145+
start_time = time.time()
146+
147+
while time.time() - start_time < max_wait_time:
148+
training_job.refresh()
149+
status = training_job.training_job_status
150+
151+
if status in ["Completed", "Failed", "Stopped"]:
152+
break
153+
154+
logger.info(
155+
f"Job {training_job.training_job_name} status: {status} "
156+
f"({int(time.time() - start_time)}s elapsed)"
157+
)
158+
time.sleep(poll_interval)
159+
160+
assert training_job.training_job_status == "Completed", (
161+
f"Training job {training_job.training_job_name} ended with status: "
162+
f"{training_job.training_job_status}"
163+
)
164+
logger.info(f"Training job completed successfully: {training_job.training_job_name}")

0 commit comments

Comments
 (0)