Skip to content

perf: add mla metadata parallel path#3465

Merged
valarLip merged 5 commits into
ROCm:mainfrom
RadeonFlow:rf-metadata
Jun 24, 2026
Merged

perf: add mla metadata parallel path#3465
valarLip merged 5 commits into
ROCm:mainfrom
RadeonFlow:rf-metadata

Conversation

@ftyghome

@ftyghome ftyghome commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Motivation

The fast_mode MLA decode metadata planner (kn_get_mla_metadata_v1_2, reached via get_mla_metadata_v1) currently runs its greedy work-distribution as a single-warp serial scan over CUs.

For MLA decode (num_heads_k = 1), the scan length is the full CU count, so this metadata kernel is on the decode critical path. In Kimi K2.5 serving, this kernel can take around 100us at medium-to-high concurrency.

Technical Details

This PR adds kn_get_mla_metadata_v1_2_parallel, a closed-form two-phase parallel planner for the pure-decode fast path.

The new planner produces metadata that matches the serial kernel, and the change is contained in v1_2_device.cuh.

Test Plan

We evaluated this change with both:

  1. a standalone metadata microbenchmark, checking that the parallel planner matches the serial planner; and
  2. an end-to-end Kimi K2.5 MXFP4 serving profile, extracting the metadata kernel latency from rocprof traces.

Test environment:

GPU: AMD Instinct MI355X
ROCm: 7.2.3

Test Result

Microbenchmark

All microbench cases produce matching metadata between the serial and parallel planners.

batch ctx_len num_works num_split_groups match serial (us) parallel (us) speedup
4 2048 60 4 True 37.352 3.649 10.24x
8 2048 120 8 True 49.426 4.413 11.20x
16 2048 240 16 True 74.159 6.743 11.00x
32 2048 256 32 True 83.567 12.111 6.90x
64 2048 256 64 True 96.279 21.408 4.50x
128 2048 256 128 True 121.891 40.531 3.01x
4 4096 64 4 True 38.078 3.599 10.58x
8 4096 128 8 True 50.858 4.385 11.60x
16 4096 256 16 True 77.058 6.740 11.43x
32 4096 256 32 True 83.559 12.210 6.84x
64 4096 256 64 True 96.273 21.435 4.49x
128 4096 256 128 True 122.000 40.488 3.01x
4 8192 64 4 True 38.083 3.591 10.61x
8 8192 128 8 True 50.882 4.392 11.59x
16 8192 256 16 True 76.959 6.743 11.41x
32 8192 256 32 True 83.551 12.366 6.76x
64 8192 256 64 True 96.332 21.410 4.50x
128 8192 256 128 True 121.902 40.505 3.01x

Geomean speedup across these microbench cases is 7.07x.

Kimi K2.5 MXFP4 E2E Profile

The table below shows the metadata kernel latency extracted from Kimi K2.5 MXFP4 end-to-end traces. All values are in ns. Lower is better.

median saving / layer is computed as (serial median - parallel median) / 62.

concurrency serial median (ns) parallel median (ns) speedup median saving / layer (ns) serial p95 (ns) parallel p95 (ns) p95 speedup
4 38,879 4,559 8.53x 553.5 39,319 4,999 7.87x
8 51,759 5,839 8.86x 740.6 52,440 6,239 8.41x
16 77,320 8,000 9.66x 1,118.1 78,200 8,559 9.14x
32 86,120 12,880 6.69x 1,181.3 87,040 13,919 6.25x
64 102,320 22,519 4.54x 1,287.1 103,640 24,560 4.22x
128 133,840 41,599 3.22x 1,487.8 136,361 45,959 2.97x

In the end-to-end Kimi K2.5 MXFP4 profile, the parallel planner reduces the metadata kernel median latency by 3.22x to 9.66x across the tested concurrencies, corresponding to about 553.5 ns to 1,487.8 ns per layer.

@ftyghome ftyghome requested review from a team and Copilot June 1, 2026 12:54
@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

🏷️ CI Guide

Runs automatically on every PR:

  • ✅ Pre-checks (submodule verification, code formatting)
  • ✅ Aiter op tests (gfx942 + gfx950)
  • ✅ Triton tests on MI35X (only when aiter/ops/triton/** or related paths are changed)

Extended tests (opt-in via labels):

Label Tests
ci:triton-300x Run an additional Triton test job on MI300X in PRs; main branch always runs both MI35X and MI300X
ci:sglang SGLang integration tests: DeepSeek-R1-MXFP4 accuracy, Qwen 3.5 accuracy
ci:atom ATOM benchmark: DeepSeek-R1-0528, GPT-OSS-120B
ci:atom_full ATOM accuracy suite for PR and main models from ATOM models_accuracy.json
ci:vllm vLLM benchmark: GPT-OSS-120B, DeepSeek-R1-0528, Kimi-K2.5
ci:all All standard extended tests (excludes ci:atom_full)

Only add ci:atom_full for FlyDSL or Triton upgrades.
Add labels via the sidebar or gh pr edit 3465 --add-label <label>

Copilot AI 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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a new “parallel planner” GPU kernel path for MLA v1.2 metadata generation, and refactors shared device-side helpers to reuse logic between kernels.

Changes:

  • Introduce kn_get_mla_metadata_v1_2_parallel with a 3-phase plan (scan + parallel fill + tail fill).
  • Extract Qo-tiling and block-sum computation into reusable device helper functions.
  • Add an env-var-controlled dispatch path to select the parallel kernel when constraints are met and LDS budget allows.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +719 to +724
if(use_parallel && (scratch_bytes + qo_bytes + kv_bytes <= lds_size))
{
using Traits =
MlaMetadataV12Traits<kPackedQoLenPerWg, kQoSplits, kUniSeqlenQo, true, kIsSparse>;
kn_get_mla_metadata_v1_2_parallel<Traits><<<grid, fill_block, lds_size, stream>>>(params);
}
Comment on lines +708 to +709
const char* parallel_env = std::getenv("AITER_MLA_META_USE_PARALLEL");
const bool parallel_wanted = (parallel_env == nullptr) || (std::atoi(parallel_env) != 0);
Comment on lines +708 to +712
const char* parallel_env = std::getenv("AITER_MLA_META_USE_PARALLEL");
const bool parallel_wanted = (parallel_env == nullptr) || (std::atoi(parallel_env) != 0);
const bool use_parallel = parallel_wanted && (max_seqlen_qo == 1) && !kQoSplits &&
!kIsSparse && (params.page_size == 1) &&
(params.qk_batch_ratio == 1);
Comment on lines +713 to +716
const int32_t scratch_bytes =
static_cast<int32_t>(sizeof(int32_t)) * (3 + 5 * params.num_batches);
const int32_t qo_bytes = is_unique ? 0 : static_cast<int32_t>(sizeof(int32_t)) * params.num_batches;
const int32_t kv_bytes = static_cast<int32_t>(sizeof(int32_t)) * params.num_batches;
@valarLip valarLip requested a review from minmengdie June 2, 2026 07:03
@ftyghome

Copy link
Copy Markdown
Contributor Author

Hi @valarLip @minmengdie,

We have added detailed microbench and Kimi K2.5 end-to-end profiling results to this PR.

Could you please take another look when you get a chance? Please let me know if there are any concerns or if any additional data would be helpful from my side.

Thanks!

@ftyghome

Copy link
Copy Markdown
Contributor Author

Hi all,

I saw the previous CI run failed because of formatting error and have it fixed.

If there are anything needed from my side, please feel free to let me know:)

@valarLip

Copy link
Copy Markdown
Collaborator

rerun ci

@ftyghome

Copy link
Copy Markdown
Contributor Author

Hi @valarLip ,

Some of the CI failed at op_tests/test_dsv4_rotate_quant.py, which seems irrelevant to this PR's change.

Would you mind taking at look at the CI results? Thanks!

@valarLip valarLip merged commit befce50 into ROCm:main Jun 24, 2026
66 of 68 checks passed
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.

3 participants