Repository navigation
Phase 5a: int8 weight-only quantization - #5
Merged
Merged
Conversation
Splits the design doc's bundled Phase 5 (quantization + speculative decoding) into two sequential sub-phases per brainstorming decision. 5a scopes an int8 weight-only grouped-GEMM kernel extension; 5b (speculative decoding) is planned separately once 5a merges.
…ate scale shape Hoist the per-output-channel scale multiply outside the K-loop instead of dequantizing the weight tile every step: tl.dot now runs on x_tile's native bf16/fp16 dtype (int8 -> that dtype is exact) instead of forcing fp32/TF32-rate tl.dot, which would have made the kernel's throughput measurement conflate quantization's real speedup with a dot-precision regression. Also reject a QuantizedTensor whose scale shape doesn't match weight (E, N), since the kernel's masked scale load trusts data's N without checking scale's own shape.
stack_expert_weights re-points every expert's Linear at a view into one shared bf16 tensor rather than copying it. patch_moe_infer_quantized quantized that stack but never dropped those views, so the model held both the bf16 originals and their int8 copies at once for every layer -- defeating quantization's whole point, and OOMing the real Phase 5a measured run on a 44GB L40 at DeepSeekMoE-16B's scale.
Kernel-level correctness gate passed on real hardware (15/15 int8, 25/25 bf16). Measured three-way run: naive bf16 kernel +72.9% over stock, int8 quantized +70.9% over stock but a throughput tie with naive (expected -- weight-only quantization saves memory, not FLOPs). Memory footprint measured directly: 49.89% reduction. Model-level agreement (quantized vs. naive) came back perfect at every tested position, stronger than the design doc's own bar required. Total cost $1.9543 of the $5 cap across all three pods, including two Community Cloud pods that hit a real host-level GPU passthrough bug.
Added the sections almanac's README has that this one was missing (In sixty seconds, Why this project, The centerpiece, an architecture diagram, a measured-results table per phase, what it doesn't do, stack/layout tables, a paper-trail index) and fixed a stale line claiming Phase 4's PR was still open after it had already merged.
grouped_matmul_int8's docstring still described the pre-fix-round-1
scale timing ("before accumulating"); integration.py's quantized patch
path was missing the type: ignore explanation its sibling function
already carries. Both flagged in the SDD ledger for the final review;
fixed directly since they were already known and trivial.
…cliff quantize_per_channel_int8 was computing absmax/scale/quotient in the input weight's own dtype. For bf16 that widens round-trip error to ~1.5x the ideal 0.5-quantization-step bound; for fp16, clamping the zero-channel guard at fp16's own tiny() (~6.1e-5) silently clamped every small-but-nonzero channel's scale upward too, cutting a channel with absmax ~0.001 down to an int8 code of ~16 instead of ~127. Found by the final whole-branch review, not caught by any prior task review since the existing round-trip test only ran fp32 and the zero-channel tests only checked for NaN, not range utilization. Now computes absmax/scale/quotient in float32 regardless of weight's dtype, and the zero-channel guard sets scale to 1.0 only when absmax is exactly zero rather than clamping every small channel toward a dtype-dependent floor. Round-trip test now parametrized over float32/bfloat16/float16 with one bound that must hold for all three; new regression test proves a small-but-nonzero fp16 channel still uses the full int8 range.
- README: fix PR-number misattribution (Phase 2 landed via PR #3 alongside Phase 3, not its own PR) and an off-by-one session count. - CLAUDE.md: refresh "Current status" past Phase 1 -- it hadn't been touched since, so it silently described a four-phase-old project. - CHANGELOG.md: same staleness, same fix, phases 2 through 5a added. - findings doc: record honestly that Phase 5a's own mutation check (named in the design doc, met by Phase 1) wasn't run this session; write up the tl.dot precision finding that previously lived only in a source docstring; note that quantizing doesn't reduce peak GPU memory, only steady-state footprint. - memory-footprint.json: add model/dtype/device, matching every other Phase 5a results JSON's own convention of carrying its full config. - backends.py: add QUANTIZED_BACKEND, used from run_baseline.py instead of two independent "quantized" string literals. - run_kernel_bench.py: note the int8 kernel has no micro-benchmark path by design, rather than leaving it to inference. - test_integration.py: add a weakref-based test proving the shared bf16 tensor stack_expert_weights builds is actually collected after patch_moe_infer_quantized, not just that the views into it are gone.
The re-review caught two slips in the previous fix-round commit: the new Phase 5a/4/3/2 entries landed after the pre-existing Phase 1 entry instead of above it, breaking CHANGELOG's own newest-first convention; and both it and README still said 119/122 tests after the same commit had already added a test, landing on 123.
bsreecharanreddy
added a commit
that referenced
this pull request
Sep 18, 2026
…gures README and CLAUDE.md now reflect Phase 5a merged (PR #5), Phase 5b complete (PR #6), the per-phase findings folders, 170 tests, and the running GPU cost ($39.95 across nine sessions). CLAUDE.md's cost discipline gains three rules that each came from a real Phase 5b incident: never leave a pod running across an unbounded wait, pull evidence off the pod before stop, and treat stop as possibly final. Also corrects errors found while re-verifying the Phase 5b findings against the committed results JSONs: the prompt-lookup gate row had mixed in the k-sweep run's numbers (the gate run matched 2 of 4 prompts at 17.7% acceptance and 47.21 tok/s, not 3 of 4 at 14.9% and 44.74); match counts were stated out of 32 instead of 16 (draft-model 13/16, prompt-lookup 9/16); prompt_000 diverges in 6 of 8 sweep configs, not 7; and the summary claimed both drafters beat baseline when draft-model is below it at every k. Adds the direct evidence that the same k=4 prompt-lookup config matched 2/4 prompts in one process and 3/4 in another.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
src/dispatch/kernels/quantization.py,grouped_gemm_int8.py) -- not sourced from bitsandbytes/AWQ, per the design doc's explicit scope decision.deepseek-ai/deepseek-moe-16b-basemodel (bf16, single L40, unbatched eager decode): int8 kernel +70.9% over stock -- a throughput tie with the bf16 naive kernel it's built on (-1.2%), as expected since weight-only quantization saves memory bandwidth, not FLOPs. 49.89% expert-weight memory reduction. Perfect model-level top-1/mutual-top-k agreement at every tested position, stronger than the design doc's own bar required.patch_moe_infer_quantizedquantized each layer's weights into int8 but never freed the original bf16 weights (stack_expert_weightsre-points every expert'sLinear.weightat a view into a shared bf16 tensor), so the model held both copies at once and OOMed a 44GB L40. Fixed with_free_expert_weightsplus a regression test (and, after the final review flagged it as insufficient, a second test using a weakref to prove the underlying tensor is actually collected, not just the views).quantize_per_channel_int8computed its arithmetic in the weight's own bf16/fp16 dtype rather than float32, widening round-trip error (bf16: up to 1.5x the ideal bound).--dtype float16 --moe-kernel quantized).cuInit()returningCUDA_ERROR_UNKNOWNeven against the base image's own stock torch) before a Secure Cloud pod worked.Test plan
make checkgreen (lint,mypy --strict, 123 tests) throughout every commitdocs/findings/2026-09-17-phase-5a-quantization-run.md