[Amazon SageMaker] Qwen3 1.7B をサーバーレスのフルファインチューニング(FFT)で、質問に Markdown 表で答えさせてみました
1 はじめに
製造ビジネステクノロジー部の平内(SIN)です。
2026年8月、Amazon SageMaker AI のサーバーレスモデルカスタマイズが、従来の PEFT(LoRA など)に加えて フルファインチューニング(FFT: 全パラメータ更新) に対応したという発表がありました。
Amazon SageMaker AI serverless model customization now supports full fine-tuning
先日、Llama 3.2 1B Instruct を SageMaker Training Job 上で LoRA ファインチューニングし、「比較・列挙系の質問には必ず Markdown 表で回答する」というフォーマットを学習させる記事を書きました。その際は、学習スクリプト(train.py)を用意し、Deep Learning Container(DLC)のバージョンを合わせ、ml.g5 系のインスタンスタイプを選ぶ、といった準備が必要でした。
一方、サーバーレスモデルカスタマイズでは、インフラのプロビジョニングや学習オーケストレーションを SageMaker 側が担うため、学習スクリプトやインスタンスタイプの指定が不要です。そこで今回は、前記事とまったく同じデータセットを渡して、サーバーレスのフルファインチューニングを試してみました。
先に結果を述べると、本検証環境(東京リージョン・2026年8月時点・評価データ30件)では、学習スクリプトを用意することなく 学習フェーズ約2分でジョブが完了し、表フォーマットの遵守率は 100%(30/30) となりました。また、ジョブの「請求可能な時間」が 0秒 と表示される(学習トークン数ベースの課金になる)点も確認できました。
なお、最初は前記事と同じ Llama 3.2 1B Instruct で FFT を実行するつもりだったのですが、検証の過程で このモデルはサーバーレスの FFT に対応していないことが分かり、ベースモデルには Qwen3 1.7B を使用しています(詳細は2章)。
本稿の実測値は、あくまで特定環境(東京リージョン・2026年8月時点)での一例として捉えてください。サンプルコードは GitHub で公開しています。
GitHub: sagemaker-serverless-fft-markdown-table
2 サーバーレスモデルカスタマイズとフルファインチューニング
(1) サーバーレスモデルカスタマイズとは
サーバーレスモデルカスタマイズは、OSS モデルのファインチューニングを、インフラ管理なしで実行できる SageMaker AI の機能です。データとモデル ID を渡すと、SageMaker がモデル・データサイズに応じて必要な計算リソースを自動的に選択・確保し、学習を実行します。利用者はインスタンスタイプの選定や学習スクリプトの用意を行う必要がありません。課金体系は手法によって異なり、今回使う SFT(および DPO)は、学習で処理したトークン数とエポック数に応じた トークン単位の課金 です(強化学習系は学習ジョブの時間課金)。詳細は 料金ページ の Model Customization タブを参照してください。
今回の発表で、この機能がフルファインチューニング(全パラメータ更新)に対応しました。gpt-oss / Gemma / Llama / Nemotron / Qwen など25以上の OSS モデルがサポートされ、東京リージョン(ap-northeast-1)でも利用できます。サポートされるモデルとカスタマイズ手法の組み合わせは、Supported models and customization types に一覧があります。
(2) フルファインチューニングと LoRA の違い
LoRA(PEFT)は、元の重みを固定したまま、追加した小さなアダプタ部分のみを学習する手法です。学習対象のパラメータが少ないため、短時間・低コストで済みます。対して、フルファインチューニングはモデルの全パラメータを更新します。ドメイン固有のパターンや用語、タスク構造をより深く学習させたい場合に適しています。
| 観点 | LoRA(PEFT) | フルファインチューニング |
|---|---|---|
| 更新するパラメータ | 追加したアダプタのみ | モデルの全パラメータ |
| 学習コスト | 相対的に小さい | 相対的に大きい |
| 成果物 | アダプタ(元の重みとは別) | 更新済みのモデル一式 |
| 向いている場面 | 軽量な適応 | より深い適応 |
なお、今回のタスク(表フォーマットの遵守)は、LoRA でも100% を達成できた比較的軽いものです。そのため「フルファインチューニングでなければ解けない」というものではなく、本記事の主眼は サーバーレスで実行する場合の手順とコストがどう変わるか の確認となっています。
(3) FFT に対応しているかはモデルごとの「レシピ」で決まる
ポイントは、サーバーレスモデルカスタマイズで各モデルがどの手法(SFT の LoRA / FULL、DPO など)に対応しているかは、SageMakerPublicHub のモデルごとに定義された「レシピ」で決まっている、という点です。
前記事と同じ Llama 3.2 1B Instruct で TrainingType.FULL を指定して実行したところ、次のエラーになりました。
ValueError: No recipes found with Smtj for technique: SFT,training_type:TrainingType.FULL
Llama 3.2 1B Instruct が持つ SFT レシピは LoRA 用のみで、FULL 用のレシピが存在しません。これは公式ドキュメントの Supported models and customization types でも確認できます。同ページの表では、Llama 3.2 Instruct 1B / 3B と Llama 3.1 Instruct 8B は SFT (LoRA) のみにチェックが付いており、SFT (FFT) は空欄になっています。
東京リージョンの SageMakerPublicHub(全714モデル)を走査したところ、FFT(FULL)レシピを持つモデルには次のようなものがありました(2026年8月時点・抜粋)。Llama 系の小型モデルが非対応である点は、上記ドキュメントの表とも一致しています。
- Qwen3(0.6B / 1.7B / 4B / 8B / 14B / 32B)
- Qwen2.5 Instruct(7B / 14B / 32B / 72B)
- DeepSeek R1 Distill(Qwen 1.5B〜32B / Llama 8B・70B)
- gpt-oss-20b / Amazon Nova(Micro / Lite / Pro)
- Llama 系は Llama 3.3 70B Instruct のみ(小型の Llama は非対応)
つまり「前記事と同じ Llama 3.2 1B で LoRA と FFT を比較する」ことはできません。そこで今回は、パラメータ規模が近く FULL レシピを持つ Qwen3 1.7B(huggingface-reasoning-qwen3-1-7b)をベースモデルに採用しました。データセット(Markdown 表遵守・330件)は前記事のものをそのまま流用します。
(4) 前記事の Training Job 方式との違い
前記事「Llama 3.2 1B を LoRA でファインチューニングし Markdown 表で回答させる」では、SageMaker Training Job(スクリプトモード)で LoRA 学習を行いました。その前記事と今回のサーバーレス方式との主な違いを、あらためて整理すると次のとおりです。
| 項目 | 前記事: Training Job + LoRA | 今回: サーバーレス FFT |
|---|---|---|
| ベースモデル | Llama 3.2 1B Instruct | Qwen3 1.7B |
| 学習スクリプト | 自分で用意(train.py) |
不要 |
| DLC バージョン管理 | 必要 | 不要 |
| インスタンスタイプ | 自分で指定(ml.g5 系) |
SageMaker が自動選択 |
| 実行 API | HuggingFace Estimator(SDK v2) | SFTTrainer(SDK V3) |
| 課金 | インスタンス時間 | 学習トークン数 |
3 全体の構成とコスト
全体の構成です。

本サンプルは、前記事と同じく「学習データを S3 に置き、カスタマイズジョブを実行し、結果を評価する」という流れです。恒久的に課金されるリソースを残さない構成にしています。
| 層 | スタック | リソース | コスト | ライフサイクル |
|---|---|---|---|---|
| 永続層 | CDK | S3 バケット / SageMaker 実行ロール / Model Package Group | ごく僅か(保存分のみ) | 最初に作成し最後に削除 |
| 実行層 | Python スクリプト | カスタマイズジョブ | 学習トークン数で課金 | 実行時のみ |
| 評価層 | Python スクリプト | 推論エンドポイント(ml.g6.4xlarge) | 稼働時間で課金 | 評価時のみ・評価後に削除 |
作業の流れとコスト発生ポイントを整理します。
| 手順 | 詳細 | コスト | |
|---|---|---|---|
| 1 | cdk deploy |
S3 / 実行ロール / Model Package Group を作成 | ごく僅か |
| 2 | データ準備 | 同梱スクリプトで Amazon Bedrock(Nova)を使って合成 | Bedrock 利用料 |
| 3 | カスタマイズ実行 | サーバーレス FFT ジョブ | 学習トークン数で課金 |
| 4 | デプロイと評価 | エンドポイントで遵守率を測定 | 稼働時間で課金(ml.g6.4xlarge / 東京リージョン・2026年8月時点で $2.40/h) |
| 5 | クリーンアップ | エンドポイント削除・cdk destroy |
削除で停止 |
ただし、エンドポイントは稼働している間ずっと課金対象になります。本サンプルの評価スクリプトは --delete オプションで評価後にエンドポイントを削除できます。途中で中断した場合はエンドポイントが残っていないか必ず確認してください。
4 作業手順
作業全体の流れを示します。詳細な前提条件やコマンドは README.ja.md を参照してください。
(1) CDK デプロイ
学習データ / 出力用の S3 バケット、SageMaker 実行ロール、そして学習済みモデルの登録先となる Model Package Group を作成します。
$ git clone https://github.com/furuya02/sagemaker-serverless-fft-markdown-table.git
$ cd sagemaker-serverless-fft-markdown-table/cdk
$ pnpm install
$ pnpm cdk deploy
# バケット名のアカウントID部分を置き換える場合
# pnpm cdk deploy -c bucket_suffix=20260811
(2) 学習データの準備
学習データは、前記事で Amazon Bedrock(Nova)を使って合成した330件(学習300 / 評価30)と同じものを使います。ただし、合成したデータ自体はリポジトリに含めていないため、同梱の3スクリプトで生成します。前記事とまったく同じスクリプトなので、前記事のリポジトリを別途クローンする必要はありません。
$ python scripts/generate_questions.py --count 330 # 質問を生成(Nova Lite)
$ python scripts/generate_answers.py # 模範回答を生成(Nova Pro)
$ python scripts/validate_dataset.py --heldout-size 30 # 検証して学習用と評価用に分割
3つのスクリプトは、次のようにファイルを受け渡しながら段階的にデータを組み立てます。
| スクリプト | 入力 | 出力 | 使用モデル |
|---|---|---|---|
generate_questions.py |
- | data/questions.jsonl |
Nova Lite |
generate_answers.py |
data/questions.jsonl |
data/dataset.jsonl |
Nova Pro |
validate_dataset.py |
data/dataset.jsonl |
data/train.jsonl / data/heldout.jsonl |
- |
最後の validate_dataset.py は、生成された回答に Markdown 表(ヘッダ行 + 区切り行)が含まれるかを検証したうえで、学習用と評価用に分割します。表を含まない回答は学習データから除外されます。
動作を確認するため、30件で実行してみました。質問生成が 約3秒、回答生成が 約42秒、検証・分割は一瞬でした。330件の場合は回答生成が件数に比例して伸びます。
# generate_questions.py
generated: 30/30
# generate_answers.py
answered: 30/30
# validate_dataset.py
total: 30, valid: 30, invalid(表なし): 0
train: 25 -> data/train.jsonl
heldout: 5 -> data/heldout.jsonl
生成されるデータは chat 形式(messages)の jsonl で、今回はこの形式のまま変換なしで学習できました。
// data/train.jsonl(1 行 1 レコード)
{ "messages": [
{ "role": "user", "content": "和食と中華、どちらが好き?" },
{ "role": "assistant", "content": "…(Markdown 表を含む模範回答)…" }
] }
評価用の data/heldout.jsonl は質問のみを持つ形式です。学習後のモデルにこの質問を投げて、表フォーマットを守るかどうかを測定します。
// data/heldout.jsonl(1 行 1 レコード)
{ "question": "朝食と昼食、どちらが栄養が取れるのか教えて" }
なお、これらのスクリプトの実行には Amazon Bedrock で Nova Lite / Nova Pro が利用できる必要があります(リージョンは ap-northeast-1、モデル ID は引数で変更できます)。Bedrock の利用料が発生しますが、330件程度であれば少額です。
(3) サーバーレス フルファインチューニングの実行
学習データを渡してカスタマイズジョブを実行します。インスタンスタイプや学習スクリプトの指定は不要です。
# フルファインチューニング
$ python scripts/run_customization.py
# LoRA(比較用)
# python scripts/run_customization.py --method lora
ジョブは通常のトレーニングジョブとしてコンソールに表示されます。

私の環境では、約7分(うち学習フェーズは約2分) で完了しました。注目したいのは、ジョブ詳細画面の「トレーニング時間: 389秒」に対して 「請求可能な時間: 0秒」 となっている点です。サーバーレスカスタマイズはインスタンス時間ではなく学習トークン数(今回は 202,155トークン)で課金されるため、時間課金は0になっています。

学習が完了すると、モデル成果物が S3 に出力され、CDK で作成した Model Package Group にモデルパッケージ(バージョン1)が自動登録されます。
モデル成果物は、出力先の checkpoints/hf_merged/ 配下に HF 形式一式(計12オブジェクト)として出力されていました。フルファインチューニングのため、成果物は LoRA のアダプタではなく、safetensors 2ファイルで 約7.5GB のモデル一式です。

(4) エンドポイントへのデプロイ
カスタマイズ済みモデルをリアルタイムエンドポイントにデプロイします。デプロイに必要なコンテナイメージ・インスタンスタイプ・環境変数は、モデルのレシピ内の HostingConfigs に定義されているので、それに従います(今回は DJL LMI (vLLM) コンテナ + ml.g6.4xlarge)。
$ python scripts/deploy_endpoint.py
Model / EndpointConfig / Endpoint が作成されます。


私の環境では、エンドポイント作成開始から 約6分半 で InService になりました。


なお、コンテナログ(CloudWatch Logs の /aws/sagemaker/Endpoints/<エンドポイント名>)を見ると、DJL LMI コンテナ内で vLLM が起動していく過程が確認できます。以下は検証用に作成した環境での標準的な起動ログの一例で、アカウント固有の設定や認証情報は含まれていません。

いくつか興味深い行を拾ってみます。
Loading weights took 1.06 seconds
Model loading took 3.2152 GiB and 1.193939 seconds
torch.compile takes 25.43 s in total
Available KV cache memory: 15.94 GiB
GPU KV cache size: 149,232 tokens
init engine (profile, create kv cache, warmup model) took 66.84 seconds
- S3 のモデル成果物は 約7.5GB でしたが、GPU へのロードは 約3.2GiB で済んでいます。パラメータ数(1.7B)から逆算すると、S3 には32bit 精度で保存された重みが、16bit 精度でロードされているものと思われます。保存時とサービング時で必要なサイズが変わる点は、インスタンス選定の際に意識しておきたいところです
- 重みのロード自体は 約1秒 で、起動時間の多くはモデルのダウンロードと vLLM の最適化(torch.compile 約25秒・CUDA グラフのキャプチャなど、エンジン初期化計 約67秒)に使われています
- L4(24GB)の空きメモリは KV キャッシュ(15.94 GiB / 149,232トークン分)として確保されています
ただし、起動中に ERROR ... Error retrieving safetensors: Repo id must be in the form 'repo_name' or 'namespace/repo_name' というログも出ます。ローカルパスのモデルを Hugging Face Hub のリポジトリ ID として問い合わせようとして失敗しているだけの無害なログなので、気にしなくていいようです。
(5) 遵守率の測定
デプロイ済みエンドポイントに held-out 30件を推論させ、Markdown 表フォーマットの遵守率を測定します。--delete を付けると評価後にエンドポイントを削除します。
$ python scripts/evaluate_endpoint.py --endpoint-name sagemaker-serverless-fft-markdown-table --delete
(6) クリーンアップ
エンドポイントは評価スクリプトの --delete で削除済みです。検証をすべて終えたら、残りのメタデータと CDK スタックを削除します。
$ aws sagemaker delete-endpoint-config --endpoint-config-name sagemaker-serverless-fft-markdown-table
$ aws sagemaker delete-model --model-name sagemaker-serverless-fft-markdown-table
$ cd cdk
$ pnpm cdk destroy
カスタマイズジョブは実行完了で自動終了するため、常時課金されるリソースは残りません。
5 コードの解説
(1) カスタマイズジョブの実行
Github scripts/run_customization.py
SageMaker Python SDK V3 の SFTTrainer を使います。学習データを S3 にアップロードし、training_type にフルファインチューニング(TrainingType.FULL)か LoRA(TrainingType.LORA)を指定して train() を呼びます。学習スクリプトやインスタンスタイプの指定はありません。
# scripts/run_customization.py(要点抜粋)
import boto3
from sagemaker.train.common import TrainingType
from sagemaker.train.sft_trainer import SFTTrainer
MODEL_ID = "huggingface-reasoning-qwen3-1-7b"
# chat 形式の jsonl を S3 へアップロード(SDK V3 には sagemaker.Session が無い)
key = f"data/{os.path.basename(args.train_file)}"
boto3.client("s3").upload_file(args.train_file, bucket, key)
train_s3 = f"s3://{bucket}/{key}"
trainer = SFTTrainer(
model=MODEL_ID,
training_type=TrainingType.FULL, # LoRA の場合は TrainingType.LORA
training_dataset=train_s3,
s3_output_path=f"s3://{bucket}/output",
role=role,
accept_eula=True,
base_job_name=PROJECT_NAME,
# サーバーレス(compute 未指定)では、学習済みモデルの登録先として必須
model_package_group=f"{PROJECT_NAME}-model-package-group",
)
trainer.hyperparameters.max_epochs = args.epochs
job = trainer.train(wait=False) # インフラは SageMaker 側が自動で確保
print(job.training_job_name)
鍵になるのは model_package_group です。サーバーレス(compute 未指定)で実行する場合、学習済みモデルの登録先として Model Package Group の指定が必須で、SDK は名前から ARN を解決するだけで自動作成はしてくれません。本サンプルでは CDK(AWS::SageMaker::ModelPackageGroup)で事前に作成しています。
なお、ハイパーパラメータは、モデルのレシピからデフォルト値が読み込まれ、属性代入で上書きできます。今回は max_epochs のみ3に変更し、他はデフォルト(learning_rate=0.0001 / global_batch_size=8 など)のままとしました。
(2) エンドポイントへのデプロイ
Github scripts/deploy_endpoint.py
学習完了時に Model Package Group に登録されたモデルパッケージから、モデル成果物の S3 パスを取得してデプロイします。コンテナイメージ・インスタンスタイプ・環境変数はレシピの HostingConfigs に定義されているものを使います。
# scripts/deploy_endpoint.py(要点抜粋)
IMAGE_URI = "763104351884.dkr.ecr.ap-northeast-1.amazonaws.com/djl-inference:0.34.0-lmi16.0.0-cu128"
INSTANCE_TYPE = "ml.g6.4xlarge"
# 最新のモデルパッケージからモデル成果物の S3 パスを取得
package = sm.describe_model_package(ModelPackageName=packages[0]["ModelPackageArn"])
model_data = package["InferenceSpecification"]["Containers"][0]["ModelDataSource"]
# HF 形式のモデル本体は checkpoints/hf_merged/ サブディレクトリにある(8 章参照)
model_data["S3DataSource"]["S3Uri"] += "checkpoints/hf_merged/"
sm.create_model(
ModelName=PROJECT_NAME,
ExecutionRoleArn=role,
PrimaryContainer={"Image": IMAGE_URI, "ModelDataSource": model_data, "Environment": ENVIRONMENT},
)
# create_endpoint_config → create_endpoint → waiter で InService を待つ
(3) 遵守率の測定
Github scripts/evaluate_endpoint.py
held-out の質問を推論させ、回答に Markdown 表(ヘッダ行 + 区切り行)が含まれるかを正規表現で判定して遵守率を算出します。判定基準は前記事の validate_dataset.py と同一です。推論のリクエストは OpenAI 互換の chat スキーマで送り、レスポンスの choices[0].message.content を取り出します。
# scripts/evaluate_endpoint.py(要点抜粋)
TABLE_RE = re.compile(r"^\|.+\|\s*\n\|[\s:\-|]+\|", re.MULTILINE)
body = {
"messages": [{"role": "user", "content": [{"type": "text", "text": question}]}],
"max_tokens": 512, "temperature": 0.0, "stream": False,
}
res = smr.invoke_endpoint(EndpointName=..., ContentType="application/json", Body=json.dumps(body))
answer = json.loads(res["Body"].read())["choices"][0]["message"]["content"]
compliant += bool(TABLE_RE.search(answer))
6 動作確認の結果
held-out 30件に対する表フォーマット遵守率の測定結果です。30件すべてで Markdown 表を含む回答が返り、遵守率は100% となりました。
| モデル | 遵守率 |
|---|---|
| サーバーレス FFT 後(Qwen3 1.7B) | 100%(30/30) |
参考までに、前記事では学習前の Llama 3.2 1B が0%、LoRA 学習後が100% でした。ただし今回はベースモデルが異なるうえ、学習前の Qwen3 1.7B は測定していないため、0% からの改善幅を今回の結果として示すことはできません。あくまで「学習後に30件すべてで表フォーマットが守られた」という結果になります。

回答例です。比較の質問に対して、学習データのフォーマットどおり Markdown 表で回答していることが確認できました。
質問: マクドナルドとケンタッキー、ファストフードとしてどちらが人気?
回答:
<think>
</think>
マクドナルドとケンタッキー、ファストフードとしての人気度を比較します。
| 項目 | マクドナルド | ケンタッキー |
|------------|-------------------------------------------|---------------------|
| 人気度 | 世界的に最も人気のあるファストフードの1つ | 特に若者に人気 |
| 価格帯 | 一般的に安価 | やや高め |
| 店舗数 | 世界的に多い | 多い |
なお、回答の先頭に空の <think></think> ブロックが付いています。Qwen3 は思考(reasoning)モードを持つモデルで、その出力形式の名残のようです。学習データ(思考なしの直接回答)で FFT した結果、思考部分は空になり、本文はデータセットのフォーマットに従うようになった、という挙動と思われます。表フォーマットの判定には影響しないため、遵守率は100% となりました。
7 前記事(LoRA / Training Job)との比較
前記事「Llama 3.2 1B を LoRA でファインチューニングし Markdown 表で回答させる」の結果と、今回のサーバーレス FFTの結果を比較します。前記事側の数値は同記事で計測したもの、今回側は本稿での実測値です。
| 観点 | 前記事: Training Job + LoRA | 今回: サーバーレス FFT |
|---|---|---|
| ベースモデル | Llama 3.2 1B Instruct | Qwen3 1.7B |
| 学習スクリプトの用意 | 必要 | 不要 |
| インスタンスタイプの選定 | 必要(ml.g5 系) |
不要(自動選択) |
| 学習時間 | 約7分(課金442秒) | 約7分(うち学習フェーズ約2分) |
| 学習コスト | 1ドル未満(インスタンス時間) | 学習トークン 202,155トークン分(時間課金は0秒) |
| 表フォーマット遵守率 | 学習前0% → 学習後100% | 学習後 100%(30/30) |
ベースモデルも手法も異なるため厳密な比較にはなりませんが、300件 × 3エポックという同じ学習データで、どちらも学習後の遵守率は100% でした。手間の面では、学習スクリプトの用意・DLC バージョン管理・インスタンスタイプの選定が不要になり、準備のステップが明確に減っています。
学習の内訳(CloudWatch Logs より)も記載しておきます。ダウンロードなどのオーバーヘッドを除いた純粋な学習は1.46分、スループットは 約2,308トークン/秒 でした。
Pending 17 秒 → Downloading 4 分 19 秒 → Training 1 分 56 秒 → Uploading 14 秒
Tokens: 202,155 / Throughput: 2307.68 tokens/second
8 つまずいた点
新しい機能(SDK V3)ということもあり、3点つまずいたので補足します。
(1) SDK V3 には sagemaker.Session が無い
V2 で定番だった sagemaker.Session().upload_data(...) は、SDK V3(今回は3.18.0)では AttributeError: module 'sagemaker' has no attribute 'Session' になります。S3 へのアップロードは boto3 で直接行う必要があります。
(2) 実行ロールに必要な権限が多い
SDK V3 は train() 実行前に実行ロールの権限を検証し、不足があるとジョブ起動前に RoleValidationError で失敗します。最小権限で組んだロールの場合、以下を追加する必要がありました。なお、エラーメッセージに不足アクションが列挙されるので、それを CDK に反映すれば解決します。
- EC2 ネットワークインターフェース系9アクション(
ec2:CreateNetworkInterface/ec2:DescribeVpcsなど) - ベースモデル参照:
sagemaker:DescribeHubContentなど Hub 参照系 - 学習済みモデル登録:
sagemaker:CreateModelPackageなど
(3) デプロイ時のモデルパスは checkpoints/hf_merged/ を指定する
モデルパッケージの ModelDataSource が指す S3 プレフィックスの直下には Hydra 設定やログ類しかなく、HF 形式のモデル本体(config.json / safetensors / tokenizer)は checkpoints/hf_merged/ サブディレクトリにあります。デプロイ時はこのサブディレクトリを指定する必要があります。
プレフィックス直下をそのまま指定すると、コンテナがモデルを検出できず、エンドポイントは Creating のまま進みません。原因は CloudWatch Logs(/aws/sagemaker/Endpoints/<name>)で確認できます。
ai.djl.repository.zoo.ModelNotFoundException: Failed to detect engine of the model: /opt/ml/model
ただし、注意点があります。この状態(Creating で起動失敗)のエンドポイントは、更新も削除もできません(Cannot update in-progress endpoint)。Failed に遷移するまで待つ必要があり、私の環境では約27分かかりました。

Failed になった後は削除できるので、モデルのパスを修正して作り直します。デプロイ前にモデル成果物のパス構造を確認しておくことをおすすめします。
9 最後に
2026年8月に FFT へ対応した SageMaker AI のサーバーレスモデルカスタマイズを、前記事の LoRA 学習と同じデータセットで試してみました。今回の検証で分かったことを4点にまとめます。
- サーバーレスモデルカスタマイズでは、学習スクリプト・DLC・インスタンスタイプの指定が不要になり、
SFTTrainerにデータとモデル ID を指定して学習を実行できる(ただし実行ロールの権限と Model Package Group は事前に用意する)。SFT の課金はトークン単位で、今回のジョブは「請求可能な時間0秒」と表示された - FFT(FULL)に対応しているかはモデルごとのレシピ次第。Llama 系の小型モデルは FFT 非対応(2026年8月時点)のため、今回は Qwen3 1.7B を使用した
- 同じデータセット(300件 × 3エポック)で、LoRA(前記事)に続き今回の FFT でも学習後の遵守率は100%(30/30)だった。学習フェーズは約2分で完了した
- デプロイ時のモデルパスは
checkpoints/hf_merged/を指定する必要がある
今回のような軽いフォーマット学習であれば LoRA でも十分ですが、学習スクリプトなしでデータを渡すだけという手軽さは、モデルやデータを変えて何度も試す場面で効いてきそうです。training_type を切り替えるだけで FFT と LoRA を同じコードで比較できるようにしてあるので、自社データでのモデルカスタマイズを検討する際のたたき台になれば嬉しいです。
この記事で使用したコードは、以下に置きました。
10 参考リンク
- Amazon SageMaker AI serverless model customization now supports full fine-tuning(発表)
- AWS 公式ワークショップ: serverless-model-customization-with-sagemaker-ai
- SageMaker 開発者ガイド: Supported models and customization types(モデル別の対応手法一覧)
- SageMaker Python SDK: Model Customization
- 前記事: Llama 3.2 1B を LoRA でファインチューニングし Markdown 表で回答させる









