追記(2026-10-28): この記事はSGLang移行の記録です。その後の検証でTensorFoldに戻しました。理由と最終構成は「7. その後」にまとめています。
前シリーズの続編です。1台の DGX Spark(GB10, 128GB)で、
SGLang を Docker で常設化して検証し、
litellm / opencode と連携、non-thinking(fast)を復活、
そして入力長エラーをKVキャッシュ調整で解決した記録です。
IP・ホスト名・認証情報はプレースホルダに置き換えています。
TL;DR
- 速度ではTensorFoldが上だが、**SGLangの価値は「penaltyが効く」**こと。
- SGLangをDocker常設サービス化してnon-thinking(
presence_penalty=1.5)を検証。 -
入力長エラー(
Input length ... exceeds the maximum allowed length)を
--mem-fraction-staticの調整で解決。 - 最終的にはTensorFoldに戻した(起動時間・decode速度・メモリ会計の3点が理由。「7. その後」参照)。
1. なぜSGLangに移行するか
前回(②エンジン比較編)で、速度はTensorFoldが上と分かりました。
それでもSGLangへ移す理由は機能です。
- TensorFold 0.6.6 は
presence_penaltyを実装していない → non-thinking がループする(①真因解明編)。 - SGLang は
presence_penalty/repetition_penaltyを実装 → 公式の非thinking推奨がそのまま効く。 - つまり non-thinking(本来のfast)を安定運用できるのが最大のメリット。
2. SGLangをDocker常設化
既存の compose(/opt/docker/TensorFold/docker-compose.yml)に sglang を既定サービスとして追加し、
従来の tensorfold は profile tf のフォールバックにしました。
services:
# 既定 = SGLang + Qwen3.8-27B (NVFP4 + DFlash2)
sglang:
image: lmsysorg/sglang:dev-cu13
container_name: sglang
devices: [nvidia.com/gpu=all]
volumes:
- ./cache:/root/.cache
- ./sglang-cache:/cache
restart: unless-stopped
shm_size: "16gb"
ipc: host
environment:
- TORCHINDUCTOR_CACHE_DIR=/cache/inductor
- SGLANG_ENABLE_REQUEST_HEADER_OVERRIDES=1
- HF_HUB_OFFLINE=1 # ← 起動ハング回避
command:
- python3 -m sglang.launch_server
- --trust-remote-code --model-path nvidia/Qwen3.8-27B-NVFP4 --tp-size 1
- --served-model-name Qwen3.8-27B
- --mem-fraction-static 0.60
- --attention-backend flashinfer --chunked-prefill-size 8192
- --disable-prefill-cuda-graph --cuda-graph-max-bs-decode 8
- --disable-flashinfer-autotune
- --speculative-algorithm DFLASH
- --speculative-draft-model-path z-lab/Qwen3.8-27B-DFlash2
- --speculative-num-draft-tokens 16
- --mamba-radix-cache-strategy extra_buffer --mamba-ssm-dtype bfloat16
- --max-mamba-cache-size 96 --max-running-requests 8
- --num-continuous-decode-steps 2 --sleep-on-idle
- --reasoning-parser qwen3 --tool-call-parser qwen3_coder
- --host 0.0.0.0 --port 30000
# フォールバック = TensorFold 27B(profile "tf")
tensorfold:
profiles: ["tf"]
# ...(従来どおり MLX-4bit + DFlash2)
litellm:
depends_on: [sglang]
切替:
cd /opt/docker/TensorFold
docker compose up -d # 既定 = SGLang
docker compose logs -f sglang
docker compose stop sglang && docker compose --profile tf up -d tensorfold # TensorFoldへ戻す
起動に約5–7分(CUDAグラフ/compile)。
HF_HUB_OFFLINE=1が無いと起動ハングすることがあります。
3. litellm / opencode 連携
litellm の qwen3.8-27b-* ルートを SGLang に向けます(--served-model-name を合わせる)。
- model_name: qwen3.8-27b-fast
litellm_params:
model: openai/Qwen3.8-27B
api_base: http://sglang:30000/v1
api_key: dummy
extra_body:
chat_template_kwargs:
enable_thinking: false
temperature: 0.7
top_p: 0.8
top_k: 20
min_p: 0.0
presence_penalty: 1.5 # ← SGLangなら実際に効く
repetition_penalty: 1.0
opencode 側は既定モデルを qwen3.8-27b-fast に設定(provider名も更新)。変更後は litellm / opencode を再起動します。
4. non-thinking を復活させる
SGLangは presence_penalty を実装しているため、本来のfast(non-thinking)に戻せる可能性があります。
- litellm
qwen3.8-27b-fastをenable_thinking: false+ 公式non-thinkingサンプリング(presence_penalty 1.5)に。 - 検証:
- litellm経由の単発 → reasoning空(non-thinking)、
finish=stop - tool多ターン → repeats=0
- 退化シナリオ(assistant-only追記)→ repeats=1(軽微)で途中
finish=stopに復帰、暴走ループなし
- litellm経由の単発 → reasoning空(non-thinking)、
→ TensorFoldで問題だった「penalty無視によるループ」は緩和。※最終判断は実運用での検証。
5. 入力長エラーとKVキャッシュ調整
運用中に litellmが500 を返す事象が発生。ログで原因を特定:
litellm.APIConnectionError: OpenAIException - Input length (188104 tokens)
exceeds the maximum allowed length (154191 tokens).
- opencodeが送った会話が 188kトークン、SGLangの上限が 154k。
- 原因は
--mem-fraction-static 0.50では KVキャッシュが ~154kトークンしか持てないこと。
修正: --mem-fraction-static を調整
| mem-fraction | KV容量(max_total_num_tokens) | 空きメモリ |
|---|---|---|
| 0.50 | ~154,000(=エラーの上限) | — |
| 0.70 | 446,231 | free 1.2Gi(危険) |
| 0.60(採用) | 291,034 | free 10Gi / available 44Gi |
- 0.60で KV 291k > モデル窓262k → 実効上限が 262,144(モデルネイティブ)に回復。
- 0.70 は KV が過剰で空きメモリが逼迫(GB10の統一メモリ罠はフリーズ要因)→ 0.60が安全側の最適。
- 検証: litellm経由で
finish=stop正常応答。188k級の会話も通る見込み。
6. 運用の注意
-
起動に約5–7分。
--sleep-on-idleのためアイドル後の初回は起こし分だけ遅い。 - decodeはTensorFoldより遅い(~96 vs ~149 tok/s)。速度より機能を取る選択。
-
GB10の統一メモリ罠: SGLangのメモリ会計が一時確保(25–40GB)を見ないため、
--mem-fraction-staticを上げすぎるとホストがフリーズする。実績ある値に従う。 - opencode の
limit.contextは SGLangの実効上限以下に(例: 240,000 < 262,144)。
7. その後: TensorFoldに戻した理由
SGLangは「penaltyが効く」点で優れていましたが、数日運用した結果、TensorFold(0.6.6)を既定に戻しました。理由は3つです。
| 観点 | SGLang | TensorFold 0.6.6 |
|---|---|---|
| 起動 | 5〜7分(CUDAグラフ/compile) | 20〜30秒 |
| decode(27B) | 約96 tok/s | 約149 tok/s(思考ON・DFlash2) |
| メモリ会計 |
--mem-fraction-static を上げるとホストごとフリーズ |
--kv-dtype int8(FNのみ)や --spill-gib で制御しやすい |
さらに、TensorFold側でnon-thinking問題への別解が取れることが分かりました。
- TensorFold 0.6.6 は
presence_penaltyを実装していませんが、思考ONで運用すればループしない(①真因解明編)。 - non-thinkingを使わない前提に立てば、速度で勝るTensorFoldを既定にするのが合理的です。
その後の検証(2026-10)
- TensorFold 1.1.0 / 1.1.1 のネイティブCUDA実装も試しましたが、この環境(GB10 + ドライバ580 + 互換モード)では prefillが0.6.6の3.3〜10倍遅く、実用外でした。
- 最終構成は TensorFold 0.6.6 + Qwen3.8-27B(MLX-4bit + DFlash2)。
--spill-gibと--checkpoint-slotsでメモリを制御しています。
つまりこの記事は「SGLangに移行した記録」ではなく、**「SGLangを試して、TensorFoldのほうが自分の用途に合うと確認した記録」**として読んでください。
まとめ
- SGLangをDocker常設化し、non-thinking(penalty)を検証した。
-
入力長エラーは
--mem-fraction-staticで解決(0.50→0.60、KV 291k)。 - ただし起動時間・decode速度・メモリ会計の面で運用負荷が高く、最終的にTensorFold(0.6.6)に戻した。
- 結論: 「penaltyが要るならSGLang、速度と運用の軽さならTensorFold」。自分の用途では後者だった、という記録。
数値は筆者環境での実測です。ドライバ・イメージ・チェックポイント・負荷で変動します。