感谢提供完整的复现脚本和冷启动对照,尤其是正反向、直调与 torch 路径的差异,这些信息有助于缩小定位范围。
问题分析
从您报告的结果看,问题集中在 Atlas A2、CANN 9.2.0 环境下,torch_npu 默认异步路径中未经过算子预热的 FLOAT32 首次调用。日志显示失败发生在 TilingParse 阶段,而不是反向计算 kernel 执行阶段。预热后成功、纯拷贝后仍失败的对照,支持优先排查平台信息的获取时机与调用路径差异。
您贴出的 TilingPrepareForBlockAttentionResidualsGrad 片段中,GetCoreNumAiv() 返回 0 后会直接返回 GRAPH_FAILED,这能够解释日志中的解析失败。不过,核数为什么在该路径下为 0,以及是否确由平台资源表尚未就绪引起,仍需进一步确认。C++ 直调 GetWorkspaceSize 成功说明该对照路径未触发同样的失败,但还不足以排除 torch 路径中平台信息传递或初始化时序的问题,也不等同于完整执行链已经验证通过。
处理方向
一个可行的方向是调整反向算子对平台信息的使用时机:在 TilingParse 中保留必要的指针检查,将核数读取及有效性检查移到实际使用核数的 Tiling 主流程。调整时需要确认 compileInfo->coreNum 的后续用途,确保分核计算和 kernel 启动前取得有效核数,不能仅删除报错后继续使用 0。若考虑降级,也需要先明确安全的核数来源和适用条件。
报错信息可以补充失败阶段、核数读取结果和必要的平台上下文;在初始化原因尚未确认前,不宜直接将错误归因为“平台信息未初始化”。
短期内,可按您已验证的方式在调用前执行一次 add/mul 作为临时绕过;ASCEND_LAUNCH_BLOCKING=1 更适合诊断,因为它会改变异步执行行为,不建议作为长期处理方式。我们会继续确认核数获取时机及其下游依赖。本条聚焦冷启动失败,不重复展开 validBlockNum 的文档问题。


现象
aclnnBlockAttentionResidualsGrad在 torch_npu 路径下,若这次调用是进程内第一次在 NPU 上执行算子(platform info 尚未被任何算子执行链初始化),则以 FLOAT32 输入调用必失败,报:同进程第二次调用即恢复正常。
最小复现
import torch, torch_npu from cann_ops_transformer import block_attention_residuals_backward torch.npu.set_device(0) T, N, H = 64, 4, 2048 torch.manual_seed(0) # 关键:全部用 .to("npu") 纯拷贝构造,不在 NPU 上触发任何算子 pb = torch.randn(T, H).to("npu").float() br = torch.randn(T, N, H).to("npu").float() pw = torch.randn(1, H).to("npu").float() nw = torch.randn(H).to("npu").float() go = torch.randn(T, H).to("npu").float() inv = (torch.rand(T, N + 1) + 0.5).to("npu") pr = torch.softmax(torch.randn(T, N + 1), dim=1).to("npu") out = block_attention_residuals_backward(pb, br, pw, nw, go, inv, pr) # 首次调用 -> 失败 torch.npu.synchronize()证据(每个数字均为实测)
ASCEND_LAUNCH_BLOCKING=1.to("npu")/ clone 纯拷贝,不跑算子归属对照(关键):
aclnnBlockAttentionResidualsGradGetWorkspaceSizerc=0, ws=17170944)BlockAttentionResiduals(torch 路径,同冷启动态)BlockAttentionResidualsGrad(torch 路径,同冷启动态)即:aclnn 本体、平台初始化均正常;同一仓、同一条
ACLNN_CMD宏链路下,正向能过、反向不过。根因
op_host/block_attention_residuals_grad_tiling.cpp:522在 TilingParse 阶段读取并硬判核数:static ge::graphStatus TilingPrepareForBlockAttentionResidualsGrad(gert::TilingParseContext* context) { ... platform_ascendc::PlatformAscendC plat(context->GetPlatformInfo()); uint32_t aivNum = plat.GetCoreNumAiv(); compileInfo->coreNum = aivNum; OP_CHECK_IF(compileInfo->coreNum == 0, OP_LOGE(context, "coreNum is 0"), return ge::GRAPH_FAILED); ... }对比同仓两个同族算子,都把核数获取放在 Tiling 主流程、TilingParse 只做指针检查:
mhc/block_attention_residuals:TilingParse只校platformInfo != nullptr,不读核数;attention/chunk_gated_delta_rule:TilingParse只校指针;GetCoreNumAiv()在InitCompileInfo()(Tiling 主流程)里,且取不到时只打印日志不返回失败。TilingParse 是 kernel 生成前的一次性解析,此时平台资源表可能尚未就绪,
GetCoreNumAiv()返回 0;反向把它当作致命错误直接GRAPH_FAILED,而正向不依赖它故不受影响。影响
任何默认异步模式下、以 FLOAT32 输入的首次调用(真实训练/推理首个 step、单测首例、脚本首次跑)都会失败。报错落在 tiling 内部且只写
coreNum is 0,调用方难以自查。属开箱即用阻塞级问题。补充:FP16/BF16 输入通常不复现,因为
tensor.to(torch.float16)会插入 cast kernel 顺带完成预热;FLOAT32 的.to(torch.float32)是 no-op,因此暴露本问题。建议修复
chunk_gated_delta_rule:TilingParse只做指针检查,核数下沉到 Tiling 主流程获取;取不到时降级而非硬失败。coreNum == 0判为致命错误,改为在主流程回填。环境信息
cann/ops-transformermaster备注
validBlockNum文档与实现的不一致(文档称仅支持 -1、实现允许 -1 或 N)已由 #6125 覆盖,本 issue 不重复报告。