[Performance] Balance the latent-MQA indexer's CP rows with a dual-chunk swap - #1762
Merged
xxyux merged 1 commit intoAug 19, 2026
Merged
Conversation
risemeup1111
suggested changes
Aug 17, 2026
Contributor
There was a problem hiding this comment.
| 序号 | 位置 | 优先级 | 状态 |
|---|---|---|---|
| 1 | indexer loss 梯度回归 | ✅ | |
| 2 | CP 模式校验契约 | ✅ |
Powered by Nyanpasu with gpt-5.5 xhigh, please check the suggestions carefully.
xxyux
force-pushed
the
feat/mqa-indexer-dualchunk-cp-develop
branch
from
August 17, 2026 09:10
f1e8133 to
68a5450
Compare
risemeup1111
approved these changes
Aug 17, 2026
xxyux
force-pushed
the
feat/mqa-indexer-dualchunk-cp-develop
branch
from
August 17, 2026 10:54
68a5450 to
2904008
Compare
Collaborator
Author
|
/re-run all-failed |
1 similar comment
Collaborator
Author
|
/re-run all-failed |
xxyux
force-pushed
the
feat/mqa-indexer-dualchunk-cp-develop
branch
from
August 18, 2026 03:42
2904008 to
d43affc
Compare
…unk swap Under contiguous CP the latent-MQA indexer scores each rank's own row slice, so with a causal mask its cost grows with the rank index: measured at 256k/cp16, 2.2ms on cp0 against 66.8ms on cp15 per layer per pass (a 30x spread), which makes the slowest rank do 1.94x the average while every other rank waits for it at the next collective. Only the indexer has this shape -- attention already runs a fixed ``index_topk + window`` columns per row, and the indexer backward a fixed ``index_topk``, both measured flat across ranks. ``mqa_indexer_cp_mode="dualchunk_p2p"`` splits the global sequence into ``2 * cp_size`` chunks and has rank ``r`` score chunks ``(2r, 2*cp_size-1-2r)`` instead of its own ``(2r, 2r+1)``. The ids sum to ``2*cp_size-1`` everywhere, and a causal row's candidate count grows linearly with its global position, so the work is equal on every rank. Three properties keep the change contained: * Only the indexer's per-row inputs move. ``query`` and the layer output stay put, which at s_local=16384/h=64 is 2.3GB of traffic not incurred. * Rank ``r`` keeps the chunk contiguous CP already gave it and swaps the other with rank ``cp_size-1-r``, so the exchange is one point-to-point sendrecv per tensor rather than an all-to-all, and it is an involution -- the same call undoes it. * The permutation lives entirely inside ``paddle.no_grad()`` on detached inputs. The indexer gradient reaches the weights through the loss scaler applied to the *unpermuted* tensors, so nothing here has to be differentiable and the backward is byte-identical between the two modes. The cuDNN indexer call is split in two because the kernel learns where its rows sit globally from ``q_causal_offsets``, one scalar per batch, so a single affine map cannot describe two disjoint segments. Each chunk is internally contiguous and carries its own ``seq_offset`` -- the same shape as the query tiling the dense path already does. The results are concatenated back before the loss scaler sees them: applying it per chunk would halve ``target.shape[1]``, where its backward reads the row-count denominator, and double the gradient. The layer output is bit-identical either way, which is what the new CP test asserts. That equality is only possible because the per-(row, column) score is itself bit-identical: a CTA covers ``q_stage * q_tokens_per_tile`` rows and both offsets are multiples of that, so a given global row lands in the same aligned tile and the kernel walks the same key blocks in the same order. Gradients are held to the rel ~2e-3 the shared cuDNN DSA backward already carries for ``dkv`` (atomics), with the off-vs-off pair checked against the same bound so the tolerance is visibly the kernel's and not this switch's.
xxyux
force-pushed
the
feat/mqa-indexer-dualchunk-cp-develop
branch
from
August 18, 2026 12:32
d43affc to
8df527d
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #1762 +/- ##
==========================================
Coverage ? 97.05%
==========================================
Files ? 3
Lines ? 68
Branches ? 13
==========================================
Hits ? 66
Misses ? 1
Partials ? 1
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
umiswing
approved these changes
Aug 19, 2026
Contributor
|
@risemeup1111 再次review |
risemeup1111
approved these changes
Aug 19, 2026
sneaxiy
approved these changes
Aug 19, 2026
sneaxiy
left a comment
Collaborator
There was a problem hiding this comment.
LGTM for changes of transformer_config.py
DanielSun11
added a commit
to DanielSun11/PaddleFleet
that referenced
this pull request
Aug 19, 2026
develop's PaddlePaddle#1762 (dual-chunk indexer CP rebalance) appended both a new ``indexer_dualchunk`` attribute and three helper methods at the exact end of ``MQALatentAttention.__init__`` where this branch had added ``global_kv_idx_remap_fusion``. Both additions are independent, so both are kept.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Category
Performance Optimization
PR Types
Improvements
Description
Under contiguous CP the latent-MQA indexer scores each rank's own row slice, so with a causal mask its cost grows with the rank index: measured at 256k/cp16, 2.2ms on cp0 against 66.8ms on cp15 per layer per pass (a 30x spread), which makes the slowest rank do 1.94x the average while every other rank waits for it at the next collective. Only the indexer has this shape -- attention already runs a fixed
index_topk + windowcolumns per row, and the indexer backward a fixedindex_topk, both measured flat across ranks.mqa_indexer_cp_mode="dualchunk_p2p"splits the global sequence into2 * cp_sizechunks and has rankrscore chunks(2r, 2*cp_size-1-2r)instead of its own(2r, 2r+1). The ids sum to2*cp_size-1everywhere, and a causal row's candidate count grows linearly with its global position, so the work is equal on every rank.Three properties keep the change contained:
queryand the layer output stay put, which at s_local=16384/h=64 is 2.3GB of traffic not incurred.rkeeps the chunk contiguous CP already gave it and swaps the other with rankcp_size-1-r, so the exchange is one point-to-point sendrecv per tensor rather than an all-to-all, and it is an involution -- the same call undoes it.paddle.no_grad()on detached inputs. The indexer gradient reaches the weights through the loss scaler applied to the unpermuted tensors, so nothing here has to be differentiable and the backward is byte-identical between the two modes.The cuDNN indexer call is split in two because the kernel learns where its rows sit globally from
q_causal_offsets, one scalar per batch, so a single affine map cannot describe two disjoint segments. Each chunk is internally contiguous and carries its ownseq_offset-- the same shape as the query tiling the dense path already does. The results are concatenated back before the loss scaler sees them: applying it per chunk would halvetarget.shape[1], where its backward reads the row-count denominator, and double the gradient.The layer output is bit-identical either way, which is what the new CP test asserts. That equality is only possible because the per-(row, column) score is itself bit-identical: a CTA covers
q_stage * q_tokens_per_tilerows and both offsets are multiples of that, so a given global row lands in the same aligned tile and the kernel walks the same key blocks in the same order. Gradients are held to the rel ~2e-3 the shared cuDNN DSA backward already carries fordkv(atomics), with the off-vs-off pair checked against the same bound so the tolerance is visibly the kernel's and not this switch's.是否引起精度变化
否