1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【DGX Spark】Qwen3.8のnon-thinkingがループする真因は「モデルではなくエンジン」だった

1
Posted at

前シリーズ(構築編/不安定化の切り分け編/3モデル比較編)の続編です。
1台の DGX Spark(GB10, 128GB ユニファイドメモリ)で TensorFold + LiteLLM + opencode を運用中。
**「non-thinking(fast)だとエージェントが同じツールコールを延々と繰り返す」**問題の真因を、
エンジンのソースまで降りて突き止めた記録です。
IP・ホスト名・認証情報はプレースホルダに置き換えています。


TL;DR

  • non-thinking でループするのは モデル(Qwen3.8)のせいではなく、エンジン(TensorFold 0.6.6)のせいだった。
  • 理由: TensorFold 0.6.6 は presence_penalty / repetition_penalty を実装していない。litellm が送っても黙って破棄される。
  • 公式Qwen3.8の非thinking推奨は presence_penalty=1.5。ループ抑制の要がエンジン側で効かない=構造的な問題。
  • 同じ症状は Flash-Next でも 27B でも再現(=モデル固有ではない)。
  • おまけ: 「27BはMTPを内包しているのでは?」という疑問も調査。27BのチェックポイントにMTP重みは無く、TensorFoldは剥がしてDFlash2を使う実装だった。

1. 症状: non-thinking で同一ツールコールを延々と繰り返す

opencode(エージェント)で fast(non-thinking)を使うと、同じツールコールを延々と再実行します。
サーバ側ログを見ると、一目で異常と分かります。

done req-... prompt=70092 thinking=False tokens=132 sha=9ec1077d8ca5 finish=tool_calls
done req-... prompt=70292 thinking=False tokens=132 sha=9ec1077d8ca5 finish=tool_calls
done req-... prompt=70492 thinking=False tokens=132 sha=9ec1077d8ca5 finish=tool_calls
... (同一 sha が 86 回)
  • 同一の出力(sha が完全一致)を 86 回出力。
  • opencodeが再送するたびに プロンプトが +200 ずつ増加。
  • 別のセッションでは 同一 sha を 147 回、+483/round で 106k→176k まで増殖。

つまり モデルが同じツールコールを出し続け、opencodeがそれを送り返すという古典的なループです。

2. 当初の誤解

最初は「モデル側・サンプリング設定の問題」だと思っていました。

  • Qwen3.8 のモデルカードに従い、non-thinking の推奨値(temperature=0.7, top_p=0.80, top_k=20, presence_penalty=1.5)を
    litellm と opencode の両方に設定。
  • それでもループは止まらない。
  • 「presence_penalty を 0.5 にしていたのを 1.5 に直した」等の調整も、根本解決には至らず。

ここで「送っている値が本当にエンジンに届いているのか?」を疑い、エンジンのソースを読みに行きました。

3. エンジン内部の調査(決定的証拠)

TensorFold 0.6.6 のパッケージを grep すると、一目で分かりました。

# tensorfold/cuda/server.py
_SAMPLING_FIELDS = ("temperature", "top_p", "top_k", "min_p", "seed")
  • エンジンが受け付けるサンプリングは temperature / top_p / top_k / min_p / seed のみ。
  • presence_penalty も repetition_penalty もコード自体が存在しない。
    → litellmが送っても黙って破棄される。

念のためパッケージ全体を検索しても penalty はヒットせず、1.0.x(ネイティブZig系)のserveフラグにも penalty はありません。
つまり 「公式レシピを送った」だけでは不十分で、エンジンがそのフィールドを実装しているかまで見る必要がある、という教訓です。

補足: 出力が決定的になりやすい

# エンジンはプロンプトから決定的にシードを引く
Sampling(int(seed) if seed is not None else seed_for(prompt), ...)
  • seed_for(prompt) により、同じ文脈では同じ出力になりやすい。
  • これがループを安定化させ、いったん入ると抜けにくくなります。

4. なぜ Flash-Next でも 27B でも同じか

  • Flash-Next(177B MoE)でも 27B(dense)でも、**同じエンジン(TensorFold 0.6.6)**を使っていました。
  • したがって non-thinking のループはモデルサイズに依存せず再現します。
  • 実際、27B に切り替えても non-thinking はループし、**「モデルを変えても直らない」**ことが確認できました。

→ 真因はエンジン。モデルを替えても解決しない、というのがこの調査の最大の収穫です。

5. おまけ: 「27BはMTPを内包しているのでは?」の真相

ループ調査の流れで「27BはMTP(Multi-Token Prediction)を内包しているのでは?」という疑問が出ました。結論:

  • 27Bの config.json は MTP を宣言している("mtp_num_hidden_layers": 1)。
  • しかし チェックポイントに MTP 重みが入っていない(model.safetensors.index.json に MTPテンソル 0個、最大 layer=63=MTP層なし)。
  • さらに TensorFold は MTP を意図的に剥がす実装:
# families/qwen3_5/__init__.py
def sanitize_without_mtp(self, weights):
    kept = {k: v for k, v in weights.items() if not (k.startswith("mtp.") or ".mtp." in k)}
# families/qwen3_5/family.py
self.mtp = drafter          # ← drafter(=DFlash2) を「mtp」として扱う
self.head_drafts = DFlashHead(drafter, ...)
  • 27Bの投機はDFlash2ドラフタ(block_size 8, 5層)が担当し、MTPは関与していません。
  • --mtp-drafts のヘルプ対象も Flash-Next / Nemotron / Qwen3.6 のみ(27Bは列挙されていない)→ だから27Bで効果ゼロでした。
  • DFlash2 は lossless(greedy出力はターゲットと厳密一致)なので、改善しても品質は変わらず速度だけが動きます。

6. 学び

  1. 「公式推奨を送った」で満足しない。エンジンがそのフィールドを実際に実装しているかまで確認する。
  2. ループの真因はエンジンの presence_penalty 未実装。モデルを替えても直らない。
  3. MTPの有無はチェックポイントとエンジン実装の両方を見る(configの宣言だけでは不十分)。

まとめ

  • Qwen3.8 の non-thinking ループは、TensorFold 0.6.6 が penalty を実装していないことが真因でした。
  • これは エンジン由来の構造問題で、モデル(Flash-Next / 27B)を替えても解決しません。
  • 次回(②エンジン比較編)では、この問題を踏まえて TensorFold と SGLang を同一条件でA/Bします。

数値は筆者環境での実測です。ドライバ・イメージ・チェックポイント・負荷で変動します。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?