12GBのコンシューマGPU 2枚で、dense 27B を192Kコンテキスト、MoE 35B-A3B を262Kコンテキストで運用しています。条件は同じで、KVキャッシュを2bit(OSCAR INT2)にすることです。
ところがこの機能、配布物としてどこにも存在しません。Escha ランタイム(sglangフォーク)のwheelには入っておらず、OSCAR INT2 KV は sglang本家の未マージPR #32129 にしかなく、しかも両者はツリーが別物です。自分で統合するしかありません。
この記事は、その作業を上から順にコピペしていけば終わる手順書にしたものです。dense版・MoE版の両方に対応しています。
📦 パッチ・対応表・スクリプト一式
https://github.com/Nakanokensetsu/sglang
作業チェックリスト
- STEP 0. 前提を確認する(5分)
- STEP 1. 環境とランタイムを入れる(15分)
- STEP 2. モデルを落とす(回線次第)
- STEP 3. PR #32129 を取得する(5分)
- STEP 4. そのままコピーできる3本を入れる(1分)
- STEP 5. 手でマージする6本を移植する(ここが半日)
- STEP 6. 型修正2点を入れる(10分)
- STEP 7. 回転行列を作る(1分)
- STEP 8. パッチを当てる(5分)
- STEP 9. 統合できたか検証する(1分)
- STEP 10. 起動して動作確認(10分)
STEP 5以外はコピペで終わります。 STEP 5だけはdiffを読んで関数を移す手作業で、ここに半日かかります。CUDAカーネルを書ける必要はありません。
対象
| dense | MoE | |
|---|---|---|
| モデル | EschaLabs/Qwen3.8-27B-Escha-W2 |
EschaLabs/Qwen3.6-35B-A3B-Escha-W2 |
| ランタイム |
escha-runtime-qwen3dense(1.2.1) |
escha-runtime-qwen3moe(1.0.2) |
| 層数 / full-attention層 | 64 / 16 | 40 / 10 |
TP=2時のkv_group_num
|
6 | 8 |
| 到達コンテキスト | 196,608 | 262,144 |
以降、MoEを例にコマンドを書きます。dense版はモデル名とランタイム名を置き換えてください(差分はSTEP 10と付録Dにまとめてあります)。
STEP 0. 前提を確認する
python3.12 --version # Python 3.12.x であること(wheelは cp312 のみ)
ldd --version | head -1 # glibc 2.28 以上
nvidia-smi --query-gpu=name,memory.total --format=csv
- GPUは合計24GB以上(12GB×2で検証)
- ディスク空き15GB程度
-
CUDA toolkitは不要(
ptxasは triton に同梱) - OSはWSL2 / Ubuntuで検証(素のLinuxでも可)
STEP 1. 環境とランタイムを入れる
python3.12 -m venv .venv && source .venv/bin/activate
pip install -U pip wheel
pip install -U "huggingface_hub[cli]" # 次のSTEPで使う hf コマンド
# ★ torch は厳密固定(下の注意を必ず読む)
pip install "torch==2.9.*" --index-url https://download.pytorch.org/whl/cu128
# ランタイム(同梱sglangフォークと依存一式)
hf download EschaLabs/escha-runtime-qwen3moe --local-dir ./escha-runtime-qwen3moe
pip install ./escha-runtime-qwen3moe/sglang/escha-*.whl
# 以降すべての基準パス
SP=$(python -c "import sglang, pathlib; print(pathlib.Path(sglang.__file__).parent.parent)")
echo "$SP"
✅ 確認
python -c "import torch, escha, sglang; print(torch.cuda.is_available(), escha.__version__)"
# True <version> と出ればOK
torch==2.9.* は提案ではなくハード要件です。 torch>=2.9 と書くと新しいマイナーに解決され、import escha が undefined symbol: _ZN3c10... で落ちます。コンパイル済み拡張がlibtorchのABIにリンクされていて、そのABIはPyTorchのマイナー間で互換がありません。
同じ環境に本家sglangを入れないでください。 wheelが入れる sglang パッケージは本家とは別物で、上書きし合います。
STEP 2. モデルを落とす
hf download EschaLabs/Qwen3.6-35B-A3B-Escha-W2 --local-dir ./Qwen3.6-35B-A3B-Escha-W2
# dense版: hf download EschaLabs/Qwen3.8-27B-Escha-W2 --local-dir ./Qwen3.8-27B-Escha-W2
✅ 確認
du -sh ./Qwen3.6-35B-A3B-Escha-W2 # MoE 約12GB / dense 約9.5GB
python -c "
import json; c=json.load(open('./Qwen3.6-35B-A3B-Escha-W2/config.json'))
c=c.get('text_config',c); print('head_dim=',c['head_dim'],'layers=',c['num_hidden_layers'])"
# head_dim= 256 layers= 40 (dense版は layers= 64)
この2つの数字はSTEP 7で使います。text_config の下に入れ子になっているので注意。
STEP 3. PR #32129 を取得する
git clone https://github.com/sgl-project/sglang.git sglang-pr32129
cd sglang-pr32129
git fetch origin pull/32129/head:oscar-int2-kv
git checkout oscar-int2-kv
git rev-parse HEAD # 記録しておく。本記事の対応表は 4b28b26 時点
PRは動きます。取得したコミットを必ず記録してください。
STEP 4. そのままコピーできる3本を入れる
wheel側に存在しないファイルなので、コピーするだけです(dense/MoE共通)。
# sglang-pr32129 ディレクトリの中で実行
# ★ QuantKernel パッケージは wheel 側に存在しないので、自分で作る
mkdir -p "$SP/sglang/QuantKernel"
: > "$SP/sglang/QuantKernel/__init__.py" # 空ファイル。理由は下の注意
for f in sglang/QuantKernel/oscar_rotation_clip_int2_kv.py \
sglang/srt/layers/attention/quantized_kv_prefill.py \
sglang/srt/mem_cache/kv_quant_kernels.py; do
cp -v "python/$f" "$SP/$f"
done
PR側の QuantKernel/__init__.py をコピーしてはいけません。 あれは fused_hadamard_int2_kv と gpu_flush_int2 を import しますが、この構成ではその2つを移植しないので ImportError になります。空の __init__.py を置いてください(本番環境も0バイトです)。
✅ 確認
ls -1 "$SP"/sglang/QuantKernel/__init__.py \
"$SP"/sglang/QuantKernel/oscar_rotation_clip_int2_kv.py \
"$SP"/sglang/srt/layers/attention/quantized_kv_prefill.py \
"$SP"/sglang/srt/mem_cache/kv_quant_kernels.py
python -c "import sglang.QuantKernel.oscar_rotation_clip_int2_kv; print('import OK')"
STEP 5. 手でマージする6本(ここが本番)
escha側のファイルをベースに、PR側のOSCAR関連の追加分だけを移植します。 逆(PR側で上書き)は絶対にやらないでください。escha固有の改造が消えてランタイムが壊れます。
作業は1ファイルずつ、この表の「移植するもの」を目印に進めます。行数は「稼働中のインストール ↔ 素のwheel」の差分(各環境の他の自前パッチも含むので規模感の目安)。
| # | ファイル | Δ dense | Δ MoE | 移植するもの |
|---|---|---|---|---|
| 1 | srt/environ.py |
11 | 12 |
SGLANG_OSCAR_{K,V}_ROTATION_PATH、SGLANG_OSCAR_{K,V}_CLIP_RATIO、SGLANG_LLOYD_MAX の定義 |
| 2 | srt/server_args.py |
51 | 11 |
--kv-cache-dtype の choices に int2 を追加/新規引数 --kv-cache-quant-group-size とフィールド |
| 3 | srt/model_executor/model_runner.py |
5 | 5 |
kv_cache_dtype == "int2" の分岐 |
| 4 | srt/model_executor/model_runner_kv_cache_mixin.py |
71 | 52 | KVプール生成のint2分岐、kv_cache_quant_group_size の受け渡し |
| 5 | srt/mem_cache/memory_pool.py |
432 | 411 |
OscarRotationConfig / load_oscar_rotation_config() / load_oscar_rotations() / _resolve_quant_grouping() / _allocate_scales_zeros_buffers() / get_{key,value}_scales_zeros() / get_raw_{key,value}_buffer() / get_oscar_rotation() / dtype == "int2" の確保パス |
| 6 | srt/layers/attention/triton_backend.py |
300 | 292 |
_forward_extend_int2() とディスパッチ(is_int2 = getattr(kv_pool, "dtype", None) == "int2") / _apply_oscar_rotation / apply_inverse_v_rotation
|
| 7 | srt/layers/attention/triton_ops/decode_attention.py |
1,494 | 1,494 | INT2デコードカーネル一式(下記) |
区分はdense版・MoE版で完全に一致します。
7番(decode_attention)だけ、取得元のパスが違います
| パス | |
|---|---|
| PR側 | python/sglang/kernels/ops/attention/decode_attention.py |
| wheel側(コピー先) | sglang/srt/layers/attention/triton_ops/decode_attention.py |
パッケージが違うので、ファイルを移すだけでは import が解決しません。 次のシンボルを移植し、importと周辺ヘルパ名を移植先のツリーに合わせて書き直します。
_fwd_kernel_stage1_quant_int2
_fwd_grouped_kernel_stage1_quant_int2
_decode_att_m_fwd_quant_int2
_decode_grouped_att_m_fwd_quant_int2
decode_attention_fwd_normal_quant_int2
decode_attention_fwd_grouped_quant_int2
decode_attention_fwd_quantized
_get_scale_group_size / _get_shared_kv_scale_group_size
PR側の decode_attention_fwd_int2_unified(ディスパッチャ)は不要です。wheel側は triton_backend.py から2つのエントリポイントを直接呼びます。
移植しない5本
PR #32129 は他に次も触りますが、手を出さないでください。
| ファイル | 理由 |
|---|---|
srt/mem_cache/kv_cache_dtype.py |
wheel側のツリーに存在しない |
srt/mem_cache/unified_kv_pool.py |
同上(sglangの新しいKVプール抽象) |
srt/models/{qwen3,glm4_moe,utils}.py |
Escha-W2には不要 |
QuantKernel/gpu_flush_int2.py |
不要(decode側のclip変種) |
QuantKernel/fused_hadamard_int2_kv.py |
不要(融合Hadamard版) |
QuantKernel/__init__.py(PR側) |
上2つをimportするので入れると壊れる。空ファイルで置き換える |
なお、プール内部のデータ不要Hadamardフォールバックも移植しません。そのためSTEP 7の回転行列パス指定が必須になります。
STEP 6. 型修正2点を入れる
対応表どおり移植しても、これが無いと起動しません。
① srt/model_executor/model_runner_kv_cache_mixin.py の get_cell_size_per_token
if self.kv_cache_dtype == "int2":
kv_size = 0.25 # 2bit = 0.25 byte/要素
else:
kv_size = torch._utils._element_size(self.kv_cache_dtype)
"int2" は文字列でPyTorchのdtypeではないので、そのまま _element_size() に渡すと TypeError で落ちます。
② 同ファイルの profile_max_num_token
return int(int(rest_memory * (1 << 30)) // cell_size)
①で cell_size がfloatになり int // float がfloatになるため、プールのshapeにfloatが混ざって torch.zeros が TypeError を投げます。
STEP 7. 回転行列を作る
K・V両方のパス指定が必須です(フォールバックを移植していないため、未設定だとサーバが起動を拒否します)。較正なし(Hadamard)なら較正データもGPUも重みも不要です。
git clone https://github.com/Nakanokensetsu/sglang.git escha_oscar_repo
python escha_oscar_repo/escha_oscar/tools/make_hadamard_rotations.py \
--config ./Qwen3.6-35B-A3B-Escha-W2/config.json \
--out-dir ./oscar_rotations
# head_dim=256 num_layers=40
# wrote ./oscar_rotations/k_rotation_hadamard_hd256.pt
# wrote ./oscar_rotations/v_rotation_hadamard_hd256.pt
export SGLANG_OSCAR_K_ROTATION_PATH=$PWD/oscar_rotations/k_rotation_hadamard_hd256.pt
export SGLANG_OSCAR_V_ROTATION_PATH=$PWD/oscar_rotations/v_rotation_hadamard_hd256.pt
スクリプトを使わず手で作る場合(20行)
import math, pathlib, torch
def hadamard(n):
h = torch.ones(1, 1)
while h.shape[0] < n:
h = torch.cat([torch.cat([h, h], 1), torch.cat([h, -h], 1)], 0)
return h
head_dim, num_layers = 256, 40 # config.json の text_config から
rot = (hadamard(head_dim) / math.sqrt(head_dim)).to(torch.float32)
assert (rot @ rot.T - torch.eye(head_dim)).abs().max().item() == 0.0
state = {"layers": {i: {"rotation": rot.clone()} for i in range(num_layers)}}
out = pathlib.Path("./oscar_rotations"); out.mkdir(parents=True, exist_ok=True)
for name in ("k", "v"):
torch.save(state, out / f"{name}_rotation_hadamard_hd{head_dim}.pt")
直感に反するが正しい点が2つ
-
層数ぶん作るが、使われるのは一部の層だけ。 ハイブリッドモデルなので
layer_typesがfull_attentionの層(dense 64中16、MoE 40中10)だけがKVキャッシュを持ちます。参照は層インデックスなので、全層ぶん作るのが正解で、多いぶんには無害 - 全層が同じ行列。 較正なしの回転は構成上どの層でも同じです。層ごとに違えば較正版だと一目で分かります
較正はモデル次第です。必ず測ってから入れてください。 OSCAR論文は較正なしの大きな劣化を報告していますが(Qwen3-32Bで平均 -66.29pt)、MoE版のチェックポイントでは逆でした。長文の識別テスト41問で 較正なし 40/41 に対し較正あり 37/41、出現順の完全列挙タスクの再現率が 0.969 → 0.819 に低下。dense版の本番は較正ありで回しており、勝敗はチェックポイント次第です。
なお、この差は needle-in-a-haystack では全く検出できません(どちらも15/15)。
STEP 8. パッチを当てる
本番投入に必要だった修正3系統(詳細は付録B)です。-p1 です。-p0 では1本も当たりません。
cd "$SP"
for p in /path/to/escha_oscar_repo/escha_oscar/patches/*.patch; do
patch -p1 --batch --backup --suffix=.orig < "$p"
done
patching file ... が6行出れば成功。他の出力が出た場合:
| 出力 | 意味 |
|---|---|
can't find file to patch |
ディレクトリ違い、または -p0 を使っている |
Hunk #1 FAILED(quantized_kv_prefill / kv_quant_kernels) |
STEP 4のコピーが未実施 |
Hunk #1 FAILED(triton_backend / decode_attention) |
STEP 5のマージ内容が本記事と違う。そのhunkだけ手で入れる(小さい) |
Reversed (or previously applied) |
適用済み。n で抜ける |
dense版では6本中3本が当たりません。 公開しているパッチはMoEのwheel向けに切ってあり、dense側ツリーでは triton_backend・schedule_policy・schedulerの2つ目のhunk が失敗します(decode_attention も fuzz 付き)。変更内容はどちらでも正しく、コンテキスト行が合わないだけです。無理に当てず、手で移植してください。
STEP 9. 統合できたか検証する
リポジトリのチェックスクリプトで、必要なファイルとシンボルが揃っているか一括確認できます。
bash escha_oscar_repo/escha_oscar/tools/check_integration.sh
OK コピー: QuantKernel/oscar_rotation_clip_int2_kv.py
OK コピー: srt/layers/attention/quantized_kv_prefill.py
OK コピー: srt/mem_cache/kv_quant_kernels.py
OK environ: 回転行列の環境変数
OK server_args: int2 の選択肢
OK server_args: グループサイズ引数
OK model_runner: int2 分岐
OK kv_cache_mixin: int2 分岐
OK memory_pool: 回転行列ローダ
OK memory_pool: 回転行列取得
OK triton_backend: int2 prefill
OK decode: int2 カーネル(normal)
OK decode: int2 カーネル(grouped)
OK decode: _safe_block_h パッチ
=> すべて揃っています
import も通るか確認します(STEP 7のexportをした状態で)。
python - <<'EOF'
from sglang.srt.mem_cache.memory_pool import load_oscar_rotation_config
from sglang.srt.layers.attention.triton_ops.decode_attention import (
decode_attention_fwd_grouped_quant_int2,
)
print("OSCAR INT2 present:", load_oscar_rotation_config())
EOF
| 結果 | 意味 |
|---|---|
OscarRotationConfig(k_rotation_path=...) が出る |
統合完了 |
ImportError |
STEP 4またはSTEP 5が未完 |
ValueError: ... requires both SGLANG_OSCAR_..._ROTATION_PATH |
統合は成功。STEP 7のexportが今のシェルに無いだけ |
STEP 10. 起動して動作確認
MoE版(262K)
NVLIBS=$(python -c "import nvidia, pathlib; print(pathlib.Path(nvidia.__file__).parent)")
export LD_LIBRARY_PATH="$(find "$NVLIBS" -maxdepth 2 -type d -name lib | paste -sd: -):/usr/lib/wsl/lib"
export MODEL=$PWD/Qwen3.6-35B-A3B-Escha-W2
export PORT=8081 HOST=127.0.0.1
export MEM=0.88 CTXLEN=262144 MAXMAMBA=64 MAXREQ=16 RADIX=1
export MAMBA_SCHED=extra_buffer ATTN_BACKEND=triton
export GRAPHS=1 CUDA_GRAPH_BS="1 2 4 8 16"
export SGLANG_CHUNK_DECODE_CAP=1536 SGLANG_CHUNK_FAIR_FLOOR=256
export ENABLE_CLP=1 # custom logit processor。思考予算の制御を使うなら必須
export SGLANG_OSCAR_K_ROTATION_PATH=$PWD/oscar_rotations/k_rotation_hadamard_hd256.pt
export SGLANG_OSCAR_V_ROTATION_PATH=$PWD/oscar_rotations/v_rotation_hadamard_hd256.pt
bash ./escha-runtime-qwen3moe/sglang/serve.sh \
--tp-size 2 --ep-size 2 \
--kv-cache-dtype int2 --kv-cache-quant-group-size 32 \
--enable-metrics --enable-cache-report --allow-auto-truncate
dense版(192K)
export MODEL=$PWD/Qwen3.8-27B-Escha-W2
export PORT=30000
export MEM=0.87 CTXLEN=196608 CHUNK=8192 MAXMAMBA=28 MAXREQ=12 RADIX=1
export ATTN_BACKEND=triton
export CUDA_GRAPH_BS="1 2 4 8 12"
export SGLANG_OSCAR_K_ROTATION_PATH=$PWD/oscar_rotations/k_rotation_hadamard_hd256.pt
export SGLANG_OSCAR_V_ROTATION_PATH=$PWD/oscar_rotations/v_rotation_hadamard_hd256.pt
bash ./escha-runtime-qwen3dense/sglang/serve.sh \
--tp-size 2 \
--kv-cache-dtype int2 --kv-cache-quant-group-size 64 \
--max-total-tokens 220000 \
--triton-attention-num-kv-splits 64 \
--enable-mixed-chunk \
--mamba-ssm-dtype float16 --mamba-scheduler-strategy extra_buffer \
--watchdog-timeout 600
✅ 確認1: 起動ログ
KV Cache is allocated. #tokens: 350257, K size: 0.21 GB, V size: 0.21 GB
max_total_num_tokens=350257, ... max_running_requests=12, context_len=262144
K size がGB単位の大きな数字なら、int2が効いていません。 起動は約33秒。
✅ 確認2: 応答
curl -s http://127.0.0.1:8081/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"escha-qwen36-35b-a3b-w2",
"messages":[{"role":"user","content":"1+1は?"}],
"max_tokens":64}' | python -m json.tool
✅ 確認3: 最大長を1本流してGPUピークを測る(これを省かないでください。理由は付録C①)
nvidia-smi --query-gpu=memory.used,utilization.gpu,power.draw --format=csv -l 2
起動しないとき
| 症状 | 原因 |
|---|---|
ValueError: Oscar int2 KV cache requires both SGLANG_OSCAR_..._ROTATION_PATH |
STEP 7のexportが今のシェル(またはsystemdユニット)に無い |
| 重みロード中にCUDA OOM | MoEで --ep-size 2 が無い(TPだけだとexpert重みが両GPUに複製され11.06GB) |
| flashinfer内部のassert |
ATTN_BACKEND=triton が無い(sm_120では必須) |
CUDA error: invalid configuration argument |
MoEで --enable-mixed-chunk を付けている(int2カーネルと衝突。dense版は使用可) |
| 起動するが利用率100%・消費電力40Wで激遅 | VRAMの崖(付録C①)。MEM を下げる |
ModuleNotFoundError: sglang.QuantKernel |
STEP 4の mkdir / 空 __init__.py が抜けている |
ImportError: cannot import name ... fused_hadamard_int2_kv |
PR側の QuantKernel/__init__.py をコピーしてしまっている |
The server is not configured to enable custom logit processor |
ENABLE_CLP=1 が無い(思考予算を使う場合) |
付録A. なぜ git apply が通らないのか
-
ツリーのレイアウトが違う。 PRは本家の現在のレイアウト、wheelは古いレイアウト。INT2デコードカーネルは
kernels/ops/attention/↔srt/layers/attention/triton_ops/で、パッケージごと違います。scheduler.pyは1ファイルで3,800行以上の差 - 触る10本のうち6本がescha改造済みファイル。 上書きするとランタイムが壊れます
つまりこれはパッチ適用ではなくマージです。
付録B. パッチ3系統の中身
| パッチ | 効果 |
|---|---|
| INT2 prefillの一時メモリ削減(3ファイル) | 192Kで −746MB(−37%)。展開したprefixを3重に持っていた(3P+2E)のを、まとめて確保して out= で直接dequantする(P+E)。長コンテキストと並列8を両立させている要因 |
| チャンク長の抑制(2ファイル) | 長文prefill中に他のストリームの生成が止まる問題。トークン間隔 5.1秒×21回 → 0.14秒。既定値0で従来と完全に同一挙動 |
_safe_block_h |
静かな正しさのバグ(下記) |
_safe_block_h: assertもNaNも出ないバグ
grouped decodeのカーネルはヘッドをこう見ています。
VALID_BLOCK_H = min(BLOCK_H, kv_group_num)
cur_head = cur_head_id * VALID_BLOCK_H + arange(BLOCK_H)
cur_kv_head = cur_head_id // cdiv(kv_group_num, BLOCK_H)
cur_head はフラットなクエリヘッド番号、cur_kv_head はブロック番号由来。一致するのはヘッドブロックが1つのKVグループに収まるとき、つまり BLOCK_H >= kv_group_num または kv_group_num % BLOCK_H == 0 のときだけです。外すと一部のクエリヘッドが別のKVヘッドのキャッシュを読みます。assertも落ちず、NaNも出ず、ベンチのスコアにしか出ません。
dense版でこそ踏みます。
kv_group_num(TP=2、1GPUあたり) |
||
|---|---|---|
| dense 27B | 12 q / 2 kv = 6 | 2のべき乗でない → 危険 |
| MoE 35B-A3B | 8 q / 1 kv = 8 | 2のべき乗 → 安全 |
本家は BLOCK_H = 16 をハードコードしているため今まで表面化していませんが、BLOCK_H をバッチサイズ依存にした瞬間に踏みます。_safe_block_h は2のべき乗に切り上げて VALID_BLOCK_H == kv_group_num(KVヘッドごとに1ブロック)を保証します。
付録C. 統合後に待っている落とし穴
① VRAMの崖
int2 KVはプールの外でVRAMを使います。full-attention層はprefillのチャンクごとに「それまでの全KV履歴」をfp16に復元し、そのワークスペースはシーケンス長に線形比例します(dense/192Kで実測 +1,890MB)。
このカードでは約 11,830 MiB を超えると崖に落ち、利用率100%・消費電力40W(定格250W)で詰まります。WSLでは確保エラーとして表面化せず「なんか遅い」にしか見えません。二値でもなく、触れた時点で速度に効きます(コールド192K: 907 → 705 tok/s)。
「起動できた」と「実際に流せる」は別物です。 プールのトークン数ではなく、最大長を1本流したときのGPUピークを必ず測ってください。
② hybrid SSMのprefix cacheには容量条件がある
prefix再利用にはKVだけでなく同じ位置のmamba再帰状態のチェックポイントが必要で、それはチャンク境界でしか作られません。
必要チェックポイント数 ≈ KVプールのトークン数 ÷ chunked_prefill_size
足りないと #cached-token: 0 が延々と出ます(エラーは一切出ません)。「当たらないからプールを増やす」は完全に逆効果で、正しい方向はCHUNKを上げることです。dense版では条件を満たした結果、192Kの再送が 212.5秒 → 1.7秒 になりました。
③ wheelを入れ直すと全部消える
統合もパッチも site-packages を直接書き換えています。pip install し直した時点で消えます。.orig バックアップを残すか、修正版を PYTHONPATH のオーバーレイに置いてください。
付録D. dense / MoE 差分一覧
| dense 27B | MoE 35B-A3B | |
|---|---|---|
| wheel | escha-1.2.1+qwen3dense |
escha-1.0.2+qwen3moe |
| 層数 / full-attention層 | 64 / 16 | 40 / 10 |
| head_dim | 256 | 256 |
kv_group_num(TP=2) |
6(_safe_block_h 必須) |
8 |
| 並列化 | --tp-size 2 |
--tp-size 2 --ep-size 2(必須) |
| KV量子化グループ | --kv-cache-quant-group-size 64 |
32 |
--enable-mixed-chunk |
使える | クラッシュ |
--triton-attention-num-kv-splits |
64(既定8は長文脈decodeで約8%損) | 既定 |
--mamba-ssm-dtype |
float16(実質必須) |
既定 |
| 本番の回転行列 | 較正あり | 較正なし(Hadamard) |
| コンテキスト | 196,608 | 262,144 |
| 統合の10ファイル区分 | 完全に同じ | 完全に同じ |
まとめ
- 配布wheelに他人のPRを載せる作業は、パッチ適用ではなくマージ。ファイル単位に「コピー3 / 手マージ6 / 移植しない5」を判断する
-
dense版とMoE版で、10ファイルの区分は完全に一致した。違うのは起動フラグと
kv_group_num由来の注意点 - 回転行列は較正不要のHadamardで始める。論文と逆の結果が出ることがあるので必ず測る。needleでは測れない
- 匿名化した
.patchは-p0で当たらない。patchはスキップして進むので気付けない - 「動いた」の確認は チェックスクリプト・起動ログのKVサイズ・最大長1本のGPUピークの3点で
📦 パッチ・対応表・スクリプト一式
https://github.com/Nakanokensetsu/sglang
環境
- GPU: RTX 5070 12GB × 2(TP=2、WSL2)
- Python 3.12 / torch 2.9系(cu128)
- ランタイム: Escha Runtime
qwen3dense1.2.1 /qwen3moe1.0.2(SGLangフォーク同梱、Apache-2.0) - モデル: EschaLabs/Qwen3.8-27B-Escha-W2、EschaLabs/Qwen3.6-35B-A3B-Escha-W2(いずれも2bit)
- KVキャッシュ: OSCAR int2(dense グループ64 / MoE グループ32)