qwen3.5-122B-A10B on DGX Spark: vLLM + DFlash + dense-bandwidth stack, one-shot installer
This commit is contained in:
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
# mtp_serve.sh — native qwen3_5 MTP-N head for the comparison (`--profile mtp`).
|
||||
# The MTP head (1 layer, reuses target KV/embed/lm_head) is in the Intel
|
||||
# checkpoint (mtp.layers.0) — no separate drafter, no unify patch needed.
|
||||
# $1 = num_speculative_tokens (default 2 = the "MTP-2" recipe); $2 = backend.
|
||||
set -euo pipefail
|
||||
NSPEC="${1:-2}"
|
||||
BACKEND="${2:-flash_attn}"
|
||||
MODEL="${MODEL:-Intel/Qwen3.5-122B-A10B-int4-AutoRound}"
|
||||
MAX_MODEL_LEN="${MAX_MODEL_LEN:-16384}"
|
||||
GPU_MEM="${GPU_MEM:-0.8}"
|
||||
PORT="${PORT:-8000}"
|
||||
echo "[mtp] qwen3_5_mtp — backend=$BACKEND, num_speculative_tokens=$NSPEC, model=$MODEL"
|
||||
exec vllm serve "$MODEL" \
|
||||
--served-model-name qwen \
|
||||
--host 0.0.0.0 --port "$PORT" \
|
||||
--max-model-len "$MAX_MODEL_LEN" \
|
||||
--max-num-seqs 16 \
|
||||
--max-num-batched-tokens "$MAX_MODEL_LEN" \
|
||||
--gpu-memory-utilization "$GPU_MEM" \
|
||||
--no-enable-prefix-caching \
|
||||
--enable-chunked-prefill \
|
||||
--trust-remote-code \
|
||||
--attention-backend "$BACKEND" \
|
||||
--speculative-config "{\"method\":\"qwen3_5_mtp\",\"num_speculative_tokens\":$NSPEC,\"model\":\"$MODEL\"}"
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
"""spark-fla-shmem: let the FLA (flash-linear-attention / GDN) Triton kernels use their
|
||||
BIG tiles on sm121 (GB10 / DGX Spark).
|
||||
|
||||
The FLA Backend gate (vllm/model_executor/layers/fla/ops/utils.py, identical in SGLang)
|
||||
picks big tiles only if device_max_shared_mem >= Backend.DEFAULT (102400 = 100 KiB):
|
||||
cumsum.py : BS_LIST = [32,64] if check_shared_mem() else [16,32]
|
||||
chunk_o.py: BKV_LIST = [64,128] if check_shared_mem() else [32,64]
|
||||
sm121 reports max_shared_mem = 101376 (99 KiB), JUST below 102400 -> check returns False
|
||||
-> small tiles -> slower GDN/linear-attention (the per-token hot path). But 101376 is
|
||||
EXACTLY Backend.ADA, and RTX 4090 (ADA, same 99 KiB) runs the big tiles fine, so they
|
||||
provably fit in 99 KiB. Lower DEFAULT to 101376 so sm121 (and ADA) pass the gate.
|
||||
"""
|
||||
import pathlib, sys
|
||||
|
||||
P = pathlib.Path(
|
||||
"/usr/local/lib/python3.12/site-packages/vllm/model_executor/layers/fla/ops/utils.py"
|
||||
)
|
||||
src = P.read_text()
|
||||
if "spark-fla-shmem" in src:
|
||||
print("[patch_fla_shmem] already patched", flush=True)
|
||||
sys.exit(0)
|
||||
|
||||
OLD = " DEFAULT = 102400 # Default"
|
||||
NEW = " DEFAULT = 101376 # spark-fla-shmem: 102400->101376 so sm121 GB10 (99 KiB) uses big GDN tiles (already fit on ADA's identical 99 KiB)"
|
||||
|
||||
if OLD not in src:
|
||||
print("[patch_fla_shmem] ERROR: target line not found — FLA source differs", flush=True)
|
||||
sys.exit(1)
|
||||
|
||||
P.write_text(src.replace(OLD, NEW))
|
||||
print("[patch_fla_shmem] patched OK (DEFAULT 102400 -> 101376; big GDN tiles on sm121)", flush=True)
|
||||
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python3
|
||||
"""spark-dflash-hybrid-fp8: port of albond's hybrid INT4+FP8 dispatch
|
||||
(patches/01-hybrid-int4-fp8/inc.py.patch) onto AEON 0.23's vllm INCConfig.
|
||||
|
||||
Adds, to vllm/model_executor/layers/quantization/inc.py:
|
||||
* INCConfig.fp8_config / fp8_layers fields
|
||||
* maybe_update_config OVERRIDE — AEON 0.23 signature (model_name, hf_config=None,
|
||||
revision=None); the base hook is already CALLED from config/vllm.py:634, so we
|
||||
only supply the override. Scans the checkpoint's safetensors metadata for
|
||||
float8_e4m3fn weights that have a .weight_scale_inv, builds an Fp8Config, and
|
||||
records those layer prefixes.
|
||||
* _is_layer_fp8 — exact + fused + substring match against fp8_layers
|
||||
* FP8 dispatch at BOTH dense short-circuits: get_quant_method's extra_config
|
||||
override AND the apply_*_quant_layer not-quantized blocks.
|
||||
|
||||
Idempotent; sentinel 'spark-dflash-hybrid-fp8'. Mirrors patch_unify2.py's style.
|
||||
"""
|
||||
import sys
|
||||
|
||||
import vllm.model_executor.layers.quantization.inc as inc_mod
|
||||
|
||||
path = inc_mod.__file__
|
||||
src = open(path).read()
|
||||
SENT = "spark-dflash-hybrid-fp8"
|
||||
if SENT in src:
|
||||
print(f"[patch_inc_hybrid] already applied: {path}")
|
||||
sys.exit(0)
|
||||
|
||||
# 1. __init__ fields (anchor unique: only INCConfig has pack_factor = Fraction(32,..))
|
||||
a1 = " self.pack_factor = Fraction(32, weight_bits)\n"
|
||||
b1 = a1 + (
|
||||
" # spark-dflash-hybrid-fp8: populated by maybe_update_config\n"
|
||||
" self.fp8_config = None\n"
|
||||
" self.fp8_layers = set()\n"
|
||||
)
|
||||
assert src.count(a1) == 1, f"anchor1 count={src.count(a1)}"
|
||||
src = src.replace(a1, b1)
|
||||
|
||||
# 2. apply_vllm_mapper: remap fp8_layers (HF->vLLM names) after extra_config remap
|
||||
a2 = (
|
||||
" if self.extra_config is not None:\n"
|
||||
" self.extra_config = hf_to_vllm_mapper.apply_dict(self.extra_config)\n"
|
||||
)
|
||||
b2 = a2 + (
|
||||
" if self.fp8_layers: # spark-dflash-hybrid-fp8\n"
|
||||
" self.fp8_layers = set(\n"
|
||||
" hf_to_vllm_mapper.apply_list(list(self.fp8_layers))\n"
|
||||
" )\n"
|
||||
)
|
||||
assert src.count(a2) == 1, f"anchor2 count={src.count(a2)}"
|
||||
src = src.replace(a2, b2)
|
||||
|
||||
# 3. insert maybe_update_config + _is_layer_fp8 before apply_awq_quant_layer
|
||||
a3 = ' def apply_awq_quant_layer(self, layer, prefix: str, backend: str = "auto"):\n'
|
||||
methods = ''' def maybe_update_config( # spark-dflash-hybrid-fp8
|
||||
self,
|
||||
model_name: str,
|
||||
hf_config=None,
|
||||
revision: str | None = None,
|
||||
):
|
||||
"""Detect FP8 dense layers in a hybrid INT4+FP8 checkpoint."""
|
||||
import torch as _torch
|
||||
from safetensors.torch import _TYPES as _SF
|
||||
from vllm.transformers_utils.config import get_safetensors_params_metadata
|
||||
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
|
||||
metadata = get_safetensors_params_metadata(model_name, revision=revision)
|
||||
fp8_weights = {}
|
||||
for pn, info in metadata.items():
|
||||
ds = info.get("dtype", None)
|
||||
if ds is None:
|
||||
continue
|
||||
if _SF.get(ds) == _torch.float8_e4m3fn and pn.endswith(".weight"):
|
||||
sn = pn.replace(".weight", ".weight_scale_inv")
|
||||
if sn in metadata:
|
||||
fp8_weights[pn] = info
|
||||
if not fp8_weights:
|
||||
logger.info("spark-dflash-hybrid-fp8: no FP8 dense layers detected")
|
||||
return
|
||||
block_size = None
|
||||
for pn, info in fp8_weights.items():
|
||||
sn = pn.replace(".weight", ".weight_scale_inv")
|
||||
ws = info.get("shape", [])
|
||||
ss = metadata[sn].get("shape", [])
|
||||
if len(ws) == 2 and len(ss) == 2:
|
||||
block_size = [ws[0] // ss[0], ws[1] // ss[1]]
|
||||
break
|
||||
if block_size is None:
|
||||
return
|
||||
self.fp8_config = Fp8Config(
|
||||
is_checkpoint_fp8_serialized=True,
|
||||
activation_scheme="dynamic",
|
||||
weight_block_size=block_size,
|
||||
)
|
||||
self.fp8_layers = {n.rsplit(".weight", 1)[0] for n in fp8_weights}
|
||||
_sample = sorted(self.fp8_layers)[:3]
|
||||
logger.info(
|
||||
"spark-dflash-hybrid-fp8: detected %d FP8 dense layers "
|
||||
"(block_size=%s) e.g. %s",
|
||||
len(self.fp8_layers), block_size, _sample,
|
||||
)
|
||||
|
||||
def _is_layer_fp8(self, prefix: str) -> bool: # spark-dflash-hybrid-fp8
|
||||
if not self.fp8_layers:
|
||||
return False
|
||||
if prefix in self.fp8_layers:
|
||||
return True
|
||||
fused = getattr(self, "packed_modules_mapping", {})
|
||||
proj = prefix.split(".")[-1]
|
||||
if proj in fused:
|
||||
shards = [prefix.replace(proj, s) for s in fused[proj]]
|
||||
return all(
|
||||
any(fl in sp for fl in self.fp8_layers) for sp in shards
|
||||
)
|
||||
return any(fl in prefix for fl in self.fp8_layers)
|
||||
|
||||
'''
|
||||
assert src.count(a3) == 1, f"anchor3 count={src.count(a3)}"
|
||||
src = src.replace(a3, methods + a3)
|
||||
|
||||
# 4. FP8 dispatch in the not-quantized blocks (awq/gptq/xpu/cpu are byte-identical;
|
||||
# guard is inert unless fp8_config set, so patching all is safe)
|
||||
a4 = (
|
||||
" if not self.check_quantized(weight_bits):\n"
|
||||
" if isinstance(layer, (LinearBase, ParallelLMHead)):\n"
|
||||
" return UnquantizedLinearMethod()\n"
|
||||
" else:\n"
|
||||
" return None\n"
|
||||
)
|
||||
b4 = (
|
||||
" if not self.check_quantized(weight_bits):\n"
|
||||
" if self.fp8_config and self._is_layer_fp8(prefix): # spark-dflash-hybrid-fp8\n"
|
||||
" from vllm.model_executor.layers.quantization.fp8 import (\n"
|
||||
" Fp8LinearMethod,\n"
|
||||
" )\n"
|
||||
" return Fp8LinearMethod(self.fp8_config)\n"
|
||||
" if isinstance(layer, (LinearBase, ParallelLMHead)):\n"
|
||||
" return UnquantizedLinearMethod()\n"
|
||||
" else:\n"
|
||||
" return None\n"
|
||||
)
|
||||
n4 = src.count(a4)
|
||||
assert n4 >= 2, f"anchor4 count={n4}"
|
||||
src = src.replace(a4, b4)
|
||||
|
||||
# 5. FP8 dispatch in get_quant_method's extra_config (bits>=16) override
|
||||
a5 = (
|
||||
' ) and self.extra_config[layer_name].get("bits", 16) >= 16:\n'
|
||||
" return UnquantizedLinearMethod()\n"
|
||||
)
|
||||
b5 = (
|
||||
' ) and self.extra_config[layer_name].get("bits", 16) >= 16:\n'
|
||||
" if self.fp8_config and self._is_layer_fp8(prefix): # spark-dflash-hybrid-fp8\n"
|
||||
" from vllm.model_executor.layers.quantization.fp8 import (\n"
|
||||
" Fp8LinearMethod,\n"
|
||||
" )\n"
|
||||
" return Fp8LinearMethod(self.fp8_config)\n"
|
||||
" return UnquantizedLinearMethod()\n"
|
||||
)
|
||||
assert src.count(a5) == 1, f"anchor5 count={src.count(a5)}"
|
||||
src = src.replace(a5, b5)
|
||||
|
||||
open(path, "w").write(src)
|
||||
print(f"[patch_inc_hybrid] applied {SENT} to {path} (not-quant blocks x{n4})")
|
||||
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python3
|
||||
"""INT8 W8A16 lm-head v3 for AEON vLLM 0.23 (sm121). Replaces ONLY the
|
||||
`lm_head.quant_method.apply(...)` call inside LogitsProcessor._get_logits with a
|
||||
batched int8 GEMV (one kernel launch for any batch), leaving the existing TP
|
||||
gather + org_vocab_size trim untouched.
|
||||
|
||||
Fixes vs the broken v2 port:
|
||||
* KEEPS the bf16 lm_head weight (DFlash drafter shares it) — does NOT zero it.
|
||||
Trades the memory saving for correctness; the speed win is the int8 read in
|
||||
_get_logits, independent of keeping bf16 around.
|
||||
* Single BATCHED kernel (dot-based, pad B->16) for ALL B — no per-row Python
|
||||
loop (the v2 B>4 loop was what made spec decode SLOWER).
|
||||
* Fixed proven config (N128/K128/w4/s3, ~227 GB/s, argmax-exact vs bf16 on the
|
||||
real [248320,3072] shape) — no autotune (avoids sm121 bad-config miscompiles).
|
||||
|
||||
Sentinel DGX_SPARK_INT8_LMHEAD_V3. Verified standalone: int8 3.35ms vs bf16 8.8ms
|
||||
(B=1) / 6.5ms (B=13); maxerr 3e-4 < quant floor, argmax 100%.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
TARGET = "/usr/local/lib/python3.12/site-packages/vllm/model_executor/layers/logits_processor.py"
|
||||
|
||||
ANCHOR = (
|
||||
" # Get the logits for the next tokens.\n"
|
||||
" logits = lm_head.quant_method.apply(lm_head, hidden_states, bias=embedding_bias)\n"
|
||||
)
|
||||
REPLACE = (
|
||||
" # DGX_SPARK_INT8_LMHEAD_V3: int8 w8a16 GEMV for the huge vocab projection\n"
|
||||
" logits = _spark_int8_lmhead_apply(self, lm_head, hidden_states, embedding_bias)\n"
|
||||
)
|
||||
|
||||
MODULE_CODE = '''
|
||||
|
||||
# ===================== DGX_SPARK_INT8_LMHEAD_V3 =====================
|
||||
import triton as _spark_triton
|
||||
import triton.language as _spark_tl
|
||||
|
||||
|
||||
@_spark_triton.jit
|
||||
def _spark_k_int8(x_ptr, w_ptr, s_ptr, o_ptr, B, N, K,
|
||||
sxb, sxk, swn, swk, sob, son,
|
||||
BLOCK_B: _spark_tl.constexpr, BLOCK_N: _spark_tl.constexpr,
|
||||
BLOCK_K: _spark_tl.constexpr):
|
||||
pid_n = _spark_tl.program_id(0)
|
||||
offs_b = _spark_tl.arange(0, BLOCK_B)
|
||||
offs_n = pid_n * BLOCK_N + _spark_tl.arange(0, BLOCK_N)
|
||||
offs_k = _spark_tl.arange(0, BLOCK_K)
|
||||
x_ptrs = x_ptr + offs_b[:, None] * sxb + offs_k[None, :] * sxk
|
||||
w_ptrs = w_ptr + offs_n[:, None] * swn + offs_k[None, :] * swk
|
||||
acc = _spark_tl.zeros((BLOCK_B, BLOCK_N), dtype=_spark_tl.float32)
|
||||
for k in range(0, K, BLOCK_K):
|
||||
km = (offs_k[None, :] + k) < K
|
||||
x = _spark_tl.load(x_ptrs, mask=(offs_b[:, None] < B) & km, other=0.0).to(_spark_tl.float16)
|
||||
w = _spark_tl.load(w_ptrs, mask=(offs_n[:, None] < N) & km, other=0).to(_spark_tl.float16)
|
||||
acc += _spark_tl.dot(x, w.T)
|
||||
x_ptrs += BLOCK_K * sxk
|
||||
w_ptrs += BLOCK_K * swk
|
||||
s = _spark_tl.load(s_ptr + offs_n, mask=offs_n < N, other=0.0).to(_spark_tl.float32)
|
||||
acc = acc * s[None, :]
|
||||
o_ptrs = o_ptr + offs_b[:, None] * sob + offs_n[None, :] * son
|
||||
_spark_tl.store(o_ptrs, acc, mask=(offs_b[:, None] < B) & (offs_n[None, :] < N))
|
||||
|
||||
|
||||
def _spark_int8_gemm(hidden, w_int8, w_scale):
|
||||
import torch
|
||||
N, K = w_int8.shape
|
||||
x = hidden.reshape(-1, K)
|
||||
B = x.shape[0]
|
||||
BLOCK_B = max(16, _spark_triton.next_power_of_2(B))
|
||||
out = torch.empty(B, N, dtype=torch.float32, device=x.device)
|
||||
xf = x.to(torch.float16)
|
||||
grid = ((N + 127) // 128,)
|
||||
_spark_k_int8[grid](xf, w_int8, w_scale, out, B, N, K,
|
||||
xf.stride(0), xf.stride(1), w_int8.stride(0), w_int8.stride(1),
|
||||
out.stride(0), out.stride(1),
|
||||
BLOCK_B=BLOCK_B, BLOCK_N=128, BLOCK_K=128,
|
||||
num_warps=4, num_stages=3)
|
||||
return out.reshape(hidden.shape[:-1] + (N,))
|
||||
|
||||
|
||||
def _spark_int8_lmhead_apply(self, lm_head, hidden_states, embedding_bias):
|
||||
import sys
|
||||
import torch
|
||||
if not getattr(lm_head, "_spark_int8_ready", None) is True and \\
|
||||
not getattr(lm_head, "_spark_int8_disabled", False):
|
||||
w = getattr(lm_head, "weight", None)
|
||||
if (w is not None and w.dtype in (torch.bfloat16, torch.float16)
|
||||
and w.dim() == 2 and w.shape[0] > 100000):
|
||||
with torch.no_grad():
|
||||
scales = (w.float().abs().amax(dim=1) / 127.0).clamp(min=1e-12)
|
||||
w_int8 = (w.float() / scales.unsqueeze(1)).round().clamp(-127, 127).to(torch.int8)
|
||||
lm_head._spark_w_int8 = w_int8.contiguous()
|
||||
lm_head._spark_w_scale = scales.to(torch.float16)
|
||||
lm_head._spark_int8_ready = True
|
||||
print("DGX_SPARK_INT8_LMHEAD_V3: lm_head -> int8 (%s), bf16 kept for shared drafter"
|
||||
% (list(w_int8.shape),), file=sys.stderr, flush=True)
|
||||
else:
|
||||
lm_head._spark_int8_disabled = True
|
||||
if getattr(lm_head, "_spark_int8_ready", False) and embedding_bias is None:
|
||||
return _spark_int8_gemm(hidden_states, lm_head._spark_w_int8, lm_head._spark_w_scale)
|
||||
return lm_head.quant_method.apply(lm_head, hidden_states, bias=embedding_bias)
|
||||
# =================== end DGX_SPARK_INT8_LMHEAD_V3 ===================
|
||||
'''
|
||||
|
||||
|
||||
def main():
|
||||
if not os.path.exists(TARGET):
|
||||
print(f"FAIL: {TARGET} not found"); sys.exit(1)
|
||||
src = open(TARGET).read()
|
||||
if "DGX_SPARK_INT8_LMHEAD_V3" in src:
|
||||
print("SKIP: int8 lm-head v3 already applied"); return
|
||||
if ANCHOR not in src:
|
||||
print("FAIL: _get_logits apply-anchor not found"); sys.exit(1)
|
||||
if src.count(ANCHOR) != 1:
|
||||
print(f"FAIL: anchor count={src.count(ANCHOR)} (expected 1)"); sys.exit(1)
|
||||
src = src.replace(ANCHOR, REPLACE)
|
||||
src = src + MODULE_CODE
|
||||
open(TARGET, "w").write(src)
|
||||
print("OK: int8 lm-head v3 applied")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""spark-dflash-unify (CORRECTED): let the DFlash drafter's attention KV spec unify
|
||||
with the hybrid GDN+mamba target's *padded* page size WITHOUT corrupting the drafter.
|
||||
|
||||
Root cause (AEON 0.23.0+aeon.sm121a.dflash): with --mamba-block-size set, vLLM's own
|
||||
hybrid alignment makes target mamba page == target attention page by PADDING the mamba
|
||||
page (e.g. "+0.54%"). That padded value becomes max_page_size. The drafter's attention
|
||||
page is smaller; unify_kv_cache_spec_page_size scales its block_size by
|
||||
ratio = max_page_size // layer_page_size, but because max_page_size is a *padded* (non
|
||||
block-size-linear) number, the scaled page lands just under it and
|
||||
`assert new_spec.page_size_bytes == max_page_size` fires.
|
||||
|
||||
The OLD spark-pad-unify patch "fixed" this by `replace(layer_spec,
|
||||
page_size_padded=max_page_size)` -- but it DROPPED the block_size scaling, leaving the
|
||||
drafter at block_size=16 behind a max-sized physical page. That mis-strided the drafter
|
||||
KV and pinned mean accept length at ~1.47 (garbage drafts), so DFlash net-lost.
|
||||
|
||||
This corrected patch mirrors what get_kv_cache_groups already does for
|
||||
HiddenStateCacheSpec layers: keep the SCALED block_size AND pad the <1% remainder.
|
||||
Strides stay correct (block_size is properly scaled); only the page tail is padded.
|
||||
"""
|
||||
import pathlib, sys
|
||||
|
||||
P = pathlib.Path("/usr/local/lib/python3.12/site-packages/vllm/v1/core/kv_cache_utils.py")
|
||||
src = P.read_text()
|
||||
|
||||
if "spark-dflash-unify" in src:
|
||||
print("[patch_unify2] already patched", flush=True)
|
||||
sys.exit(0)
|
||||
|
||||
OLD = ''' new_spec = replace(layer_spec, block_size=new_block_size)
|
||||
assert new_spec.page_size_bytes == max_page_size
|
||||
new_kv_cache_spec[layer_name] = new_spec'''
|
||||
|
||||
NEW = ''' new_spec = replace(layer_spec, block_size=new_block_size)
|
||||
if new_spec.page_size_bytes != max_page_size:
|
||||
# spark-dflash-unify: max_page_size is a *padded* hybrid page; the
|
||||
# scaled attention page lands just under it. Pad the remainder while
|
||||
# KEEPING the scaled block_size, exactly as get_kv_cache_groups does
|
||||
# for HiddenStateCacheSpec. (The old patch dropped the scaling ->
|
||||
# mis-strided the DFlash drafter -> accept len stuck ~1.47.)
|
||||
new_spec = replace(
|
||||
layer_spec,
|
||||
block_size=new_block_size,
|
||||
page_size_padded=max_page_size,
|
||||
)
|
||||
assert new_spec.page_size_bytes == max_page_size
|
||||
new_kv_cache_spec[layer_name] = new_spec'''
|
||||
|
||||
if OLD not in src:
|
||||
print("[patch_unify2] ERROR: target block not found — vLLM source differs", flush=True)
|
||||
sys.exit(1)
|
||||
|
||||
P.write_text(src.replace(OLD, NEW))
|
||||
print("[patch_unify2] patched OK (scaled-block + pad-remainder)", flush=True)
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
# serve.sh — runs INSIDE the sm121 vLLM container (mounted at /host). Applies the
|
||||
# runtime monkeypatches, then `vllm serve`s the Qwen3.5-122B-A10B INT4 target with
|
||||
# the DFlash drafter. Driven by install.sh; can also be run by hand.
|
||||
#
|
||||
# args: $1 = num_speculative_tokens (0 = no-spec baseline)
|
||||
# $2 = target attention backend (flash_attn | FLASHINFER)
|
||||
# env: MODEL target path/repo (default Intel INT4; /model for hybrid)
|
||||
# INC_HYBRID=1 apply the hybrid INT4+FP8 dense-expert dispatch patch
|
||||
# INT8_LMHEAD_V3=1 apply the int8 lm-head GEMV patch
|
||||
# MAX_MODEL_LEN GPU_MEM PORT
|
||||
#
|
||||
# Stack rationale: the DFlash drafter is non-causal -> needs FLASH_ATTN (FA2). The
|
||||
# hybrid GDN+mamba+MoE target's KV page geometry won't absorb the drafter's
|
||||
# attention spec without patch_unify2 (scale-block unify) + prefix-caching OFF
|
||||
# (NoPrefixCache coordinator, dodges the hash assert). See docs/FINDINGS.md.
|
||||
set -euo pipefail
|
||||
NSPEC="${1:-12}"
|
||||
BACKEND="${2:-flash_attn}"
|
||||
MODEL="${MODEL:-Intel/Qwen3.5-122B-A10B-int4-AutoRound}"
|
||||
DRAFT="${DRAFT:-z-lab/Qwen3.5-122B-A10B-DFlash}"
|
||||
MAX_MODEL_LEN="${MAX_MODEL_LEN:-16384}"
|
||||
GPU_MEM="${GPU_MEM:-0.8}"
|
||||
PORT="${PORT:-8000}"
|
||||
|
||||
# FLA sm121 big-tile shmem fix (prefill/TTFT only on sm121; harmless, free).
|
||||
echo "[serve] FLA sm121 big-tile shmem patch"
|
||||
python3 /host/patch_fla_shmem.py || true
|
||||
|
||||
if [ "${INC_HYBRID:-0}" = "1" ]; then
|
||||
echo "[serve] hybrid INT4+FP8 dispatch patch (inc.py)"
|
||||
python3 /host/patch_inc_hybrid.py
|
||||
fi
|
||||
if [ "${INT8_LMHEAD_V3:-0}" = "1" ]; then
|
||||
echo "[serve] int8 lm-head v3 patch (batched w8a16 GEMV)"
|
||||
python3 /host/patch_int8_lmhead_v3.py
|
||||
fi
|
||||
|
||||
if [ "$NSPEC" = "0" ]; then
|
||||
SPEC_ARG=()
|
||||
echo "[serve] NO-SPEC baseline (identical flags, prefix-off)"
|
||||
else
|
||||
SPEC_ARG=(--speculative-config "{\"method\":\"dflash\",\"model\":\"$DRAFT\",\"num_speculative_tokens\":$NSPEC,\"attention_backend\":\"FLASH_ATTN\"}")
|
||||
echo "[serve] DFlash n=$NSPEC, target-backend=$BACKEND, drafter=FLASH_ATTN, model=$MODEL"
|
||||
fi
|
||||
python3 /host/patch_unify2.py || { [ "$NSPEC" = "0" ] && true; }
|
||||
|
||||
exec vllm serve "$MODEL" \
|
||||
--served-model-name qwen \
|
||||
--host 0.0.0.0 --port "$PORT" \
|
||||
--max-model-len "$MAX_MODEL_LEN" \
|
||||
--max-num-seqs 16 \
|
||||
--max-num-batched-tokens "$MAX_MODEL_LEN" \
|
||||
--gpu-memory-utilization "$GPU_MEM" \
|
||||
--no-enable-prefix-caching \
|
||||
--enable-chunked-prefill \
|
||||
--trust-remote-code \
|
||||
--attention-backend "$BACKEND" \
|
||||
"${SPEC_ARG[@]}"
|
||||
Reference in New Issue
Block a user