
DeepSeek V4.1 Flash 1017をAWSのG7eでセルフホストし、SGLangで生成速度をチューニングしてみた
こんちには。製造ビジネステクノロジー部の中村(@nokomoro3)です。
今回は新しく発表されたDeepSeek V4.1 Flash 1017をAWS上でセルフホスティングをしてみます。
DeepSeek-V4.1-Flash-1017は、DeepSeek-V4.1-Flashのマイナーアップデート版です。
モデルカードでは、推論、コーディング、エージェントタスク、画像を扱うベンチマークで性能が改善し、あわせてタスクの実行時間と出力の長さを減らしたことで、より低いコストで性能を高めたと説明されています。
ライセンスはMITで、コンテキスト長は最大100万トークンです。
モデルカードに掲載されているベンチマークから、エージェント関連の一部を抜粋します。
| ベンチマーク | V4.1-Flash-1017 | V4.1-Flash(前版) | Kimi K3 |
|---|---|---|---|
| Terminal-Bench 4.0 | 42.3 | 31.2 | 12.6 |
| DeepSWE v1.1 | 77.0 | 74.2 | 67.5 |
| ProgramBench | 29.1 | 20.3 | 17.5 |
| Agents' Last Exam | 35.4 | 31.8 | 27.6 |
| HLE w/o tools | 42.6 | 36.8 | 43.5 |
前版からはどのベンチマークでも向上しています。
伸びが大きいのは、ターミナルでコマンドを実行しながらタスクを進める能力を測るTerminal-Bench(+11.1ポイント)と、プログラミング系のProgramBench(+8.8ポイント)です。Kimi K3との差もこの2つで大きく、シェル操作やコーディングを伴うエージェントタスクに特に強みがあります。
一方、ツールを使わずに難問を解く推論力を測るHLEでは、Kimi K3とほぼ同じ水準となっています。
結論
最初に実際にデプロイできた環境について説明します。
- 実行環境
g7e.48xlarge(8 GPU)では、Engramをオフロードせずに動作g7e.24xlarge(4 GPU)では、EngramのテーブルをCPU側のメモリへオフロードすることで、モデルのロード、短文生成、ツール呼び出しに成功
- 生成速度
- 単一リクエストの生成速度は、初期構成(Eager)で8.53 tok/sec
- Decode用CUDA Graphで22.98 tok/sec、さらにDSparkで53.71 tok/secまで改善(累計約6.3倍)
- DSparkの速度は生成される文章で変わり、同じ構成でも46〜65 tok/sec程度の幅があった
- DSparkを有効にすると、4 GPU+Engramオフロードと8 GPUの速度はほぼ同じ(54〜57 tok/sec)
- 推論エンジン
- 今回はSGLangを使用
- vLLMは公式stable(0.31.0)、公式nightlyとも、SM120向けのカーネル制約により起動に失敗
検証用コード
検証に使用したTerraform、起動スクリプト、ベンチマークスクリプトは以下のリポジトリにあります。
実行環境
検証対象のモデル
検証対象は、DeepSeekが公開している deepseek-ai/DeepSeek-V4.1-Flash-1017 です。
Hugging Faceのrevisionは 1bae0e1088993b9b82de08f59de5be5a34e42170 に固定しました。
モデルのconfigから読み取れる主な仕様は以下のとおりです。
| 項目 | 値 |
|---|---|
| アーキテクチャ | DeepseekV41ForCausalLM(MoE) |
| 総パラメータ数 | 763B(モデルカードの記載) |
| 重みのサイズ | 約475.25 GiB |
| 量子化 | FP8(block 32×32)、Expertの重みはFP4 |
| 層数 | 40 |
| Routed Expert数 | 384(1トークンあたり6を選択)、Shared Expert 1 |
| 最大位置埋め込み | 1,048,576 |
| Engramを持つ層 | 先頭から2番目と15番目(configのインデックスは1と14) |
V4.1では、Engramと呼ばれる仕組みが導入されています。
Engramは、各トークンとその直前の数トークン(最大4トークン)の並びをハッシュ化し、巨大な埋め込みテーブルから対応するベクトルを引いて隠れ状態に足し込む仕組みです。
固有名詞や定型句のような「覚えておけば済む知識」の想起をこのテーブルに任せることで、Transformerの層を推論や文脈の理解に回すことを狙っています。
この参照はDecodeだけでなく、Prefillでも入力の全トークン位置で行われます。V4.1では、全40層のうち先頭から2番目と15番目の層(configのインデックスでは1と14)にこのテーブルがあり、容量は合計約189 GiBです。
後述するCPUオフロードは、このテーブルをCPU側のメモリに置くSGLangの実装です。
テーブル自体は大きいものの、1トークン位置あたりに読むのは、トークンIDから計算した数行だけです。
読む行は隠れ状態に依存しないため、層の計算を待たずに特定できるという利点もあります。
このため、テーブルをCPU側に置いてもGPU側のメモリを節約しつつ、速度への影響を小さく抑えられると期待できます。
AWS構成
AWS環境はTerraformで構築し、前回の記事と同じ構成にしています。
- GPUインスタンスとInternal ALBはPrivate Subnetに置き、パブリックIPは付与しない
- 外向き通信(コンテナイメージやモデルの取得)は、Public Subnetに置いたNAT Gateway経由
- S3はGateway Endpoint経由
- 推論サーバーはECS Service(EC2起動、awsvpcネットワーク)で動かし、Capacity ProviderでASGのGPUインスタンスを0〜1台に増減
- APIはInternal ALBの80番で受け、ECSタスクの8000番に転送
Security Groupは、GPUインスタンスへの受信をすべて閉じ、ECSタスクへの受信はALBからの8000番だけを許可しています。
ベンチマークは、VPC内のGPUインスタンスからSSM Run Commandで実行し、Internal ALBのDNS名に対してリクエストを送っています。
リージョンは変数で外から渡す構成にしており、今回はすべてロンドン(eu-west-2)で検証しました。
東京(ap-northeast-1)とバージニア(us-east-1)では、G7eの空き容量がなく確保できませんでした。
ECSサービスの起動および停止
Terraformでは、リージョンごとのAZやAMIを regions/<region>.tfvars.json にまとめ、stateはリージョン名のworkspaceに分けています。
REGION=eu-west-2
terraform -chdir=infra/stack workspace select -or-create $REGION
terraform -chdir=infra/stack apply \
-var-file=regions/$REGION.tfvars.json \
-var=runtime_mode=serve \
-var=enable_cuda_graph=true \
-var=enable_dspark=true
ECS Serviceのdesired countは0で作成し、検証時だけ1に上げます。
desired countを1にすると、Capacity ProviderがASGを1台に増やし、タスクが配置されます。
python3 scripts/operate.py start --region $REGION # ECS Serviceを1台にする(GPUインスタンスが起動)
python3 scripts/operate.py status --region $REGION
python3 scripts/operate.py logs --region $REGION
python3 scripts/operate.py stop --region $REGION # ECS Serviceを0台にし、ASGを0へ戻してEC2終了まで確認
構成を切り替えるときは、変数を変えてapplyします。
Task Definitionが更新されるとECS Serviceが新しいタスクに入れ替えるので、同じGPUインスタンスのまま別の構成を測定できます。
EC2の構成
| 項目 | g7e.24xlarge | g7e.48xlarge |
|---|---|---|
| GPU | RTX PRO 6000 Blackwell Server Edition × 4 | 同 × 8 |
| GPU間接続 | PCIe(NVLinkなし) | PCIe(NUMAノードをまたぐ) |
| ホストRAM | 1 TiB | 約2 TiB |
| ドライバー | 580.178.04 | 580.178.04 |
| ストレージ | gp3 1,500 GiB(1,500 MiB/秒、12,000 IOPS) | 同左 |
| On-Demand料金(eu-west-2) | 28.80344 USD/時 | 57.60687 USD/時 |
料金は2026年10月19日にAWS Pricing APIで確認したLinux On-Demandの値です。
SGLang
今回は推論エンジンには、SGLangを使いました。
SGLangは、LMSYSを中心に開発されているオープンソースのLLM推論サーバーです。
vLLMと同様にOpenAI互換のAPIを提供し、Continuous Batchingで複数のリクエストをまとめて処理することも可能です。
その他、プロンプトの共通部分をRadix Treeで管理してKVキャッシュを再利用するRadixAttentionのほか、テンソル並列、エキスパート並列、投機的デコーディングにも対応しています。
DeepSeek V4.1については、Engramのオフロードや後述するDSparkといったモデル固有の機能がSGLang側に実装されています。
使用したのは、SGLangの公式cookbookでDeepSeek V4.1向けに案内されている開発版イメージ lmsysorg/sglang:dev-dsv41 です。
2026年9月11日ビルドのamd64イメージで、digest(sha256:c1633485...)とSGLangのコミット(da64c5cb...)を固定しています。
前回の記事と同じvLLMでの起動も試しましたが、vLLM公式のstable(0.31.0)とnightlyのどちらも、SM120(RTX PRO 6000 Blackwell)向けのSparse MLAとIndexerのカーネル制約により、起動処理の途中で停止しました。このため、今回はSGLangを使っています(関連するissueはvllm-project/vllm#59203)。
初期構成の起動コマンドは以下です。
python3 -m sglang.launch_server \
--model-path <モデルのパス> \
--served-model-name deepseek-ai/DeepSeek-V4.1-Flash-1017 \
--host 0.0.0.0 --port 8000 \
--tp-size 4 --ep-size 4 \
--context-length 4096 \
--chunked-prefill-size 2048 \
--mem-fraction-static 0.85 \
--max-running-requests 1 \
--disable-custom-all-reduce \
--disable-radix-cache \
--reasoning-parser auto \
--tool-call-parser auto \
--enable-deepseek-v4-fp4-indexer \
--cuda-graph-backend-decode disabled \
--cuda-graph-backend-prefill disabled
SM120では --enable-deepseek-v4-fp4-indexer がDeepGEMM用のメタデータ生成も制御していました。このフラグを省くとメタデータが作られないため、追加して解消しています。
CUDA Graphを有効にするには以下のように設定をします。
--cuda-graph-backend-decode full \
--cuda-graph-backend-prefill disabled \
--cuda-graph-max-bs-decode 1
DSparkを有効にするには以下を設定します。
--speculative-algorithm DSPARK \
--speculative-dspark-block-size 5
あわせて、以下の環境変数を指定しています。
前半の4つはEngramのオフロードに関する設定、残りの2つはSM120で起動するためのAttentionの実装と、Thinkingの無効化です。
# EngramのテーブルをGPUではなくホスト(CPU側)のメモリに置く(g7e.24xlargeでは必須)
SGLANG_ENABLE_DSV41_ENGRAM_HOST_TABLE=true
# 各rank(GPUごとのプロセス)が担当分のテーブルを専用メモリに持つ
SGLANG_DSV41_ENGRAM_HOST_TABLE_LAYOUT=private
# テーブルをpinned memoryに確保し、GPUへの転送を速くする(memlockの上限解除が必要)
SGLANG_DSV41_ENGRAM_HOST_TABLE_PIN=true
# ロード時に重みファイルのページキャッシュを捨てない(再ロードが約40秒で済む)
SGLANG_ENABLE_DSV41_ENGRAM_DROP_PAGE_CACHE=false
# SM120でSparse MLAの計算にTriton実装を使う(既定のFlashInferではprefillで停止する)
SGLANG_SM120_FLASHMLA_BACKEND=triton
# Thinking(Reasoning)を既定で無効にする
SGLANG_DEFAULT_THINKING=false
この設定で無事に起動できることを確認しています。
推論の動作確認
APIはOpenAI互換です。
Internal ALBはVPC内からしか呼べないため、手元から試す場合は、GPUインスタンス経由のポートフォワード(operate.py tunnel、Session Manager pluginが必要)でALBに接続します。
# 別のターミナルで維持する
python3 scripts/operate.py tunnel --region $REGION
curl -s http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-ai/DeepSeek-V4.1-Flash-1017",
"messages": [{"role": "user", "content": "日本の首都を日本語で答えてください。"}],
"temperature": 0,
"max_tokens": 128
}' | jq -r '.choices[0].message.content'
日本の首都は**東京**です。
ツール呼び出しも、OpenAI互換の tools で指定できます。
curl -s http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-ai/DeepSeek-V4.1-Flash-1017",
"messages": [{"role": "user", "content": "Call get_weather for Tokyo."}],
"tools": [{"type": "function", "function": {
"name": "get_weather",
"description": "Get weather for a city",
"parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
}}],
"tool_choice": "required",
"temperature": 0
}' | jq '.choices[0].message.tool_calls[0].function'
{
"name": "get_weather",
"arguments": "{\"city\": \"Tokyo\"}"
}
どちらも、ポートフォワード経由で実際に実行したときの出力です。
性能測定方法
測定に使用するデータ
今回は短文の1条件のみで測定を行いました。
| 区分 | 入力 | 実入力tokens | 出力上限 |
|---|---|---|---|
| 短文 | 「日本の首都の歴史と特徴を詳しく説明してください。」 | 16 | 128 |
同じプロンプトの再利用で速くなる効果を避けるため、Radix Cacheは無効にしています。各構成でwarmupを2回行い、その後の3回の中央値を採用しました。
g7e.24xlarge の3構成と g7e.48xlarge の4構成は、それぞれ同じGPUインスタンスのまま、Task Definitionだけを入れ替えて測定しています。
リクエストはすべてInternal ALBを経由しています。
測定する指標
以下の2つを今回は測定しています。
| 指標 | 計算方法 | 見方 |
|---|---|---|
| TTFT | リクエスト送信から最初のトークン受信までの時間 | Prefillの速さ。チャットでの待ち時間の体感に効く |
| E2E output tok/sec | 出力tokens ÷ リクエスト全体の所要時間 | Prefillを含む、1リクエストあたりの生成速度 |
前回の記事にあったDecode output tok/secとAggregate output tok/secは、今回は測っていません。
E2Eの値はPrefillを含むため、前回の固定256 tokensのDecode測定とは直接比較できません。
測定結果
4 GPUでの高速化
g7e.24xlarge(4 GPU+Engramオフロード)で、初期構成から順に高速化の設定を加えた結果です。
| 構成 | E2E output tok/sec | TTFT | 128 tokensの生成時間 | 前段からの改善 |
|---|---|---|---|---|
| Eager | 8.53 | 0.253秒 | 約15.0秒 | - |
| CUDA Graph | 22.98 | 0.176秒 | 約5.6秒 | 約2.7倍 |
| CUDA Graph+DSpark | 53.71 | 0.180秒 | 約2.4秒 | 約2.3倍 |
- CUDA Graph:Decodeで毎トークン発生するGPUカーネルの起動をまとめて再生し、起動のオーバーヘッドを減らす(Decodeのみ有効化)
- DSpark:先読みしたトークンを本体のモデルでまとめて検証し、1ステップで複数トークンを確定させる投機的デコーディング(先読み数5)
Eagerでは128 tokensの生成に約15秒かかっていましたが、2つの設定で約2.4秒になりました(累計約6.3倍)。DSparkの速度は生成される文章によって効果が変わる点は注意が必要です。
GPU数とEngramオフロードの比較
次に高速化した状態でGPUインスタンスとEngranオフロードの影響を確認しました。
| 構成 | CUDA Graph | CUDA Graph+DSpark | GPU上の重み |
|---|---|---|---|
| 4 GPU+Engramオフロード(g7e.24xlarge) | 22.98 | 53.71 | 72.73 GiB/GPU |
| 8 GPU(g7e.48xlarge) | 27.06 | 55.55 | 61.47 GiB/GPU |
| 8 GPU+Engramオフロード(g7e.48xlarge) | 27.03 | 57.48 | 37.91 GiB/GPU |
8 GPUにすると、CUDA Graphだけの構成では約18%速くなりましたが、DSparkを加えると4 GPUとの差はほぼなくなりました。
Engramオフロードの有無による速度差はほぼありませんでした(8 GPUで約0.1%)。オフロードすると、GPUあたりの重みは約23.56 GiB減ります。
今回の構成ではEngramオフロードによる性能低下はあまりなかったと言えそうです。
まとめ
DeepSeek V4.1 Flash 1017を、前回の記事と同じAWS構成のまま、推論エンジンをSGLangに替えてセルフホストしました。
V4.1で追加されたEngramのテーブルをCPU側のメモリに置けるため、4 GPUの g7e.24xlarge でも動かせたのは大きな収穫でした。
CUDA GraphとDSparkを組み合わせると、単一リクエストで50 tok/secを超え、対話用途で使える速度になります。
一方で、今回は短い入力と同時1リクエストの条件しか測っていません。
次は、長文入力と並列数を変えた測定で、Engramのオフロードが速度にどう影響するかと、マネージドAPIと比べた費用を確認する予定です。
SM120に対応したvLLMのビルドが公開されたら、vLLMでも改めて試してみます。
また、G7eはどのリージョンでも空き容量が少なく、東京やバージニアでは確保できませんでした。
検証の際は、複数のリージョンで起動を試せるように準備しておくのがおすすめです。












