Skip to content

Commit a21cd4c

Browse files
committed
[None][perf] DSV4 o_proj: fuse fp8/UE8M0 quantize into o_a CuTe-DSL epilogue (gated)
Fold the o_lora bf16->fp8 + 1x128 packed-UE8M0 quantization into the o_a blockwise GEMM epilogue (single-pass, tree-reduce amax), so o_a emits fp8+sf directly and o_b (DeepGEMM 1d1d) consumes it without the separate fp8_quantize_1x128_packed kernel and the bf16 o_lora HBM round-trip. - blockwise_gemm.py: optional fp8+SF epilogue path, gated by sf_out tensor; the bf16 path is byte-identical when off. Single-pass register cache, tree-reduce amax (byte-exact), packed-UE8M0 MN-major sf store, predicated for tail-M, config-guarded to (128,128)/1-CTA. - cute_dsl_custom_ops.py: new trtllm::cute_dsl_fp8_bmm_blackwell_fp8out op. - attention.py: gated DSV4_FUSE_OPROJ branch in _deepseek_v4_o_proj (tp_size==1), falls back to the existing path when the flag is off. Validated byte-exact (G=1/G=16/tail-M/negative guards) and net-positive (+1.6us/o_a in decode, nsys hardware-timestamp measured). Default OFF -> zero functional change unless DSV4_FUSE_OPROJ=1. Signed-off-by: Mingyang Hao <mingyangh@nvidia.com>
1 parent ec3da87 commit a21cd4c

3 files changed

Lines changed: 713 additions & 33 deletions

File tree

tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3849,6 +3849,114 @@ def _(
38493849
assert output.shape == (batch_size, m,
38503850
n), "CuTe DSL fp8 bmm output shape is incorrect"
38513851

3852+
# a/b: fp8, scale: fp32; output: fp8 e4m3 + packed-UE8M0 1x128 scale factors.
3853+
# Fused-o_proj producer: emits o_lora as fp8+sf directly (for the FLATTENED
3854+
# [M, L*N] o_lora), ready to feed deep_gemm.fp8_gemm_nt without a separate
3855+
# quantize kernel + bf16 round-trip. The (fp8, sf) bytes are byte-identical to
3856+
# per_token_cast_to_fp8(use_ue8m0=True)+get_mn_major_tma_aligned_packed_ue8m0_tensor
3857+
# (validated bit-exact, B200 & B300, oproj_fuse/test_glue.py).
3858+
# NOTE: PENDING container e2e (P4). Uses the validated direct-call path
3859+
# (fixed (128,128)/1-CTA tactic, matching o_a's locked config + the kernel's
3860+
# fp8-mode guards); autotune/cute.compile caching is a P3-time optimization.
3861+
@torch.library.custom_op("trtllm::cute_dsl_fp8_bmm_blackwell_fp8out",
3862+
mutates_args=("output_fp8", "sf_out"),
3863+
device_types="cuda")
3864+
def cute_dsl_fp8_bmm_blackwell_fp8out(
3865+
input: torch.Tensor, # [L, M, K] e4m3
3866+
weight: torch.Tensor, # [L, N, K] e4m3 (per-group N = o_lora_rank)
3867+
input_scale: torch.
3868+
Tensor, # fp32, same layout as cute_dsl_fp8_bmm_blackwell
3869+
weight_scale: torch.Tensor, # fp32
3870+
output_fp8: torch.Tensor, # [L, M, N] e4m3 (mutated)
3871+
sf_out: torch.
3872+
Tensor, # [M, NKB4] int32, MN-major stride (1, aligned_mn) (mutated)
3873+
) -> None:
3874+
if not is_sm_100f():
3875+
raise ValueError(
3876+
f"cute_dsl_fp8_bmm_blackwell_fp8out requires SM100 family, got {get_sm_version()}"
3877+
)
3878+
from ..cute_dsl_kernels.blackwell.blockwise_gemm.blockwise_gemm import \
3879+
Sm100BlockwiseGemmKernel
3880+
from ..cute_dsl_kernels.blackwell.utils import AddressSpace, make_ptr
3881+
3882+
L, M, K = input.shape[0], input.shape[1], input.shape[2]
3883+
N = weight.shape[1]
3884+
sf_m = pad_up(M, 4)
3885+
sf_k = ceil_div(K, 128)
3886+
sf_n = ceil_div(N, 128)
3887+
aligned_mn = pad_up(M, 4) # == get_tma_aligned_size(M, 4) for int32
3888+
n_tiles_per_group = ceil_div(
3889+
N, 128) # per-group N-tiles; global kblock = group*this + local
3890+
3891+
gemm = Sm100BlockwiseGemmKernel(
3892+
cutlass.Float32,
3893+
use_2cta_instrs=False,
3894+
mma_tiler_mn=(128, 128),
3895+
cluster_shape_mn=(1, 1),
3896+
)
3897+
max_active_clusters = cutlass.utils.HardwareInfo(
3898+
).get_max_active_clusters(1)
3899+
3900+
a_ptr = make_ptr(cutlass.Float8E4M3FN,
3901+
input.data_ptr(),
3902+
AddressSpace.gmem,
3903+
assumed_align=16)
3904+
b_ptr = make_ptr(cutlass.Float8E4M3FN,
3905+
weight.data_ptr(),
3906+
AddressSpace.gmem,
3907+
assumed_align=16)
3908+
a_sf_ptr = make_ptr(cutlass.Float32,
3909+
input_scale.data_ptr(),
3910+
AddressSpace.gmem,
3911+
assumed_align=16)
3912+
b_sf_ptr = make_ptr(cutlass.Float32,
3913+
weight_scale.data_ptr(),
3914+
AddressSpace.gmem,
3915+
assumed_align=16)
3916+
c_tmp = output_fp8.permute(1, 2, 0)
3917+
c_cute = cute.runtime.from_dlpack(c_tmp).mark_layout_dynamic(
3918+
leading_dim=1)
3919+
sf_out_ptr = make_ptr(cutlass.Int32,
3920+
sf_out.data_ptr(),
3921+
AddressSpace.gmem,
3922+
assumed_align=4)
3923+
stream = cuda.CUstream(torch.cuda.current_stream().cuda_stream)
3924+
3925+
gemm.wrapper_fp8sf(
3926+
M,
3927+
N,
3928+
K,
3929+
sf_m,
3930+
sf_n,
3931+
sf_k,
3932+
L,
3933+
a_ptr,
3934+
b_ptr,
3935+
a_sf_ptr,
3936+
b_sf_ptr,
3937+
c_cute,
3938+
max_active_clusters,
3939+
stream,
3940+
sf_out_ptr,
3941+
aligned_mn,
3942+
n_tiles_per_group,
3943+
)
3944+
3945+
@torch.library.register_fake("trtllm::cute_dsl_fp8_bmm_blackwell_fp8out")
3946+
def _(
3947+
input: torch.Tensor,
3948+
weight: torch.Tensor,
3949+
input_scale: torch.Tensor,
3950+
weight_scale: torch.Tensor,
3951+
output_fp8: torch.Tensor,
3952+
sf_out: torch.Tensor,
3953+
) -> None:
3954+
L, M, K = input.shape
3955+
N = weight.shape[1]
3956+
assert output_fp8.dtype == torch.float8_e4m3fn and output_fp8.shape == (
3957+
L, M, N)
3958+
assert sf_out.dtype == torch.int32
3959+
38523960
# =============================================================================
38533961
# Dense GEMM with SwiGLU Fusion (FC1 Kernel for MoE as Dense GEMM)
38543962
# =============================================================================

0 commit comments

Comments
 (0)