已合并
engram反向超时DFX #11577
engram反向超时DFX #11577
已合并
luozhonglin创建于 7 天前
luozhonglin
luozhonglin
7 天前

描述

1.反向超时的DFX
2.UB溢出修复
3.scatter汇总和sort分核一致性修复

关联的Issue

测试

文档更新

类型标签

检视概览

  • PR: https://gitcode.com/cann/ops-transformer/pull/11577
  • 算子: engram_fetch_grad(mc2/engram_fetch_grad/)
  • 代码侧别: 混合(Tiling 1 文件 + Kernel 4 文件)
  • 变更规模: 5 个文件,有效变更约 207 行(+120 / -87)
  • 变更主题: ① Host 侧新增 sort/unique 两阶段 UB 池峰值 fail-fast 校验(CalcSortUbBytes 镜像 Kernel GetUbSize),Kernel 侧 unique 阶段 entryBuf_ 由 64KB 收缩为 uniqueEntryBytes_;② 删除 RUNTIME_ABORT 宏与 COMM_RETRY_COUNT 重试,8 处失败点统一 ascendc_assert 直报,TimeoutCheck 增加 TimeoutSite;③ sort ProcessHist/ProcessScatter tile→core 归属由 batch-major 改 core-major;④ numRecvCores_ 扣除 flagCore 修正
  • 检视文档: cpp-secure / cpp-general / ascendc-api / ascendc-perf / ascendc-topk / mc2-specific / regbase-review-checks / simt-api-analysis(compile-secure / python-secure / cpp-style 按声明式匹配跳过)
  • 总条例数: 135 条(32 组 / 6 波并行检视)
  • 检视时间: 2026-09-09
  • 配套产物: code_summary.md(代码概要+变量溯源)、api_prestudy.md(API 约束预研)

检视统计

状态 条例数 占比
PASS 123 91.1%
FAIL(发现问题) 6 4.4%
SUSPICIOUS(需关注/疑似) 6 4.4%
范围外备注 1 -

核心结论

6 个 FAIL 高度同源,可归并为 2 个根因:

  • 根因 A(4 个 FAIL:cpp-secure 1.2/1.3/4.1、cpp-general 1.1/12.1):本 PR 删除 RUNTIME_ABORT 宏(含 while(true) release 兜底)并将 8 处失败点改为 ascendc_assert。经 CANN 9.0.0 头文件实证(kernel_operator_dump_tensor_intf.h:65-68),未定义 ASCENDC_DUMPascendc_assert 展开为空宏,且本算子构建未定义 ASCENDC_DUMP。release 产物下:commContext(设备侧外部数据)rankSize 一致性校验完全失效 → 除零 UB / workspace displs 越界写;pendingHandles 固定数组(8 元素)越界守卫失效;通信失败/超时静默继续(且删除了原有 3 次重试)。
  • 根因 B(1 个 FAIL:cpp-secure 2.2):numRecvCores_==0 兜底分支中 totalBlocks_==1 时 numSendCores_ 由 0 改为 1,导致 recvIdx = aivId_ - numSendCores_ 在 aivId_=0 时发生 uint32 回绕,接收循环零迭代,单核形态多 rank 场景远端梯度静默丢失(功能回归)。

正面确认:sort tile 归属 core-major 改写修复了 tileOffsets 跨核免屏障假设失效的竞态(多方交叉验证一致);UB 池收缩(entryBuf_ 64KB→uniqueEntryBytes_)Host/Kernel 两侧公式逐项核算等值、构造性安全;新增两道 Host fail-fast 校验(sortUb 峰值 / minUniqueNeed)release 生效,覆盖 Host 可建模部分。


发现问题(HIGH 置信度)

[cpp-secure 2.2] 无符号回绕 —— recvIdx 回绕致单核形态接收零迭代

  • 问题描述:PR 修改 Init() 的单核 fallback(numSendCores_ 旧值 totalBlocks_-1U(totalBlocks_==1 时为 0)改为 1)后,RecvGradFromPeers 中无守卫的 uint32 减法 aivId_ - numSendCores_ 在 totalBlocks_==1 && numRanks_>1 时发生 0-1=0xFFFFFFFF 回绕。totalBlocks_==1 时 isReceiver_ 恒为 true(:525),回绕后的 recvIdx 使 for (wIdx = recvIdx; wIdx < totalWorkUnits; ...) 零迭代——本核静默跳过全部接收,对端发送核在 credit/tokenRead 计数器上自旋直至超时。旧代码该形态 numSendCores_=0、recvIdx=0 可完整覆盖(功能回归);Host 侧无 aivNum 与 rankSize 的关联校验,PR 又为 totalBlocks_<=1 专门设置了同核收发 fallback 分支,说明该形态是被明确支持的目标场景。
  • 代码片段(mc2/engram_fetch_grad/op_kernel/arch35/engram_fetch_grad_arch35.h 行 514-526):
        numSendCores_ = sendersPerRank_ * numRanks_;
        if (numSendCores_ > halfBlocks) {
            numSendCores_ = halfBlocks;
        }
    
        numRecvCores_ = (totalBlocks_ > numSendCores_ + 1U) ? (totalBlocks_ - numSendCores_ - 1U) : 0U;
        if (numRecvCores_ == 0U) {
            numRecvCores_ = 1U;
            numSendCores_ = (totalBlocks_ > 1U) ? (totalBlocks_ - 1U) : 1U;   // ← 本 PR 新增:totalBlocks_==1 时 numSendCores_=1
        }
        isSender_ = (aivId_ < numSendCores_) || (totalBlocks_ <= 1U);
        isReceiver_ = (aivId_ >= numSendCores_ && aivId_ < totalBlocks_ - 1U) || (totalBlocks_ <= 1U);  // totalBlocks_==1 时恒 true
        isFlagCore_ = (aivId_ == totalBlocks_ - 1U);
    
  • 代码片段(mc2/engram_fetch_grad/op_kernel/arch35/engram_fetch_grad_arch35.h 行 968-988):
    __aicore__ inline void EngramFetchGradArch35::RecvGradFromPeers()
    {
        if (!isReceiver_) {
            return;
        }
    
        GM_ADDR localWinBase = (GM_ADDR)ctxPtr_->commBuffer[rankId_];
        uint32_t recvIdx = aivId_ - numSendCores_;          // ← aivId_=0, numSendCores_=1 时回绕为 0xFFFFFFFF
        uint32_t totalWorkUnits = (numRanks_ - 1U) * sendersPerRank_;
        if (totalWorkUnits == 0U) {
            return;
        }
    
        // Each work unit must be owned by EXACTLY one receiver core: with numRecvCores_ >
        // totalWorkUnits, `recvIdx % totalWorkUnits` would map several cores onto the same
        // (srcRank, senderIdx) unit and race on the tokenRead counters (flow-control slots
        // get reused while the slower duplicate is still copying -> recvGrad corruption).
        for (uint32_t wIdx = recvIdx; wIdx < totalWorkUnits; wIdx += numRecvCores_) {   // ← 0xFFFFFFFF < totalWorkUnits 恒假,零迭代
            uint32_t adjustedSrcRank = wIdx / sendersPerRank_;
    
  • 假设检验证据
    • 正向证据:

      证据类型 分值 证据描述
      规范违反 +40% uint32 减法无守卫,回绕实证(回绕值用作循环起始索引,命中条例"减法回绕"模式)
      上下文防御缺失 +30% aivId_ < numSendCores_ 场景无钳位/分支防护
      PR 归属 +20% 回绕可达性由本 PR :522 fallback 0→1 直接引入
      数据流风险 +15% numSendCores_ 值域被 PR 改变
      领域关联 +10% 领域规则交叉印证(TIL-1 独立检出同一问题)
    • 负向证据:

      证据类型 分值 证据描述
      防御存在 -20% 循环条件 wIdx < totalWorkUnits 兜住内存访问,后果限于功能失效而非越界
    • 自信值 = 40+30+20+15+10-20 = 95% ≥ 70% → 判定违规

  • 修复建议:fallback 分支保持 totalBlocks_==1 时 numSendCores_=0(维持旧覆盖行为),或将接收循环起点改为饱和计算 uint32_t recvIdx = (aivId_ > numSendCores_) ? (aivId_ - numSendCores_) : 0U;;同时建议在 fallback 中保证至少有一个核的 recvIdx 落在 [0, totalWorkUnits) 内。

[cpp-secure 4.1] 外部输入校验 —— rankSize 一致性校验 release 归零

  • 问题描述:PR 将 commContext(设备侧外部数据)rankSize 一致性校验的强制力由"release 下降级为确定性挂死(RUNTIME_ABORT 的 while(true) 兜底)"改为纯 ascendc_assert。已验证本算子构建不定义 ASCENDC_DUMP,该断言在 release 产物中被预处理为空宏,外部输入 numRanks_ 在发布形态下零有效校验。若 commContext 与 tiling 的 rankSize 不一致(两个独立来源,正是代码注释 :470-471 自证"必须一致性校验,否则…越界"),Kernel 将带非法值继续执行:numRanks_=0 时在 :504 sendersPerRank_ = halfBlocks / numRanks_、:544 tokenSlotSize_ = tokenArea / numRanks_ / NUM_SLOTS 触发设备侧除零 UB;numRanks_ > tilingData->rankSize 时 displs 区(:570-572 按 numRanks_ 规划)、statusBuf/tempBuf/indicesBuf 尺寸全面偏离 Host 按 tilingData->rankSize 规划的 workspace → GM 越界写。旧代码两条路径(debug abort / release 挂死)均不会发生内存越界,本 PR 造成 release 侧安全回退。
  • 代码片段(mc2/engram_fetch_grad/op_kernel/arch35/engram_fetch_grad_arch35.h 行 464-480):
        aivId_ = GetBlockIdx();
        totalBlocks_ = GetBlockNum();
        tileBytes_ = TILE_BYTES;
    
        ctxPtr_ = (__gm__ EngramCommContext *)commContext;
        rankId_ = ctxPtr_->rankId;
        numRanks_ = ctxPtr_->rankSize;
        // commContext 为设备侧外部数据,与 Host 侧 tiling 的 rankSize(sendCounts.dim0/8)互为独立来源,
        // 必须一致性校验,否则 workspace 的 displs 区按 Host rankSize 规划而 Kernel 按 numRanks_ 写入会越界
        ascendc_assert(numRanks_ != 0U && numRanks_ <= Mc2Kernel::MAX_QP_SIZE && numRanks_ == tilingData->rankSize,
                       "invalid rankSize: commContext=%u, tiling=%u", numRanks_, tilingData->rankSize);
        channelsPerRank_ = ctxPtr_->channelsPerRank;
        if (channelsPerRank_ == 0) {
            channelsPerRank_ = 1;
        }
    
    (原代码为 if (numRanks_ == 0U || numRanks_ > MAX_QP_SIZE || numRanks_ != tilingData->rankSize) { RUNTIME_ABORT(...); },RUNTIME_ABORT 含 while(true) release 兜底)
  • 假设检验证据
    • 正向证据:

      证据类型 分值 证据描述
      规范违反 +40% 外部输入必须有效校验;release 下校验被预处理为空,且 PR 移除了唯一 release 生效的 fail-stop 路径
      PR 归属 +20% 变更在 diff 内
      数据流风险 +15% numRanks_ 直流除数 :504/:544 与 displs/status/temp/indices 尺寸 :570-572/:588/:600-613
      调用链风险 +15% 传入 uniqueScatter_/win counter 地址计算贯穿全程
    • 负向证据:

      证据类型 分值 证据描述
      防御存在 0% assert 在 release 产物中为空宏,校验有效性验证不通过,不得计分
      上游校验 0% Host :276 校验的是 tiling 来源 rankSize,与 commContext 来源非同一变量,不覆盖当前风险变量
    • 自信值 = 40+20+15+15 = 90% ≥ 70% → 判定违规

  • 修复建议:对防越界的外部输入一致性校验保留确定性失败路径,不应用纯 ascendc_assert 承载。建议改为显式分支:校验失败时置错误标志走安全退出路径(如 numRanks_ 无效时跳过通信阶段直接写零输出并 return),或恢复带确定性终止语义的兜底(保留 RUNTIME_ABORT 形态仅用于此类安全校验,与纯日志性 assert 区分)。

[cpp-secure 1.2] 内存安全 —— RUNTIME_ABORT→ascendc_assert 后可达越界写

  • 问题描述:PR 将全部 8 处 RUNTIME_ABORT(assert 后 while(true) 无条件死旋,release 下仍确定性挂死、内存安全)替换为 ascendc_assert(release 空宏,已验证)。因此 shipping/release 构建下这些校验完全消失,失败后流程带着非法状态继续执行,产生可达的越界写。最严重一处:pendingHandles_ 固定数组(MAX_PENDING_HANDLES=8,声明于 :156)越界写——发送核 handle 数 = ⌈numRanks_/halfBlocks⌉(SendGradToPeers :859 步长 numSendCores_,SendGradRemote 每次 :957 追加 1 个),当 numRanks_ > 4×totalBlocks(Host 仅校验 rankSize ≤ 1024,tiling.cpp:276,无 rankSize 与核数关系约束;aivNum=48 时 rankSize ∈ (192,1024] 即触发)时 count 可达 ~43 ≫ 8,release 下 :956 直接写越界 280+ 字节,篡改相邻成员(isSender_/gradSortedGM_/recvGradGM_ 等 GM 指针)→ 后续任意 GM 写。次严重:numRanks_ 一致性校验失效(:472)后 displs 区写入越出 Host 规划的 workspace。对比 :474-477 channelsPerRank_ 仍保留 release 生效的 if 回退——同函数内正确的防御范式反衬此处弱化。
  • 代码片段(mc2/engram_fetch_grad/op_kernel/arch35/engram_fetch_grad_arch35.h 行 944-958):
            int32_t ret = hcomm_.WriteWithNotifyNbi(handle, remoteSlotAddr, srcAddr, dataBytes, remoteCounterAddr,
                                                    static_cast<uint64_t>(localWriteCnt + 1));
            ascendc_assert(ret == 0, "WriteWithNotifyNbi failed, ret=%d, tag=ExTok_data, rankId=%u, dstRank=%u", ret,
                           rankId_, dstRank);
    
            localWriteCnt++;
            totalSent += chunkLen;
        }
        RetireCreditCounter();
    
        // 单核 remote handle 数随 numRanks_/numSendCores_ 配置增长,必须守卫固定数组边界
        ascendc_assert(pendingHandleCount_ < Mc2Kernel::MAX_PENDING_HANDLES,
                       "pendingHandles overflow: count=%u, max=%u, rankId=%u, dstRank=%u", pendingHandleCount_,
                       Mc2Kernel::MAX_PENDING_HANDLES, rankId_, dstRank);
        pendingHandles_[pendingHandleCount_] = handle;
        pendingHandleCount_++;
    
  • 代码片段(mc2/engram_fetch_grad/op_kernel/arch35/engram_fetch_grad_arch35.h 行 467-477):
        ctxPtr_ = (__gm__ EngramCommContext *)commContext;
        rankId_ = ctxPtr_->rankId;
        numRanks_ = ctxPtr_->rankSize;
        // commContext 为设备侧外部数据,与 Host 侧 tiling 的 rankSize(sendCounts.dim0/8)互为独立来源,
        // 必须一致性校验,否则 workspace 的 displs 区按 Host rankSize 规划而 Kernel 按 numRanks_ 写入会越界
        ascendc_assert(numRanks_ != 0U && numRanks_ <= Mc2Kernel::MAX_QP_SIZE && numRanks_ == tilingData->rankSize,
                       "invalid rankSize: commContext=%u, tiling=%u", numRanks_, tilingData->rankSize);
        channelsPerRank_ = ctxPtr_->channelsPerRank;
        if (channelsPerRank_ == 0) {
            channelsPerRank_ = 1;
        }
    
  • 假设检验证据
    • 正向证据:

      证据类型 分值 证据描述
      规范违反 +40% release 可达越界写,违反内存越界防护要求(宏展开与构建配置已实证)
      上下文防御缺失 +30% 唯一防线为 release 空宏
      PR 归属 +20% 8 处替换均在 diff 内
      调用链风险 +15% SendGradToPeers:859→SendGradRemote:956 完整追踪
      数据流风险 +15% numRanks_ 来自设备侧外部数据 commContext
    • 负向证据(核查后不计分):各失败路径下游均为挂死而非越界(TimeoutCheck/通信失败 4 类点 release 下为无限自旋挂死,与旧挂死等价);缓冲区收缩构造性安全(FlushAccum 借用区 castTailBytes == uniqueEntryBytes_,两侧公式一致)。

    • 自信值 = 40+30+20+15+15 = 120% ≥ 70% → 判定违规

  • 修复建议:对"不可恢复的容量/一致性校验"保留 release 生效的确定性终止(恢复轻量 RUNTIME_ABORT 的 while(true) 挂死语义,不依赖 ASCENDC_DUMP 门控的 ascendc_assert 作唯一防线);或 Host 侧补齐对应约束:Tiling 校验 rankSize 与 aivNum 的上界关系(如 ⌈numRanks_/halfBlocks⌉ ≤ MAX_PENDING_HANDLES),并在 commContext 接入处前置校验 rankSize 一致性,使 assert 仅作调试辅助。

[cpp-secure 1.3] 未定义行为 —— release 可达整数除零

  • 问题描述:与 1.2 同源的防线弱化在本条款构成可达的 ISO C++ 未定义行为:release 构建下 ascendc_assert(:472)为空宏(已验证 kernel_operator_dump_tensor_intf.h:65-68,且工程未定义 ASCENDC_DUMP),设备侧外部数据 numRanks_ = ctxPtr_->rankSize(:469,原 RUNTIME_ABORT 挂死路径已删除)未被拦截。当 commContext 中 rankSize 为 0 时,执行至 :544 tokenArea / numRanks_ / NUM_SLOTS 发生整数除零——ISO C++ [expr.mul] 明确规定的未定义行为;numRanks_ 非 0 但与 Host rankSize 不一致时,:976 (numRanks_ - 1U) * sendersPerRank_ 回绕/超大值驱动接收循环对 recvCountsGM_ + srcRank*4 越界读。变更前 while(true) 无条件挂死使 :544 在 release 下不可达,本 PR 使其可达。
  • 代码片段(mc2/engram_fetch_grad/op_kernel/arch35/engram_fetch_grad_arch35.h 行 535-548):
        uint64_t tokenAreaRaw = (winSize_ > tokenDataOffset_) ? (winSize_ - tokenDataOffset_) : 0U;
        uint64_t tokenArea = tokenAreaRaw;
        if (hiddenBytes_ > 0) {
            uint64_t hiddenBytes = static_cast<uint64_t>(hiddenBytes_);
            tokenArea = Ceil(tokenAreaRaw, hiddenBytes) * hiddenBytes;
            if (tokenArea > tokenAreaRaw) {
                tokenArea = (tokenAreaRaw / hiddenBytes) * hiddenBytes;
            }
        }
        tokenSlotSize_ = tokenArea / numRanks_ / NUM_SLOTS;
        maxTokensPerSlot_ = static_cast<uint32_t>(tokenSlotSize_ / static_cast<uint64_t>(hiddenBytes_));
        if (maxTokensPerSlot_ == 0U) {
            maxTokensPerSlot_ = 1U;
        }
    
  • 假设检验证据
    • 正向证据:

      证据类型 分值 证据描述
      规范违反 +40% 除零属 [expr.mul] 未定义行为,且除数为设备侧外部输入
      上下文防御缺失 +30% 原防线 RUNTIME_ABORT 被替换为 release 空宏,校验变量与风险变量为同一变量
      PR 归属 +20% :472-473 替换在 diff 内
      数据流风险 +15% numRanks_ 源自 __gm__ EngramCommContext 设备侧外部内存,与 Host 独立
    • 自信值 = 40+30+20+15 = 105% ≥ 70% → 判定违规

  • 修复建议:在 :544 前以 release 生效的普通条件分支保证除数非零(如 tokenSlotSize_ = (numRanks_ != 0U) ? tokenArea / numRanks_ / NUM_SLOTS : 0U; 并对 numRanks_ 非法时的后续流程短路),或将 numRanks_ 合法性校验还原为不依赖 ASCENDC_DUMP 的确定性终止,确保 release 构建下除法不可达。

[cpp-general 12.1] 断言 —— 运行期错误仅用断言校验且删除既有错误处理

  • 问题描述:本 PR 将 8 个失败点(9 处代码)的「错误处理代码(COMM_RETRY_COUNT=3 重试)/ release 有效的 RUNTIME_ABORT fail-stop」统一改为裸 ascendc_assert 直报,属「运行期错误仅用断言校验」的条款禁止模式,且语义实质性弱化:① release 下防护归零(CANN 9.0.0 头文件实证空宏,项目无 ASCENDC_DUMP/NDEBUG 定义);② 删除了真实错误处理——WriteNbiChecked(:197)/DrainChecked(:203)/WriteWithNotifyNbi(:944) 三处原含 3 次重试,本 PR 连重试一并删除,通信失败在 release 下被静默忽略 → 数据未发出/对端计数器不更新 → 对端自旋,而超时兜底 TimeoutCheck 同为空宏 → 全作业无诊断静默挂死或脏数据继续(旧代码为确定性 fail-stop);③ 内存安全守卫失效(rankSize 一致性 :472、pendingHandles :953、UB 池授权 :632)。
  • 代码片段(mc2/engram_fetch_grad/op_kernel/arch35/engram_fetch_grad_arch35.h 行 194-212):
    __aicore__ inline void EngramFetchGradArch35::WriteNbiChecked(uint64_t handle, GM_ADDR dst, GM_ADDR src, uint64_t len)
    {
        int32_t ret = hcomm_.WriteNbi(handle, dst, src, len);
        ascendc_assert(ret == 0, "WriteNbi failed, ret=%d, rankId=%u, aivId=%u", ret, rankId_, aivId_);
    }
    
    __aicore__ inline void EngramFetchGradArch35::DrainChecked(uint64_t handle)
    {
        int32_t ret = hcomm_.Drain(handle);
        ascendc_assert(ret == 0, "Drain failed, ret=%d, rankId=%u, aivId=%u", ret, rankId_, aivId_);
    }
    
    __aicore__ inline void EngramFetchGradArch35::TimeoutCheck(uint64_t startTime, TimeoutSite site)
    {
        uint64_t nowUs = static_cast<uint64_t>(AscendC::GetSystemCycle()) / ENGRAM_GRAD_CYCLES_PER_US;
        ascendc_assert((nowUs - startTime) < ENGRAM_GRAD_TIMEOUT_US,
                       "timeout, tag=%d, rankId=%u, aivId=%u, elapsed=%llu us\n", static_cast<int>(site), rankId_, aivId_,
                       nowUs - startTime);
    }
    
  • 代码片段(mc2/engram_fetch_grad/op_kernel/arch35/engram_fetch_grad_arch35.h 行 944-958,通信失败+数组越界守卫):见 [cpp-secure 1.2] 片段。
  • 假设检验证据
    • 正向证据:

      证据类型 分值 证据描述
      规范违反 +40% 9 处均为运行期可达错误(hcomm 返回码/60s 等待超时/设备侧外部数据不一致/运行态数组越界/UB 授权超限),且删除了既有错误处理代码
      上下文防御缺失 +30% CANN 9.0.0 头文件实证空宏 + 项目无 ASCENDC_DUMP/NDEBUG 定义 + release 下通信失败静默继续
      PR 归属 +20% 全部改动点在 diff 内
      调用链风险 +15% WriteNbiChecked/DrainChecked 位于 SendGradToPeers→SendGradRemote→FinishExchangeGrad 主通信链,TimeoutCheck 4 个调用点全在跨 rank 自旋循环内
      数据流风险 +15% numRanks_ 来自设备侧外部数据 commContext
      领域关联 +10% 命中「12. 断言」章节
    • 负向证据:

      证据类型 分值 证据描述
      防御存在 -20% FlushAccum 点:Host :612-621 OP_TILING_CHECK(release 生效)+ entryBuf_ 精确分配,该点降级为不可达兜底
      上游校验 -15% UB 池点:本 PR 新增两道 OP_TILING_CHECK fail-fast 覆盖 Host 可建模部分
    • 自信值 = 40+30+20+15+15+10-20-15 = 95% ≥ 70% → 判定违规(≥80% → HIGH)

  • 修复建议:① 通信失败点(:197/:203/:275/:944)恢复错误处理语义:保留至少一次重试;不可恢复时改用 release 生效的 fail-stop(显式 while(true) 自旋 + 可选 PRINTF,而非依赖 ASCENDC_DUMP 门控的调试断言);② 影响内存安全/全局一致性的校验(:472 rankSize 一致性、:953 pendingHandles 越界、:632 UB 池授权)定义项目级 ALWAYS_ACTIVE_CHECK 宏(不满足即确定性自旋);③ TimeoutCheck 若接受「超时即不可恢复」语义,应显式 fail-stop 自旋并保留 site 标记。

[cpp-general 1.1] 外部数据合法性 —— commContext 外部数据 release 无校验

  • 问题描述:PR 将 Kernel 侧设备态外部数据 ctxPtr_->rankSize(来自 __gm__ EngramCommContext,代码注释自证其为与 Host tiling "互为独立来源" 的外部输入)的唯一一致性校验由 if(...) RUNTIME_ABORT(...) 改写为裸 ascendc_assert。release/生产构建下该外部数据完全无校验直接投入使用:numRanks_ 直接参与除法 sendersPerRank_ = halfBlocks / numRanks_(:504)、tokenSlotSize_ = tokenArea / numRanks_ / NUM_SLOTS(:544)及 workspace 窗口偏移规划(:528-533)——与 Host 侧规划的 displs 区不一致时越界写,rankSize==0 时设备侧除零。旧代码在 release 下 if 条件是真实生效的判断(失败降级为挂死,数据不会被非法使用),本 PR 将 if 整体删除后,该防线在 release 下不复存在,属外部数据校验的真实回退。注意:其余被改写点(FlushAccum castTailBytes、UB 池 :632、pendingHandleCount_ :953)均有覆盖同一变量的 Host 侧上游校验(tiling.cpp:615、新增 :669/:684、:697-702),双层防护成立,不构成本条例违规;hcomm 返回码/超时检查不属于"外部数据"范畴,不在本条例边界内。
  • 代码片段(mc2/engram_fetch_grad/op_kernel/arch35/engram_fetch_grad_arch35.h 行 463-477):
        aivId_ = GetBlockIdx();
        totalBlocks_ = GetBlockNum();
        tileBytes_ = TILE_BYTES;
    
        ctxPtr_ = (__gm__ EngramCommContext *)commContext;
        rankId_ = ctxPtr_->rankId;
        numRanks_ = ctxPtr_->rankSize;
        // commContext 为设备侧外部数据,与 Host 侧 tiling 的 rankSize(sendCounts.dim0/8)互为独立来源,
        // 必须一致性校验,否则 workspace 的 displs 区按 Host rankSize 规划而 Kernel 按 numRanks_ 写入会越界
        ascendc_assert(numRanks_ != 0U && numRanks_ <= Mc2Kernel::MAX_QP_SIZE && numRanks_ == tilingData->rankSize,
                       "invalid rankSize: commContext=%u, tiling=%u", numRanks_, tilingData->rankSize);
        channelsPerRank_ = ctxPtr_->channelsPerRank;
    
  • 假设检验证据
    • 正向证据:

      证据类型 分值 证据描述
      规范违反 +40% release 下外部数据零校验(空宏实证)
      上下文防御缺失 +30% 作用域内无其余防御,:475-477 仅兜底 channelsPerRank_
      PR 归属 +20% 改写位于 diff 内
      调用链风险 +15% numRanks_ 直达 :504/:514/:528/:544 除法与窗口偏移
      数据流风险 +15% 来源为设备态 GM 外部结构,算子不可控
      领域关联 +10% MC2 跨 rank 通信上下文
    • 负向证据:

      证据类型 分值 证据描述
      上游校验 0% tiling.cpp:276 校验对象是 sendCounts 形状派生的 Host rankSize,与 commContext 来源非同一变量(:470-471 注释自证独立),不得计分
      防御存在 -20% ascendc_assert 在 ASCENDC_DUMP 调试构建下仍生效,仅保护调试路径
    • 自信值 = 40+30+20+15+15+10-20 = 110% ≥ 70% → 判定违规

  • 修复建议:对 commContext 设备态外部数据恢复与构建模式无关的显式 if 运行时判断,失败走确定性失败路径(如向状态/输出 GM 写入错误标记后短路后续通信与计算阶段,或至少恢复旧 if(...) { ... } 判定结构),不得将跨源一致性校验唯一寄托于仅调试构建生效的 ascendc_assert;其余纯内部预算自检点(有 Host 双层防护的)可保留 assert 形态。

需关注(MED 置信度)

[cpp-secure 2.3] 除零保护 SUSPICIOUS(75%,需人工确认)

  • 问题描述:Init() 中两处以 numRanks_ 为除数的除法(:504 sendersPerRank_ = halfBlocks / numRanks_;、:544 tokenSlotSize_ = tokenArea / numRanks_ / NUM_SLOTS;)的除数来自设备侧外部数据 commContext。唯一守卫是本 PR 由 RUNTIME_ABORT 改写的 ascendc_assert(:472-473)——release 编译为空宏,而旧 RUNTIME_ABORT 的 while(true) 死循环在 release 下仍生效。即:release 构建中本 PR 移除了对外部输入 numRanks_==0 的最后屏障,畸形 commContext 将直达除法产生除零 UB。按条例守卫模式表,ASSERT 类守卫仅在 moe/ 族视为有效,本算子属 mc2/,条款自身规定该场景降级为 SUSPICIOUS。其余除法均安全:alignUb/tmpAlignedCount 除数为 constexpr 32;availableForCast/(fp32RowStride*ACCUM_BUF_COPIES)(tiling.cpp:693)除数 fp32RowStride ≥ 32 由 Host :231 hiddenDim>0 上游保证;availableForCast 减法链由本 PR 新增的 minUniqueNeed 校验(:684-690)先行兜底。除法行本身未变更(暴露为存量),防线弱化由 PR 引入。
  • 代码片段(mc2/engram_fetch_grad/op_kernel/arch35/engram_fetch_grad_arch35.h 行 535-548):见 [cpp-secure 1.3] 片段;守卫点见 [cpp-secure 4.1] 片段 :472-473。
  • 假设检验证据:正向 规范违反 +40 + 上下文防御缺失 +30 + PR 归属 +20 + 数据流风险 +15 + 领域关联 +10 = 95%;负向 debug 构建 assert 生效 -20 → 75%。依条例"非 moe 族 ASSERT 守卫降级 SUSPICIOUS"强制规则,判定 SUSPICIOUS 75% ≥ 70% → 需人工确认。
  • 修复建议:对 numRanks_ 的零值/越界防御不要依赖 release 空宏——改为显式分支(if (numRanks_ == 0U || numRanks_ > MAX_QP_SIZE || ...) { 置错误状态并短路 Process; return; }),或保留一个 release 下仍编译的确定性兜底,再叠加 ascendc_assert 用于调试定位。
likedislike
Pull Request已成功合入, 合并人@CANN-robot
(感谢 luozhonglin 的贡献)
luozhonglinluozhonglin
7 天前 创建了 pull request,commit 974103c5
atomgit-bot
atomgit-bot
7 天前 评论:

变更摘要

该 PR 主要围绕 engram 反向梯度拉取流程的**超时诊断(DFX)**与确定性失败能力做增强:为 Kernel 侧四个卡死等待点引入 TimeoutSite 超时部位标记,使超时报错能直接定位等待位置;同时将自研 RUNTIME_ABORT 宏全面替换为 ascendc_assert,并移除通信原语的重试循环;Host 侧 tiling 新增 sort/unique 两个阶段的 UB 池峰值 fail-fast 校验,容量问题在算图阶段即确定性失败;另外调整了 sort 阶段 tile 到核的分发方式与收/发核划分逻辑。

主要改动

  • 超时定位打点TimeoutCheck 增加 TimeoutSite 参数,通过新增枚举(TIMEOUT_CREDIT_READ_WAITTIMEOUT_STATUS_FLAG_WAITTIMEOUT_SEND_CREDIT_WAITTIMEOUT_RECV_COUNTER_WAIT)标记 CompleteCreditCounterWaitAllStatusFlagsSendGradRemoteRecvGradFromPeers 四个等待点,超时信息输出对应 tag,便于定位卡死位置。

  • 错误处理统一为 ascendc_assert:删除 engram_fetch_grad_utils.h 中的 RUNTIME_ABORT 宏定义,将 WriteNbiCheckedDrainCheckedPrefetchCreditCounter、rankSize 一致性校验、SendGradRemoteFlushAccum 等处的 RUNTIME_ABORT/重试逻辑全部改为 ascendc_assert,同时移除 WriteNbiDrainWriteWithNotifyNbiCOMM_RETRY_COUNT 重试循环,通信失败改为一次性确定性失败。

  • UB 池 fail-fast 建模engram_fetch_grad_tiling.cpp 新增 CalcSortUbBytes(镜像 sort 各缓冲常量并随核数计算 sort 池峰值),在 SetTilingData 中新增 sort 阶段与 unique 阶段(GRAD_BUF_BYTES + uniqueEntryBytes + accumNeed + cast)两个池溢出检查,超限直接返回 GRAPH_FAILED,并相应重算 availableForCast

  • entryBuf_ 按需分配:Kernel 侧新增 UniqueEntryBytes 计算 unique 阶段 entry 缓冲实际字节数,ProcessentryBuf_ 由固定 ENTRY_BUF_BYTES 改为按 uniqueEntryBytes_ 初始化,与 Host 侧建模保持一致。

  • 核划分与 tile 分发调整InitnumRecvCores_ 改为 totalBlocks_ - numSendCores_ - 1(预留一核);ProcessHist/ProcessScatter 的 tile 分配由"按 batch 跨核均分"改为"按核连续分片"(coreStart = coreId * batchCount),使每个核处理连续的 tile 区间。

likedislike
不准确?
CANN-robotCANN-robot成员
7 天前 添加了label:stat/needs-squash
CANN-robotCANN-robot成员
7 天前 添加了label:cann-cla/yes
CANN-robot
CANN-robot成员
7 天前 评论:

Thanks for your pull-request.
The full list of commands accepted by me can be found at here.
You can get sig-info at here.
You can self-configure the PR merge rules for this repository. For more details, please refer to Here.
For more, you also can visit HICANN.


PR Approval Progress

Congratulations! All modules have met the lgtm and approve requirements.

Module Approval Details

module lgtm status approve status
mc2 wang-minbo, tgwsakiko_ (2/2) wang-minbo (1/1)

💡 Tip:

  • Committer can comment /approve or /lgtm
  • Commenting /approve implies both code review (lgtm) and intent to merge (approve)

CLA Signature Pass

luozhonglin222, thanks for your pull request. All authors of the commits have signed the CLA. 👍

likedislike
CANN-robotCANN-robot成员
7 天前 将captainmiaow,monologue815,jiang-lirui,Allan_Yu,wang-minbo,tgwsakiko_,liudan12,chenjunjian11,yangzeheng,liuboxi,luobaiqing,libohao6,mabing1118,macech设为评审人
CANN-robotCANN-robot成员
7 天前 将captainmiaow,monologue815,jiang-lirui,Allan_Yu,wang-minbo,tgwsakiko_,liudan12,chenjunjian11,yangzeheng,liuboxi,luobaiqing,libohao6,mabing1118,macech设为审查人
Huang-Peng7 天前进行代码检视3
mc2/engram_fetch_grad/op_kernel/arch35/engram_fetch_grad_unique.h
已过期
@@ -472,3 +471,1 @@
472- RUNTIME_ABORT("FlushAccum cast staging overflow: need %u bytes, entryBuf=%u bytes, hiddenDim=%u",
473- castTailBytes, Mc2Kernel::ENTRY_BUF_BYTES, static_cast<uint32_t>(hiddenDim_));
474- }
471+ ascendc_assert(castTailBytes <= Mc2Kernel::ENTRY_BUF_BYTES,
Huang-Peng7 天前评论:

此 PR 将 entryBuf_ 的分配大小从常量 Mc2Kernel::ENTRY_BUF_BYTES(64KB)改为动态 uniqueEntryBytes_,但 FlushAccum 中的 assert(第 471 行)仍以 Mc2Kernel::ENTRY_BUF_BYTES 为上限检查 castTailBytes。当 hiddenDim > 11264 时,uniqueEntryBytes_ = FLUSH_CAST_HEAD_BYTES + 2*align(hiddenDim*2, 32) > 65536,assert 会误触发(false positive),即使 entryBuf_ 实际分配空间足够。请将 assert 的上限改为 uniqueEntryBytes_(或 entryBuf_.GetSize())以匹配实际 buffer 大小。

likedislike
System
系统消息系统
7 天前 评论:

changed this line on ce8c2ddc view diff detail

luozhonglin
luozhonglin
7 天前 评论:

已解决

luozhonglinluozhonglin
7 天前 关联了issue:[Requirement|需求建议]: engram算子反向性能优化
此处折叠了6条事件消息 查看更多
CANN-robotCANN-robot成员
7 天前 添加了label:cann-cla/yes
luozhonglin
luozhonglin
7 天前 评论:

compile

likedislike
luozhonglinluozhonglin
7 天前 预合并成功(commit_id: 7c9f04dbc0461362803d40f9c073dbba895cad95)
CANN-robot
CANN-robot成员
7 天前 评论:
🚀 CI 流水线已启动
📋 执行详情: 点击查看流水线
likedislike
CANN-robotCANN-robot成员
7 天前 添加了label:ci-pipeline-running
CANN-robotCANN-robot成员
7 天前 删除了label:ci-pipeline-running
CANN-robotCANN-robot成员
7 天前 添加了label:ci-pipeline-passed
luozhonglinluozhonglin
7 天前 修改了pull request 的描述
Oblivionis
Oblivionis成员
7 天前 评论:

/lgtm

likedislike
wang-minbo成员
6 天前 评论:

/lgtm
/approve

likedislike
CANN-robotCANN-robot成员
6 天前 添加了label:lgtmapproved
CANN-robot
CANN-robot成员
6 天前 评论:

The MR can not be merged, because of CodeReview discussion not resolved

If you want to solve this problem, you can click here to do it in the FAQs.

likedislike
王亮培王亮培成员
6 天前 解决了最后一个问题
CANN-robotCANN-robot成员
6 天前 合入了pull request