0
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?

Sonar (Aphrodite Engine) + EXL3 + GDNハイブリッド構成 検証記録

0
Posted at

Sonar (Aphrodite Engine) + EXL3 + GDNハイブリッド構成 検証記録

日付: 2026-07-15
環境: cadsv (WSL2 Ubuntu, RTX 5070 ×2 / 12GB×2)

背景

TabbyAPI(exllamav3)でQwopus3.6-27B(EXL3量子化)を運用中、OpenWebUI+Hermesの同時利用でリクエストが直列処理になる問題があった。Continuous Batchingを持つSonar(Aphrodite Engine, vLLMフォーク)への移行を検証した。

発見した2つの実バグと修正

Aphrodite-engine 0.21.0はEXL3量子化に対応しているが、Qwen3.6/Qwen3.5系のハイブリッドMamba/GDNアーキテクチャ + LoRA有効時の組み合わせで2つのバグを踏んだ。いずれもローカルパッチで解決。

バグ1: EXL3のin_proj_qkv層のshard_id不一致

症状: --enable-lora指定時、ValueError: Missing EXL3 tensors for ...in_proj_qkv: suh[0], suh[1], suh[2], ...で起動失敗。

原因: aphrodite/model_executor/models/qwen3_5.pyload_weights()は、LoRA有効時にin_proj_qkvの重みをタプル(0, 1, 2)という単一キーでロードする:

if self.enable_lora:
    stacked_params_mapping.extend([
        ("in_proj_qkv", "in_proj_qkv", (0, 1, 2)),  # ← タプル1個で1回だけ呼ばれる
        ("in_proj_z", "in_proj_z", 0),
    ])

しかしaphrodite/model_executor/layers/quantization/exl3.pyの_shard_ids_for_layer()にはLoRA無効時の統合レイヤー名in_proj_qkvz向けの分岐しかなくLoRA有効時の単体レイヤー名in_proj_qkv向けの分岐が無かったそのため汎用フォールバックに落ちて[0, 1, 2]という3つの別々の整数キーを期待してしまい実際にロードされたタプルキー(0, 1, 2)と一致しなかった

exllamav3自体のアーキテクチャ定義(architecture/qwen3_5.py)を確認したところGDN層のqkv投影は常にq+k+vを1つの融合trellis行列として量子化する仕様でありLoRA有無に関わらず一貫していることを確認した

修正(1行追加):
# aphrodite/model_executor/layers/quantization/exl3.py
if prefix.endswith("in_proj_qkv"):
    return [(0, 1, 2)]

バグ2: LoRAレイヤーのデバイス判定がEXL3を認識しない

症状: ValueError: Unsupported base layer: MergedColumnParallelLinear(...)

原因: aphrodite/lora/layers/utils.pyの_get_lora_device()量子化方式ごとに決め打ちの属性名weight, weight_packed, qweight, w2_weight等でデバイスを判定していたがEXL3の属性名(suh/svh/trellis/mcg/mul1)がリストに無かったEXL3+LoRAの組み合わせがAphrodite全体として一度も配線されていなかったことを意味する

修正:
elif hasattr(base_layer, "trellis"):
    return torch.device("cuda", torch.cuda.current_device())
(EXL3はCUDA専用実装のため常にcurrent_deviceで問題ない)

環境要因: WSL2でのlibcuda.soリンクエラー

flashinferのJITビルド時に/usr/bin/x86_64-linux-gnu-ld.bfd: cannot find -lcuda/usr/local/cudaがconda-forge由来のCUDAツールキット(exllamav3 micromamba環境)を指すシンボリックリンクになっておりそちらのdevスタブはtargets/x86_64-linux/lib/stubs/libcuda.soという非標準レイアウトだったためPyTorchが期待する$CUDA_HOME/lib64/stubs/libcuda.soが見つからなかったsonar環境専用の独立スタブディレクトリ(/usr/lib/wsl/lib/libcuda.so.1へのシンボリックリンク)を作成しLIBRARY_PATHに追加することでexl3既存環境に触れずに解決

ベンチマーク結果

モデル設定

- モデル: Qwen3.6-27B-exl3-3.08bpw (4.00bpw版も検証したが3.08bpwの方が余裕があり実用的)
- TP=2, KVキャッシュ FP8, gpu-memory-utilization=0.88
- max-model-len=81920, max-num-seqs=6
- LoRA無し(LoRA込みだとメモリ予算が厳しく別途要検討)

CUDAグラフ有効化の効果

--enforce-eager(CUDAグラフ/torch.compile無効)で最初動かしていたが速度が出なかったため通常モードに切り替えた

┌────────┬──────────────────────┬─────────────────────────┐
 並列数  eagerモード(合計t/s)  CUDAグラフ有効(合計t/s) 
├────────┼──────────────────────┼─────────────────────────┤
 1       11.23                 32.62                   
├────────┼──────────────────────┼─────────────────────────┤
 2       20.39                 60.32                   
├────────┼──────────────────────┼─────────────────────────┤
 3       32.28                 85.60                   
├────────┼──────────────────────┼─────────────────────────┤
 4       44.88                 99.81                   
├────────┼──────────────────────┼─────────────────────────┤
 5       56.06                 125.68                  
├────────┼──────────────────────┼─────────────────────────┤
 6       64.81                 146.67                  
└────────┴──────────────────────┴─────────────────────────┘

CUDAグラフ有効化で単発速度は約2.9(11.232.6 t/s)6並列合計では約2.3(64.8146.7 t/s)に向上

トレードオフとしてCUDAグラフ用バッファの分だけKVキャッシュプールが減少
- eagerモード: 143,360トークン (81,920トークン1本あたり concurrency 1.75x)
- CUDAグラフ有効: 111,177トークン ( concurrency 1.36x)

並列処理の実地検証(Continuous Batching)

1人が約54,000トークン(実測狙いは80K)のロングコンテキストを使いながら他5人がランダムな短めのリクエストを同時に投げるテスト:

- 開始直後: running=5, waiting=1(KVブロック空き待ちで1件だけ一時的に待機)
- 数秒後: running=6, waiting=0(全員着席)
- 全リクエストHTTP 200で正常完了(約60秒)

大きいリクエストが小さいリクエストを締め出さないContinuous Batchingの狙い通りの挙動を確認

精度検証

基本問題(8enable_thinking=false): 8/8正解(算数3問事実確認2問指示追従2問簡単な文章題1問)

難問(6enable_thinking=true):
- Needle-in-haystack (18,367トークン中に埋め込んだ秘密コード):  正確に抽出
- Needle-in-haystack (45,917トークン中に埋め込んだ秘密コード):  正確に抽出
- 論理パズル(3人の帽子の色):  正解
- 多段階計算(原価定価割引後価格):  正解(2通りの方法で検算していた)
- コード生成(重複除去関数):  正しい実装
- 要約(継続的バッチ処理の説明文):  自然で要点を押さえた要約

46,000トークン先の情報を正確に検索できた点は特筆に値するハイブリッドMamba/GDNアーキテクチャ(大半の層がlinear_attention4層に1層だけfull_attention)でもEXL3量子化+今回のパッチ込みで長距離検索が機能することの実証になった

繰り返し(ループ)チェック: n-gram分析で3つの長文回答(734800トークン)を検査繰り返しループは検出されずmax_tokens不足で打ち切られたケースも単に丁寧な多角的検討の途中で切れただけで退化的な繰り返しではなかった

検証方法の反省点

論理パズルの正誤判定で期待値を自分で間違えて設定した上に(正解はなのにと誤記)、「期待文字列が回答全文に含まれるかという単純な部分一致で判定していたためモデルの思考過程中に別の色への言及があったことで偶然一致と誤判定していたreasoningモデルの出力を自動採点する際は思考過程を含む全文ではなく最終回答部分を明確に切り出して照合する必要がある

プリフィルチャンクサイズ(--max-num-batched-tokens)の比較

Continuous Batchingではchunked prefillがデフォルトで有効(enable_chunked_prefill=True)チャンクサイズによって公平性総スループットがトレードオフになることを1人が約54,000トークンの巨大リクエスト他5人が短いリクエストという混在ワークロードで実測した

┌──────────────────┬──────────────────────┬──────────────────────────┐
    chunk size     完了時刻の幅(公平性)  全体完了(総スループット) 
├──────────────────┼──────────────────────┼──────────────────────────┤
 256               約60.9(最悪)        72.1(最遅)             
├──────────────────┼──────────────────────┼──────────────────────────┤
 512               約0.95              59.2                   
├──────────────────┼──────────────────────┼──────────────────────────┤
 2048(デフォルト)  約5.48              51.1                   
├──────────────────┼──────────────────────┼──────────────────────────┤
 4096              約0.89(最良)        52.7(僅差2位)          
└──────────────────┴──────────────────────┴──────────────────────────┘

256まで小さくすると公平性速度の両方が悪化する行き過ぎ領域に入る(1ステップあたりのスケジューリングオーバーヘッドが支配的になるためと推測)4096が本ワークロードでは最もバランスが良く最終構成に採用した

同時ユーザー数(--max-num-seqs)の調整

--max-num-seqsを610に増やしたところプロファイリング時の見積もりメモリが増えKV不足でクラッシュ(max-model-len=81920との両立に必要な予算が足りずgpu-memory-utilizationを上げてもXwayland使用量の変動で不安定)max-model-len=81920を維持したままmax-num-seqs=8まで妥協した結果無事起動動作を確認

最終構成での再検証(N=18スケーリング)

--tensor-parallel-size 2 --kv-cache-dtype fp8 --gpu-memory-utilization 0.88
--max-model-len 81920 --max-num-seqs 8 --max-num-batched-tokens 4096
--enable-auto-tool-choice --tool-call-parser qwen3_coder

┌────────┬─────────┬──────────────┐
 並列数  合計t/s  1人あたりt/s 
├────────┼─────────┼──────────────┤
 1       31.92    31.92        
├────────┼─────────┼──────────────┤
 2       60.81    30.41        
├────────┼─────────┼──────────────┤
 3       88.44    29.48        
├────────┼─────────┼──────────────┤
 4       117.24   29.31        
├────────┼─────────┼──────────────┤
 5       134.47   26.89        
├────────┼─────────┼──────────────┤
 6       156.57   26.10        
├────────┼─────────┼──────────────┤
 7       185.75   26.54        
├────────┼─────────┼──────────────┤
 8       194.11   24.26        
└────────┴─────────┴──────────────┘

max-num-seqs引き上げ後大小混在テストも再実施完了時刻の幅が約0.41全体完了49.79秒とこれまでで最良の結果(公平性速度とも)を記録した

tool-calling / プロキシの本番反映

- --enable-auto-tool-choice --tool-call-parser qwen3_coderを追加し実際にfunction callingがfinish_reason: "tool_calls"で正しく動作することを確認
- 既存のOpenWebUI向けプロキシ(8082=nothink, 8083=think)GSQOSCARSonarと過去の移行を経てTARGET_MODELが古いパスに固定されたまま放置されておりSonarへの切り替え後は実際には全リクエストが404していたことが判明動的モデルID取得(get_model_real())を使うよう修正し復旧

upstreamへの還元

発見した2つのバグはdphnAI/sonar(Aphrodite-engine)にPRとして提出済み:
- PR #1711 — EXL3 in_proj_qkv shard_id不一致の修正
- PR #1712 — LoRAデバイス判定にEXL3を追加

最終結論

- EXL3 + Qwen3.6ハイブリッドMamba/GDN + Aphrodite-engineの組み合わせは2箇所のローカルパッチで動作した(upstreamには未報告のバグPR提出済み)
- 3.08bpw量子化 + CUDAグラフ有効 + チャンクサイズ/同時ユーザー数のチューニングで精度を落とさずに実用的な速度を達成: 単発約32 t/s8並列で合計約194 t/s
- 46Kトークンの長距離検索も正確精度劣化なし
- tool-callingOpenWebUI向けプロキシも含めて本番投入可能な状態まで到達
- 課題: LoRA有効時はメモリ予算がさらに厳しくなり81920トークン級の大きいコンテキストとの両立は要調整2×12GBという比較的小さいVRAM構成ではフレームワークオーバーヘッド(CUDA context/NCCL/コンパイルバッファ等vLLM本家由来で約2.2GiB)の存在が効いてくる
0
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
0
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?