Skip to content

[None][perf] DSV4 o_proj: fuse fp8/UE8M0 quantize into o_a proj and introduce splitk cutedsl o_b proj - #16346

Open
mingyangHao wants to merge 20 commits into
NVIDIA:mainfrom
mingyangHao:user/mingyangh/dsv4-oproj-fp8-fuse
Open

[None][perf] DSV4 o_proj: fuse fp8/UE8M0 quantize into o_a proj and introduce splitk cutedsl o_b proj#16346
mingyangHao wants to merge 20 commits into
NVIDIA:mainfrom
mingyangHao:user/mingyangh/dsv4-oproj-fp8-fuse

Conversation

@mingyangHao

@mingyangHao mingyangHao commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Description

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.

The O_a baseline is the BF16-output CuTe BMM followed by the standalone
fp8_quantize_1x128_packed_ue8m0 kernel. The fused path emits FP8 and packed
UE8M0 scales directly from the BMM epilogue. The benchmark uses the production
[M, G, N] backing storage and [G, M, N] BMM transpose view.

The O_b baseline is upstream DeepGEMM followed by mHC. The candidate is the
CuTe DSL GEMM followed by mHC, with split-partial reduction folded into mHC.
Therefore the O_b table includes the split reduction cost.

O_a Projection + Quantization

M BF16 BMM + quant (us) Fused FP8-output BMM (us) Saved (us) Reduction
1 15.863 13.798 +2.065 +13.02%
2 14.013 11.436 +2.577 +18.39%
4 14.048 11.442 +2.606 +18.55%
8 14.110 11.497 +2.613 +18.52%
16 14.146 11.478 +2.668 +18.86%
32 14.462 12.006 +2.456 +16.98%
64 14.685 12.044 +2.641 +17.98%
128 15.464 12.576 +2.887 +18.67%
256 27.179 22.067 +5.112 +18.81%
512 45.411 36.017 +9.394 +20.69%
1024 74.263 57.409 +16.854 +22.70%
2048 142.964 109.295 +33.669 +23.55%
4096 275.663 209.079 +66.584 +24.15%
8192 541.167 414.459 +126.708 +23.41%
16384 1087.635 853.445 +234.190 +21.53%

O_a is positive at all 15 points, with a latency reduction of 13.02% to
24.15%. FP8 output bytes and packed UE8M0 scale words matched the standalone
quantization baseline exactly at every M.

O_b Projection + mHC (M=1 to 16384)

The O_b sweep uses the same power-of-two M points as O_a:
1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384.

M SK DG GEMM (us) CuTe GEMM (us) GEMM gain CuTe SOL DG+mHC (us) CuTe+mHC (us) Chain gain
1 4 24.765 21.917 +11.50% 0.2% 37.097 34.514 +6.96%
2 4 24.909 23.072 +7.38% 0.5% 38.067 36.674 +3.66%
4 4 24.999 22.945 +8.22% 0.9% 36.895 35.207 +4.58%
8 4 25.077 22.331 +10.95% 1.9% 36.969 34.402 +6.94%
16 4 25.020 22.495 +10.09% 3.7% 39.411 37.058 +5.97%
32 2 25.552 23.590 +7.68% 7.1% 45.427 44.199 +2.70%
64 2 26.498 23.895 +9.82% 14.0% 53.086 51.831 +2.36%
128 2 27.895 24.887 +10.78% 26.8% 56.219 54.677 +2.74%
256 1 32.522 30.328 +6.75% 44.1% 65.455 63.577 +2.87%
512 1 41.091 39.465 +3.96% 67.7% 79.107 77.357 +2.21%
1024 1 67.876 66.230 +2.42% 80.7% 125.894 124.181 +1.36%
2048 1 128.822 126.999 +1.42% 84.2% 228.784 227.518 +0.55%
4096 1 237.626 235.870 +0.74% 90.6% 418.302 416.542 +0.42%
8192 1 462.003 457.638 +0.94% 93.4% 797.674 793.958 +0.47%
16384 1 924.115 921.403 +0.29% 92.8% 1584.576 1583.072 +0.09%

O_b improves at all 15 points. The production selector uses SK4 for
M <= 16, SK2 for 16 < M <= 128, and SK1 above 128. Isolated GEMM gains
range from 0.29% to 11.50%, and the O_b+mHC chain gains range from 0.09% to
6.96%. The tuned M=2048 path is 1.42% faster before mHC and 0.55% faster end
to end, with no DeepGEMM fallback. CuTe partial sums matched DeepGEMM, and all
four mHC outputs matched the reduced-input reference across the sweep. Useful
SOL is 2*M*N*K / latency; it reaches 90.6% to 93.4% of the nominal
4.5 PFLOP/s dense FP8 peak at M >= 4096.

Combined O-Projection

This sum counts O_a quantization once and includes O_b split reduction through
mHC.

M Baseline O_a + O_b/mHC (us) Fused O_a + O_b/mHC (us) Saved (us) Reduction
1 52.960 48.312 +4.649 +8.78%
2 52.079 48.110 +3.969 +7.62%
4 50.944 46.649 +4.295 +8.43%
8 51.079 45.899 +5.180 +10.14%
16 53.557 48.536 +5.020 +9.37%
32 59.888 56.204 +3.684 +6.15%
64 67.771 63.875 +3.896 +5.75%
128 71.683 67.254 +4.430 +6.18%
256 92.634 85.644 +6.990 +7.55%
512 124.518 113.374 +11.144 +8.95%
1024 200.157 181.590 +18.568 +9.28%
2048 371.748 336.812 +34.935 +9.40%
4096 693.965 625.621 +68.344 +9.85%
8192 1338.841 1208.417 +130.424 +9.74%
16384 2672.210 2436.517 +235.693 +8.82%

Combined O-projection latency improves at all points by 5.75% to 10.14%.

Test Coverage

PR Checklist

Please review the following before submitting your PR:

  • PR description clearly explains what and why. If using CodeRabbit's summary, please make sure it makes sense.

  • PR Follows TRT-LLM CODING GUIDELINES to the best of your knowledge.

  • Test cases are provided for new code paths (see test instructions)

  • If PR introduces API changes, an appropriate PR label is added - either api-compatible or api-breaking. For api-breaking, include BREAKING in the PR title.

  • Any new dependencies have been scanned for license and vulnerabilities

  • CODEOWNERS updated if ownership changes

  • Documentation updated as needed

  • Update tava architecture diagram if there is a significant design change in PR.

  • The reviewers assigned automatically/manually are appropriate for the PR.

  • Please check this after reviewing the above items as appropriate for this PR.

GitHub Bot Help

To see a list of available CI bot commands, please comment /bot help.

Dev Engineer Review

Correctness and consistency

  • Adds fused DeepSeek-V4 o_a BF16-to-FP8 quantization with packed UE8M0 scales.
  • Adds split-K CuTe DSL support for the o_b projection and integrates partial reduction with mHC.
  • Adds split-major x_prev support for mHC with x_num_splits values of 1, 2, or 4.
  • Extends TF32 and FMA fused-HC kernels, launchers, THOP bindings, and PyTorch validation.
  • Adds Blackwell FP8-output epilogues, packed scale handling, dynamic MXF8 MMA support, and persistent blockscaled GEMM options.
  • Adds runtime tuning, kernel caching, warmup, fallback selection, and strict tensor-layout validation.
  • The fused path is disabled by default and requires DSV4_FUSE_OPROJ=1 with tp_size==1.
  • Reported outputs and scales match the standalone baseline. mHC outputs match their references.
  • The tuned M=2048 path avoids DeepGEMM fallback.
  • Public launcher APIs preserve compatibility through x_num_splits=1 defaults.
  • No configuration-file or test-list changes are reported.

Performance

  • o_a latency reduction: 13.02%–24.15%.
  • o_b GEMM latency reduction: 0.29%–11.50%.
  • o_b plus mHC latency reduction: 0.09%–6.96%.
  • Combined O-projection latency reduction: 5.75%–10.14%.

CI

  • Multiple earlier /bot run jobs failed, and one job was aborted.
  • The latest PR_Github run #62377 completed successfully with merge-request pipeline #50544.

QA Engineer Review

Test changes

  • Added extensive DeepSeek-V4 O-projection tests for:
    • Fusion gating and fallback behavior.
    • Split-K selection and overrides.
    • Runtime tuning, bucket mapping, compile-key behavior, and kernel caching.
    • Packed scale handling and FP8-output equivalence.
    • DeepGEMM warmup and fallback behavior.
  • Added test_mhc_fused_hc_reduces_split_x_for_production_backends.
  • Refactored test_deepseek_v4_o_proj through _build_dsv4_o_proj_case.
  • Updated the return annotation for test_deepseek_v4_o_proj.

Test-list coverage

  • The modified test files are covered by existing CI test-db entries:
    • tests/integration/test_lists/test-db/l0_b200.yml
    • tests/integration/test_lists/test-db/l0_gb200_multi_gpus.yml

Verdict

Sufficient

@mingyangHao
mingyangHao requested review from a team as code owners July 14, 2026 02:32
@mingyangHao
mingyangHao requested review from QiJune and liji-nv July 14, 2026 02:32
@mingyangHao

Copy link
Copy Markdown
Collaborator Author

/bot run

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds split-x support to fused mHC kernels and launchers. It validates split-major inputs and accumulates x splits. It also adds Blackwell FP8 CuTe-DSL GEMM/BMM paths, packed-scale execution, dynamic MXF8 MMA helpers, and fused DeepSeek-V4 projection routing with tests.

Changes

mHC split-x execution

Layer / File(s) Summary
Split-x kernel buffering and accumulation
cpp/tensorrt_llm/kernels/mhcKernels/fused_tf32_pmap_gemm.cuh, cpp/tensorrt_llm/kernels/mhcKernels/mhc_fused_fma.cuh
TF32 and FMA kernels support one, two, or four x splits through split-specific buffering, barriers, loads, and accumulation.
Split-x launch selection and API wiring
cpp/tensorrt_llm/kernels/mhcKernels/mhcFusedHcKernel.cu, cpp/tensorrt_llm/kernels/mhcKernels/mhcKernels.h
Launchers select x-split TF32/FMA variants, update descriptors and shared-memory sizing, and expose optional x_num_splits.
Split-major input validation and dispatch
cpp/tensorrt_llm/thop/mhcOp.cpp, tensorrt_llm/_torch/modules/mhc/*, tensorrt_llm/_torch/modules/mhc/hyper_connection.py, tests/unittest/_torch/modules/test_mhc.py
mHC validates split-major x_prev, forwards split counts through CUDA dispatch, selects split tactics, and tests split-versus-reduced results.

Blackwell FP8 and DeepSeek-V4 paths

Layer / File(s) Summary
FP8 split-K and output-producing custom operations
tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py
Adds FP8 split-K GEMM and FP8-output BMM runners, validation, compilation, dispatch, and fake registrations.
Blockwise GEMM FP8 epilogues
tensorrt_llm/_torch/cute_dsl_kernels/blackwell/blockwise_gemm/blockwise_gemm.py
Adds FP8 quantization, UE8M0 scale output, cooperative BF16 staging, FP8 validation, and wrapper ABI support.
Persistent packed-scale GEMM execution
tensorrt_llm/_torch/cute_dsl_kernels/blackwell/dense_blockscaled_gemm_persistent.py
Adds packed-scale modes, scale pipelines, dynamic MMA-N scheduling, L2 swizzling, optional alpha/PDL behavior, and scale-warp loading.
Dynamic MXF8 MMA emission
tensorrt_llm/_torch/cute_dsl_kernels/blackwell/dynamic_mxf8_mainloop.py
Adds runtime N-shift calculation and dynamic Tcgen05 MXF8 MMA emission with packed-scale handling.
DeepSeek-V4 fused projection routing
tensorrt_llm/_torch/modules/mla.py, tensorrt_llm/_torch/models/modeling_deepseekv4.py, tests/unittest/_torch/attention/sparse/deepseek_v4/test_deepseek_v4_o_proj.py
Adds split-K policy selection, fused FP8 attention-to-projection dispatch, model wiring, warmup, CuTe/DeepGEMM routing, reference construction, and integration coverage.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MLA
  participant FP8BMM
  participant SplitKGEMM
  participant DeepGEMM
  MLA->>FP8BMM: produce FP8 O_a intermediate and scales
  MLA->>SplitKGEMM: execute eligible split-K O_b projection
  MLA->>DeepGEMM: execute out-of-bucket O_b projection
  SplitKGEMM->>MLA: return projection partials
  DeepGEMM->>MLA: return projection output
Loading

Suggested reviewers: qijune, nv-xtf

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the DSV4 o_proj FP8 quantization fusion and split-K CuTe DSL o_b projection changes.
Description check ✅ Passed The description explains the implementation, performance results, validation claims, and checklist, although the dedicated Test Coverage section is not populated.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59071 [ ] completed with state FAILURE. Commit: ``

Link to invocation

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
tests/unittest/_torch/modules/test_mhc.py (1)

817-824: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover both supported split-x specializations.

Parameterize x_splits over (2, 4); XS=2 and XS=4 instantiate distinct CUDA kernels and barrier schedules.

As per path instructions, assess TensorRT-LLM test coverage and provide a concrete file-level follow-up; coverage in tests/unittest/_torch/modules/test_mhc.py is insufficient for XS=2.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unittest/_torch/modules/test_mhc.py` around lines 817 - 824,
Parameterize the test case around x_partials and x_reduced over x_splits values
2 and 4 so both CUDA kernel and barrier-schedule specializations execute,
preserving the existing setup for each parameter. Also assess TensorRT-LLM
coverage and add a concrete follow-up for the relevant file to cover XS=2 there.

Source: Path instructions

tests/unittest/_torch/attention/sparse/deepseek_v4/test_deepseek_v4_o_proj.py (1)

49-49: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Annotate the new test and helper functions.

Add parameter and return annotations, including pytest.MonkeyPatch, -> None, and a precise tuple[...] return for _build_dsv4_o_proj_case.

As per coding guidelines, “Annotate every function.”

Also applies to: 54-54, 62-62, 100-100, 105-105, 127-135, 160-160, 182-186, 200-200, 219-226, 247-247, 272-272, 383-383, 580-580, 639-639

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tests/unittest/_torch/attention/sparse/deepseek_v4/test_deepseek_v4_o_proj.py`
at line 49, Add complete type annotations to every new test and helper function
in this file, including parameter types, pytest.MonkeyPatch for monkeypatch
parameters, -> None for tests without return values, and the precise tuple[...]
return annotation for _build_dsv4_o_proj_case. Ensure all listed functions
follow the project’s annotation conventions.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cpp/tensorrt_llm/kernels/mhcKernels/mhcFusedHcKernel.cu`:
- Line 493: Update the disabled-DeepGEMM `mhcFusedHcLaunch` stub to accept the
trailing `int x_num_splits` parameter, matching the declaration and enabled
implementation used by the header and Torch binding. Keep the stub’s existing
behavior unchanged.

In `@tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py`:
- Around line 4278-4406: Update forward_fp8out and the corresponding FP8-output
launch path to validate every input tensor’s CUDA/common device, dtype, rank,
compatible shape, and required layout before creating raw pointers or launching
the kernel. Require sf_out_tensor to have shape (M, ceil_div(batch_size *
ceil_div(N, 128), 4)) in addition to its existing dtype and stride checks. Add
negative-contract coverage for invalid shapes, devices, dtypes, ranks, and
layouts in the DeepSeek V4 o-projection tests.

In `@tensorrt_llm/_torch/modules/mhc/mhc_cuda.py`:
- Around line 1064-1070: Update the x_num_splits branch in the tactic-selection
logic so the B > 32 path reuses the capability-aware fallback rather than
unconditionally selecting _FUSED_HC_FALLBACK_TACTIC_MMA. Preserve the existing B
<= 32 FMA choice while allowing unsupported SM100 MMA or hidden_size
configurations to select the FMA fallback.

In `@tensorrt_llm/_torch/modules/mla.py`:
- Around line 1133-1144: Update _should_use_fused_oproj to require BF16 output
eligibility, matching the BF16 partial allocation near the fused CuTe split-K
path; do not allow FP16 or unset self.dtype configurations to enter fusion.

In
`@tests/unittest/_torch/attention/sparse/deepseek_v4/test_deepseek_v4_o_proj.py`:
- Around line 651-668: Update _build_dsv4_o_proj_case to accept a
use_cute_dsl_blockscaling_mm parameter, and pass False from this test’s fp8
fixture so mla.o_b_proj.use_cute_dsl_blockscaling_mm remains disabled. Preserve
the existing fixture behavior for other callers while ensuring
_should_use_fused_oproj() reaches the fused path.
- Around line 701-721: Update the test around
CuteDSLFp8BlackwellBmmRunner.kernel_cache to isolate the class-global cache per
case using monkeypatch, so fp8out_keys contains only the current compilation
before asserting its length. In the num_tokens == 16 validation, compare
out_cached against out_fused instead of out_unfused while preserving the cache
identity check.

---

Nitpick comments:
In
`@tests/unittest/_torch/attention/sparse/deepseek_v4/test_deepseek_v4_o_proj.py`:
- Line 49: Add complete type annotations to every new test and helper function
in this file, including parameter types, pytest.MonkeyPatch for monkeypatch
parameters, -> None for tests without return values, and the precise tuple[...]
return annotation for _build_dsv4_o_proj_case. Ensure all listed functions
follow the project’s annotation conventions.

In `@tests/unittest/_torch/modules/test_mhc.py`:
- Around line 817-824: Parameterize the test case around x_partials and
x_reduced over x_splits values 2 and 4 so both CUDA kernel and barrier-schedule
specializations execute, preserving the existing setup for each parameter. Also
assess TensorRT-LLM coverage and add a concrete follow-up for the relevant file
to cover XS=2 there.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5c94ea48-177a-413b-9fcf-ff9d5a16faba

📥 Commits

Reviewing files that changed from the base of the PR and between 13f8716 and 2032525.

📒 Files selected for processing (14)
  • cpp/tensorrt_llm/kernels/mhcKernels/fused_tf32_pmap_gemm.cuh
  • cpp/tensorrt_llm/kernels/mhcKernels/mhcFusedHcKernel.cu
  • cpp/tensorrt_llm/kernels/mhcKernels/mhcKernels.h
  • cpp/tensorrt_llm/kernels/mhcKernels/mhc_fused_fma.cuh
  • cpp/tensorrt_llm/thop/mhcOp.cpp
  • tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py
  • tensorrt_llm/_torch/cute_dsl_kernels/blackwell/blockwise_gemm/blockwise_gemm.py
  • tensorrt_llm/_torch/cute_dsl_kernels/blackwell/dense_blockscaled_gemm_persistent.py
  • tensorrt_llm/_torch/cute_dsl_kernels/blackwell/dynamic_mxf8_mainloop.py
  • tensorrt_llm/_torch/modules/mhc/hyper_connection.py
  • tensorrt_llm/_torch/modules/mhc/mhc_cuda.py
  • tensorrt_llm/_torch/modules/mla.py
  • tests/unittest/_torch/attention/sparse/deepseek_v4/test_deepseek_v4_o_proj.py
  • tests/unittest/_torch/modules/test_mhc.py

Comment thread cpp/tensorrt_llm/kernels/mhcKernels/mhcFusedHcKernel.cu
Comment thread tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py
Comment thread tensorrt_llm/_torch/modules/mhc/mhc_cuda.py Outdated
Comment thread tensorrt_llm/_torch/modules/mla.py
Comment thread tests/unittest/_torch/attention/sparse/deepseek_v4/test_deepseek_v4_o_proj.py Outdated
Comment on lines +701 to +721
expected_smem_epilogue = num_tokens <= 32
expected_smem_row_iters = (num_tokens + 15) // 16 if expected_smem_epilogue else 1
fp8out_keys = [
key
for key in cute_dsl_custom_ops.CuteDSLFp8BlackwellBmmRunner.kernel_cache
if key[0] == "fp8out"
and key[-2] == expected_smem_row_iters
and key[-1] == expected_smem_epilogue
]
assert len(fp8out_keys) == 1

if num_tokens == 16:
compiled_gemm = cute_dsl_custom_ops.CuteDSLFp8BlackwellBmmRunner.kernel_cache[
fp8out_keys[0]
]
out_cached = mla._deepseek_v4_o_proj(attn_out_latent.clone(), position_ids)
assert (
cute_dsl_custom_ops.CuteDSLFp8BlackwellBmmRunner.kernel_cache[fp8out_keys[0]]
is compiled_gemm
)
torch.testing.assert_close(out_cached, out_unfused, rtol=0, atol=0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Isolate the kernel cache and compare against the fused run.

kernel_cache is class-global, making len(fp8out_keys) == 1 order-dependent. Reset it with monkeypatch for each case. The cached result should also be compared with out_fused, not the numerically distinct unfused baseline.

Proposed fix
+    monkeypatch.setattr(
+        cute_dsl_custom_ops.CuteDSLFp8BlackwellBmmRunner,
+        "kernel_cache",
+        {},
+    )
...
-        torch.testing.assert_close(out_cached, out_unfused, rtol=0, atol=0)
+        torch.testing.assert_close(out_cached, out_fused, rtol=0, atol=0)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expected_smem_epilogue = num_tokens <= 32
expected_smem_row_iters = (num_tokens + 15) // 16 if expected_smem_epilogue else 1
fp8out_keys = [
key
for key in cute_dsl_custom_ops.CuteDSLFp8BlackwellBmmRunner.kernel_cache
if key[0] == "fp8out"
and key[-2] == expected_smem_row_iters
and key[-1] == expected_smem_epilogue
]
assert len(fp8out_keys) == 1
if num_tokens == 16:
compiled_gemm = cute_dsl_custom_ops.CuteDSLFp8BlackwellBmmRunner.kernel_cache[
fp8out_keys[0]
]
out_cached = mla._deepseek_v4_o_proj(attn_out_latent.clone(), position_ids)
assert (
cute_dsl_custom_ops.CuteDSLFp8BlackwellBmmRunner.kernel_cache[fp8out_keys[0]]
is compiled_gemm
)
torch.testing.assert_close(out_cached, out_unfused, rtol=0, atol=0)
monkeypatch.setattr(
cute_dsl_custom_ops.CuteDSLFp8BlackwellBmmRunner,
"kernel_cache",
{},
)
expected_smem_epilogue = num_tokens <= 32
expected_smem_row_iters = (num_tokens + 15) // 16 if expected_smem_epilogue else 1
fp8out_keys = [
key
for key in cute_dsl_custom_ops.CuteDSLFp8BlackwellBmmRunner.kernel_cache
if key[0] == "fp8out"
and key[-2] == expected_smem_row_iters
and key[-1] == expected_smem_epilogue
]
assert len(fp8out_keys) == 1
if num_tokens == 16:
compiled_gemm = cute_dsl_custom_ops.CuteDSLFp8BlackwellBmmRunner.kernel_cache[
fp8out_keys[0]
]
out_cached = mla._deepseek_v4_o_proj(attn_out_latent.clone(), position_ids)
assert (
cute_dsl_custom_ops.CuteDSLFp8BlackwellBmmRunner.kernel_cache[fp8out_keys[0]]
is compiled_gemm
)
torch.testing.assert_close(out_cached, out_fused, rtol=0, atol=0)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tests/unittest/_torch/attention/sparse/deepseek_v4/test_deepseek_v4_o_proj.py`
around lines 701 - 721, Update the test around
CuteDSLFp8BlackwellBmmRunner.kernel_cache to isolate the class-global cache per
case using monkeypatch, so fp8out_keys contains only the current compilation
before asserting its length. In the num_tokens == 16 validation, compare
out_cached against out_fused instead of out_unfused while preserving the cache
identity check.

@mingyangHao
mingyangHao requested review from a team as code owners July 14, 2026 04:37
@mingyangHao

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59110 [ run ] triggered by Bot. Commit: c498381 Link to invocation

Comment thread tensorrt_llm/_torch/modules/mla.py
@mingyangHao
mingyangHao requested a review from yuxianq July 14, 2026 06:22
Comment thread cpp/tensorrt_llm/kernels/mhcKernels/mhcKernels.h Outdated
Comment thread tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py Outdated
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59110 [ run ] completed with state FAILURE. Commit: c498381
/LLM/main/L0_MergeRequest_PR pipeline #47623 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

Comment thread tensorrt_llm/_torch/modules/mla.py Outdated
Comment thread tensorrt_llm/_torch/modules/mla.py Outdated
@mingyangHao

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59184 [ run ] triggered by Bot. Commit: b3c0330 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59184 [ run ] completed with state FAILURE. Commit: b3c0330
/LLM/main/L0_MergeRequest_PR pipeline #47687 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@mingyangHao

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #59317 [ run ] triggered by Bot. Commit: b3c0330 Link to invocation

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tensorrt_llm/_torch/models/modeling_deepseekv4.py (1)

1763-1784: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve the required default-off O-projection gate.

_resolve_enable_fused_hc() can resolve to True by default, and this value is now passed as allow_dsv4_split_output. The supplied MLA._should_use_fused_oproj() predicate only checks the TRTLLM_DSV4_DISABLE_FUSED_OPROJ kill switch, so eligible layers can enter the fused path without the PR’s required DSV4_FUSE_OPROJ=1 gate. Enforce the positive rollout gate here or in the downstream predicate.

Based on the supplied mla.py:1249-1262 contract and PR objective.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/models/modeling_deepseekv4.py` around lines 1763 - 1784,
Ensure the fused O-projection path remains disabled unless the explicit
DSV4_FUSE_OPROJ=1 rollout gate is enabled. Update the enablement flow involving
_resolve_enable_fused_hc(), DeepseekV4Attention’s allow_split_output, or the
downstream MLA._should_use_fused_oproj() predicate so the positive gate is
required in addition to the existing kill switch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@tensorrt_llm/_torch/models/modeling_deepseekv4.py`:
- Around line 1763-1784: Ensure the fused O-projection path remains disabled
unless the explicit DSV4_FUSE_OPROJ=1 rollout gate is enabled. Update the
enablement flow involving _resolve_enable_fused_hc(), DeepseekV4Attention’s
allow_split_output, or the downstream MLA._should_use_fused_oproj() predicate so
the positive gate is required in addition to the existing kill switch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 46fd0f4d-c4d4-4c42-af98-29fae0fbe3da

📥 Commits

Reviewing files that changed from the base of the PR and between 433a5d1 and 7554020.

📒 Files selected for processing (3)
  • tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py
  • tensorrt_llm/_torch/models/modeling_deepseekv4.py
  • tests/unittest/_torch/attention/sparse/deepseek_v4/test_deepseek_v4_o_proj.py
💤 Files with no reviewable changes (1)
  • tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py

@mingyangHao
mingyangHao force-pushed the user/mingyangh/dsv4-oproj-fp8-fuse branch from 7554020 to 79a23ad Compare August 3, 2026 01:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (6)
tensorrt_llm/_torch/cute_dsl_kernels/blackwell/blockwise_gemm/blockwise_gemm.py (4)

2591-2609: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Parameterize the existing helper instead of duplicating it.

epilog_bf16_smem_copy_and_partition repeats epilog_smem_copy_and_partition (Lines 2553-2589) verbatim. The only difference is the dtype passed to sm100_utils.get_smem_store_op: cutlass.BFloat16 here, self.c_dtype there.

Add an optional dtype parameter to the existing method and delete the copy.

♻️ Proposed consolidation
     def epilog_smem_copy_and_partition(
         self,
         tiled_copy_t2r: cute.TiledCopy,
         tTR_rC: cute.Tensor,
         tidx: cutlass.Int32,
         sC: cute.Tensor,
+        store_dtype: Optional[Type[cutlass.Numeric]] = None,
     ) -> Tuple[cute.TiledCopy, cute.Tensor, cute.Tensor]:
         copy_atom_r2s = sm100_utils.get_smem_store_op(
-            self.c_layout, self.c_dtype, self.acc_dtype, tiled_copy_t2r
+            self.c_layout,
+            self.c_dtype if store_dtype is None else store_dtype,
+            self.acc_dtype,
+            tiled_copy_t2r,
         )

Then delete epilog_bf16_smem_copy_and_partition and update the call site at Lines 1935-1940:

                 ) = self.epilog_bf16_smem_copy_and_partition(
                     tiled_copy_t2r,
                     tTR_rBf16,
                     epi_tidx,
                     sEpiBf16,
+                    store_dtype=cutlass.BFloat16,
                 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tensorrt_llm/_torch/cute_dsl_kernels/blackwell/blockwise_gemm/blockwise_gemm.py`
around lines 2591 - 2609, Parameterize epilog_smem_copy_and_partition with an
optional dtype argument, defaulting to self.c_dtype, and pass that argument to
sm100_utils.get_smem_store_op. Remove epilog_bf16_smem_copy_and_partition and
update its call site to invoke the existing helper with cutlass.BFloat16.

134-175: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add docstrings to the two new public DSL helpers.

ceil_to_ue8m0_device and stg_u8_raw each carry a docstring. abs_f32_device and max_nan_f32_device do not. Both are module-level public names.

Ruff's D rule set is enabled for this repository, so a missing docstring on a public function can fail lint.

♻️ Proposed docstrings
 def abs_f32_device(
     value: cutlass.Float32,
     *,
     loc=None,
     ip=None,
 ) -> cutlass.Float32:
+    """Return the absolute value of an FP32 register."""
     return cutlass.Float32(
 def max_nan_f32_device(
     lhs: cutlass.Float32,
     rhs: cutlass.Float32,
     *,
     loc=None,
     ip=None,
 ) -> cutlass.Float32:
+    """Return the NaN-propagating maximum of two FP32 registers."""
     return cutlass.Float32(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tensorrt_llm/_torch/cute_dsl_kernels/blackwell/blockwise_gemm/blockwise_gemm.py`
around lines 134 - 175, Add docstrings to the public module-level helpers
abs_f32_device and max_nan_f32_device, matching the concise documentation style
used by ceil_to_ue8m0_device and stg_u8_raw. Leave their implementations and
behavior unchanged.

Source: Learnings


3103-3126: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the arguments of wrapper_fp8sf.

The sibling wrapper at Lines 3026-3061 documents every argument. wrapper_fp8sf has a one-line docstring only. It is a public entry point with a wider ABI, including the non-obvious sf_out_ptr address-carrying tensor, sf_aligned_mn, n_tiles_per_group, and the two Constexpr epilogue controls.

The coding guidelines require Google-style docstrings that document public function arguments.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tensorrt_llm/_torch/cute_dsl_kernels/blackwell/blockwise_gemm/blockwise_gemm.py`
around lines 3103 - 3126, Expand the docstring for wrapper_fp8sf to Google-style
documentation covering every parameter, matching the sibling wrapper’s argument
descriptions where applicable. Explicitly document sf_out_ptr as the
address-carrying output-scale pointer, sf_aligned_mn and n_tiles_per_group, and
the fp8_smem_epilogue Constexpr controls, while preserving the existing function
behavior.

Source: Coding guidelines


478-507: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Make the one-stage trade for the BF16 staging tile explicit.

Line 480 drops one A/B stage to pay for sEpiBf16. The trade is exact only for the enforced configuration: with a 128x128 FP8 tile, one A/B stage is 128*128 + 128*128 = 32 KiB, and the BF16 staging tile is 128 * 128 * 2 = 32 KiB. The validation at Lines 772-785 pins that configuration, so the arithmetic holds today.

The invariant is implicit. A later change to mma_tiler_mn or to fp8_bf16_stage_count would silently overflow shared memory or leave num_ab_stage at zero. Add an assertion that ties the two sizes together.

♻️ Proposed guard
         if self.fp8_smem_epi_mode:
             # Trade one A/B stage for the BF16 epilogue tile.
+            if self.num_ab_stage < 2:
+                raise ValueError(
+                    "The cooperative FP8 epilogue needs at least two A/B "
+                    f"stages, got {self.num_ab_stage}"
+                )
             self.num_ab_stage -= 1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tensorrt_llm/_torch/cute_dsl_kernels/blackwell/blockwise_gemm/blockwise_gemm.py`
around lines 478 - 507, In the fp8_smem_epi_mode path near fp8_bf16_stage_count
and the staged shared-memory layouts, add an assertion verifying that the
shared-memory size released by exactly one A/B stage equals the BF16 epilogue
staging tile size. Keep the existing num_ab_stage decrement, and ensure the
assertion also protects against an invalid zero or negative remaining A/B stage
under the enforced configuration.
tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py (1)

4515-4550: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Build the CuTe pointers and DLPack tensors only when they are needed.

In the TVM-FFI path the launch at Lines 4591-4603 passes raw data_ptr() values and torch tensors. It never uses a_ptr, b_ptr, a_sf_ptr, b_sf_ptr, c_cute_tensor, or alpha_cute_tensor. These objects are still built on every call, including two cute.runtime.from_dlpack conversions. This adds per-invocation host overhead on a decode-latency path.

CuteDSLFp8BlackwellBmmRunner.forward (Lines 4876-4905) and forward_fp8out (Lines 5137-5159) already guard the equivalent construction. Apply the same pattern here.

Note that alpha itself must stay outside the branch, because the TVM-FFI launch passes the torch tensor.

♻️ Proposed restructuring
-            a_ptr = make_ptr(
-                cutlass.Float8E4M3FN,
-                kernel_a.data_ptr(),
-                cute.AddressSpace.gmem,
-                assumed_align=16,
-            )
-            b_ptr = make_ptr(
-                cutlass.Float8E4M3FN,
-                kernel_b.data_ptr(),
-                cute.AddressSpace.gmem,
-                assumed_align=16,
-            )
-            a_sf_ptr = make_ptr(
-                cutlass.Uint32,
-                kernel_sfa.data_ptr(),
-                cute.AddressSpace.gmem,
-                assumed_align=16,
-            )
-            b_sf_ptr = make_ptr(
-                cutlass.Uint32,
-                kernel_sfb.data_ptr(),
-                cute.AddressSpace.gmem,
-                assumed_align=16,
-            )
-            c_cute_tensor = cute.runtime.from_dlpack(
-                output).mark_layout_dynamic(leading_dim=1)
             alpha = self.__class__.alpha_cache.get(device_key)
             if alpha is None:
                 alpha = torch.ones((1, ),
                                    device=device_key,
                                    dtype=torch.float32)
                 self.__class__.alpha_cache[device_key] = alpha
-            alpha_cute_tensor = cute.runtime.from_dlpack(alpha)
-            stream = (cute.runtime.make_fake_stream(
-                use_tvm_ffi_env_stream=True) if self.use_tvm_ffi else
-                      cuda.CUstream(torch.cuda.current_stream().cuda_stream))
+
+            need_cute_args = (cache_key not in self.__class__.kernel_cache
+                              or not self.use_tvm_ffi)
+            if need_cute_args:
+                a_ptr = make_ptr(cutlass.Float8E4M3FN, kernel_a.data_ptr(),
+                                 cute.AddressSpace.gmem, assumed_align=16)
+                b_ptr = make_ptr(cutlass.Float8E4M3FN, kernel_b.data_ptr(),
+                                 cute.AddressSpace.gmem, assumed_align=16)
+                a_sf_ptr = make_ptr(cutlass.Uint32, kernel_sfa.data_ptr(),
+                                    cute.AddressSpace.gmem, assumed_align=16)
+                b_sf_ptr = make_ptr(cutlass.Uint32, kernel_sfb.data_ptr(),
+                                    cute.AddressSpace.gmem, assumed_align=16)
+                c_cute_tensor = cute.runtime.from_dlpack(
+                    output).mark_layout_dynamic(leading_dim=1)
+                alpha_cute_tensor = cute.runtime.from_dlpack(alpha)
+                stream = (cute.runtime.make_fake_stream(
+                    use_tvm_ffi_env_stream=True) if self.use_tvm_ffi else
+                          cuda.CUstream(
+                              torch.cuda.current_stream().cuda_stream))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py` around lines 4515 -
4550, In the forward path around the pointer and DLPack setup, keep alpha tensor
creation outside the backend branch, but construct a_ptr, b_ptr, a_sf_ptr,
b_sf_ptr, c_cute_tensor, and alpha_cute_tensor only when use_tvm_ffi is false.
Mirror the conditional construction pattern used by
CuteDSLFp8BlackwellBmmRunner.forward and forward_fp8out, while preserving the
TVM-FFI launch’s use of raw data_ptr values and the torch alpha tensor.
tensorrt_llm/_torch/cute_dsl_kernels/blackwell/dense_blockscaled_gemm_persistent.py (1)

65-68: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Share the packed-scale constants with the custom-op layer.

_SCALE_BLOCK_K, _SCALES_PER_WORD, and _PACKED_SCALE_K are redefined in tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py at Lines 4339-4341 as CuteDSLFp8SplitKGemmRunner._SCALE_BLOCK_K, _SCALES_PER_WORD, and _PACKED_SCALE_K.

These values form a producer-consumer contract. The custom op validates k % (packed_scale_k * num_splits) == 0 with its copy (Line 4658) and derives packed_k = k // packed_scale_k (Line 4663). The kernel derives packed_k_per_split = split_k // _PACKED_SCALE_K with this copy (Line 494). If the two copies ever diverge, the scale tensor layout is wrong and the GEMM produces incorrect results with no error.

Export the constants from this module and import them in the custom-op module.

♻️ Proposed consolidation

In cute_dsl_custom_ops.py, replace the local copies:

-        _SCALE_BLOCK_K = 128
-        _SCALES_PER_WORD = 4
-        _PACKED_SCALE_K = _SCALE_BLOCK_K * _SCALES_PER_WORD
+        _SCALE_BLOCK_K = _SCALE_BLOCK_K
+        _SCALES_PER_WORD = _SCALES_PER_WORD
+        _PACKED_SCALE_K = _PACKED_SCALE_K

with an import of the kernel-module constants at the top of the file, for example:

from ..cute_dsl_kernels.blackwell.dense_blockscaled_gemm_persistent import (
    _PACKED_SCALE_K, _SCALE_BLOCK_K, _SCALES_PER_WORD)

Consider renaming them without the leading underscore and adding them to __all__, since they are now a cross-module contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@tensorrt_llm/_torch/cute_dsl_kernels/blackwell/dense_blockscaled_gemm_persistent.py`
around lines 65 - 68, Export the shared constants _SCALE_BLOCK_K,
_SCALES_PER_WORD, and _PACKED_SCALE_K from the dense blockscaled GEMM kernel
module, then remove their duplicate definitions in CuteDSLFp8SplitKGemmRunner
and import the kernel-module values in the custom-op module. Update references
as needed so validation and packed-scale calculations use the same constants;
consider public names and __all__ only if required by the module’s export
conventions.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py`:
- Around line 5065-5099: Add a capacity validation beside the existing sf_out
stride check to require storage for all padded rows: verify sf_out’s backing
storage can hold at least sf_m * packed_sf_n int32 elements, not merely its
logical shape. Keep the existing shape and MN-major stride checks, and raise
ValueError when the available storage is insufficient.

In
`@tensorrt_llm/_torch/cute_dsl_kernels/blackwell/blockwise_gemm/blockwise_gemm.py`:
- Around line 2246-2293: Update the FP8 validation block around the existing
validation logic to enforce that the computed epilogue tile N dimension,
epi_tile_n, equals 32, matching the four cooperative epilogue subtiles assumed
by fp8_subtile_idx and the TMA drain loop. Reject unsupported configurations
before the staging and transfer paths execute, using the existing validation
mechanism.
- Around line 2027-2109: Reject non-identity epilogue_op when FP8 scaling output
is enabled, since both FP8 register and cooperative paths bypass the callback.
Define a module-level _identity_epilogue_op sentinel, use it as the __call__
default instead of the inline lambda, and add validation in the existing
__call__ validation block to accept only that sentinel for the sf_out_tensor/FP8
path.

In
`@tensorrt_llm/_torch/cute_dsl_kernels/blackwell/dense_blockscaled_gemm_persistent.py`:
- Around line 2239-2242: Update the epilogue sequence around acc_vec and
epilogue_op so apply_alpha multiplies the accumulator in its higher-precision
type before converting to self.c_dtype. Preserve the existing conversion and
epilogue flow for non-alpha paths, ensuring alpha_value is not applied after
output-type rounding.
- Around line 793-801: Handle packed MXF4 SFA staging consistently at the
packed-scale sizing logic in dense_blockscaled_gemm_persistent.py: either reject
packed Float4 inputs or use a packed SFA layout whose stage stride is
cta_tile_shape_mnk[0] multiplied by _PACKED_SCALE_BYTES, matching
make_smem_layout_sfa and sSFA_raw allocation. Apply the same correction at the
anchor scale-storage calculation around lines 793-801 and the sibling packed-SFA
handling around lines 1061-1078; the existing Float8 packed caller must remain
unchanged.

---

Nitpick comments:
In `@tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py`:
- Around line 4515-4550: In the forward path around the pointer and DLPack
setup, keep alpha tensor creation outside the backend branch, but construct
a_ptr, b_ptr, a_sf_ptr, b_sf_ptr, c_cute_tensor, and alpha_cute_tensor only when
use_tvm_ffi is false. Mirror the conditional construction pattern used by
CuteDSLFp8BlackwellBmmRunner.forward and forward_fp8out, while preserving the
TVM-FFI launch’s use of raw data_ptr values and the torch alpha tensor.

In
`@tensorrt_llm/_torch/cute_dsl_kernels/blackwell/blockwise_gemm/blockwise_gemm.py`:
- Around line 2591-2609: Parameterize epilog_smem_copy_and_partition with an
optional dtype argument, defaulting to self.c_dtype, and pass that argument to
sm100_utils.get_smem_store_op. Remove epilog_bf16_smem_copy_and_partition and
update its call site to invoke the existing helper with cutlass.BFloat16.
- Around line 134-175: Add docstrings to the public module-level helpers
abs_f32_device and max_nan_f32_device, matching the concise documentation style
used by ceil_to_ue8m0_device and stg_u8_raw. Leave their implementations and
behavior unchanged.
- Around line 3103-3126: Expand the docstring for wrapper_fp8sf to Google-style
documentation covering every parameter, matching the sibling wrapper’s argument
descriptions where applicable. Explicitly document sf_out_ptr as the
address-carrying output-scale pointer, sf_aligned_mn and n_tiles_per_group, and
the fp8_smem_epilogue Constexpr controls, while preserving the existing function
behavior.
- Around line 478-507: In the fp8_smem_epi_mode path near fp8_bf16_stage_count
and the staged shared-memory layouts, add an assertion verifying that the
shared-memory size released by exactly one A/B stage equals the BF16 epilogue
staging tile size. Keep the existing num_ab_stage decrement, and ensure the
assertion also protects against an invalid zero or negative remaining A/B stage
under the enforced configuration.

In
`@tensorrt_llm/_torch/cute_dsl_kernels/blackwell/dense_blockscaled_gemm_persistent.py`:
- Around line 65-68: Export the shared constants _SCALE_BLOCK_K,
_SCALES_PER_WORD, and _PACKED_SCALE_K from the dense blockscaled GEMM kernel
module, then remove their duplicate definitions in CuteDSLFp8SplitKGemmRunner
and import the kernel-module values in the custom-op module. Update references
as needed so validation and packed-scale calculations use the same constants;
consider public names and __all__ only if required by the module’s export
conventions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 98063236-5277-4a27-9948-8a22ae782857

📥 Commits

Reviewing files that changed from the base of the PR and between 7554020 and 79a23ad.

📒 Files selected for processing (10)
  • cpp/tensorrt_llm/kernels/mhcKernels/fused_tf32_pmap_gemm.cuh
  • cpp/tensorrt_llm/kernels/mhcKernels/mhcFusedHcKernel.cu
  • cpp/tensorrt_llm/kernels/mhcKernels/mhcKernels.h
  • cpp/tensorrt_llm/kernels/mhcKernels/mhc_fused_fma.cuh
  • cpp/tensorrt_llm/thop/mhcOp.cpp
  • tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py
  • tensorrt_llm/_torch/cute_dsl_kernels/blackwell/blockwise_gemm/blockwise_gemm.py
  • tensorrt_llm/_torch/cute_dsl_kernels/blackwell/dense_blockscaled_gemm_persistent.py
  • tensorrt_llm/_torch/cute_dsl_kernels/blackwell/dynamic_mxf8_mainloop.py
  • tensorrt_llm/_torch/models/modeling_deepseekv4.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • cpp/tensorrt_llm/kernels/mhcKernels/mhc_fused_fma.cuh
  • cpp/tensorrt_llm/kernels/mhcKernels/mhcKernels.h
  • cpp/tensorrt_llm/kernels/mhcKernels/fused_tf32_pmap_gemm.cuh
  • cpp/tensorrt_llm/thop/mhcOp.cpp
  • tensorrt_llm/_torch/cute_dsl_kernels/blackwell/dynamic_mxf8_mainloop.py
  • cpp/tensorrt_llm/kernels/mhcKernels/mhcFusedHcKernel.cu
  • tensorrt_llm/_torch/models/modeling_deepseekv4.py

Comment on lines +5065 to +5099
sf_m = pad_up(m, 4)
sf_k = ceil_div(k, 128)
sf_n = ceil_div(n, 128)
packed_sf_n = ceil_div(batch_size * sf_n, 4)
expected_shapes = (
("weight", b, (batch_size, n, k)),
("input_scale", a_sf, (batch_size, sf_k, sf_m)),
("weight_scale", b_sf, (batch_size, sf_n, sf_k)),
("output_fp8", output, (m, batch_size * n)),
("sf_out", sf_out, (m, packed_sf_n)),
)
for name, tensor, shape in expected_shapes:
if tensor.shape != shape:
raise ValueError(
f"{name} must have shape {shape}, got {tuple(tensor.shape)}"
)

for name, tensor in (
("input", a),
("weight", b),
("input_scale", a_sf),
("weight_scale", b_sf),
):
if not tensor.is_contiguous():
raise ValueError(f"{name} must be contiguous")
if tensor.data_ptr() % 16 != 0:
raise ValueError(f"{name} must be 16-byte aligned")

if not output.is_contiguous():
raise ValueError("output_fp8 must be contiguous")
if output.data_ptr() % 16 != 0:
raise ValueError("output_fp8 must be 16-byte aligned")
if sf_out.stride() != (1, sf_m):
raise ValueError(
f"sf_out must have MN-major stride (1, {sf_m})")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Also validate that sf_out has storage for the padded rows.

The kernel writes padded scale rows beyond M. In blockwise_gemm.py the register path (Lines 2091-2097) and the cooperative path (Lines 2271-2278) store to runtime_m + 0..2 while padding_m < sf_aligned_mn. sf_aligned_mn is the aligned_m value passed at Line 5213, which is pad_up(m, 4).

The current checks accept sf_out with shape (m, packed_sf_n) and stride (1, sf_m). That describes a strided view. The checks do not confirm that the backing storage holds sf_m * packed_sf_n int32 elements. If a caller allocates exactly m * packed_sf_n elements and applies as_strided, the padded-row stores in the last column write past the allocation. That is an out-of-bounds device store.

Add a capacity check next to the stride check.

🛡️ Proposed capacity check
             if sf_out.stride() != (1, sf_m):
                 raise ValueError(
                     f"sf_out must have MN-major stride (1, {sf_m})")
+            required_sf_out_elems = sf_m * packed_sf_n
+            available_sf_out_elems = (sf_out.untyped_storage().nbytes() //
+                                      sf_out.element_size() -
+                                      sf_out.storage_offset())
+            if available_sf_out_elems < required_sf_out_elems:
+                raise ValueError(
+                    f"sf_out must back {required_sf_out_elems} int32 elements "
+                    f"for M padding, got {available_sf_out_elems}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sf_m = pad_up(m, 4)
sf_k = ceil_div(k, 128)
sf_n = ceil_div(n, 128)
packed_sf_n = ceil_div(batch_size * sf_n, 4)
expected_shapes = (
("weight", b, (batch_size, n, k)),
("input_scale", a_sf, (batch_size, sf_k, sf_m)),
("weight_scale", b_sf, (batch_size, sf_n, sf_k)),
("output_fp8", output, (m, batch_size * n)),
("sf_out", sf_out, (m, packed_sf_n)),
)
for name, tensor, shape in expected_shapes:
if tensor.shape != shape:
raise ValueError(
f"{name} must have shape {shape}, got {tuple(tensor.shape)}"
)
for name, tensor in (
("input", a),
("weight", b),
("input_scale", a_sf),
("weight_scale", b_sf),
):
if not tensor.is_contiguous():
raise ValueError(f"{name} must be contiguous")
if tensor.data_ptr() % 16 != 0:
raise ValueError(f"{name} must be 16-byte aligned")
if not output.is_contiguous():
raise ValueError("output_fp8 must be contiguous")
if output.data_ptr() % 16 != 0:
raise ValueError("output_fp8 must be 16-byte aligned")
if sf_out.stride() != (1, sf_m):
raise ValueError(
f"sf_out must have MN-major stride (1, {sf_m})")
sf_m = pad_up(m, 4)
sf_k = ceil_div(k, 128)
sf_n = ceil_div(n, 128)
packed_sf_n = ceil_div(batch_size * sf_n, 4)
expected_shapes = (
("weight", b, (batch_size, n, k)),
("input_scale", a_sf, (batch_size, sf_k, sf_m)),
("weight_scale", b_sf, (batch_size, sf_n, sf_k)),
("output_fp8", output, (m, batch_size * n)),
("sf_out", sf_out, (m, packed_sf_n)),
)
for name, tensor, shape in expected_shapes:
if tensor.shape != shape:
raise ValueError(
f"{name} must have shape {shape}, got {tuple(tensor.shape)}"
)
for name, tensor in (
("input", a),
("weight", b),
("input_scale", a_sf),
("weight_scale", b_sf),
):
if not tensor.is_contiguous():
raise ValueError(f"{name} must be contiguous")
if tensor.data_ptr() % 16 != 0:
raise ValueError(f"{name} must be 16-byte aligned")
if not output.is_contiguous():
raise ValueError("output_fp8 must be contiguous")
if output.data_ptr() % 16 != 0:
raise ValueError("output_fp8 must be 16-byte aligned")
if sf_out.stride() != (1, sf_m):
raise ValueError(
f"sf_out must have MN-major stride (1, {sf_m})")
required_sf_out_elems = sf_m * packed_sf_n
available_sf_out_elems = (
sf_out.untyped_storage().nbytes() // sf_out.element_size()
- sf_out.storage_offset()
)
if available_sf_out_elems < required_sf_out_elems:
raise ValueError(
f"sf_out must back {required_sf_out_elems} int32 elements "
f"for M padding, got {available_sf_out_elems}"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tensorrt_llm/_torch/custom_ops/cute_dsl_custom_ops.py` around lines 5065 -
5099, Add a capacity validation beside the existing sf_out stride check to
require storage for all padded rows: verify sf_out’s backing storage can hold at
least sf_m * packed_sf_n int32 elements, not merely its logical shape. Keep the
existing shape and MN-major stride checks, and raise ValueError when the
available storage is insufficient.

@mingyangHao
mingyangHao force-pushed the user/mingyangh/dsv4-oproj-fp8-fuse branch from 5e00659 to 04b99e3 Compare August 3, 2026 04:43
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
…actic pool

The runner always queried get_max_active_clusters(2), so (1,1)-cluster
tactics launched with a 74-cluster persistent grid on 148 SMs (half the
machine). Query per tactic cluster size instead, cached per
(device, cluster size).

Expand _SPLIT_K1_TACTICS from 10 to 17: add noswap (256,112/160/224/240)
and swap (256,208/224/240) tiles. Fine tile-N noswap variants raise SM
fill at mid M (tile-M count 28 under swap never fills the grid), and the
224/240 tiles recover the M>=4096 range where the previous pool lost to
DeepGEMM under realistic data. Fallback entries are unchanged and the
runtime AutoTuner selects winners per token bucket.

Paired-capture sweep vs the previous pool (ratio vs DeepGEMM within
shared captures, B200): decode M=1-32 +4.7-6.8%, M=256 +3.1%,
M=4096/8192/16384 +1.9/+3.0/+1.9%; no regressions at 15 sweep points.

Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
The packed-scale pipeline's empty-buffer release used
ab_pipeline.consumer_mask, which spans the whole A/B TMA multicast
group. The scale pipeline is pair-local (producers are the MMA pair's
scale warps, consumer is the pair leader), so for any cluster larger
than the pair the release lands spurious arrivals on the other pair's
scale barriers, corrupting their phases and deadlocking the kernel.
(2,1) clusters worked only because the two masks coincide there.

Release with the pair-local V-mode image mask instead. For (2,1) the
mask value is identical by construction; all shipped tactics validated
byte-exact with unchanged latency, and previously-hanging (2,2) and
(4,1) configurations now run and validate byte-exact against DeepGEMM.

Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
Signed-off-by: Mingyang Hao <200044211+mingyangHao@users.noreply.github.com>
@mingyangHao
mingyangHao force-pushed the user/mingyangh/dsv4-oproj-fp8-fuse branch from f6560f2 to e565933 Compare August 5, 2026 08:19
@mingyangHao

Copy link
Copy Markdown
Collaborator Author

/bot run --disable-fail-fast

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #64000 [ run ] triggered by Bot. Commit: e565933 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #64000 [ run ] completed with state SUCCESS. Commit: e565933
/LLM/main/L0_MergeRequest_PR pipeline #51935 completed with status: 'FAILURE'

CI Report

⚠️ Action Required:

  • Please check the failed tests and fix your PR
  • If you cannot view the failures, ask the CI triggerer to share details
  • Once fixed, request an NVIDIA team member to trigger CI again

CI Agent Failure Analysis

Link to invocation

@mikeiovine mikeiovine left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stamp on behalf of runtime devs, delegating review to @NVIDIA/trt-llm-torch-attention-devs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants