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?

【手順書】dense でも MoE でも通る — sglang の未マージPR(OSCAR INT2 KV)を Escha-W2 ランタイムに統合

0
Last updated at Posted at 2026-09-15

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

以前の2本(dense版の導入 / MoE版の統合)の統合パートを1本にまとめ、実機の site-packages と素のwheelをdiffして取り直した対応表に差し替えたものです。

作業チェックリスト

  • 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 が通らないのか

  1. ツリーのレイアウトが違う。 PRは本家の現在のレイアウト、wheelは古いレイアウト。INT2デコードカーネルは kernels/ops/attention/ ↔ srt/layers/attention/triton_ops/ で、パッケージごと違います。scheduler.py は1ファイルで3,800行以上の差
  2. 触る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 qwen3dense 1.2.1 / qwen3moe 1.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)
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?