Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
35103d5
fix: load and run GlmMoeDsaForCausalLM with quark mxfp4+ptpcfp8-attn …
ColinZ22 Jun 17, 2026
f4585de
defensive check and style
ColinZ22 Jun 19, 2026
051bae4
avoid unnecessarily quantizing to group-128 FP8 for quark FP8 path
ColinZ22 Jun 24, 2026
8ad495b
merge main
ColinZ22 Jun 26, 2026
8395177
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Jun 26, 2026
d1fa68b
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Jun 29, 2026
e763e2b
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Jul 1, 2026
f137031
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Jul 8, 2026
f078a89
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Jul 10, 2026
09c730a
fix merge typo
ColinZ22 Jul 13, 2026
1f228ad
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Jul 15, 2026
9d16fe7
review fixes
ColinZ22 Jul 16, 2026
36d8c2e
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Jul 16, 2026
bdc4db3
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Jul 16, 2026
7d06bc4
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Jul 18, 2026
da302c9
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Jul 20, 2026
e08143c
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Jul 21, 2026
aa23525
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Aug 4, 2026
7645794
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Aug 19, 2026
f078967
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Aug 20, 2026
2a6eaf9
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Aug 24, 2026
651c3a8
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Aug 25, 2026
1fc359e
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Aug 28, 2026
0b73fb9
removed unused function
ColinZ22 Sep 2, 2026
4608fa9
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Sep 11, 2026
fa66091
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Sep 22, 2026
5803d11
Merge branch 'main' into glm52-mxfp4-ptpcfp8-attn-fixes
ColinZ22 Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions python/sglang/srt/layers/attention/dsa/dsa_indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,16 @@ def _get_q_k_bf16(
enable_dual_stream: bool,
forward_batch: ForwardBatch,
):
# fused_rms_fp8_group_quant with output_unquantized_inp1=True produces a
# (fp8, scale, bf16) 3-tuple; extract the bf16 for the unquantized wk layer.
if isinstance(x, tuple):
if len(x) >= 3:
x = x[2]
else:
raise ValueError(
f"_get_q_k_bf16 received a {len(x)}-tuple; expected a plain tensor "
"or a 3-tuple (fp8, scale, bf16) from fused_rms_fp8_group_quant."
)
Comment thread
ColinZ22 marked this conversation as resolved.
weights_raw = None
if enable_dual_stream:
current_stream = torch.cuda.current_stream()
Expand Down Expand Up @@ -675,6 +685,16 @@ def _get_k_bf16(
*,
apply_hadamard: bool = True,
):
# fused_rms_fp8_group_quant with output_unquantized_inp1=True produces a
# (fp8, scale, bf16) 3-tuple; extract the bf16 for the unquantized wk layer.
if isinstance(x, tuple):
if len(x) >= 3:
x = x[2]
else:
raise ValueError(
f"_get_k_bf16 received a {len(x)}-tuple; expected a plain tensor "
"or a 3-tuple (fp8, scale, bf16) from fused_rms_fp8_group_quant."
)
# Non-fusion path only; self.wk does not exist when fusion is on.
key, _ = self.wk(x)
key = self.k_norm(key)
Expand Down
8 changes: 6 additions & 2 deletions python/sglang/srt/layers/quantization/fp8_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1988,6 +1988,9 @@ def channel_quant_to_tensor_quant(
x_q_channel: torch.Tensor,
x_s: torch.Tensor,
) -> Tuple[torch.Tensor, torch.Tensor]:
# Reshape per-output-channel scale [N] -> [N, 1, ...] to broadcast over K dims.
while x_s.dim() < x_q_channel.dim():
x_s = x_s.unsqueeze(-1)
x_dq_channel = x_q_channel.to(torch.float32) * x_s
x_q_tensor, scale = (
scaled_fp8_quant(x_dq_channel)
Expand Down Expand Up @@ -2353,9 +2356,10 @@ def apply_fp8_ptpc_linear(
compressed_tensor_quant: bool = False,
) -> torch.Tensor:
"""FP8 per-token per-channel linear. Only used with the aiter (ROCm) backend."""
# Handle pre-quantized (fp8_tensor, scale) tuple from fused RMSNorm+Quant
# Handle pre-quantized (fp8_tensor, scale[, bf16]) tuple from fused RMSNorm+Quant.
# The optional 3rd element is the unquantized bf16 tensor kept for DSA; ignore it.
if isinstance(input, tuple):
q_input, x_scale = input
q_input, x_scale = input[0], input[1]
q_input = q_input.view(-1, q_input.shape[-1])
output_shape = [*q_input.shape[:-1], weight.shape[0]]
output = aiter.gemm_a8w8_bpreshuffle(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,14 @@ def apply_weights(
x: torch.Tensor,
bias: Optional[torch.Tensor] = None,
) -> torch.Tensor:

# Activations must be a plain bf16 tensor: per-channel FP8 (weight_scale
# [N, 1]) is gated off the aiter fused RMSNorm+quant kernel upstream by
# _is_block_scale_fp8, so the per-token quant happens in apply_fp8_linear.
assert not isinstance(x, tuple), (
"quark W8A8 FP8 linear received a pre-quantized tuple; a fused "
"RMSNorm+quant producer was not gated off by _is_block_scale_fp8 "
"(per-channel FP8 must fall through to the plain bf16 path)."
)
return apply_fp8_linear(
x,
layer.weight,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""CPU-only regression tests for the Quark PTPC-FP8 (W8A8 FP8) MLA attention fix.

These guard the hardware-independent pieces of the fix that enables loading and
running MLA models (e.g. GlmMoeDsaForCausalLM) with Quark-quantized PTPC FP8
attention on ROCm/gfx95.
"""

from sglang.test.ci.ci_register import register_cpu_ci

register_cpu_ci(est_time=6, suite="base-a-test-cpu")

import unittest

import torch

from sglang.srt.layers.quantization.fp8_utils import channel_quant_to_tensor_quant
from sglang.test.test_utils import CustomTestCase


class TestChannelQuantToTensorQuantBroadcast(CustomTestCase):
"""Regression for the 1D per-channel scale failing to broadcast against a 2D
weight.
"""

def test_2d_weight_1d_scale_broadcasts_per_output_channel(self):
n, k = 8, 4
weight = torch.ones(n, k, dtype=torch.float8_e4m3fn)
# Distinct per-channel scales so a wrong-axis broadcast would be visible.
scale = torch.arange(1, n + 1, dtype=torch.float32)

q_tensor, tensor_scale = channel_quant_to_tensor_quant(weight, scale)

# Round-trip: dequantized tensor must equal weight * per-row scale. The
# requant to a single tensor-wide FP8 scale is lossy, so tolerance is
# loose — a wrong-axis broadcast would be off by whole factors (>>10%).
dq = q_tensor.to(torch.float32) * tensor_scale
expected = torch.ones(n, k, dtype=torch.float32) * scale.unsqueeze(-1)
torch.testing.assert_close(dq, expected, rtol=0.1, atol=0.5)

def test_mismatched_n_k_would_fail_without_unsqueeze(self):
# N != K: the pre-fix ``weight * scale`` raises; the fix must not.
n, k = 6, 3
weight = torch.ones(n, k, dtype=torch.float8_e4m3fn)
scale = torch.arange(1, n + 1, dtype=torch.float32)

q_tensor, tensor_scale = channel_quant_to_tensor_quant(weight, scale)

self.assertEqual(q_tensor.shape, torch.Size([n, k]))

def test_scale_already_matching_rank_is_unchanged(self):
# A [N, 1] scale must not be unsqueezed further (guards the while-loop
# condition from over-reshaping).
n, k = 5, 4
weight = torch.ones(n, k, dtype=torch.float8_e4m3fn)
scale = torch.arange(1, n + 1, dtype=torch.float32).unsqueeze(-1)

q_tensor, tensor_scale = channel_quant_to_tensor_quant(weight, scale)

dq = q_tensor.to(torch.float32) * tensor_scale
expected = torch.ones(n, k, dtype=torch.float32) * scale
torch.testing.assert_close(dq, expected, rtol=0.1, atol=0.5)


if __name__ == "__main__":
unittest.main()
Loading