Skip to content

Commit 884232d

Browse files
committed
fix(sm120): address DeepGEMM review blockers
1 parent c495c98 commit 884232d

12 files changed

Lines changed: 328 additions & 144 deletions

File tree

rtp_llm/model_loader/per_block_fp8_quant_weight.py

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def cast_to_fp8(x: torch.Tensor):
100100
return x.to(torch.float8_e4m3fn)
101101

102102

103-
def per_block_cast_to_fp8(
103+
def per_block_cast_to_fp8_grouped(
104104
x: torch.Tensor, group_size: int
105105
) -> tuple[torch.Tensor, torch.Tensor]:
106106
is_2d = x.dim() == 2
@@ -247,6 +247,9 @@ def create_w8a8_fp8_per_block_weight(
247247

248248

249249
class PerBlockFp8Weight(CompositeWeight, QuantWeight):
250+
def _uses_direct_ue8m0(self) -> bool:
251+
return False
252+
250253
w8a8_weight_list: Dict[str, str] = {
251254
W.attn_qkv_w: W.attn_qkv_s,
252255
W.attn_o_w: W.attn_o_s,
@@ -838,11 +841,19 @@ def _postprocess(
838841
)
839842
# kernel_weight, scale_weight = load_config.exported_device.convert_fp8_weight_params(kernel_weight, scale_weight)
840843

841-
# Online SM100/SM120 loading can already produce the final packed
844+
# Online SM120 loading can already produce the final packed
842845
# UE8M0 representation directly from source weights. Legacy/pre-quantized
843846
# inputs still arrive with floating-point block scales and require
844847
# the old dequantize/requantize conversion here.
845-
if is_deep_gemm_e8m0_used() and scale_weight.dtype != torch.int32:
848+
if self._uses_direct_ue8m0():
849+
from rtp_llm.models_py.kernels.cuda.fp8_kernel import (
850+
pack_weight_scale_ue8m0,
851+
)
852+
853+
scale_weight = pack_weight_scale_ue8m0(
854+
scale_weight, kernel_weight.shape[-2]
855+
)
856+
elif is_deep_gemm_e8m0_used():
846857
kernel_weight, scale_weight = requant_weight_ue8m0(
847858
kernel_weight, scale_weight
848859
)
@@ -896,6 +907,16 @@ def __init__(
896907
self.kernel = kernel
897908
self.scale = scale
898909

910+
def _uses_direct_ue8m0(self) -> bool:
911+
from rtp_llm.models_py.utils.arch import is_sm12x
912+
913+
return (
914+
self.scale is not None
915+
and self.kernel.name not in (W.moe_w1, W.moe_w2)
916+
and self.group_size == 128
917+
and is_sm12x()
918+
)
919+
899920
def _load_raw_tensor(
900921
self,
901922
tensor_source: TensorSource,
@@ -907,26 +928,23 @@ def _load_raw_tensor(
907928
tensor_source, layer_id, device, load_config
908929
)
909930

910-
from rtp_llm.models_py.kernels.cuda.deepgemm_wrapper import (
911-
is_deep_gemm_e8m0_used,
912-
)
913-
914-
is_dense_weight = self.kernel.name not in (W.moe_w1, W.moe_w2)
915-
direct_ue8m0 = (
916-
self.scale is not None and is_dense_weight and is_deep_gemm_e8m0_used()
917-
)
918-
if direct_ue8m0 and self.group_size != 128:
919-
raise ValueError(
920-
"SM100/SM120 DeepGEMM packed UE8M0 requires group_size=128, "
921-
f"got {self.group_size} for {self.kernel.name}"
922-
)
931+
direct_ue8m0 = self._uses_direct_ue8m0()
932+
if (
933+
self.scale is not None
934+
and self.kernel.name not in (W.moe_w1, W.moe_w2)
935+
and not direct_ue8m0
936+
):
937+
from rtp_llm.models_py.utils.arch import is_sm12x
923938

939+
if is_sm12x() and self.group_size != 128:
940+
raise ValueError(
941+
"SM120 DeepGEMM packed UE8M0 requires group_size=128, "
942+
f"got {self.group_size} for {self.kernel.name}"
943+
)
924944
res = {}
925945
scale = None
926946
if direct_ue8m0:
927-
from rtp_llm.models_py.kernels.cuda.fp8_kernel import (
928-
quant_weight_ue8m0_packed,
929-
)
947+
from rtp_llm.models_py.kernels.cuda.fp8_kernel import quant_weight_ue8m0
930948

931949
source_weight = kernel.get(self.kernel.name)
932950
if source_weight.dim() != 2:
@@ -936,11 +954,11 @@ def _load_raw_tensor(
936954
f"{self.kernel.name}"
937955
)
938956
source_weight = source_weight.T
939-
quant_kernel, scale = quant_weight_ue8m0_packed(
940-
source_weight.contiguous().to(device)
957+
quant_kernel, scale = quant_weight_ue8m0(
958+
source_weight.contiguous().to(device), [128, 128]
941959
)
942960
elif self.scale:
943-
quant_kernel, scale = per_block_cast_to_fp8(
961+
quant_kernel, scale = per_block_cast_to_fp8_grouped(
944962
kernel.get(self.kernel.name), self.group_size
945963
)
946964
if quant_kernel.dim() == 2:
@@ -956,10 +974,10 @@ def _load_raw_tensor(
956974
res = {self.kernel.name: quant_kernel.contiguous().to(device)}
957975
if self.scale:
958976
scale = scale.T if scale.dim() == 2 and not direct_ue8m0 else scale
959-
# Packed UE8M0 scales intentionally use a non-contiguous TMA
960-
# layout (stride(-2) == 1). Do not normalize that layout here.
961-
scale = scale.to(device) if direct_ue8m0 else scale.contiguous().to(device)
962-
res.update({self.scale.name: scale})
977+
# Keep ordinary [N/128, K/128] float scales through TP/DP/EP
978+
# splitting. _postprocess packs each rank's local scale into the
979+
# non-contiguous DeepGEMM TMA layout.
980+
res.update({self.scale.name: scale.contiguous().to(device)})
963981

964982
return res
965983

rtp_llm/models_py/kernels/cuda/deepgemm_wrapper.py

Lines changed: 75 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import functools
2+
import threading
23
from contextlib import contextmanager
34
from typing import Any, Callable, Generator, List, NoReturn, Optional, Tuple
45

@@ -24,6 +25,7 @@
2425
"transpose_packed_fp4",
2526
"tf32_hc_prenorm_gemm",
2627
"has_deep_gemm",
28+
"has_deep_gemm_mk_alignment",
2729
"is_deep_gemm_e8m0_used",
2830
"configure_deep_gemm_num_sms",
2931
"configure_deep_gemm_mk_alignment",
@@ -80,6 +82,7 @@
8082
_cast_back_from_fp4_impl: Callable[..., Any] | None = None
8183
_transpose_packed_fp4_impl: Callable[..., Any] | None = None
8284
_tf32_hc_prenorm_gemm_impl: Callable[..., Any] | None = None
85+
_mk_alignment_lock = threading.Lock()
8386

8487

8588
@functools.cache
@@ -88,10 +91,46 @@ def has_deep_gemm() -> bool:
8891
return has_module("deep_gemm")
8992

9093

94+
@functools.cache
95+
def has_deep_gemm_mk_alignment() -> bool:
96+
"""Whether DeepGEMM exposes the SM120 contiguous-layout tuning API."""
97+
if not has_deep_gemm():
98+
return False
99+
import deep_gemm
100+
101+
return all(
102+
hasattr(deep_gemm, name)
103+
for name in (
104+
"get_theoretical_mk_alignment_for_contiguous_layout",
105+
"get_mk_alignment_for_contiguous_layout",
106+
"set_mk_alignment_for_contiguous_layout",
107+
)
108+
)
109+
110+
111+
def normalize_paged_mqa_context_lens(context_lens: torch.Tensor) -> torch.Tensor:
112+
"""Adapt paged-MQA context lengths to the installed DeepGEMM contract.
113+
114+
DeepGEMM 2.5 uses ``[batch, queries]`` while the cuda12 DeepGEMM 2.2
115+
package accepts the legacy one-dimensional decode shape. The mk-alignment
116+
API is a stable capability marker introduced with the 2.5 package.
117+
"""
118+
if has_deep_gemm_mk_alignment():
119+
if context_lens.dim() == 1:
120+
return context_lens.unsqueeze(-1)
121+
if context_lens.dim() == 2:
122+
return context_lens
123+
raise ValueError(
124+
"DeepGEMM 2.5 paged MQA context_lens must be 1D or 2D, "
125+
f"got shape={tuple(context_lens.shape)}"
126+
)
127+
return context_lens
128+
129+
91130
@functools.cache
92131
def is_deep_gemm_e8m0_used() -> bool:
93132
# Blackwell SM100 and SM120 DeepGEMM kernels consume packed UE8M0 scales.
94-
# SM120 support requires the vLLM-pinned DeepGEMM build (a6b593d or newer).
133+
# SM120 support is pinned in deps to DeepGEMM 2.5.0+d7d5eca.cu129.
95134
return torch.cuda.get_device_capability()[0] in (10, 12)
96135

97136

@@ -118,34 +157,51 @@ def configure_deep_gemm_num_sms(num_sms: int) -> Generator[None, None, None]:
118157
def get_theoretical_mk_alignment_for_contiguous_layout(
119158
expected_m: int, num_groups: int
120159
) -> int:
121-
"""Return DeepGEMM's SM-specific BLOCK_M for a grouped call."""
122-
import deep_gemm
160+
"""Return BLOCK_M for a contiguous grouped call.
123161
124-
try:
125-
return int(
126-
deep_gemm.get_theoretical_mk_alignment_for_contiguous_layout(
127-
expected_m, num_groups
128-
)
162+
``expected_m`` is the total routed-token count. This differs from masked
163+
grouped GEMM, whose similarly named argument is a per-expert estimate.
164+
"""
165+
if not has_deep_gemm_mk_alignment():
166+
raise RuntimeError(
167+
"SM120 contiguous grouped GEMM requires DeepGEMM >= 2.5.0 "
168+
"with mk-alignment APIs"
129169
)
130-
except TypeError:
131-
return int(
132-
deep_gemm.get_theoretical_mk_alignment_for_contiguous_layout(
133-
(expected_m + num_groups - 1) // num_groups
134-
)
170+
import deep_gemm
171+
172+
return int(
173+
deep_gemm.get_theoretical_mk_alignment_for_contiguous_layout(
174+
expected_m, num_groups
135175
)
176+
)
136177

137178

138179
@contextmanager
139180
def configure_deep_gemm_mk_alignment(alignment: int) -> Generator[None, None, None]:
140181
"""Match DeepGEMM's scheduler BLOCK_M to contiguous workspace padding."""
182+
if not has_deep_gemm_mk_alignment():
183+
raise RuntimeError(
184+
"SM120 contiguous grouped GEMM requires DeepGEMM >= 2.5.0 "
185+
"with mk-alignment APIs"
186+
)
141187
import deep_gemm
142188

143-
original = deep_gemm.get_mk_alignment_for_contiguous_layout()
144-
deep_gemm.set_mk_alignment_for_contiguous_layout(alignment)
145-
try:
146-
yield
147-
finally:
148-
deep_gemm.set_mk_alignment_for_contiguous_layout(original)
189+
# The alignment is process-global. Serialize set/launch/restore so two
190+
# concurrent forwards cannot launch with each other's scheduler setting.
191+
with _mk_alignment_lock:
192+
original = deep_gemm.get_mk_alignment_for_contiguous_layout()
193+
deep_gemm.set_mk_alignment_for_contiguous_layout(alignment)
194+
configured = deep_gemm.get_mk_alignment_for_contiguous_layout()
195+
if configured != alignment:
196+
deep_gemm.set_mk_alignment_for_contiguous_layout(original)
197+
raise RuntimeError(
198+
"DeepGEMM rejected contiguous-layout mk alignment: "
199+
f"requested={alignment}, configured={configured}"
200+
)
201+
try:
202+
yield
203+
finally:
204+
deep_gemm.set_mk_alignment_for_contiguous_layout(original)
149205

150206

151207
def _missing_deep_gemm() -> NoReturn:

rtp_llm/models_py/kernels/cuda/fp8_kernel/__init__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
create_per_token_group_quant_fp8_output_scale,
77
cutlass_moe_mm_fp8_scaled,
88
get_best_config_swap_ab,
9+
pack_weight_scale_ue8m0,
910
per_block_cast_to_fp8,
1011
per_token_cast_to_fp8,
11-
quant_weight_ue8m0_packed,
12+
quant_weight_ue8m0,
1213
requant_weight_ue8m0,
1314
scaled_fp8_per_tensor_quant,
1415
scaled_fp8_per_token_quant,
@@ -21,9 +22,10 @@
2122
"scaled_fp8_per_token_quant",
2223
"cutlass_moe_mm_fp8_scaled",
2324
"get_best_config_swap_ab",
25+
"pack_weight_scale_ue8m0",
2426
"per_token_cast_to_fp8",
2527
"per_block_cast_to_fp8",
26-
"quant_weight_ue8m0_packed",
28+
"quant_weight_ue8m0",
2729
"requant_weight_ue8m0",
2830
"create_per_token_group_quant_fp8_output_scale",
2931
]

rtp_llm/models_py/kernels/cuda/fp8_kernel/fp8_kernel.py

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -430,39 +430,16 @@ def quant_weight_ue8m0(
430430
return out_w, out_s
431431

432432

433-
def quant_weight_ue8m0_packed(
434-
weight_dequant: torch.Tensor,
435-
) -> Tuple[torch.Tensor, torch.Tensor]:
436-
"""Quantize source weights once to FP8 with packed UE8M0 scales.
437-
438-
This is the native SM100/SM120 DeepGEMM representation. Keeping the
439-
original floating-point tensor until this operation avoids the lossy
440-
float-scale FP8 -> BF16 -> UE8M0 FP8 requantization path.
441-
"""
442-
if weight_dequant.dim() != 2:
433+
def pack_weight_scale_ue8m0(
434+
weight_scale: torch.Tensor, weight_rows: int
435+
) -> torch.Tensor:
436+
"""Pack split-local UE8M0 block scales into DeepGEMM's TMA layout."""
437+
if weight_scale.dtype != torch.float32 or weight_scale.dim() != 2:
443438
raise ValueError(
444-
"Direct packed UE8M0 weight quantization requires a 2D tensor, "
445-
f"got shape {tuple(weight_dequant.shape)}"
446-
)
447-
448-
# Limit floating-point temporaries during online loading. Quantizing the
449-
# complete matrix in one call materializes both a padded source and a
450-
# scaled floating-point tensor in addition to the master weight. Chunking
451-
# on a 128-row boundary preserves exactly the same quantization blocks.
452-
row_chunk_size = 1024
453-
n, k = weight_dequant.shape
454-
out_w = torch.empty((n, k), dtype=fp8_dtype, device=weight_dequant.device)
455-
scale_chunks = []
456-
for row_start in range(0, n, row_chunk_size):
457-
row_end = min(row_start + row_chunk_size, n)
458-
chunk_w, chunk_s = per_block_cast_to_fp8(
459-
weight_dequant[row_start:row_end], use_ue8m0=True
439+
"UE8M0 block scales must be a 2D float32 tensor before packing, "
440+
f"got shape={tuple(weight_scale.shape)}, dtype={weight_scale.dtype}"
460441
)
461-
out_w[row_start:row_end].copy_(chunk_w)
462-
scale_chunks.append(chunk_s)
463-
464-
out_s = torch.cat(scale_chunks, dim=0)
465-
return out_w, _transform_scale_ue8m0(out_s, mn=out_w.shape[-2])
442+
return _transform_scale_ue8m0(weight_scale, mn=weight_rows)
466443

467444

468445
def requant_weight_ue8m0(

rtp_llm/models_py/modules/base/cuda/indexer_op.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -385,10 +385,9 @@ def _get_topk_paged(
385385
attention_inputs.kv_cache_kernel_block_id_device.shape[1] * self.blocksize
386386
)
387387

388-
# DeepGEMM paged MQA represents the number of queries decoded together
389-
# as the second dimension. RTP-LLM currently decodes one query per
390-
# sequence, so expand [batch] to [batch, 1].
391-
context_lens = fmha_params.kvlen_d.view(-1, 1)
388+
# DeepGEMM 2.5 requires [batch, queries], while the cuda12 2.2 package
389+
# retains the legacy one-dimensional decode contract.
390+
context_lens = deep_gemm.normalize_paged_mqa_context_lens(fmha_params.kvlen_d)
392391
schedule_metadata = deep_gemm.get_paged_mqa_logits_metadata(
393392
context_lens,
394393
self.blocksize,

0 commit comments

Comments
 (0)