需求跟踪





波动:



堆栈保护代码膨胀:



efb2ca6e484c4f6daf414ceca4cbd7bb.txt
6cfbf58eb7194982a2d0aea17d4daab0.txt
4. Ctrl / Sche 热路径栈数组分布
说明:仅统计函数内本地数组;类成员数组(如
readyIds_[...])不触发“因本地数组而插桩”这一条。
device_ctrl.cpp/device_sche.cpp本身几乎无本地栈数组;热点在 inline 头文件与 context 中。
4.1 Sche(调度)——优先处理
| 函数 | 文件 | 栈数组 | 规模 | 热度 |
|---|---|---|---|---|
AiCoreManager::ResolveDepForAllAiCore |
aicore_manager.h |
ResolveTaskContext resolveCtx[MAX_RESOLVE_TASK_NUM] |
144(MAX_MANAGER_AIV_NUM * 2) |
[[gnu::hot]],每轮 resolve |
AiCoreManager::TryBatchSendTask |
同上 | uint32_t readyId[MAX_MANAGER_AIV_NUM] |
72 | [[gnu::hot]],发任务前 dequeue |
AiCoreManager::ResolveHubMixDepDyn |
同上 | uint32_t taskIds[MAX_WRAP_TASK_NUM] |
3 | mix 依赖解析 |
AiCoreManager::ResolveHubMixDepDynStitched |
同上 | uint32_t taskIds[MAX_WRAP_TASK_NUM] |
3 | mix stitch 依赖 |
WrapManager::DispatchReadyTasksImpl |
wrap_manager.h |
WrapInfo* localTasks[8] |
8 | DispatchMixCoreTask |
相关常量(aicore_constants.h / machine_ws_intf.h / aikernel_data.h):
| 常量 | 值 |
|---|---|
MAX_AICORE_NUM |
108 |
MAX_MANAGER_AIV_NUM |
72 |
MAX_RESOLVE_TASK_NUM |
144 |
MAX_WRAP_TASK_NUM |
3 |
SCH_DEVTASK_MAX_PARALLELISM |
8(1 << 3) |
DEV_SHAPE_DIM_MAX |
6 |
Sche 最大头:ResolveDepForAllAiCore 的 resolveCtx[144]。
stack.txt 中与 sche 对应的带 canary 符号包括:
AiCoreManager::TryBatchSendTaskAiCoreManager::BatchSendTaskAiCoreManager::ResolveDepForAllAiCoreAiCoreManager::ResolveHubMixDepDynAiCoreManager::ResolveDepDynWrapManager::ResolveDepForOneMixWrapManager::PushTaskToTasklistDeviceTaskContext::ProcessWrapQueue/ProcessAivBatchTasks/BuildReadyQueue*等
4.2 Ctrl(stitch / slot / execute)
| 函数 | 文件 | 栈数组 | 规模 | 热度 |
|---|---|---|---|---|
DeviceStitchContext::PartialUpdateStitchConsumer |
device_stitch_context.cpp |
consumerOffset/ValidShape[DEV_SHAPE_DIM_MAX] |
6×2 | stitch 消费侧循环内 |
DeviceStitchContext::FullCoverDefaultUpdateStitch |
同上 | fullCoverOffset/ValidShape[6] |
6×2 | full-cover stitch |
DeviceStitchContext::PartialUpdateStitchProducer |
同上 | producerOffset/ValidShape[6] |
6×2 | stitch 生产侧 |
CellMatchHandle / CellMatchFill* |
dev_encode_function_stitch.h |
rangeBegin/End[6]、offset/validShape[6] |
6 | cell-match 填充 |
PrepareRuntimeDynamicPartialUpdateTables |
device_slot_context.cpp |
uint8_t stackPreparedBySlot[2048] |
2048 | 函数开头已 return;,当前近乎死代码 |
单次 [6] 体积不大,但 stitch 在 consumer/producer 循环内反复建栈帧,-strong 会稳定插桩。
4.3 次热点 / 冷路径
| 函数 | 栈数组 | 说明 |
|---|---|---|
InitAicoreParallelDevTask |
localFuncData/Ids[SCH_DEVTASK_MAX_PARALLELISM](8) |
初始化 |
HandShakeByGmWithPreSendTask |
bool handFlag[MAX_AICORE_NUM](108) |
handshake |
DumpAicoreLog |
char buf[512] |
dump |
DeviceTrace 宏 |
char buf[MAX_MSG_LEN] |
若宏打进热函数,会连带保护调用方 |
dump / prof / perf_event_sampler |
各类 buf / counts |
非调度主路径 |
5. 编码层面消除影响(建议优先级)
5.1 原则
- 热路径去掉本地数组(最有效)
- 避免无必要地取局部变量地址
- 大临时对象外提到成员 / TLS / 调用方缓冲
- 极少数已审计热点可用
__attribute__((no_stack_protector))(安全换性能,慎用)


连续跑,ctrl间隔短性能就会更好:
去掉aicore执行:
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.064.009 [device_sche.h:370]11798 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 19115"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.064.751 [device_sche.h:370]11796 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 11003"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.065.228 [device_sche.h:370]11800 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7937"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.065.807 [device_sche.h:370]11798 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7733"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.066.363 [device_sche.h:370]11796 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8097"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.066.819 [device_sche.h:370]11800 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7717"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.067.306 [device_sche.h:370]11798 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 6923"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.067.806 [device_sche.h:370]11796 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7662"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.068.274 [device_sche.h:370]11800 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8002"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.068.756 [device_sche.h:370]11798 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7153"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.069.257 [device_sche.h:370]11796 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7429"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.069.711 [device_sche.h:370]11800 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7412"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.070.211 [device_sche.h:370]11798 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7071"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.070.719 [device_sche.h:370]11796 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8122"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.071.176 [device_sche.h:370]11800 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7384"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.071.659 [device_sche.h:370]11798 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 6815"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.072.166 [device_sche.h:370]11796 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7985"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.072.626 [device_sche.h:370]11800 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7400"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.073.114 [device_sche.h:370]11798 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 6852"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.073.664 [device_sche.h:370]11796 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7898"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.074.173 [device_sche.h:370]11800 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7607"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.074.713 [device_sche.h:370]11798 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7133"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.075.261 [device_sche.h:370]11796 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7960"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.075.728 [device_sche.h:370]11800 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7748"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.076.219 [device_sche.h:370]11798 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7047"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.076.731 [device_sche.h:370]11796 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7846"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.077.195 [device_sche.h:370]11800 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7387"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.077.741 [device_sche.h:370]11798 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7078"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.078.252 [device_sche.h:370]11796 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8217"
[ERROR] AICPU(11784,aicpu_scheduler):2026-07-21-09:42:56.078.720 [device_sche.h:370]11800 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 7492"
ctrl:间隔1.2ms 平均:7539

不去掉aicore执行:
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.686.081 [device_sche.h:370]20531 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 18807"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.687.580 [device_sche.h:370]20529 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8791"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.689.007 [device_sche.h:370]20527 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 9809"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.690.359 [device_sche.h:370]20531 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8000"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.691.732 [device_sche.h:370]20529 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8319"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.693.214 [device_sche.h:370]20527 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 9000"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.694.550 [device_sche.h:370]20531 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8341"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.696.052 [device_sche.h:370]20529 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 9162"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.697.483 [device_sche.h:370]20527 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8693"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.698.840 [device_sche.h:370]20531 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8151"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.700.252 [device_sche.h:370]20529 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 9763"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.701.633 [device_sche.h:370]20527 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8686"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.702.976 [device_sche.h:370]20531 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8507"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.704.358 [device_sche.h:370]20529 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8541"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.705.822 [device_sche.h:370]20527 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8551"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.707.185 [device_sche.h:370]20531 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8771"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.708.567 [device_sche.h:370]20529 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 9277"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.710.033 [device_sche.h:370]20527 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8529"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.711.385 [device_sche.h:370]20531 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 9389"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.712.810 [device_sche.h:370]20529 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 9636"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.714.172 [device_sche.h:370]20527 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8427"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.715.517 [device_sche.h:370]20531 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8073"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.716.910 [device_sche.h:370]20529 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8173"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.718.380 [device_sche.h:370]20527 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8784"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.719.734 [device_sche.h:370]20531 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 9330"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.721.246 [device_sche.h:370]20529 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 9413"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.722.650 [device_sche.h:370]20527 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 9380"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.724.022 [device_sche.h:370]20531 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 9368"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.725.381 [device_sche.h:370]20529 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8037"
[ERROR] AICPU(20515,aicpu_scheduler):2026-07-21-09:47:33.726.841 [device_sche.h:370]20527 EntrySplittedStreamCtrl ErrCode: F00000! "Ctrl time: 8522"
ctrl:间隔1.2ms 平均:8807

一句话: 欧拉上能导致「越密越快」的,主要是 cpufreq/cpuidle 空闲降频与唤醒代价,加上 本核 cache/TLB/预测器变冷;不是 AICore 和 AICPU 共用一套 cache。










models/glm_v4_5/glm_attention_fusion.py
优化前:

profiling后20轮平均值:416
优化后:

profiling后20轮平均值:223


不同算子性能热点是有差别的,比如:
(1)models/glm_v4_5/glm_attention_fusion.py

(2)custom/chunk_kda/test_chunk_kda.py

波动:194.276 --> 232.435 37us(19%)


后续:
1、当前优化性能看护:看下平均值波动。考虑CI和测试侧都加看护。
2、第二阶段性能优化:
(1)编码优化
(2)大流程?? 数据结构访问优化,计算分担给aicore。
(3)buildtask里的数据结构
(4)fast stitch用hub优化,可能会牺牲并行度
(5)tensor内存分配&&slot管理 梳理下 -- 杰神
3、ctrl cpu 性能波动分析:目前波动在20%。性能降下来波动应该会小,接整网带宽、资源竞争也可能影响。
4、不同算子性能热点不同,分析前端差异带来的性能差异点。
5、高性能编码skill完善、落地:https://gitcode.com/cann/pypto/pull/5097
6、资料补充下分析buildtask头开销的说明。msprof + aicpu perf结合分析。 -- 建芬
7、cellmatch优化完库上用例批跑分析下stitch性能,再定性能
8、场景
非值依赖:走host cache。
值依赖:一个task;多个task。看首task开销。


PR 5531 优化点
perf(machine): Optimize stitch code
优化点清单
| # | 优化点 | 改动文件 | 改前 | 改后 | 优化类型 |
|---|---|---|---|---|---|
| 1 | 属性基址缓存 | dev_callop_attribute.h |
每次调用 3 次 GetOperationAttr 查表获取 offset/rawShape/validShape 指针 |
cachedAttrBase 懒加载 1 次 GetSymoffset,后续指针偏移直接算 |
热路径查表消除 |
| 2 | 结构体字段瘦身 | dev_encode_operation.h |
shapeAttrIdx + wrapTaskHubOpIdx 两个字段 |
删除 shapeAttrIdx(改为 offsetAttrIdx+dim 按需算),wrapTaskHubOpIdx → stitchOpIdx(encode 期预赋值) |
结构体缩小 + runtime 查表消除 |
| 3 | offsetAttrIdx 预累加 | dev_encode.cpp |
runtime 每次查 opAttrOffsetList_ 获取 opAttrBase |
encode 期 offsetAttrIdx += opAttrBase,runtime 直接读 |
以存代算 |
| 4 | stitchOpIdx 预赋值 | dev_encode.cpp |
runtime if (wrapTaskHubOpIdx != INVALID) 判断 + 赋值 |
encode 期 stitchOpIdx = (hub存在) ? hubIdx : operationIdx |
以存代算 + 分支消除 |
| 5 | 构造函数参数减少 | dev_encode.cpp |
emplace_back(opIdx, coaIndex, coaIndex+dimSize, ...) 传 3 个 index |
emplace_back(opIdx, coaIndex, ...) 传 2 个,shape 索引按需算 |
调用开销减少 |
| 6 | skipExpression 模板消除 | dev_callop_attribute.h |
template <bool skipExpression> 循环内 3×dim 次 if (skipExpression) 运行时分支 |
去掉模板参数和运行时分支 | 编译期分支消除 |
| 7 | 循环合并 | dev_callop_attribute.h |
CheckOffsetAndValidShapeInRawShape 两个独立 for 循环 |
合并为一个循环 | 循环开销减半 |
| 8 | IsCellMatchDescFillReady 删除 | dev_callop_attribute.h |
runtime 每次遍历所有维度检查 cellShape/stride > 0 | 删除函数,host encode 期保证 desc 已填充 | device 侧检查消除 |
| 9 | Dump 隔离 | device_slot_context.cpp / device_stitch_context.cpp / dump_device_topo.cpp |
DumpProducerCellAccess/DumpConsumerCellAccess 裸调用 |
DEV_IF_NONDEVICE 包裹,device 编译期排除 |
device 侧零开销 |
| 10 | unlikely 标注 | dev_callop_attribute.h |
if (clamped) 无分支预测提示 |
if (unlikely(clamped)) |
分支预测优化 |
| 11 | CellMatchProcessByDim 局部变量 | dev_encode_function_stitch.h |
switch (cellMatchTableDesc.cellShape.dimSize) 每次读结构体字段 |
switch (dims) 用局部变量 |
循环不变量外提 |
| 12 | CellMatchHandle dims 外提 | dev_encode_function_stitch.h |
循环内 cellMatchTableDesc.GetDimensionSize() 重复调用 |
提到循环外存 dims |
循环不变量外提 |
| 13 | Process 签名加 desc | dev_encode_function_stitch.h |
每个 Handler 内部重复读 cellMatchTableDesc |
通过参数传入 const DevCellMatchTableDesc& desc |
参数传递优化 |
| 14 | runtime wrapTaskHubOpIdx 查表消除 | device_stitch_context.cpp |
runtime if (consumer.wrapTaskHubOpIdx != INVALID) 判断 + 赋值 consumerOpIdx |
直接用 consumer.stitchOpIdx |
runtime 分支消除 |
| 15 | GetTensorOffsetAndValidShape 调用优化 | device_stitch_context.cpp |
传 operationIndex + offsetAttrIdx,函数内部查 GetOperationAttr |
传 offsetAttrIdx + cachedAttrBase,函数内部用缓存指针 |
热路径查表消除 |
性能数据
| 场景 | 方向 | 收益 |
|---|---|---|
| FAST_STITCH | bwd | -4.6% |
| UPDATE_SLOT | bwd | -3.0% |
| DEV_TASK_BUILD | first | -3.1% ~ -3.6% |


PR 5584 优化:
GDR 算子 Stitch 遍历开销分析与优化方案
采集方法:单轮测试,device 日志中
DEV_ERROR统计。
采集时间:2026-08-07(fwd)/ 2026-08-08(bwd)
pypto: commit243edc032a(run 包安装,device 侧代码编译生效)
1. RootFunc 结构
fwd — varlen_T32K_H8(234 轮,3 个 rootfunc)
| funcKey | outcastSize | incastSize | outSlots | outPartial | outNonPartial | inSlots | inPartial | inNonPartial |
|---|---|---|---|---|---|---|---|---|
| 0 | 4 | 0 | 4 | 0 | 4 | 0 | 0 | 0 |
| 1 | 5 | 12 | 5 | 1 | 4 | 12 | 0 | 12 |
| 2 | 5 | 12 | 5 | 1 | 4 | 12 | 0 | 12 |
每轮总计:outSlots=14(partial=2, nonPartial=12),inSlots=24(partial=0, nonPartial=24)。
bwd — varlen64_t32k_h8_bt64(4784 轮,多个 rootfunc 循环)
| funcKey | outcastSize | incastSize | outSlots | outPartial | outNonPartial | inSlots | inPartial | inNonPartial |
|---|---|---|---|---|---|---|---|---|
| 4 | 2 | 5 | 2 | 1 | 1 | 5 | 0 | 5 |
| 0 | 2 | 6 | 2 | 1 | 1 | 6 | 0 | 6 |
| 8 | 2 | 6 | 2 | 1 | 1 | 6 | 0 | 6 |
| 25 | 2 | 7 | 2 | 1 | 1 | 7 | 0 | 7 |
每轮总计:outSlots=2(partial=1, nonPartial=1),inSlots=5~7(partial=0, nonPartial=5~7)。
2. 全集遍历统计
fwd — varlen_T32K_H8(234 轮累积)
UpdateSlotsImpl
| 分类 | 次数 | 说明 |
|---|---|---|
| outTotal | 3392 | outcast→toSlotList 总遍历 |
| ├ outPartialWarWaw | 576 | partial + WAR/WAW → PartialStitch |
| └ outNonPartial | 2816 | nonPartial → FullCoverStitch |
| inTotal | 6912 | incast→fromSlotList 总遍历 |
| └ inProcess | 0 | 100% 无效(全 nonPartial,无 RAW) |
PartialStitch / FullCoverStitch
| 函数 | called | 空跑 | filled |
|---|---|---|---|
| PartialStitch | 576 | 0 | 576(100% 有效) |
| FullCoverStitch | 2816 | 0 | 2816(100% 有效) |
FastStitchConsumer / FastStitchProducer
| 函数 | total | skipInvalidIdx | skipNonPartial | skipNoRawWaw | process |
|---|---|---|---|---|---|
| FastStitchConsumer | 6804 | 4536 | 0 | 0 | 2268 |
| FastStitchProducer | 3339 | 2 | 2772 | 0 | 565 |
bwd — varlen64_t32k_h8_bt64(4784 轮累积)
UpdateSlotsImpl
| 分类 | 次数 | 说明 |
|---|---|---|
| outTotal | 9568 | outcast→toSlotList 总遍历 |
| ├ outPartialWarWaw | 7136 | partial + WAR/WAW → PartialStitch |
| └ outNonPartial | 2432 | nonPartial → FullCoverStitch |
| inTotal | 23808 | incast→fromSlotList 总遍历 |
| └ inProcess | 704 | partial + RAW(有效,非 0) |
| └ 无效 | 23104 | nonPartial 跳过(97% 无效) |
PartialStitch / FullCoverStitch
| 函数 | called | 空跑 | filled |
|---|---|---|---|
| PartialStitch | 7136 | 0 | 7136(100% 有效) |
| FullCoverStitch | 2432 | 0 | 2432(100% 有效) |
FastStitchConsumer / FastStitchProducer
| 函数 | total | skipInvalidIdx | skipNonPartial | skipNoRawWaw | process |
|---|---|---|---|---|---|
| FastStitchConsumer | 23444 | 22042 | 0 | 0 | 1402 |
| FastStitchProducer | 9421 | 5 | 2394 | 0 | 7022 |
3. 无效遍历汇总
fwd
| 空跑类型 | 次数 | 根因 |
|---|---|---|
| UpdateSlotsImpl incast nonPartial | 6912 | slot 全是 nonPartial,无 RAW 依赖 |
| FastStitchConsumer skipInvalidIdx | 4536 | stitchDupIdx 未设置(无前驱) |
| FastStitchProducer skipNonPartial | 2772 | slot 是 nonPartial |
| 总计 | 14220 | / |
有效遍历:576 + 2816 + 2268 + 565 = 6225。无效/有效 = 2.3:1。
bwd
| 空跑类型 | 次数 | 根因 |
|---|---|---|
| UpdateSlotsImpl incast nonPartial | 23104 | 97% incast slot 是 nonPartial |
| FastStitchConsumer skipInvalidIdx | 22042 | stitchDupIdx 未设置(无前驱) |
| FastStitchProducer skipNonPartial | 2394 | slot 是 nonPartial |
| 总计 | 47540 | / |
有效遍历:7136 + 2432 + 704 + 1402 + 7022 = 18696。无效/有效 = 2.5:1。
fwd vs bwd 对比
| 指标 | fwd | bwd | bwd/fwd |
|---|---|---|---|
| outTotal | 3392 | 9568 | 2.8x |
| inTotal | 6912 | 23808 | 3.4x |
| inProcess | 0 | 704 | bwd 有 RAW |
| 无效遍历总计 | 14220 | 47540 | 3.3x |
| FastStitchConsumer skipInvalidIdx | 4536 | 22042 | 4.9x |
| FastStitchProducer skipNonPartial | 2772 | 2394 | 0.9x |
4. 优化方案:预计算 slot 列表
设计原则:按"消费者需要的筛选条件"划分列表,而非按"数据属性"划分。每个列表只包含其消费者需要的 slot,不多不少。
所有 4 个列表均在 encode 阶段构建:slot 的stitchCtrlBitMask在 encode 阶段确定(partial slot 和 fullcover slot 都计算)。
首个 rootfunc 跳过:FastStitch入口已有devNextIdx == 0检查(首个 rootfunc 无前驱),stitchIncastSlotList和stitchOutCastSlotList的消费者在FastStitch内,devNextIdx == 0时整体跳过,不遍历任何列表。
前置改造1:DevAscendProgramPartialUpdate → DevAscendProgramUpdate + isPartial
// dev_encode_function_stitch.h
struct DevAscendProgramUpdate { // 原 DevAscendProgramPartialUpdate
int slotIndex{-1};
StitchCtrlBitMask stitchCtrlBitMask{STITCH_CTRL_NONE};
bool isPartial{false}; // true=partial slot, false=fullcover slot
DevCellMatchTableDesc cellMatchTableDesc;
DevRelocVector<uint64_t> cellMatchRuntimePartialUpdateTable;
bool Empty() const { return cellMatchRuntimePartialUpdateTable.size() == 0; }
};
DevAscendProgram::partialUpdateList 同步重命名为 updateList。InitPartialUpdateSlot 重命名为 InitUpdateSlot。
前置改造2:DeviceExecuteSlot 新增 stitchCtrlBitMask
// dev_encode_program_ctrlflow_cache.h
struct DeviceExecuteSlot {
// ... 现有成员 ...
StitchCtrlBitMask stitchCtrlBitMask{STITCH_CTRL_NONE};
DevAscendProgramUpdate* partialUpdate{nullptr}; // 类型改为 DevAscendProgramUpdate*
};
MarkPartialUpdateSlots 改造——统一赋值 stitchCtrlBitMask,用 isPartial 区分:
// device_slot_context.cpp
slotList[slotIndex].stitchCtrlBitMask = update.stitchCtrlBitMask;
if (update.isPartial) {
slotList[slotIndex].isPartialUpdateStitch = true;
slotList[slotIndex].partialUpdate = &update;
}
前置改造3:SlotMaskEntry 结构体(dev_encode_function.h)
mask 计算结果的数据载体,在 RunEncodeStage 中作为局部变量,贯穿 RefilterSlotLists 和 InitUpdateSlot:
struct SlotMaskEntry {
uint8_t mask{0};
bool isPartial{false};
uint32_t maxReadCount{0};
};
数据结构
在 dev_encode_function.h 中新增 2 个 entry 结构体 + 4 个 DevLocalVector:
struct OutCastSlotEntry {
int slotIdx;
int outcastIdx; // 关联的 outcast 索引,避免运行时查找
};
struct IncastSlotEntry {
int slotIdx;
int incastIdx; // 关联的 incast 索引,避免运行时查找
};
// 列表1: UpdateSlotsImpl outcast 循环用(全集,运行时按标记判断是否做 cell match fill)
// encode 不筛选,运行时还有其他处理(RuntimeOutcastTensorAssign 等)不能按标记排除
DevLocalVector<OutCastSlotEntry> updateOutCastSlotList;
// 列表2: FastStitchConsumer 用(写后读的 consumer stitch)
// encode 阶段用 stitchCtrlBitMask & WAR 筛选,排除没有写后读场景的 slot
DevLocalVector<IncastSlotEntry> stitchIncastSlotList;
// 列表3: UpdateSlotsImpl incast 循环用(读后写的 consumer fill)
// encode 阶段用 stitchCtrlBitMask & RAW 筛选
DevLocalVector<IncastSlotEntry> updateIncastSlotList;
// 列表4: FastStitchProducer 用(写后写+读后写的 producer stitch)
// encode 阶段用 stitchCtrlBitMask & (RAW|WAW) 筛选
DevLocalVector<OutCastSlotEntry> stitchOutCastSlotList;
场景映射
| 列表 | 场景 | 消费者 | encode 筛选条件 | 运行时判断 |
|---|---|---|---|---|
updateOutCastSlotList |
写后写 + 写后读(producer fill) | UpdateSlotsImpl outcast | 无(全集) | 按 slot.stitchCtrlBitMask 判断是否做 cell match fill |
stitchIncastSlotList |
写后读(consumer stitch) | FastStitchConsumer | mask & WAR |
无需 continue |
updateIncastSlotList |
读后写(consumer fill) | UpdateSlotsImpl incast | isPartial && (mask & RAW) |
无需 continue |
stitchOutCastSlotList |
写后写 + 读后写(producer stitch) | FastStitchProducer | isPartial && (mask & (RAW|WAW)) |
无需 continue |
列表3/4 必须加
isPartial条件:UpdateSlotsForIncastStitch和PartialUpdateStitchProducer只处理 partial slot,fullcover slot 不能进入这两个列表。SlotMaskEntry中同时存isPartial标记。
encode 判断逻辑(最终实现:两阶段 + 单源 mask)
原方案设想在
InitIncastOutcast中直接从partialUpdateList构建slotMaskMap并筛选 4 列表。实际实现改为两阶段,因为CalculateStitchCtrlBitMaskForSlot依赖devEncodeList(全部 root 编码后才有完整数据),不能在 per-root 的InitIncastOutcast中调用。
阶段一:编码循环内 — 保守填充(InitSlotPrefilterLists,per-root)
在 InitIncastOutcast 末尾调用 InitSlotPrefilterLists,遍历 outcast→outcastSlot 和 incast→incastSlot,不做任何 mask 筛选,把所有 slot 全部加入 4 列表(全集):
// dev_encode_function.h 声明 / dev_encode.cpp 实现
void DevAscendFunction::InitSlotPrefilterLists(initOffset, slot, incastTensorList, outcastTensorList, fillContent)
{
// 计算全集 count
for (outcast) { updateOutCnt += size; stitchOutCnt += size; }
for (incast) { stitchInCnt += size; updateInCnt += size; }
// 分配空间(update 组 + stitch 组分组排列)
updateOutCastSlotList.HostInitDataSizeOffset(initOffset, updateOutCnt);
updateIncastSlotList.HostInitDataSizeOffset(initOffset, updateInCnt);
stitchIncastSlotList.HostInitDataSizeOffset(initOffset, stitchInCnt);
stitchOutCastSlotList.HostInitDataSizeOffset(initOffset, stitchOutCnt);
// 填充 entry(全集)
ONFILLCONTENT {
for (outcast) { At(updateOutCastSlotList, uo++) = {slotIdx, index};
At(stitchOutCastSlotList, so++) = {slotIdx, index}; }
for (incast) { At(updateIncastSlotList, ui++) = {slotIdx, index};
At(stitchIncastSlotList, si++) = {slotIdx, index}; }
}
}
为什么保守?
CalculateStitchCtrlBitMaskForSlot内部调用CalculateSlotMaxReadCount,后者通过devEncodeListInput[funcKey]reinterpret_cast 成DevAscendFunction*,读取outcast.producerConsumerList中的opType == READ。这要求所有 root 的DevAscendFunction已编码完成,而InitIncastOutcast在 per-root 编码循环内执行,此时devEncodeList尚未全部完成。
阶段二:编码循环后 — 构建 slotMaskMap + 过滤 + 传递(RunEncodeStage 末尾)
编码循环结束后,devEncodeList 完整可用。在 RunEncodeStage(backend.cpp)中:
// backend.cpp — RunEncodeStage 末尾
auto slotMaskMap = dynamic::BuildSlotMaskMap(attr); // 1. 算 mask(唯一计算点)
for (auto& devRoot : attr->funcGroup.devRootList) {
int devRootKey = attr->funcGroup.devRootList.GetIndex(devRoot);
DevAscendFunction* funcBin = reinterpret_cast<DevAscendFunction*>(&attr->devEncodeList[devRootKey][0]);
funcBin->RefilterSlotLists(slotMaskMap); // 2. 过滤 4 列表
}
SetDyndevProgBinary(function, disableCtrlFlowCache, &slotMaskMap); // 3. 传给 InitUpdateSlot
slotMaskMap 是 RunEncodeStage 的局部变量(不存入 DyndevFunctionAttribute),贯穿三个消费点。
BuildSlotMaskMap(dev_encode.cpp)— 唯一 mask 计算点
std::unordered_map<int, SlotMaskEntry> BuildSlotMaskMap(const std::shared_ptr<DyndevFunctionAttribute>& dyndevAttr)
{
std::unordered_map<int, SlotMaskEntry> slotMaskMap;
// 构建 kernelInputAssembleSlots(input ∩ assemble,用于 rootFuncOk gate)
// 构建 partialSlotSet(用于 isPartial 判定)
for (int slotIndex = 0; slotIndex < inoutLink.totalSlot; slotIndex++) {
uint32_t maxReadCount = 0;
uint8_t mask = CalculateStitchCtrlBitMaskForSlot(
slotIndex, dyndevAttr->devEncodeList, dyndevAttr->rootFuncKeyDict,
dyndevAttr->slotRootIncastDict, dyndevAttr->slotRootOutcastDict,
kernelInputAssembleSlots, &maxReadCount);
if (mask == STITCH_CTRL_NONE) { continue; }
slotMaskMap[slotIndex] = {mask, partialSlotSet.count(slotIndex) != 0, maxReadCount};
}
return slotMaskMap;
}
CalculateStitchCtrlBitMaskForSlot 的 mask 计算逻辑:
hasNormalAttr:遍历slotRootOutcastDict[slotIndex]中所有 root 的 outcast,检查HasAttr("NORMAL")maxReadCount:CalculateSlotMaxReadCount— incastCount + outcastConsumerCount(outcast 中opType == READ的数量),依赖devEncodeList读取已编码的DevAscendFunctionoutcast 数据rootFuncOkgate:仅对kernelInputAssembleSlots(input ∩ assemble)中的 slot 检查CountSlotRootFuncNum >= 2- mask 赋值:
hasNormalAttr → WAW,maxReadCount > 0 → WAR|RAW
RefilterSlotLists(dev_encode.cpp,DevAscendFunction 方法)— 原地压缩过滤
void DevAscendFunction::RefilterSlotLists(const std::unordered_map<int, SlotMaskEntry>& slotMaskMap)
{
auto keepWar = [&](int slotIdx) {
auto it = slotMaskMap.find(slotIdx);
return it != slotMaskMap.end() && (it->second.mask & STITCH_CTRL_WAR);
};
auto keepRawPartial = [&](int slotIdx) {
auto it = slotMaskMap.find(slotIdx);
return it != slotMaskMap.end() && it->second.isPartial && (it->second.mask & STITCH_CTRL_RAW);
};
auto keepRawWawPartial = [&](int slotIdx) {
auto it = slotMaskMap.find(slotIdx);
return it != slotMaskMap.end() && it->second.isPartial
&& (it->second.mask & (STITCH_CTRL_RAW | STITCH_CTRL_WAW));
};
auto compact = [&](auto& list, auto&& pred) {
size_t writeIdx = 0;
for (size_t readIdx = 0; readIdx < list.size(); readIdx++) {
auto& entry = At(list, static_cast<int>(readIdx));
if (pred(entry.slotIdx)) {
At(list, static_cast<int>(writeIdx)) = entry;
writeIdx++;
}
}
list.AssignOffsetSize(list.Offset(0), writeIdx);
};
compact(stitchIncastSlotList, keepWar);
compact(updateIncastSlotList, keepRawPartial);
compact(stitchOutCastSlotList, keepRawWawPartial);
// updateOutCastSlotList 不过滤(全集)
}
原地压缩安全性:
DevLocalVector::AssignOffsetSize(offset, newSize)只改offset_和size_,不改底层数据- 压缩是前向双指针(
writeIdx <= readIdx),不会覆盖未读数据DevAscendFunction::Reloc是 no-op(dev_encode_function.h:232),不影响DevLocalVector的 offset- 保守列表分配了足够空间,过滤后
size_只会变小,不会越界
InitUpdateSlot — 从 slotMaskMap 直接取 mask(不再重复计算)
SetDyndevProgBinary → EncodeDevAscendProgram → Init → InitUpdateSlot,slotMaskMap 通过指针一路传递。InitSinglePartialUpdateSlot / InitSingleFullCoverSlot 直接从 slotMaskMap 取 mask/isPartial/maxReadCount,不再调 CalculateStitchCtrlBitMaskForSlot:
// InitSingleFullCoverSlot — 从 slotMaskMap 取
auto it = slotMaskMap->find(slotIndex);
if (it == slotMaskMap->end() || it->second.isPartial) { return; } // 非 fullcover 跳过
uint8_t stitchCtrlBitMask = it->second.mask;
// InitSinglePartialUpdateSlot — 从 slotMaskMap 取
auto maskIt = slotMaskMap->find(slotIndex);
if (maskIt == slotMaskMap->end() || !maskIt->second.isPartial) { // 非 partial 跳过
FillEmptyPartialUpdateStub(prog, slotIndex); return 0;
}
uint8_t stitchCtrlBitMask = maskIt->second.mask;
uint32_t maxReadCount = maskIt->second.maxReadCount;
size-only pass 保护:
EncodeDevAscendProgramSizeOnly(base == nullptr)时slotMaskMap == nullptr,InitUpdateSlot中if (slotMaskMap == nullptr) { return; }提前返回。
mask 一致性保证(单源)
CalculateStitchCtrlBitMaskForSlot 只在 BuildSlotMaskMap 中调用一次,结果存入局部 slotMaskMap,被两个消费方共用:
RefilterSlotLists— 过滤 4 列表InitSinglePartialUpdateSlot/InitSingleFullCoverSlot(通过InitUpdateSlot)→updateList[i].stitchCtrlBitMask
因此 4 列表的筛选条件与 updateList 的 mask(device 侧 MarkPartialUpdateSlots 赋值给 slot.stitchCtrlBitMask)完全一致,且 mask 只算一次。
时机依赖
RunEncodeStage (backend.cpp)
├── for each devRoot:
│ └── EncodeDevAscendFunction → InitIncastOutcast → InitSlotPrefilterLists
│ └── 阶段一:保守填充 4 列表(全集,不筛选)
│ ⚠️ 此时 devEncodeList 尚未全部完成,不能调 CalculateStitchCtrlBitMaskForSlot
│
├── slotMaskMap = BuildSlotMaskMap(attr) ← 全部 root 编码后,devEncodeList 完整
│ └── CalculateStitchCtrlBitMaskForSlot(唯一计算点)
│
├── for each devRoot:
│ └── funcBin->RefilterSlotLists(slotMaskMap) ← 阶段二:压缩过滤 4 列表
│
└── SetDyndevProgBinary(function, disableCtrlFlowCache, &slotMaskMap)
└── EncodeDevAscendProgram(func, offset, base, &slotMaskMap)
└── InitUpdateSlot(..., slotMaskMap)
├── InitSinglePartialUpdateSlot ← 从 slotMaskMap 取 mask/isPartial/maxReadCount
└── InitSingleFullCoverSlot ← 从 slotMaskMap 取 mask
→ updateList[slotIndex].stitchCtrlBitMask(与 slotMaskMap 同源)
关键约束:
BuildSlotMaskMap必须在编码循环后(devEncodeList完整)RefilterSlotLists必须在BuildSlotMaskMap后InitUpdateSlot接收slotMaskMap指针,size-only pass 传nullptr
一对多扁平化说明
incast/outcast 与 slot 是一对多关系——incastSlot[idx] 和 outcastSlot[idx] 都是 vector<int>,一个 incast/outcast 可关联多个 slot。
原代码外层循环遍历 incast/outcast,GetIncast(i) / GetOutcast(i) 在外层取一次,内层遍历 fromSlotList / toSlotList 复用。
扁平化后同一个 outcastIdx / incastIdx 出现在多个 entry 中,每个 entry 各自调 GetOutcast(entry.outcastIdx) / GetIncast(entry.incastIdx)。但 GetOutcast / GetIncast 是 DevLocalVector 的 At(outcastList, index) 指针偏移计算,内联后为一条地址计算指令,开销可忽略。连续相同 outcastIdx / incastIdx 的 entry 编译器会 CSE(公共子表达式消除)复用前一次结果。
device 侧消费改造
原则:encode 阶段已做的
stitchCtrlBitMask和isPartialUpdateStitch判断在运行时去掉,其他运行时判断(stitchDupIdx、越界保护等)保留。
UpdateSlotsImpl(device_slot_context.cpp):
// outcast 循环改为遍历 updateOutCastSlotList(全集):
for (size_t i = 0; i < devRootSrc->updateOutCastSlotList.size(); ++i) {
auto& entry = devRootSrc->At(devRootSrc->updateOutCastSlotList, i);
auto& outcast = devRootSrc->GetOutcast(entry.outcastIdx);
auto& slot = slotList[entry.slotIdx];
// 以下对所有 slot 执行(保持现有逻辑,不按标记排除):
slot.stitchDupIdx = devNextIdx;
slot.stitchOutcastIdx = static_cast<uint32_t>(entry.outcastIdx);
DEV_IF_NONDEVICE { topo_dump::DumpProducerCellAccess(...); }
workspace->RuntimeOutcastTensorAssign(slot.rtOutcastIter, outcastDesc.GetRtOutcastIter());
// cell match fill:用 slot.stitchCtrlBitMask 判断是否有 WAR|WAW 场景
// 用 slot.isPartialUpdateStitch 判断 partial/fullcover 分流(替代原 isPartialUpdateStitch + partialUpdate->stitchCtrlBitMask)
uint32_t cellMatchTagId = CellMatchBuildTagId(slot.slotAllocIterId, cellMatchTagSeq);
uint32_t errCode = 0;
if (slot.stitchCtrlBitMask & (STITCH_CTRL_WAR | STITCH_CTRL_WAW)) {
if (slot.isPartialUpdateStitch) { // partial slot → PartialStitch fill
if (slot.partialUpdate->cellMatchRuntimePartialUpdateTable.size() == 0) {
PrepareRuntimeDynamicPartialUpdateTable(workspace, slot.partialUpdate);
}
auto producerList = &devRootSrc->At(outcast.producerConsumerList, 0);
errCode = UpdateSlotsForOutCastPartialStitch(...);
} else { // fullcover slot + WAR|WAW → FullCover fill
errCode = UpdateSlotsForOutCastFullCoverStitch(...);
}
}
// slot.stitchCtrlBitMask == NONE → 跳过 cell match fill(但仍做了上面的其他处理)
}
// incast 循环改为遍历 updateIncastSlotList(已筛选 isPartial && RAW):
for (size_t i = 0; i < devRootSrc->updateIncastSlotList.size(); ++i) {
auto& entry = devRootSrc->At(devRootSrc->updateIncastSlotList, i);
auto& incast = devRootSrc->GetIncast(entry.incastIdx);
auto& slot = slotList[entry.slotIdx];
// 直接调 UpdateSlotsForIncastStitch(已筛选 isPartial && RAW,无需判断 isPartialUpdateStitch / stitchCtrlBitMask)
uint32_t errCode = UpdateSlotsForIncastStitch(...);
}
FastStitchConsumer(device_stitch_context.cpp):
// 改为遍历 stitchIncastSlotList(已筛选 WAR):
for (size_t i = 0; i < nextSrc->stitchIncastSlotList.size(); ++i) {
auto& entry = nextSrc->At(nextSrc->stitchIncastSlotList, i);
auto slotIdx = entry.slotIdx;
if (slotIdx >= (int)slotSize) { continue; } // 越界保护(保留)
auto& incast = nextSrc->GetIncast(entry.incastIdx);
auto& slot = slotList[slotIdx];
if (slot.stitchDupIdx == INVALID_STITCH_IDX) { continue; } // 运行时保护(保留)
// stitchCtrlBitMask 已在 encode 阶段筛选(WAR),无需判断
// isPartialUpdateStitch 仍需判断(区分 partial → PartialUpdateStitchConsumer,nonPartial → FullCoverUpdateStitch)
if (slot.isPartialUpdateStitch) {
matchCount = PartialUpdateStitchConsumer(...);
} else {
if (slot.rtOutcastIter == ITEM_POOL_INVALID_INDEX) { continue; } // 保留
matchCount = FullCoverUpdateStitch(...);
}
}
注意:
FastStitchConsumer中isPartialUpdateStitch保留——列表2 同时包含 partial+WAR 和 fullcover+WAR 的 slot,运行时需按isPartialUpdateStitch分流到不同处理函数。去掉的只是stitchCtrlBitMask & WAR判断(已在 encode 筛选)和stitchCtrlBitMask相关的 skipNonPartial/skipNoRawWaw 判断(不存在于此函数原逻辑中)。
FastStitchProducer(device_stitch_context.cpp):
// 改为遍历 stitchOutCastSlotList(已筛选 isPartial && (RAW|WAW)):
for (size_t i = 0; i < nextSrc->stitchOutCastSlotList.size(); ++i) {
auto& entry = nextSrc->At(nextSrc->stitchOutCastSlotList, i);
auto slotIdx = entry.slotIdx;
if (slotIdx >= (int)slotSize) { continue; } // 越界保护(保留)
auto& outcast = nextSrc->GetOutcast(entry.outcastIdx);
auto& slot = slotList[slotIdx];
if (slot.stitchDupIdx == INVALID_STITCH_IDX) { continue; } // 运行时保护(保留)
// isPartialUpdateStitch 和 stitchCtrlBitMask 已在 encode 阶段筛选(isPartial && RAW|WAW),无需判断
matchCount += PartialUpdateStitchProducer(...);
}
冗余遍历消除对照
fwd
| 无效遍历 | 次数 | 解决方式 | 列表 |
|---|---|---|---|
| UpdateSlotsImpl incast nonPartial | 6912 | updateIncastSlotList 只含 isPartial && (mask & RAW) |
updateIncastSlotList |
| FastStitchConsumer skipInvalidIdx | 4536 | stitchIncastSlotList 只含 stitchCtrlBitMask & WAR,排除无写后读场景的 slot |
stitchIncastSlotList |
| FastStitchProducer skipNonPartial | 2772 | stitchOutCastSlotList 只含 `isPartial && (mask & (RAW |
WAW))` |
| 总计 | 14220 | 全部清零 |
bwd
| 无效遍历 | 次数 | 解决方式 | 列表 |
|---|---|---|---|
| UpdateSlotsImpl incast nonPartial | 23104 | updateIncastSlotList 只含 isPartial && (mask & RAW) |
updateIncastSlotList |
| FastStitchConsumer skipInvalidIdx | 22042 | stitchIncastSlotList 只含 stitchCtrlBitMask & WAR,排除无写后读场景的 slot |
stitchIncastSlotList |
| FastStitchProducer skipNonPartial | 2394 | stitchOutCastSlotList 只含 `isPartial && (mask & (RAW |
WAW))` |
| 总计 | 47540 | 全部清零 |
updateOutCastSlotList 不筛选(fwd 3392 次 / bwd 9568 次,全有效),无冗余。
列表与场景映射
| 场景 | UpdateSlotsImpl 消费 | FastStitch 消费 |
|---|---|---|
| WAW/写后写 | updateOutCastSlotList(partial+WAW 分支) |
stitchOutCastSlotList |
| WAR/写后读 | updateOutCastSlotList(partial+WAR + fullcover+WAR 分支) |
stitchIncastSlotList |
| RAW/读后写 | updateIncastSlotList |
stitchOutCastSlotList |
改动范围(实现版)
| 文件 | 改动 |
|---|---|
dev_encode_function_stitch.h |
DevAscendProgramPartialUpdate → DevAscendProgramUpdate,新增 isPartial |
dev_encode_function.h |
新增 SlotMaskEntry + OutCastSlotEntry/IncastSlotEntry + 4 个 DevLocalVector + accessor + RefilterSlotLists/InitSlotPrefilterLists 声明 |
dev_encode_function.cpp |
4 列表 dump |
dev_encode_function_param.h |
#include <cstdint> |
dev_encode_program.h |
partialUpdateList → updateList,InitPartialUpdateSlot → InitUpdateSlot(参数精简),friend 声明更新 |
dev_encode_program.cpp |
dump 更新(isPartial) |
dev_encode_program_ctrlflow_cache.h |
DeviceExecuteSlot 新增 stitchCtrlBitMask,partialUpdate 类型改为 DevAscendProgramUpdate* |
dev_encode.h |
BuildSlotMaskMap 声明 + EncodeDevAscendProgram 加 slotMaskMap 参数 |
dev_encode.cpp |
InitSlotPrefilterLists + CalculateStitchCtrlBitMaskForSlot + BuildSlotMaskMap + RefilterSlotLists + InitSingleFullCoverSlot/InitSinglePartialUpdateSlot(从 slotMaskMap 取 mask)+ InitUpdateSlot(合并遍历) |
dev_encode_workspace.cpp |
DevAscendProgramUpdate 重命名 |
device_slot_context.cpp |
MarkPartialUpdateSlots 改造 + UpdateSlotsImpl 遍历预计算列表 + 统计日志 |
device_stitch_context.cpp |
FastStitchConsumer/FastStitchProducer 遍历预计算列表 + 统计日志 |
backend.cpp |
RunEncodeStage 末尾 BuildSlotMaskMap + RefilterSlotLists + SetDyndevProgBinary 传 slotMaskMap |
test_cell_match_dynamic.cpp |
DevAscendProgramUpdate 重命名 |
test_dev_encode_function_stitch.cpp |
DevAscendProgramUpdate 重命名 |
风险与解决
→ 已解决:抽取公共函数InitSingleFullCoverSlot的分析逻辑需与InitSinglePartialUpdateSlot完全一致CalculateStitchCtrlBitMaskForSlot,BuildSlotMaskMap调用一次,两者都从slotMaskMap取结果→ 已验证:编译通过,精度 PASSDevAscendProgramPartialUpdate新增成员会增加结构体大小→ 已验证DeviceExecuteSlot新增stitchCtrlBitMask成员同上→ 已验证DevLocalVector的 entry 结构体需支持两遍模式fullcover slot 设→ 已解决:slotIndex为正值后影响slotIndex < 0判断MarkPartialUpdateSlots用isPartial区分- 两阶段时机约束:
BuildSlotMaskMap必须在编码循环后 → 已解决:在RunEncodeStage末尾调用 - 原地压缩安全性:
RefilterSlotLists前向双指针 → 已验证 - size-only pass:
slotMaskMap == nullptr→ 已解决:InitUpdateSlot中if (slotMaskMap == nullptr) { return; }
方案5:合并 UpdateSlotsImpl + FastStitch 的 outcast 遍历(待定)
待定:后续考虑减少重复 cell match 的开销时再评估。涉及调用时机变更,需单独分析
CallRootFunctionAlloc与CallRootFunctionStitch之间 cell match 数据的依赖关系。
5. 优先级
| 优先级 | 列表 | 无效遍历消除 | 时机 | 实测消除率 |
|---|---|---|---|---|
| 高 | updateIncastSlotList |
fwd 6912 / bwd 23104 | encode(阶段二 Refilter) | fwd -100% / bwd -97% |
| 高 | stitchIncastSlotList |
fwd 4536 / bwd 22042 | encode(阶段二 Refilter) | fwd -100% / bwd -96.9% |
| 中 | stitchOutCastSlotList |
fwd 2772 / bwd 2394 | encode(阶段二 Refilter) | -100% |
| 低 | updateOutCastSlotList |
0(统一遍历,消除嵌套) | encode(阶段一保守填充) | — |
4 个列表在阶段一保守填充(全集),阶段二 RefilterSlotLists 过滤(除 updateOutCastSlotList 全集不变)。
6. 对比命令
# fwd
rm -f /home/developer/ascend/log/debug/device-0/device-*.log
export PTO_TILE_LIB_CODE_PATH="/mnt/workspace/gitCode/cann/pto-isa"
export TILE_FWK_DEVICE_ID=0
python3 pypto-gym/custom/gdr_fwd/test_gdr_fwd.py --case varlen_T32K_H8
LATEST=$(ls -t /home/developer/ascend/log/debug/device-0/device-*.log | head -1)
grep "UpdateSlotsImpl FINAL" "$LATEST" | tail -1
grep "FastStitchConsumer stats" "$LATEST" | tail -1
grep "FastStitchProducer stats" "$LATEST" | tail -1
grep "newFuncKey" "$LATEST" | head -5
# bwd
rm -f /home/developer/ascend/log/debug/device-0/device-*.log
python3 pypto-gym/custom/gdr_bwd/test_gdr_bwd.py --case varlen64_t32k_h8_bt64
LATEST=$(ls -t /home/developer/ascend/log/debug/device-0/device-*.log | head -1)
grep "UpdateSlotsImpl FINAL" "$LATEST" | tail -1
grep "FastStitchConsumer stats" "$LATEST" | tail -1
grep "FastStitchProducer stats" "$LATEST" | tail -1
grep "newFuncKey" "$LATEST" | head -5
7. 优化后验证方法
步骤1:回退统计日志
当前 device_slot_context.cpp 和 device_stitch_context.cpp 中有统计用的 DEV_ERROR + static volatile 变量。实现预计算 slot 列表优化前需先回退这些统计日志:
git checkout -- framework/src/machine/device/dynamic/context/device_slot_context.cpp
git checkout -- framework/src/machine/device/dynamic/context/device_stitch_context.cpp
步骤2:实现预计算 slot 列表
按第 4 节方案实现 4 个列表的 encode + device 侧消费改造。
步骤3:加回统计日志验证
在优化后的代码中加回统计日志(参照本文档第 2-3 节的统计点),编译安装后单轮测试:
rm -f /home/developer/ascend/log/debug/device-0/device-*.log
export PTO_TILE_LIB_CODE_PATH="/mnt/workspace/gitCode/cann/pto-isa"
export TILE_FWK_DEVICE_ID=0
python3 pypto-gym/custom/gdr_fwd/test_gdr_fwd.py --case varlen_T32K_H8
LATEST=$(ls -t /home/developer/ascend/log/debug/device-0/device-*.log | head -1)
grep "UpdateSlotsImpl FINAL" "$LATEST" | tail -1
grep "FastStitchConsumer stats" "$LATEST" | tail -1
grep "FastStitchProducer stats" "$LATEST" | tail -1
验证标准
fwd
| 指标 | 优化前(基线) | 优化后(预期) |
|---|---|---|
| UpdateSlotsImpl inTotal | 6912 | 0(updateIncastSlotList 为空或仅含有效 RAW slot) |
| FastStitchConsumer total | 6804 | 2268(仅含 outcast 重叠的 incast slot) |
| FastStitchProducer total | 3339 | 565(仅含 partial+RAW|WAW 的 outcast slot) |
| 无效遍历总计 | 14220 | 0 |
bwd
| 指标 | 优化前(基线) | 优化后(预期) |
|---|---|---|
| UpdateSlotsImpl inTotal | 23808 | 704(仅含 partial+RAW 的 incast slot) |
| FastStitchConsumer total | 23444 | 2106(仅含 outcast 重叠的 incast slot) |
| FastStitchProducer total | 9421 | 7022(仅含 partial+RAW|WAW 的 outcast slot) |
| 无效遍历总计 | 47540 | 0 |
步骤4:性能对比
回退统计日志后,用多轮累积差分法做性能对比(见 SKILL.md 优化前后对比测试完整流程),对比优化前后的 STAGE_STITCH / FAST_STITCH / UPDATE_SLOT 指标。
8. 优化后实测验证报告(2026-08-10)
commit:
b6a25c9ea(prefilter slot lists at encode time)
采集方法:单轮测试,device 日志DEV_ERROR统计(与第 2-3 节基线同方法)
基线对照:commit243edc032a(第 2-3 节数据)
8.1 遍历统计对比 — fwd(varlen_T32K_H8)
| 函数.指标 | 优化前 | 优化后 | 变化 | 说明 |
|---|---|---|---|---|
| UpdateSlotsImpl | ||||
| outTotal | 3392 | 3392 | 不变 | updateOutCastSlotList 全集,不筛选 |
| ├ outPartialWarWaw | 576 | 576 | 不变 | partial + WAR|WAW |
| ├ outFullCoverWarWaw | 2816 | 2816 | 不变 | fullcover + WAR|WAW |
| └ outSkip (mask=NONE) | — | 0 | 新增 | 全集列表中无 NONE slot |
| inTotal | 6912 | 0 | -100% | updateIncastSlotList 按 isPartial && RAW 筛选,fwd 无 RAW slot |
| └ inProcess | 0 | 0 | 不变 | |
| FastStitchConsumer | ||||
| total | 6804 | 2268 | -66.7% | stitchIncastSlotList 按 WAR 筛选 |
| ├ skipInvalidIdx | 4536 | 0 | -100% | encode 筛选消除 |
| └ process | 2268 | 2268 | 不变 | 有效遍历不变 |
| FastStitchProducer | ||||
| total | 3339 | 567 | -83.0% | stitchOutCastSlotList 按 `isPartial && (RAW |
| ├ skipNonPartial | 2772 | 0 | -100% | encode 筛选消除 |
| └ process | 565 | 565 | 不变 | 有效遍历不变 |
| 无效遍历总计 | 14220 | 0 | -100% | 全部清零 |
8.2 遍历统计对比 — bwd(varlen64_t32k_h8_bt64)
| 函数.指标 | 优化前 | 优化后 | 变化 | 说明 |
|---|---|---|---|---|
| UpdateSlotsImpl | ||||
| outTotal | 9568 | 9568 | 不变 | updateOutCastSlotList 全集,不筛选 |
| ├ outPartialWarWaw | 7136 | 7136 | 不变 | partial + WAR|WAW |
| ├ outFullCoverWarWaw | 2432 | 2432 | 不变 | fullcover + WAR|WAW |
| └ outSkip | — | 0 | 新增 | |
| inTotal | 23808 | 704 | -97.0% | updateIncastSlotList 按 isPartial && RAW 筛选 |
| └ inProcess | 704 | 704 | 不变 | 有效遍历不变 |
| FastStitchConsumer | ||||
| total | 23444 | 2088 | -91.1% | stitchIncastSlotList 按 WAR 筛选 |
| ├ skipInvalidIdx | 22042 | 686 | -96.9% | encode 筛选消除大部分,残留 686 为运行时 stitchDupIdx == INVALID |
| └ process | 1402 | 1402 | 不变 | 有效遍历不变 |
| FastStitchProducer | ||||
| total | 9421 | 7027 | -25.4% | stitchOutCastSlotList 按 `isPartial && (RAW |
| ├ skipNonPartial | 2394 | 0 | -100% | encode 筛选消除 |
| └ process | 7022 | 7022 | 不变 | 有效遍历不变 |
| 无效遍历总计 | 47540 | 686 | -98.6% | 残留 686 为运行时状态,encode 无法预计算 |
8.3 残留无效遍历分析
bwd FastStitchConsumer 残留 686 次 skipInvalidIdx:这些 slot 有 WAR mask(encode 阶段合理加入 stitchIncastSlotList),但运行时 stitchDupIdx == INVALID_STITCH_IDX(前驱 rootfunc 未执行,未在 UpdateSlotsImpl 中设置 stitchDupIdx)。这是运行时控制流状态,encode 阶段无法预计算——只有当 devNextIdx == 0(首个 rootfunc 无前驱)或控制流跳过某些 rootfunc 时才会出现。
8.4 有效遍历不变性验证
所有 process / inProcess 计数优化前后完全一致,证明预计算列表正确包含了所有需要处理的 slot,没有遗漏或多余。
| 用例 | UpdateSlotsImpl inProcess | FastStitchConsumer process | FastStitchProducer process |
|---|---|---|---|
| fwd 优化前 | 0 | 2268 | 565 |
| fwd 优化后 | 0 | 2268 | 565 |
| bwd 优化前 | 704 | 1402 | 7022 |
| bwd 优化后 | 704 | 1402 | 7022 |
8.5 性能对比
8.5.0 commit2 stitch attr 优化对比(commit1 → commit2,11 轮 skip 1,us)
baseline:
078a5a738(dump only,无优化)
optimized:43edc032a(stitch attr access 优化)
采集方法:commit 拆分模式,11 轮同进程,忽略首轮,后 10 轮差分均值。两轮采集(先基线再优化 / 先优化再基线),结果一致可复现。
fwd — varlen_T32K_H8
| 指标 | BASELINE(commit1) | OPTIMIZED(commit2) | 变化 | 轮2变化 |
|---|---|---|---|---|
| STAGE_STITCH | 1690 | 1401 | -17.1% | -16.7% |
| FAST_STITCH | 1100 | 971 | -11.7% | -11.0% |
| UPDATE_SLOT | 568 | 409 | -28.0% | -28.0% |
| DEV_TASK_BUILD total | 2446 | 2149 | -12.1% | -11.7% |
| DEV_TASK_BUILD first | 355 | 316 | -10.9% | -8.4% |
bwd — varlen64_t32k_h8_bt64
| 指标 | BASELINE(commit1) | OPTIMIZED(commit2) | 变化 | 轮2变化 |
|---|---|---|---|---|
| STAGE_STITCH | 12411 | 10057 | -19.0% | -13.1% |
| FAST_STITCH | 8060 | 6552 | -18.7% | -7.3% |
| UPDATE_SLOT | 4134 | 3308 | -20.0% | -23.0% |
| DEV_TASK_BUILD total | 18426 | 17662 | -4.1% | -1.8% |
| DEV_TASK_BUILD first | 378 | 360 | -4.8% | -5.1% |
commit2 优化特性清单(43edc032a)
- Optimize GetTensorOffsetAndValidShape: replace 3x GetOperationAttr with single GetSymoffset + cachedAttrBase
- Use if constexpr for skipExpression branch elimination
- Merge CheckOffsetAndValidShapeInRawShape two loops into one
- Remove unused shapeAttrIdx field, compute from offsetAttrIdx + dim on demand
- Precompute offsetAttrIdx at encode time
- Add const overload for GetOpAttrOffsetAddr
- Move operationIndex to last param (cold path) to reduce register pressure
- Add unlikely(clamped) to isolate DumpCellMatchAccessRange cold path
- Isolate DumpConsumerCellAccess/DumpProducerCellAccess with DEV_IF_NONDEVICE
- Replace wrapTaskHubOpIdx with stitchOpIdx: precompute at encode time
- Remove IsCellMatchDescFillReady redundant check
- Remove skipExpression template param (always false, dead code elimination)
- Change GetTensorOffsetAndValidShape to inline void
- Extract dims to local var in CellMatchHandle
- Extract maxCount out of CellMatchHandleFillEnhanceExec loop
- Unify Process signature: pass cellMatchTableDesc from CellMatchProcessByDim
- Merge updateFuncIdx + operationIdx into taskId via MakeTaskID
- Merge PrepareRuntimeDynamicPartialUpdateTables into UpdateSlotsImpl main loop
- Isolate DEV_ASSERT_MSG with DEV_IF_NONDEVICE
- Add unlikely(MaybeHaveAtomic)
- Keep attrList view in DevAscendOperation
commit2 结论
- 两轮结果一致,优化效果稳定可复现
- UPDATE_SLOT 改善最大(fwd -28%、bwd -20%~23%):合并遍历 + assert 隔离 + unlikely(atomic)
- STAGE_STITCH 显著改善(fwd -17%、bwd -13%~19%):cachedAttrBase + stitchOpIdx + CellMatchHandle 优化
- FAST_STITCH 显著改善(fwd -11%、bwd -7%~19%)
- DEV_TASK_BUILD total:fwd -12%,bwd -2%~4%
8.5.1 prefilter 单独优化对比(commit2 → prefilter,12 轮 skip 2,us)
baseline: commit2
43edc032a(stitch attr 优化后,prefilter 前)
optimized: prefilter92a36b4fe(在 commit2 基础上加 prefilter slot lists)
采集方法:多轮累积差分法,parse_perf_cumulative.py --skip 2
fwd — varlen_T32K_H8
| 指标 | BASELINE(commit2) | OPTIMIZED(prefilter) | 变化 |
|---|---|---|---|
| STAGE_STITCH | 1397 | 1346 | -3.7% |
| FAST_STITCH | 965 | 915 | -5.2% |
| UPDATE_SLOT | 410 | 407 | -1.0% |
bwd — varlen64_t32k_h8_bt64
| 指标 | BASELINE(commit2) | OPTIMIZED(prefilter) | 变化 |
|---|---|---|---|
| STAGE_STITCH | 10083 | 10031 | -0.5% |
| FAST_STITCH | 6593 | 6645 | +0.8% |
| UPDATE_SLOT | 3293 | 3159 | -4.1% |
prefilter 三轮汇总
| 指标 | 轮1(优化先,skip1) | 轮2(基线先,skip1) | 轮3(基线先,skip2) | 趋势 |
|---|---|---|---|---|
| fwd STAGE_STITCH | -5.3% | -2.8% | -3.7% | 稳定改善 -3%~5% |
| fwd FAST_STITCH | -7.8% | -4.5% | -5.2% | 稳定改善 -5%~8% |
| fwd UPDATE_SLOT | -0.4% | +0.3% | -1.0% | 持平 |
| bwd STAGE_STITCH | +0.5% | -2.8% | -0.5% | 噪声范围内 |
| bwd FAST_STITCH | +1.7% | -0.0% | +0.8% | 持平~略差 |
| bwd UPDATE_SLOT | -3.1% | -9.0% | -4.1% | 稳定改善 -3%~9% |
8.5.2 全部优化点 vs 优化前基线(commit1 → commit2+prefilter,12 轮 skip 2,us)
baseline:
078a5a738(dump only,无任何优化)
optimized:92a36b4fe(commit2 stitch attr 优化 + prefilter slot lists,全部优化点)
采集方法:两轮交叉验证(先基线再优化 / 先优化再基线)
fwd — varlen_T32K_H8
| 指标 | BASELINE(commit1) | OPTIMIZED(全部) | 轮1变化(基线先) | 轮2变化(优化先) |
|---|---|---|---|---|
| STAGE_STITCH | 1679 | 1299 | -22.6% | -21.3% |
| FAST_STITCH | 1090 | 891 | -18.3% | -16.0% |
| UPDATE_SLOT | 567 | 383 | -32.5% | -33.1% |
| DEV_TASK_BUILD total | 2430 | 2033 | -16.3% | -14.9% |
| DEV_TASK_BUILD first | 344 | 294 | -14.8% | -11.9% |
bwd — varlen64_t32k_h8_bt64
| 指标 | BASELINE(commit1) | OPTIMIZED(全部) | 轮1变化(基线先) | 轮2变化(优化先) |
|---|---|---|---|---|
| STAGE_STITCH | 11862 | 9768 | -17.7% | -23.2% |
| FAST_STITCH | 7412 | 6426 | -13.3% | -21.8% |
| UPDATE_SLOT | 4227 | 3112 | -26.4% | -27.3% |
| DEV_TASK_BUILD total | 17851 | 17929 | +0.4% | -6.0% |
| DEV_TASK_BUILD first | 370 | 353 | -4.7% | -6.5% |
优化点贡献分解
| 优化点 | 所属 commit | fwd 影响 | bwd 影响 |
|---|---|---|---|
| cachedAttrBase + stitchOpIdx + CellMatchHandle + if constexpr | commit2 43edc032a |
STAGE_STITCH -17%~19%、FAST_STITCH -11%~12% | STAGE_STITCH -13%~19%、FAST_STITCH -7%~19% |
| 合并遍历 + assert 隔离 + unlikely(atomic) | commit2 43edc032a |
UPDATE_SLOT -28% | UPDATE_SLOT -20%~23% |
| prefilter slot lists(消除无效遍历) | prefilter 92a36b4fe |
STAGE_STITCH -3%~5%、FAST_STITCH -5%~8% | UPDATE_SLOT -3%~9% |
| 合计 | 两个 commit | STAGE_STITCH -22%、FAST_STITCH -18%、UPDATE_SLOT -33% | STAGE_STITCH -18%~23%、FAST_STITCH -13%~22%、UPDATE_SLOT -27% |
8.6 结论
- 遍历统计:无效遍历 fwd 14220→0(-100%)、bwd 47540→686(-98.6%),与第 7 节预期一致(bwd 残留 686 为运行时
stitchDupIdx == INVALID,encode 无法预计算) - 有效遍历不变:所有
process计数完全一致,证明预计算列表正确性 - prefilter 单独性能:fwd 稳定改善(STAGE_STITCH -3%~5%、FAST_STITCH -5%~8%),bwd UPDATE_SLOT 稳定改善(-3%~9%),bwd STAGE_STITCH/FAST_STITCH 无改善(主要无效遍历已消除但耗时占比小/残留运行时 skip)
- 全部优化点合计性能:fwd STAGE_STITCH -22%、FAST_STITCH -18%、UPDATE_SLOT -33%;bwd STAGE_STITCH -18%~23%、FAST_STITCH -13%~22%、UPDATE_SLOT -27%。两轮交叉验证方向一致,效果稳定可复现
- bwd FAST_STITCH 在 prefilter 单独对比中无改善,但在全部优化点对比中改善 -13%~22%:因为 commit2 的 cachedAttrBase/stitchOpIdx 等优化贡献了主要改善,prefilter 在 bwd 中主要贡献是 UPDATE_SLOT


pr 5588 优化:
优化概述
将 task 级字段从 DynFuncData 移至 DynFuncHeader(每 task 一次赋值,而非每 func),缩小 per-func 结构体大小;隔离 debug-only 和 host-only 代码路径。
优化点
- Task 级字段上移
startArgs / stackWorkSpaceAddr / stackWorkSpaceSize 从 DynFuncData 移至 DynFuncHeader
FillOneDynFuncData 移除 startArgs / workspace 参数,在 BuildDynFuncData 中一次性赋值到 header
Reloc/backup/restore 在 header 层执行(每 task 一次,非每 func) - Debug-only 代码隔离
CheckAddrAligned 和 leafFunc 统计包裹在 DEV_IF_DEBUG 中
host-only 字段(exprNum / opAttrSize / rawTensorAddrSize / rawTensorDescSize)包裹在 DEV_IF_NONDEVICE 中 - 局部变量提取
dupFunc.GetSource() 提取为局部变量 source(13 次调用 → 1 次)








控制流性能优化需求开发文档
1. 背景与目标
控制流(Control Flow / AICPU)路径在多 Kernel 反复启动、表达式求值、Stitch/UpdateSlots 等高频场景中开销偏大,表现为:
memcpy/clear_cache本需求针对上述瓶颈做结构性优化,降低单次启动与热路径常数因子,不改变算子语义与精度预期。
2. 模块一:AOT 控制流代码池
2.1 改动前问题
原实现为单槽 BSS 代码池:每次构造控制流 AOT 时无条件将二进制
memcpy进固定区域并clear_cache。多 Program / 多 Kernel 交替执行时,即便同一 hash 的控制流代码刚被拷入池中,再次启动仍会重复拷贝,
MAPEXE_MEMCPY成为固定开销。2.2 改动原理
引入进程级多槽 hash 代码池(Device 16 槽 / Host·仿真 4 槽):
hashKey,并维护 LRUEnsureCached(hash):hash 仍在池内 → 跳过 memcpy;否则选空槽或 LRU victim 再拷贝aotPoolSlotId作为上次命中槽位 hint,加速查找收益:同 Program 重复启动或池内尚未被覆盖时,消除冗余 AOT 拷贝与 I-cache 刷新。
3. 模块二:表达式公共子表达式消除(CSE)
3.1 改动前问题
SetExprBatch与控制流中的表达式按树逐个展开。同一 batch / 同一 launch 内多次出现的:RUNTIME_GetInputShapeDim(ARG_*, imm)RUNTIME_GetInputData(ARG_*, RUNTIME_*, imm)会被重复生成与执行,AICPU 侧无效调用偏多。
3.2 改动原理
两层 CSE,且仅对固定叶子入参生效(
Immediate/ARG_*/RUNTIME_*;排除VALUE_*、嵌套表达式):Launch CSE
跨表达式表收集可复用的 ShapeDim,在
ControlFlowEntry栈上计算一次CSE_sd[],作为参数传入各SetExprBatch。Batch-local CSE
同一 batch 内相同目标 call 出现 ≥2 次时,在函数顶部提取
__cse_i,后续引用标识符而非再次展开。因 AICPU 控制流只加载
.pyptosection,不能依赖 namespace 全局变量,故采用栈数组 + 参数传递,而非静态全局。收益:减少控制流与 SetExprBatch 中重复的 Shape/Data 查询,缩短表达式求值路径。
4. 模块三:CellMatch Tag 与 Slot 去重
4.1 改动前问题
unordered_set对 partial update 去重 → 热路径堆分配 + hash。devTaskId等塞进 tag 低位,与长跑 task 计数强耦合,位宽易耗尽,且重置策略成本高。4.2 改动原理
Slot 字节 bitmap 去重
slotSize ≤ 2048:栈上 bitmapslotDedupBitmapSize,从 metadata.general 分配unordered_set独立
cellMatchTagSeq世代号PushTask推进;接近上限时整池 reset 后从 0 重新编号池初始化前移
收益:降低 UpdateSlots/Stitch 的常数开销,稳定长跑下的 CellMatch 元数据管理。
5. 模块四:MakeTask / Dup / Cache 路径微优化
5.1 改动前问题
DeviceTask(含大 wrap 数组)先memset再写字段MAX_STITCH_FUNC_NUM拷贝,而非实际使用条数memset(大量字段稍后才赋值)5.2 改动原理
dynFuncDataCacheListSize拷贝收益:压缩 MakeTask、Root Dup、cache 录制/回放路径的内存与初始化开销。
6. 模块五:Value-depend 跳过 Host Control-Flow Cache
6.1 改动前问题
Value-depend 图仍会:
CACHESTOPstitchBuildControlFlowCache/ Emulation 录 cache录完的 cache 对 value-depend 无实际复用价值,反而增加 host 启动与 emulation 成本,并引入无意义的 CACHESTOP 停顿语义。
6.2 改动原理
disableCtrlFlowCache,不再 emit CACHESTOP stitchKernelBinary::BuildControlFlowCache、EmulationLauncher、PythonDeviceRunOnce/BuildCache等在 disable 时直接跳过构建收益:value-depend 场景去掉无效 host cache 构建与 CACHESTOP 开销。