Open
内存扫描的问题 #5
hedongdongAdd on  Sep 20
hedongdongCollaborators
Sep 20 Create

C.oob_buffer — hyper_parallel/core/multicore/ops/hyper_mega_moe/op_host/hyper_mega_moe_tiling.cpp:30

  • 最后改动该行: hedongdong <hedongdong@huawei.com> — 03df4fbb758 fix(multicore): address runtime memory safety findings
  • scan 置信度: low / 复核置信度: medium
  • 判定: 防御性缺口 / 实际可触发: hypothetical
  • 反驳尝试: (1) 代码模式核对:Read hyper_mega_moe_tiling.cpp:26-40 与 70-85。coreNum = GetCoreNumAic() 只有 coreNum == 0 下界检查,随后直接 SetBlockDim(coreNum),全函数无上界 clamp;新增的拓扑校验(75-81)确实只覆盖 rankId/ep/expertNum/hiddenSize/seqSize 与 expertNum/ep <= MAX_EXPERT_NUM_PER_RANK —— 模式属实。(2) 算术反驳(失败):Read runtime_config.hpp:18-30 与 306-319,确认 NUM_WORKERS_CUBE=24、MAX_GROUP_LIST=512、MAX_EXPERT_NUM_PER_RANK=16、GROUP_LIST_CACHE_LINE_BYTES=128;slot 步长 168=128B,group-list 区域 5128=4096B,对齐最多吃掉 127B,故容量上限 floor((4096-127)/128)=31 个 worker,而 static_assert 只证明到 24 —— finding 的越界算术成立。(3) 兜底反驳(失败):host Python 侧 scheduler/config.py 的 _validate_num_cube_cores 只拒非正值,gen_runtime_data 仅设 num_workers = 2*num_cube_cores,两处都不绑定 NUM_WORKERS_CUBE;device 侧 getGroupedMatmulGroupListOffsetById 的两个生产调用点均直接传 AscendC::GetBlockIdx(),无 clamp。(4) 可达性反驳(成立,决定性):越界要求 GetCoreNumAic() 返回 >= 32(相对 static_assert 的文档不变量则是 > 24)。现网 Ascend 芯片的 AIC 数量远低于该值,finding 自身也承认"no currently shipping Ascend AIC count reaches 32",且这不是一条罕见代码路径而是不存在的硬件配置 —— 属于"若未来硬件/常量变更则触发",命中 pattern 5。(5) enum 反驳:type 标 C.oob_buffer,写越界的形状对得上,不以归类反驳。
  • 未被驳倒的原因 / 反驳如何成立: 越界算术与校验缺口都被 runtime_config.hpp 的常量和 static_assert 直接证实,且 host/device 两侧都没有补救;但触发需要一个当前不存在的硬件(AIC >= 32)或常量变更,因此是尚未可达的潜在不变量缺口而非现存 bug。
  • 影响范围: 若将来平台 AIC 数超过 24/32:cube worker 的 group-list 写入越过 4096B 区域,覆盖相邻的 atomic_add_values,导致 grouped-matmul 分组数据静默损坏(结果错误)乃至设备侧非法访问;当前硬件上无可观测影响
  • scan 阶段证据:
    The diff adds topology validation (rankId/ep/expertNum/hiddenSize/seqSize at lines 75-81, incl. the new `expertNum / ep > MAX_EXPERT_NUM_PER_RANK` bound) and a `coreNum == 0` lower bound at lines 30-33, but no upper bound on coreNum, even though the device side needs one. Each cube worker publishes its group list at `getGroupedMatmulGroupListOffsetById(runtimeConfigPtr, AscendC::GetBlockIdx())` = `aligned_offset + MAX_EXPERT_NUM_PER_RANK * 8 * worker_id` (runtime_config.hpp:310-315) inside a region that is only `MAX_GROUP_LIST * 8 = 4096` bytes long (runtime_config.hpp:317-318, getAtomicAddValuesOffset). runtime_config.hpp:28-29 encodes exactly this invariant as a static_assert against the compile-time constant `NUM_WORKERS_CUBE = 24`, i.e. the slots are only proven to fit for worker_id < 24 (arithmetically up to 31). `context->SetBlockDim(coreNum)` with coreNum from `GetCoreNumAic()` makes GetBlockIdx() run to coreNum-1 at runtime, so on a platform reporting >= 32 AIC cores the SetValue loop at worker_kernel.cpp:160-168 and the `cacheWriteThrough(grouped_list_real, ...)` at line 169 write past the group-list region into atomic_add_values and beyond. The host Python path has the same gap: `_validate_num_cube_cores` (scheduler/config.py:292-295) only rejects non-positive values, and `_validate_runtime_config` (config.py:360-364) only ties num_workers to 2*num_cube_cores, never to NUM_WORKERS_CUBE. Confidence is low because no currently shipping Ascend AIC count reaches 32, so this is a latent bound rather than a reachable overflow today.
    
  • 修复建议: Reject coreNum > NUM_WORKERS_CUBE next to the existing coreNum == 0 check (and mirror it in _validate_num_cube_cores in scheduler/config.py), or derive the group-list slot capacity from the actual core count instead of the MAX_GROUP_LIST constant.
  • scan 来源: batch_37
likedislike
hedongdongCollaborators
Sep 20 Comments:

C.oob_buffer — hyper_parallel/core/multicore/ops/hyper_mega_moe_grad/op_host/hyper_mega_moe_grad_tiling.cpp:30

  • 最后改动该行: hedongdong <hedongdong@huawei.com> — 03df4fbb758 fix(multicore): address runtime memory safety findings
  • scan 置信度: low / 复核置信度: medium
  • 判定: 防御性缺口 / 实际可触发: hypothetical
  • 反驳尝试: (1) 试反驳「代码模式不符」:直读 hyper_mega_moe_grad_tiling.cpp:26-113,第 30-33 行确实只有 coreNum==0 检查,第 34 行 SetBlockDim(coreNum),第 76-82 行只校验 MoE 拓扑,全函数无 coreNum 上界——与 finding 相符。(2) 试反驳「内核侧其实有 clamp」:读 grad op_kernel/worker_kernel.cpp:160-161,确用未经裁剪的 AscendC::GetBlockIdx() 作为 worker_id 传入 getGroupedMatmulGroupListOffsetById,并在 174-183 写 expert_num_single_rank 个 int64 —— 无 clamp。(3) 试反驳「buffer 本来就够大」:runtime_config.hpp:26-30 的 static_assert 只保证 NUM_WORKERS_CUBE=24 个 worker(stride 128B)加 127B 对齐余量落在 MAX_GROUP_LIST*8=4096B 内;worker_id>=32 时槽位会越过 group-list 区,踩进 getAtomicAddValuesOffset(317-318) 的 atomic-add 区。越界成立。(4) 最有力反驳——触发条件能否发生:NUM_WORKERS_CUBE=24 / NUM_WORKERS_VECTOR=48 是整套 runtime 的硬编码假设,当前目标平台 GetCoreNumAic() 不超过 24;要触发必须换到 AIC>=32 的新平台,届时 24/48 这些常量本身也已失配,属于「平台变更后会踩」而非现存可达路径。仓内 scheduler/config.py:292-295 的 _validate_num_cube_cores 只校验正数,也没有别的地方把 coreNum 压到 24。
  • 未被驳倒的原因 / 反驳如何成立: 反驳(4)部分成立:越界算式确实存在且 host 侧缺上界检查,但在现有平台常量体系下 coreNum 恒 <= NUM_WORKERS_CUBE,不是现在可达的越界。属于缺一条与 static_assert 对齐的运行期护栏(成本极低:coreNum > NUM_WORKERS_CUBE 直接 GRAPH_FAILED),因此判 hardening 而非 situational。
  • 影响范围: 若在 AIC>=32 平台运行:每个高编号 worker 向 runtime-config 的 atomic-add 区写 group list,造成静默设备内存损坏(计数器被覆盖 → 同步错乱/挂死),难定位
  • scan 阶段证据:
    Same defect as the forward tiling, duplicated verbatim by this batch: lines 30-33 add only `coreNum == 0` and lines 81-87 validate the MoE topology, but nothing bounds coreNum against NUM_WORKERS_CUBE. `context->SetBlockDim(coreNum)` at line 34 lets GetBlockIdx() reach coreNum-1, and the backward kernel indexes its per-worker group-list scratch with it at hyper_mega_moe_grad/op_kernel/worker_kernel.cpp:160-161, then writes expert_num_single_rank int64 slots there (lines 174-183). The slot stride/capacity invariant is only asserted for NUM_WORKERS_CUBE = 24 workers (runtime_config.hpp:26-30) against a 4096-byte region (MAX_GROUP_LIST * 8, runtime_config.hpp:317-318).
    
  • 修复建议: Add coreNum > NUM_WORKERS_CUBE rejection alongside the existing zero check, mirroring whatever fix is applied to the forward tiling.
  • scan 来源: batch_37
likedislike
hedongdongCollaborators
Sep 20 Comments:

X.uncategorized_memory — hyper_parallel/core/expert_parallel/expert_parallel.py:1401

  • 最后改动该行: zhaochunxu <chunxu1212@gmail.com> — 27353eacb29 feat(expert_parallel): async combine a2a overlap with shared expert
  • scan 置信度: medium / 复核置信度: medium
  • 📌 既有缺陷: 非本次 diff 引入,scan 通读文件时顺带发现
  • 判定: 真但场景受限 / 实际可触发: conditional
  • 反驳尝试: (1) X.uncategorized_memory 按模板不以「enum 对不上」反驳,只问是否真是内存问题 —— 把一份整层 MoE 激活挂在 module 属性上、生命周期延长到 backward 释放图之后,属于内存占用类缺陷,成立。(2) 代码模式核实:expert_parallel.py:1395-1401 与 1618-1623 两处确认 module._ep_combine_handle = handle;AsyncHandle.init(61-85)强引用 _tensor,_DeredundencyCombineHandle(606-640)额外持有 _combined 与 _ctx。(3) 是否有清理?grep 全仓仅赋值点与测试断言,无 del/None/del,反驳失败。(4) 是否已被 autograd 图引用因而不增加额外常驻?前向到 backward 期间确实重复引用,但 backward 释放图后只剩该属性持有,直到下一次 forward 覆盖 —— 这段窗口(backward 尾段 + optimizer step)正是显存另一个高峰,反驳只部分成立。(5) 是否无界增长?不是:每 module 一个、下次 forward 覆盖,属于有界的常驻放大而非泄漏,这削弱了严重度。(6) 配置门控:init 签名 async_combine: bool = False(1305/1536),默认关闭,必须显式开启才走该路径 —— 这是最有效的一条反驳。(7) pre_existing=true 按模板不作为反驳。
  • 未被驳倒的原因 / 反驳如何成立: 保留行为与缺乏清理路径都被源码证实,注释只声明 _ep_dispatch_ctx 的生命周期契约,对 _ep_combine_handle 仅写「for external inspection」,不构成已声明契约。但该路径由默认关闭的 async_combine 门控,且占用有界(每层一份、下轮覆盖),故判 situational。
  • 影响范围: 开启 async_combine 时每个 MoE 层多常驻一份 [total_tokens, hidden] 激活(deredundency 变体再加 dispatch_indices/router_coeff),贯穿 backward 尾段与 optimizer step,可能把大模型推到 OOM;不会数据损坏。
  • scan 阶段证据:
    In the async-combine path, `module._ep_combine_handle = handle` (line 1401 for ExpertParallel, line 1623 for the 2-D EP+TP variant) stores an AsyncHandle whose `_tensor` (and, for _DeredundencyCombineHandle, `_combined`) is the full combine output of shape [total_tokens, hidden] — the MoE layer's activation. The comment says it is kept only 'for external inspection / advanced use cases'. Nothing ever deletes or overwrites it until that same module's next forward, so after backward has released the autograd graph, one full-size activation per MoE module stays resident through the optimizer step, keeping peak memory at forward-peak level for the whole backward+step window. _DeredundencyCombineHandle additionally pins _ctx (dispatch_indices, router_coeff, gathered_shape).
    
  • 修复建议: Do not keep the handle on the module by default: either drop the attribute after handle.wait() returns, hold it in a weakref/None-able one-shot slot cleared at the end of combine, or gate the assignment behind an explicit debug flag.
  • scan 来源: batch_34
likedislike
hedongdongCollaborators
Sep 20 Comments:

B.leak_ref — hyper_parallel/core/utils/moe_utils.py:223

  • 最后改动该行: wangleiit03 <wangleiit03@chinamobile.com> — 5da0c36b93f feat(moe): add load-balance aux_loss with AutoScaler, expert_bias sync, and sequence_partition_group
  • scan 置信度: high / 复核置信度: high
  • 判定: 真但场景受限 / 实际可触发: conditional
  • 反驳尝试: (1) 试图证明代码模式不符:读 moe_utils.py:205-231,register() 第 223 行确为无条件 self.hook_handle = optimizer.register_step_post_hook(lambda *: self.on_step_end()),注册前不检查既有 handle;remove()(227-231) 只处理当前字段;init(159-167) 确认 callback 强持有 self.model 且只有一个 _hook_handle 槽——模式完全吻合。(2) 试图找兜底:全仓无 del/weakref/handle 容器,测试 test_moe_utils.py 也只覆盖单次 register/remove——反驳失败。(3) 试图论证 make_shared/防御代码等已知误报 pattern:均不适用。(4) 最有效反驳(pattern 6):触发需要调用方在未 remove() 的情况下对同一个 MoEMonitorCallback 实例重复 register(),这是 API 误用;正常用法 register 一次、remove 一次不会触发。但 docstring(205-216) 并未声明 register 只能调用一次,约束是隐式的,按 pattern 6 的反例条款不应彻底降级。
  • 未被驳倒的原因 / 反驳如何成立: 缺陷本身确凿(handle 被覆盖 → 旧 hook 永久不可注销且会重复执行 on_step_end,同时经 lambda 闭包永久持有整个 model),无任何兜底;只是触发依赖一条未文档化的隐式调用约束,故 situational。
  • 影响范围: 旧 optimizer 的 hook registry 永久持有 callback→整个 model(模型级内存泄漏),并且每次 optimizer.step() 会重复执行 expert bias 更新 → silent data corruption(bias 被按注册次数多次更新)
  • scan 阶段证据:
    register() 第 223 行每次注册新的 optimizer step post-hook 后直接覆盖 self._hook_handle。若同一 callback 重复 register(同一或不同 optimizer),旧 RemovableHandle 的唯一引用丢失;但旧 optimizer 的 hook registry 仍保存第 224 行捕获 self 的 lambda,从而持续持有 callback 及其第 160 行引用的整个 model。remove() 第 227-231 行只能移除最后一次注册的 handle,早先 hook 无法再注销并会重复执行。
    
  • 修复建议: 让 register() 幂等:注册前若已有 handle,先调用其 remove();或保存所有 handles 并在 remove() 中逐个注销。也可在已注册时直接抛出明确异常。
  • scan 来源: batch_24
likedislike