Skip to content

Commit 64dccc1

Browse files
committed
Add decode-optimized SQ W4 path and logprob probe
1 parent 070f48c commit 64dccc1

9 files changed

Lines changed: 1543 additions & 227 deletions

runtime/examples/docs/QUANTIZATION_SPEC.md

Lines changed: 169 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,44 +30,96 @@ The old TorchInductor demo is no longer the mainline.
3030

3131
- `runtime/examples/sglang_quant/approx_quant_patch.py`
3232
- runtime patch for SGLang `UnquantizedLinearMethod`
33-
- prepares quantized weights and scales
33+
- prepares quantized weights, scales, and SmoothQuant calibration state
3434
- redirects selected linear call sites to quantized Triton kernels
3535

3636
- `runtime/examples/sglang_quant/approx_kernels.py`
37-
- W8A16 and W8A8 Triton kernels
37+
- W8A16, W8A8, and SmoothQuant-style W4A16 Triton kernels
3838
- same-signature substitute kernels used by approxMLIR function substitution
3939

40+
- `runtime/examples/sglang_quant/compare_sglang_logprobs.py`
41+
- exact-vs-approx accuracy probe using SGLang logprob APIs
42+
- reports teacher-forced token-logprob drift and top-k distribution drift
43+
- uses stepwise decode when `APPROX_SGLANG_DECODE_ONLY=1`
44+
4045
- `runtime/examples/sglang_quant/bootstrap/sitecustomize.py`
4146
- installs hooks inside SGLang worker subprocesses
4247

4348
## Current Quantization Strategy
4449

45-
The useful path today is `APPROX_SGLANG_BACKEND=triton_w8a16`.
50+
The useful path today is still `APPROX_SGLANG_BACKEND=triton_w8a16`.
51+
52+
The new lower-bit path is:
53+
54+
- `APPROX_SGLANG_BACKEND=triton_sq_w4a16`
55+
56+
This is not a naive int4 path. It is a SmoothQuant-style calibrated `W4A16`
57+
flow with:
58+
59+
- offline activation-stat collection on targeted linear layers
60+
- smoothing-factor computation from activation and weight statistics
61+
- one-time load-time packing into group-wise int4 weights
62+
- online activation-side inverse smoothing inside the Triton kernel
4663

4764
It is weight-only at runtime:
4865

4966
- activation input stays fp16/bf16
50-
- weight is prequantized outside the kernel to int8 plus fp32 scale
51-
- the Triton kernel loads int8 weight and scale, dequantizes weight values, and
52-
multiplies by fp16/bf16 activation
67+
- weight is prequantized outside the kernel to low-bit values plus fp32 scale
68+
- the Triton kernel loads quantized weight and scale, dequantizes weight values,
69+
and multiplies by fp16/bf16 activation
5370
- no activation quantization is used in the current best result
5471

5572
This is intentionally different from the older W8A8 attempts. The W8A8 paths
5673
are still present for comparison, but they are not the current useful frontier
5774
because activation quantization cost can dominate at this granularity.
5875

76+
## SmoothQuant-Style Split: Offline, Load Time, Online
77+
78+
The `triton_sq_w4a16` path is split across three phases.
79+
80+
Offline calibration:
81+
82+
- run the exact model with `APPROX_SGLANG_SQ_COLLECT=1`
83+
- collect per-layer activation absmax statistics
84+
- merge worker shards into one artifact with
85+
`runtime/examples/sglang_quant/build_smoothquant_artifact.py`
86+
87+
Load time:
88+
89+
- read the artifact for each targeted layer
90+
- fold smoothing into the weight tensor
91+
- quantize to group-wise int4
92+
- pack the weight tensor once and cache it on the layer
93+
94+
Online serving:
95+
96+
- route the targeted linear call site to `sglang_sq_w4a16_linear_kernel`
97+
- apply the activation-side inverse smoothing in-kernel
98+
- unpack int4 weights, load group-wise scales, and run the matmul
99+
- if `M==1` and tile metadata matches, use a decode-only tiled W4 layout that is
100+
repacked at load time; otherwise fall back to the generic packed W4 path
101+
59102
## Function Substitution Boundary
60103

61104
Current function substitution is same-ABI:
62105

63106
```text
64107
sglang_w8a16_linear_kernel(...)
65108
-> approx_sglang_w8a16_linear_kernel_1(...)
109+
110+
sglang_sq_w4a16_linear_kernel(...)
111+
-> approx_sglang_sq_w4a16_linear_kernel_1(...)
66112
```
67113

68114
The ABI change from original SGLang fp weight to `qweight + scale` is currently
69115
done by the SGLang runtime patch, not by the compiler pass.
70116

117+
For `triton_sq_w4a16`, that ABI is:
118+
119+
- packed int4 weight
120+
- group-wise scale tensor
121+
- activation smoothing inverse vector
122+
71123
What this proves:
72124

73125
- approxMLIR can hook Triton compilation inside SGLang workers
@@ -78,8 +130,10 @@ What remains future work:
78130

79131
- compiler-level call-site / ABI rewrite from original fp weights to quantized
80132
weight metadata
81-
- passing offline information such as AWQ scales through explicit metadata
82-
rather than Python monkeypatch state
133+
- more optimized W4 kernels; current decode-tiled SQ-W4 is now ahead on speed
134+
but still behind on accuracy
135+
- richer calibration schemes such as AWQ/GPTQ-style protection on especially
136+
sensitive layers
83137

84138
## Current Best Configuration
85139

@@ -107,27 +161,131 @@ APPROX_SGLANG_BLOCK_K=64
107161

108162
Observed repeated result on the RTX 4060 Laptop:
109163

110-
- exact SGLang/Triton batch=4, 8-token e2e median: about `0.212s`
111-
- approximate repeated medians: `0.166s`, `0.150s`, `0.151s`
112-
- speedup range: about `1.27x` to `1.41x`
164+
- exact SGLang/Triton batch=4, 8-token e2e median: about `0.204s`
165+
- `gate_up_proj` W8A16 median: about `0.156s`
166+
- speedup: about `1.31x`
113167
- substitution hits: `100%`
114168
- output text matched the exact run in the checked prompt
115169

116170
Use medians for reporting. SGLang e2e latency has occasional outliers.
117171

172+
## Current Accuracy Definition
173+
174+
For decode-oriented points, the useful accuracy definition is no longer
175+
"output text matched on one prompt".
176+
177+
The current accuracy probe is:
178+
179+
- exact model generates a reference continuation
180+
- exact and approx are then compared on that same continuation
181+
- when `APPROX_SGLANG_DECODE_ONLY=1`, the comparison is done as stepwise decode:
182+
one token at a time, conditioned on the exact prefix
183+
- metrics are computed from SGLang logprob APIs
184+
185+
Current reported metrics:
186+
187+
- `teacher_forced_perplexity_ratio`
188+
- ratio of approx NLL to exact NLL on the exact continuation
189+
- `1.0` is exact; higher is worse
190+
- `teacher_forced_mean_logprob_delta`
191+
- mean `(approx_logprob - exact_logprob)` on the reference token
192+
- `top1_agreement_rate`
193+
- fraction of decode steps where approx and exact agree on top-1 token
194+
- `topk_js_mean`
195+
- Jensen-Shannon divergence on returned top-k token distributions
196+
- `0.0` is exact; lower is better
197+
198+
This is still not full-vocab KL, because the public SGLang interface returns
199+
top-k / selected-token logprobs rather than full logits. But it is much more
200+
meaningful than a single prompt-level text match.
201+
202+
## Current Pareto Frontier
203+
204+
Measured on:
205+
206+
- `MODEL_PATH=Qwen/Qwen3.5-2B`
207+
- `BATCH_SIZE=4`
208+
- `MAX_NEW_TOKENS=8`
209+
- target: `gate_up_proj`
210+
- decode-heavy serving with CUDA graph enabled
211+
- accuracy probe on 4 prompts x 8 generated tokens = 32 scored decode steps
212+
213+
Latency points:
214+
215+
- `exact`: median about `0.202s`
216+
- `gate_up_proj` `triton_w8a16`: median about `0.176s` (`~1.15x`)
217+
- decode-tiled `gate_up_proj` `triton_sq_w4a16`: median about `0.136s`
218+
(`~1.48x` vs exact, `~1.29x` vs W8)
219+
220+
Decode-step logprob accuracy:
221+
222+
- `exact`
223+
- `teacher_forced_perplexity_ratio = 1.0000`
224+
- `top1_agreement_rate = 1.000`
225+
- `topk_js_mean = 0.000000`
226+
- `W8A16`
227+
- `teacher_forced_perplexity_ratio = 1.0048`
228+
- `teacher_forced_mean_logprob_delta = -0.00483`
229+
- `top1_agreement_rate = 1.000`
230+
- `topk_js_mean = 0.000643`
231+
- decode-tiled `W4A16`
232+
- `teacher_forced_perplexity_ratio = 1.1569`
233+
- `teacher_forced_mean_logprob_delta = -0.14578`
234+
- `top1_agreement_rate = 0.875`
235+
- `topk_js_mean = 0.009500`
236+
237+
Interpretation:
238+
239+
- `W8A16` is the current safe Pareto point
240+
- clearly faster than exact
241+
- logits distribution remains very close to exact
242+
- decode-tiled `W4A16` is a strong speed point but not yet on the safe
243+
accuracy/performance frontier
244+
- it is much faster than both exact and W8
245+
- but the decode-step distribution drift is now clearly visible under the new
246+
logprob definition
247+
118248
## Frontier Interpretation
119249

120250
The current frontier is selective coverage:
121251

122252
- `gate_up_proj` W8A16 is the best target so far
123253
- `qkv_proj` W8A16 gives a smaller positive point
124254
- `down_proj` is not a Pareto point with the current W8A16 kernel
255+
- `triton_sq_w4a16` full coverage is not a Pareto point with the current W4
256+
kernel
257+
- decode-tiled `triton_sq_w4a16` now gives a major speedup, but under the
258+
decode-step logprob definition it is still too inaccurate to count as a safe
259+
frontier point
125260
- blindly increasing coverage can erase the speedup
126261

127262
CUDA graph must be enabled for serving-like measurements. With CUDA graph
128263
disabled, launch overhead dominates and the same replacement can look flat or
129264
negative.
130265

266+
## Accuracy Probe Command
267+
268+
Decode-step exact-vs-approx logprob comparison:
269+
270+
```bash
271+
source .venv/bin/activate
272+
MODEL_PATH=Qwen/Qwen3.5-2B \
273+
BATCH_SIZE=4 \
274+
MAX_NEW_TOKENS=8 \
275+
TOP_LOGPROBS_NUM=20 \
276+
SGLANG_MEM_FRACTION_STATIC=0.75 \
277+
SGLANG_DISABLE_CUDA_GRAPH=0 \
278+
PROMPTS_JSON='["The capital of France is","The capital of France is","The capital of France is","The capital of France is"]' \
279+
APPROX_SGLANG_QUANT=1 \
280+
APPROX_SGLANG_MODE=approx \
281+
APPROX_SGLANG_TARGET=gate_up_proj \
282+
APPROX_SGLANG_BACKEND=triton_w8a16 \
283+
APPROX_SGLANG_DECODE_ONLY=1 \
284+
python3 approxMLIR/runtime/examples/sglang_quant/compare_sglang_logprobs.py
285+
```
286+
287+
Swap `APPROX_SGLANG_BACKEND` and related W4 envs to compare other points.
288+
131289
## Run Commands
132290

133291
Single best-config run:

runtime/examples/sglang_quant/README.md

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,25 +60,62 @@ Useful environment variables:
6060
- `APPROX_SGLANG_QUANT`: set `1` to monkeypatch SGLang linear layers
6161
- `APPROX_SGLANG_MODE`: `exact` or `approx`, default `exact`
6262
- `APPROX_SGLANG_TARGET`: comma-separated substring filter, default `proj`
63-
- `APPROX_SGLANG_BACKEND`: `triton_w8a16`, `triton_prequant`, `triton`, or
64-
`sgl_kernel`; current useful path is `triton_w8a16`
63+
- `APPROX_SGLANG_BACKEND`: `triton_w8a16`, `triton_sq_w4a16`,
64+
`triton_prequant`, `triton`, or `sgl_kernel`
6565
- `APPROX_SGLANG_DECODE_ONLY`: only patch M=1 decode calls, default `1`
6666
- `APPROX_SGLANG_USE_SUBSTITUTE`: set `1` to run approxMLIR function
6767
substitution on the selected Triton kernel
6868
- `APPROX_SGLANG_DROP_ORIGINAL_WEIGHT`: drop original fp weights after
6969
prequantization for wide coverage; only safe in approx mode
70+
- `APPROX_SGLANG_SQ_COLLECT`: collect per-layer activation absmax for
71+
SmoothQuant-style calibration
72+
- `APPROX_SGLANG_SQ_STATS_DIR`: directory for calibration shards
73+
- `APPROX_SGLANG_SQ_ARTIFACT_PATH`: merged SmoothQuant artifact path
74+
- `APPROX_SGLANG_SQ_ALPHA`: smoothing exponent, default `0.85`
75+
- `APPROX_SGLANG_SQ_GROUP_SIZE`: W4 group size, default `128`
76+
- `APPROX_SGLANG_SQ_BLOCK_K`: SQ-W4 K tile size, default `64`
77+
78+
SmoothQuant-style calibration flow:
79+
80+
```bash
81+
source .venv/bin/activate
82+
83+
OUT_DIR=/tmp/approx_sq_cal \
84+
MODEL_PATH=Qwen/Qwen3.5-2B \
85+
BATCH_SIZE=4 \
86+
MAX_NEW_TOKENS=8 \
87+
WARMUP_RUNS=1 \
88+
MEASURE_RUNS=1 \
89+
APPROX_SGLANG_QUANT=1 \
90+
APPROX_SGLANG_MODE=exact \
91+
APPROX_SGLANG_TARGET=gate_up_proj \
92+
APPROX_SGLANG_BACKEND=triton_sq_w4a16 \
93+
APPROX_SGLANG_SQ_COLLECT=1 \
94+
TRITON_PASS_PLUGIN_PATH=$PWD/approxMLIR/external-tools/approx-triton-plugin/build/lib/libApproxTritonPlugin.so \
95+
python3 approxMLIR/runtime/examples/sglang_quant/probe_sglang_triton_dump.py
96+
97+
APPROX_SGLANG_SQ_STATS_DIR=/tmp/approx_sq_cal/sq_stats \
98+
APPROX_SGLANG_SQ_ARTIFACT_PATH=/tmp/approx_sq_cal/sq_artifact.pt \
99+
python3 approxMLIR/runtime/examples/sglang_quant/build_smoothquant_artifact.py
100+
```
70101

71102
Current Qwen3.5-2B result on the RTX 4060 Laptop, batch=4, 8 generated tokens,
72103
CUDA graph enabled:
73104

74-
- exact SGLang/Triton median: `0.212s`
75-
- `gate_up_proj` W8A16 substitute median: `0.152s` (`1.40x`)
76-
- `qkv_proj` W8A16 substitute median: `0.201s` (`1.06x`)
77-
- `gate_up_proj,qkv_proj` median: `0.206s`
78-
- `gate_up_proj,down_proj` median: `0.211s`
105+
- exact SGLang/Triton median: about `0.204s`
106+
- `gate_up_proj` W8A16 substitute median: about `0.156s` (`1.31x`)
107+
- `qkv_proj` W8A16 substitute median: about `0.199s` (`1.02x`)
108+
- `gate_up_proj` SmoothQuant-style W4A16 full coverage median: about `0.226s`
109+
with checked-output divergence
110+
- `gate_up_proj` SmoothQuant-style W4A16 decode-only, group size `128`,
111+
block size `64`: about `0.194s` (`1.05x`) with checked-output match on
112+
the probe prompt
79113

80114
The practical frontier is selective, not maximum coverage. `gate_up_proj` is
81115
the current useful target; `down_proj` regresses because the W8A16 kernel is not
82-
a fast path for that shape. CUDA graph must be enabled for serving-like numbers,
116+
a fast path for that shape. On the tested Qwen3.5-2B setup, the new
117+
`triton_sq_w4a16` path is calibrated and functional, but its current kernel only
118+
becomes a useful secondary point when tuned for decode-only with `group=128`
119+
and `block_k=64`. CUDA graph must be enabled for serving-like numbers,
83120
otherwise launch overhead dominates and the same replacement can look flat or
84121
negative.

0 commit comments

Comments
 (0)