fix(kernel): avoid row-wise _scaled_mm stall on sm_89 with torch<2.12 - #243
Merged
jason-fxz merged 1 commit intoAug 29, 2026
Merged
Conversation
Collaborator
|
Verified on RTX 4060 Laptop (sm_89, torch 2.11.0+cu130). The fix itself LGTM. Three requests before merge: Comments Keep them short, and only where the code isn't self-explanatory — the header block can be 3 lines. Three statements need fixing:
Tests One test is enough: run the same fused input through the row-wise and per-part paths (monkeypatch Commit message Use |
PyTorch < 2.12 runs row-wise FP8 _scaled_mm on sm_89 through a CUTLASS stream-K kernel whose launch ignored the current stream (pytorch/pytorch#177651, fixed by pytorch/pytorch@252bb4a in 2.12). FreeToken issues the fused per-tensor-FP8 projections (q/k/v and GDN qkv|z of the NVFP4 checkpoints) from a side stream, so on Ada every prefill of >= 256 tokens stalled the GPU and the worker hung or died (FlashML-org#182, FlashML-org#72, FlashML-org#220). Windows torch builds ship no row-wise kernel at all (FlashML-org#227). Tensor-wise scaling is unaffected. Where row-wise is unsafe (sm_89 on torch < 2.12, or a probe on the default stream raises), a fused projection now runs one tensor-wise GEMM per part over its row slice and concatenates: the same W8A8 scheme (rel ~7e-4 to row-wise, accumulation order), one extra launch per part. The parts' row ranges come from the load-time weight_scale run-lengths; the decision and its probe run at load, never under CUDA-graph capture. FREETOKEN_FP8_ROWWISE_MM=0/1 forces either path for A/B. Tested on RTX 4070 SUPER (sm_89), driver 591.86, torch 2.11.0+cu130, WSL2. Sweep over M on a side stream: row-wise stalls at M >= 256, the new path completes at every M. tests/kernels/test_fp8_pertensor_linear.py: the side-stream test fails on main (rc=124, 0/128 GEMMs complete) and passes here; the per-part path is compared directly against row-wise at M=1/4/64/300. Three pre-existing test_w8a8_matches_w8a8_reference cases miss the 1e-2 tolerance on this GPU (rel 0.0103-0.0107) on main and on this branch alike. Assisted-by: Claude Fable 5
bernimccoy
force-pushed
the
fix/sm89-rowwise-scaled-mm
branch
from
August 29, 2026 03:04
d264239 to
ae00da6
Compare
Contributor
Author
|
Thanks for the review and the 4060 run. All three addressed in the amended commit:
PR body updated to match. |
Collaborator
|
Nice work, thanks. Merging. |
This was referenced Aug 29, 2026
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.
Fixes #182. Same root cause as #72 and the prefill hangs in #220; also covers #227.
Complements #184 (torch 2.13): this keeps the current pin and is a no-op on torch >= 2.12.
Problem
On sm_89, torch < 2.12 runs row-wise
_scaled_mmthrough a CUTLASS kernel whose launchignores the current stream (pytorch/pytorch#177651, fixed in 2.12 by pytorch/pytorch@252bb4a;
isolated by @endenis in #182).
fp8_pertensor_lineartakes that path for fused projections(q/k/v, GDN qkv|z: one scalar per part) from a side stream. At M >= 256 it stalls the GPU:
no
Prefill batchline, 100 % util,kill -9required; decode-sized M completes, so shortprompts work and long ones hang. Windows torch builds have no row-wise kernel at all (#227).
Fix
Tensor-wise scaling is unaffected. Where row-wise is unsafe, a fused
projection runs one tensor-wise
_scaled_mmper part over its row slice (weight[s:e].t(),still stride-only) and concatenates. Same W8A8 scheme (rel ~7e-4 to row-wise, accumulation order), one extra launch per part.
rowwise_scaled_mm_ok(): False on sm_89 + torch < 2.12; otherwise a one-off row-wise probeon the default stream (safe there), so a build without the kernel fails at load. Warmed in
load_state_dict, never under graph capture.weight_scale_segments(): the parts' row ranges from the load-timeweight_scalerun-lengths, next to the existing
_uniform_scalecheck. A genuine per-row scale (> 8segments or not 16-row aligned) stays on the W8A16 triton path.
FREETOKEN_FP8_ROWWISE_MM=0/1forces either path.Unchanged: uniform-scale weights, no
input_scale, sm_90+/sm_120, sm_89 on torch >= 2.12.Tested on
RTX 4070 SUPER 12 GB (sm_89), driver 591.86, WSL2, torch 2.11.0+cu130, triton 3.6.0,
flashinfer 0.6.17, sglang-kernel 0.4.5.
nvidia/Qwen3.6-35B-A3B-NVFP4, HF safetensors:main(=FREETOKEN_FP8_ROWWISE_MM=1)max_tokens=32kill -9)Sweep of the fused [8192, 512, 512] x 2048 projection on a side stream: row-wise completes at
M <= 128, stalls at M >= 256; the per-part path completes at every M from 1 to 2010.
tests/kernels/test_fp8_pertensor_linear.py: side-stream test at the #182 shape, in asubprocess so a stall fails instead of hanging pytest; fails on
main(rc=124), passeshere. Per-part path vs row-wise on the same fused input at M=1/4/64/300: rel 6.9e-4 to 8.5e-4.
Not tested on sm_90+/sm_120, torch >= 2.12, or Windows (#227 relies on the probe).
Caveats
Pre-existing on this GPU, unrelated: 3
test_w8a8_matches_w8a8_referencecases miss the 1e-2tolerance (rel 0.0103-0.0107) on
mainand here alike, incl. the untouched uniform case.Assisted by: Claude Code