Skip to content

perf(components): gather compressed keys with index_select, not two index tensors - #1094

Open
weich97 wants to merge 1 commit into
mindspore-ai:masterfrom
weich97:perf/gather-index-select
Open

weich97 wants to merge 1 commit into
mindspore-ai:masterfrom
weich97:perf/gather-index-select

Conversation

@weich97

@weich97 weich97 commented Oct 11, 2026 •

Copy link
Copy Markdown

问题

candidate 链与 Indexer KL loss 都要按选中的位置取压缩 key,取法是用两个索引张量
做高级索引(一个 batch id、一个 position)。这种形式没有 AI Core 实现,直接回退
AI CPU,并随即吃掉这两条路径的绝大部分时间。

三处(均在 shared_compressed_dsa_attention.py,master 上存在):

  • compressed_candidate_topk → key[batch_indices, safe_positions]
  • _SharedCompressedIndexerKLLoss.forward → index_key[batch_indices, safe_indices]
  • 同上 → compressed_key[batch_indices, safe_indices]

定位

Ascend 910 单卡,16 个 index 头、head_dim 128、bf16、seq 4096、comp_len 2048,
对 compressed_candidate_topk 的 torch 路径做 profiling 拆解:

类别 耗时 占设备算子时间
AI_CPU — 97.8%
└ Index 382.9 ms/步 85.5%
└ Sort 50.2 ms/步 11.2%
└ MaskedFill 5.1 ms/步 1.1%
Vector 6.9 ms/步 1.6%
Cube 2.5 ms/步 0.6%

该链 Stage 448.6 ms/步,与直接计时的 454.5 ms 吻合。Index 是大头,不是矩阵乘。
(einsum 此前被怀疑过,经单独画像洗清:孤立仅 2.1 ms。)

修法

把 batch 偏移折进位置索引,使之成为对展平后 key 的单次 index_select,在设备上执行:

flat_index = (batch_indices * compressed_length + positions).reshape(-1)
gathered = key.reshape(-1, head_dim).index_select(0, flat_index)
return gathered.view(*positions.shape, head_dim)

实测(同卡,逐位相同)

孤立 gather:

形状 两索引张量 index_select 倍数 逐位相同
1024 行 × 1024 位置 101.04 ms 0.719 ms 140.6× ✅
1024 行 × 512 位置 51.52 ms 0.351 ms 146.9× ✅
1024 行 × 512,comp_len 16384 59.28 ms 0.350 ms 169.2× ✅

整条链(选中位置逐位相同):

链 两索引张量 index_select 省 倍数
compressed_candidate_topk seq 4096 / comp 2048 406.22 ms 55.91 ms 350.30 ms 7.26×

第一种形状是 candidate 链每个 query chunk 取的量(topk_blocks × block_size 个位置);
第二种是 KL loss 每个 chunk 取两次的量(sparse_count 个位置,index key 与
attention key 各一次)。两条路径都是每个 Indexer source 层每步各跑一遍。

对照(同进程内切回高级索引做 A/B,不是跨跑比较)。

Indexer KL loss:按默认 recipe 的真实几何端到端实测

发布配置 head_dim=512、index_head_dim=128、index_topk=512、
num_attention_heads=64、index_n_heads=32;默认验证 recipe 的
text_parameter_divisor=8 只缩放头数、不缩放头维,所以实跑形状是
4 个 128 维 index 头 + 8 个 512 维 attention 头。该 recipe 下 index_n_heads=4
使融合算子门控恒假,这条 torch 路径就是唯一会跑的路径。

seq comp_len 两索引张量 index_select 省 倍数 loss 相等
4096 2048 1011.91 ms 31.91 ms 980.00 ms 31.7× ✅
4096 4096 1065.43 ms 31.92 ms 1033.51 ms 33.4× ✅

即每个 Indexer source 层每步约 1.0 s → 约 32 ms,loss 值逐位相等。
该配置有 8 个 Indexer source 层。

机理核对:每个 chunk 取 index_key 134 MB + compressed_key 537 MB
(后者最后一维是 512),seq 4096 / chunk 1024 共 4 个 chunk ⇒ 每层每步搬约 2.7 GB。
按孤立测得的 AI_CPU 约 2.6 GB/s,2.7 GB 约需 1.0 s,与实测 1011.91 ms 吻合;
index_select 侧约 383 GB/s,余下 32 ms 为 einsum 与 softmax。量级自洽。

适用范围

本修改在 torch 参考路径上,不依赖任何融合算子、不涉及头数门控,因此对
默认验证 recipe 直接生效(该 recipe 下 text_parameter_divisor=8 使
index_n_heads=4,融合算子要求头数 ∈ (16,24,32,48,64),融合路径本就不生效)。

与 #1093(位置排序走 float32)互相独立、可分别合入:两者改的是不同算子、不同行;
按本次画像,Index 占该链 85.5%、Sort 占 11.2%,合入两者后该链实测
454.50 ms → 55.91 ms(8.1×)。

测试

tests/ut/components/modules/test_compressed_key_gather.py,8 条,CPU only:
与高级索引在 batch=1 与 batch>1 下相等、批之间不串(最容易写错的就是折叠后的
批偏移)、结果 shape 与 dtype、非连续 key、重复位置、以及 candidate 链在
batch 1 与 batch 3 下输出不变。

pytest tests/ut/components tests/ut/core/context_parallel tests/ut/auto_models
289 条 + 165 子测试全过;产品文件 pylint 零违规、lizard 无超限。

关键结果一览

汇总视图,数字全部取自上文实测表,无新增测量。三个层级跨三个数量级,
故按层级分开列,不塞进同一张图。

层级 口径 两索引张量 index_select 倍数
孤立算子 1024 行 × 512 位置,comp_len 16384 59.28 ms 0.350 ms 169.2×
整条链 compressed_candidate_topk seq 4096 406.22 ms 55.91 ms 7.26×
端到端(默认 recipe 真实几何) Indexer KL loss,seq 4096 / comp 4096 1065.43 ms 31.92 ms 33.4×

两条每层每步都要跑一遍的路径(ms,柱=两索引张量高级索引,线=index_select)

xychart-beta
    title "每个 Indexer source 层每步的耗时(ms)"
    x-axis ["KL loss 4096/2048", "KL loss 4096/4096", "candidate 链 4096/2048"]
    y-axis "毫秒" 0 --> 1100
    bar [1011.91, 1065.43, 406.22]
    line [31.91, 31.92, 55.91]
Loading

该配置有 8 个 Indexer source 层,两条路径都是每层每步各跑一遍。
KL loss 侧 loss 值逐位相等、candidate 链选中位置逐位相同。

机理核对(为什么是 1.0 s 而不是别的量级):每个 chunk 搬 index_key 134 MB +
compressed_key 537 MB,seq 4096 / chunk 1024 共 4 chunk ⇒ 每层每步约 2.7 GB;
按孤立测得的 AI_CPU 约 2.6 GB/s,2.7 GB 约需 1.0 s,与实测 1011.91 ms 吻合。
index_select 侧约 383 GB/s,余下 32 ms 为 einsum 与 softmax。量级自洽。

与 #1093 合入后该链实测 454.50 ms → 55.91 ms(8.1×)。

@weich97

weich97 commented Oct 11, 2026

Copy link
Copy Markdown
Author

/check-pr

@weich97

weich97 commented Oct 11, 2026

Copy link
Copy Markdown
Author

/retest

@mindspore-ai-bot

Copy link
Copy Markdown

🔵 The pipeline #567 is running. Please wait a moment... (Link)

@mindspore-ai-bot

Copy link
Copy Markdown
Project Name Build_Stage Build Result Details
Hyper-parallel_Github_Gate - ❌ FAILURE 567
Check_Pylint FAILURE 567

@mindspore-ai-bot

Copy link
Copy Markdown

@weich97, your gate pipeline failed at the code check stage. You can configure some code check tools locally following the (pre-push quick guide), to enable automatic checking when you push your code every time. Tools included are: Check_CMakelint, Check_Codespell, Check_Cpplint, Check_Lizard, Check_Pylint, Check_ClangFormat, Check_ShellCheck.

…ndex tensors

The candidate chain and the Indexer KL loss gather compressed keys at selected
positions by indexing with two index tensors, a batch id and a position. That
form of advanced indexing has no AI Core implementation, so it runs on AI CPU
and then accounts for most of the time in both paths.

A profiling breakdown of `compressed_candidate_topk` on the torch path (Ascend
910, 16 index heads, head_dim 128, bf16, sequence 4096, compressed length
2048) put 97.8% of device time on AI CPU, and named `Index` as 85.5% of it:
383 ms of a 449 ms step, with the position sort second at 50 ms.

Folding the batch offset into the position index turns the gather into one
`index_select` over the flattened key, which runs on device. Measured on the
same card, with the gathered keys identical bit for bit:

| gather shape                     | two tensors | index_select |  ratio |
|----------------------------------|-------------|--------------|--------|
| 1024 rows x 1024 positions       |   101.04 ms |     0.719 ms | 140.6x |
| 1024 rows x  512 positions       |    51.52 ms |     0.351 ms | 146.9x |
| 1024 rows x  512, comp_len 16384 |    59.28 ms |     0.350 ms | 169.2x |

End to end on the chain, with the selected positions identical bit for bit:
`compressed_candidate_topk` at sequence 4096 and compressed length 2048 goes
from 406.22 ms to 55.91 ms, a 350.30 ms saving per call.

The first shape is what the candidate chain gathers per query chunk
(`topk_blocks * block_size` positions); the second is what the KL loss gathers
twice per chunk (`sparse_count` positions), once for the index key and once for
the attention key. Both paths run per Indexer source layer per step, and the KL
loss runs whenever the post-training Indexer stage is exercised.

The gather is extracted into one helper so the three call sites stay consistent
and the reason for the form is stated once. Unit tests cover equality with the
advanced indexing for one batch and for several, that batches do not mix, the
result shape and dtype, a non-contiguous key, repeated positions, and the
unchanged output of the candidate chain at batch 1 and batch 3 (CPU only).
@weich97
weich97 force-pushed the perf/gather-index-select branch from c866d7c to ce675d3 Compare October 11, 2026 04:05
@weich97

weich97 commented Oct 11, 2026

Copy link
Copy Markdown
Author

/check-pr

@weich97

weich97 commented Oct 11, 2026

Copy link
Copy Markdown
Author

/retest

@mindspore-ai-bot

Copy link
Copy Markdown

🔵 The pipeline #568 is running. Please wait a moment... (Link)

@weich97

weich97 commented Oct 11, 2026

Copy link
Copy Markdown
Author

/retest

@mindspore-ai-bot

Copy link
Copy Markdown

🔵 The pipeline #573 is running. Please wait a moment... (Link)

@mindspore-ai-bot

Copy link
Copy Markdown
Project Name Build_Stage Build Result Details(Full/Incremental) Detail
Coverage Line Coverage ❌ FAILURE 65%/66% Link

@mindspore-ai-bot

Copy link
Copy Markdown
Platform Type Module Excution Time Time Limit
_torch - - -
- activation_memory 162.84037s 120.0s
- shard 236.238s 300.0s
- pipeline_parallel 597.853s 600.0s
- optimizer 233.657s 300.0s
- accuracy 86.317s 120.0s
- fully_shard 299.203s 600.0s
- clip_grad 74.116s 120.0s
- init_weights 70.005s 118.75s
- swap_optimizer 80.595s 120.0s
- multicore 73.97425s 120.0s
- context_parallel 78.97s 120.0s
- tensor_parallel 209.86s 300.0s
- distributed_checkpoint 70.592s 120.0s
- dtensor 73.077s 120.0s
- expert_parallel 66.515s 120.0s
- llama3_examples 63.605s 120.0s
- process_group 58.964s 90.0s

@mindspore-ai-bot

Copy link
Copy Markdown
Project Name Build_Stage Build Result Details
Hyper-parallel_Github_Gate - ❌ FAILURE 573
Check_Pylint FAILURE 567
Smoke_Ascend910b_Torch FAILURE 568
Smoke_Ascend910b_Torch FAILURE 573

@mindspore-ai-bot

Copy link
Copy Markdown

@weich97, your gate pipeline failed at the code check stage. You can configure some code check tools locally following the (pre-push quick guide), to enable automatic checking when you push your code every time. Tools included are: Check_CMakelint, Check_Codespell, Check_Cpplint, Check_Lizard, Check_Pylint, Check_ClangFormat, Check_ShellCheck.

@weich97

weich97 commented Oct 11, 2026

Copy link
Copy Markdown
Author

/retest

@mindspore-ai-bot

Copy link
Copy Markdown

🔵 The pipeline #582 is running. Please wait a moment... (Link)

@mindspore-ai-bot

Copy link
Copy Markdown
Project Name Build_Stage Build Result Details(Full/Incremental) Detail
Coverage Line Coverage ✅ SUCCESS 65%/66% Link

@mindspore-ai-bot

Copy link
Copy Markdown
Platform Type Module Excution Time Time Limit
_torch - - -
- activation_memory 116.97725s 120.0s
- shard 169.333s 300.0s
- pipeline_parallel 545.195s 600.0s
- accuracy 81.314s 120.0s
- optimizer 244.652s 300.0s
- fully_shard 290.394s 600.0s
- swap_optimizer 79.569s 120.0s
- context_parallel 79.517s 120.0s
- tensor_parallel 219.246s 300.0s
- dtensor 78.694s 120.0s
- llama3_examples 63.456s 120.0s
- clip_grad 78.189s 120.0s
- distributed_checkpoint 71.126s 120.0s
- expert_parallel 70.407s 120.0s
- multicore 74.88575s 120.0s
- init_weights 68.687s 118.75s
- process_group 68.111s 90.0s

@mindspore-ai-bot

Copy link
Copy Markdown
Project Name Build_Stage Build Result Details
Hyper-parallel_Github_Gate - ✅ SUCCESS 582

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants