[Cherry-Pick][Operator Mechanism] fix Broadcast support for big tensor(#79439)#79440
Conversation
CI报告基于以下代码生成(30分钟更新一次): 1 Required任务 : 47/48 通过
2 失败详情🔴 Api-Benchmark / Performance data storage — 未知(置信度: 中)分析器: 通用分析(fallback) 失败用例: (按根因聚类合并)
关键日志:
修复建议:
关联变更: |
|
你的PR提交成功,感谢你对开源项目的贡献! |
| */ | ||
| struct BroadcastConfig { | ||
| funcs::FastDivMod<int> divmoders[DDim::kMaxRank]; | ||
| funcs::FastDivMod<int64_t> divmoders[DDim::kMaxRank]; |
There was a problem hiding this comment.
这里当前无条件使用 FastDivMod<int64_t>,会让普通小 tensor 广播也走 64 位 divmod;但本 PR 的 launcher 仍专门选择 uint32_t 快路径。源 PR #79439 最新 head 已将 BroadcastConfig 模板化为 BroadcastConfig,小 tensor 保留 32 位、仅大 tensor 用 64 位。建议同步该最新修正,避免 release 分支引入全量 broadcast 热路径的性能回退。
| static __device__ __forceinline__ void Apply(const Array &ins, | ||
| ArgsT *args, | ||
| uint32_t index_bc[][VecSize]) { | ||
| uint64_t index_bc[][VecSize]) { |
| #else | ||
| if (LoadType == kBroadcast) { | ||
| uint32_t index_bc[Arity][VecSize] = {0}; | ||
| uint64_t index_bc[Arity][VecSize] = {0}; |
| #pragma unroll | ||
| for (int k = 0; k < VecSize; ++k) { | ||
| uint32_t idx = thread_offset + k; | ||
| uint64_t idx = thread_offset + k; |
| uint64_t block_offset = | ||
| static_cast<uint64_t>(BLOCK_ID_X) * BLOCK_NUM_X * read_lens; | ||
| uint64_t stride = static_cast<uint64_t>(BLOCK_NUM_X) * GRID_NUM_X * read_lens; | ||
| for (; block_offset < static_cast<uint64_t>(main_offset); | ||
| block_offset += stride) { |
| int64_t num = | ||
| static_cast<int64_t>(numel) - static_cast<int64_t>(block_offset); |
| configs, | ||
| tail_tid, | ||
| block_offset, | ||
| static_cast<uint32_t>(num), |
There was a problem hiding this comment.
尾部 scalarize 的元素数受单 block 工作量约束,恒在 uint32 内,Impl 尾部形参也是 uint32_t,
| NumOuts, | ||
| VecSize, | ||
| kElementwise> | ||
| false> |
| const IndexT numel = static_cast<IndexT>(classifier.numel); | ||
| const int threads = 64; | ||
| const int blocks = 8; | ||
| int read_lens = classifier.configs[0].buf_len; | ||
| auto stream = dev_ctx.x_context()->xpu_stream; | ||
| const IndexT block_len = static_cast<IndexT>(read_lens) * threads; | ||
| const IndexT main_offset = (numel / block_len) * block_len; | ||
| const IndexT tail_tid = numel % block_len; |
There was a problem hiding this comment.
这里在GPU Kernel外部,可以用int64
There was a problem hiding this comment.
此处 host 侧的 IndexT 是用来选择 device 侧模板特化的标签,而不是参与大数运算。这里将决定实例化哪一个kernel模版
| struct BroadcastConfig { | ||
| funcs::FastDivMod<int> divmoders[DDim::kMaxRank]; | ||
| funcs::FastDivMod<int64_t> divmoders[DDim::kMaxRank]; |
There was a problem hiding this comment.
这个Config在Broadcast Kernel里使用。因此是GPU运算使用的代码。需要用IndexT区分
| launch_with_index_t(uint32_t{0}); | ||
| #else | ||
| const uint64_t index_limit = std::numeric_limits<uint32_t>::max(); | ||
| const uint64_t grid_stride = | ||
| static_cast<uint64_t>(gpu_config.block_per_grid.x) * | ||
| static_cast<uint64_t>(gpu_config.GetBlockSize()) * VecSize; | ||
| if (static_cast<uint64_t>(classifier.numel) <= index_limit && | ||
| grid_stride <= index_limit) { | ||
| launch_with_index_t(uint32_t{0}); | ||
| } else { | ||
| VectorizedBroadcastKernel<Functor, OutT, Arity, NumOuts, VecSize, kMixed> | ||
| <<<blocks, threads, 0, stream>>>(classifier.ins_data, | ||
| classifier.outs_data, | ||
| classifier.use_broadcast, | ||
| numel, | ||
| classifier.configs, | ||
| main_offset, | ||
| tail_tid, | ||
| VecSize, | ||
| func); | ||
| launch_with_index_t(uint64_t{0}); |
There was a problem hiding this comment.
XPU的逻辑可以不用动。
GPU的逻辑区分std::numeric_limits<uint32_t>::max()即可。
结合实际情况看看launch_with_index_t是否必要,如果必要就封装,不必要可以平铺写。
There was a problem hiding this comment.
lambda内需要根据uint32、uint64构造多个kernel变体,封装代码更为简洁。
Make the GPU BroadcastConfig a template on IndexT so normal-sized tensors use 32-bit FastDivMod again instead of always paying for 64-bit divmod. Build the typed configs at launch time (once IndexT is chosen) from the simplified dims stored on the classifier, and tighten the uint32 fast-path gate to INT_MAX for 32-bit FastDivMod ctor safety.
PaddlePaddle-bot
left a comment
There was a problem hiding this comment.
🤖 Paddle-CI-Agent | pr_review |
2026-07-15 19:17:37
📋 Review 摘要
PR 概述:修复 Broadcast 在大 tensor 场景下的 GPU/XPU 索引溢出问题。
变更范围:paddle/phi/kernels/funcs/broadcast_function.h、paddle/phi/kernels/primitive/datamover_primitives.h
影响面 Tag:Operator Mechanism
问题
| 级别 | 文件 | 概述 |
|---|---|---|
| 🟡 建议 | paddle/phi/kernels/funcs/broadcast_function.h:657 |
大 tensor 的 64 位 broadcast 路径缺少回归覆盖 |
📝 PR 规范检查
符合规范。历史标题问题已修复:当前标题已使用 [Cherry-Pick][Operator Mechanism] ...(#79439) 格式,描述包含 develop 原 PR 链接并填写了精度变化。
总体评价
实现方向与问题描述一致,索引类型和尾块 underflow 风险已有针对性处理。建议补上触发 64 位 broadcast 分支的回归测试,避免这类静默错误后续回归。
| tail_tid, | ||
| VecSize, | ||
| func); | ||
| launch_with_index_t(uint64_t{0}); |
There was a problem hiding this comment.
🟡 建议 这里新增了 uint64_t 广播索引路径,但本 PR 没有同步增加覆盖该路径的回归测试。
这条路径只有 classifier.numel 或 grid_stride 超过 INT_MAX 时才会执行,普通小 tensor 测试仍会走 uint32_t 分支,无法防止 FastDivMod<int64_t>、BroadcastConfig<uint64_t> 或尾块 grid-stride 逻辑回退。原问题是静默错误,缺测试后续很难发现。
建议修复方式:
补充一个 GPU 回归用例,显式构造会触发 launch_with_index_t(uint64_t{0}) 的 broadcast 场景并校验抽样结果;如果完整分配超过 2^31 元素成本过高,可以新增更底层的 BroadcastConfig<uint64_t> / kernel smoke test,至少覆盖大于 INT_MAX 的输出维度和 tail block。
PR Category
Operator Mechanism
PR Types
Bug fixes
Description
BroadcastConfig 中使用 FastDivMod 对输出线性索引进行维度坐标拆解。当 tensor 某一维度大小超过 INT_MAX(2^31 - 1)时,int 类型发生溢出,导致索引计算错误,广播结果产生静默错误。
修复
将 BroadcastConfig 中的 FastDivMod 替换为 FastDivMod<int64_t>,移除 PADDLE_ENFORCE_LE_INT_MAX 的限制,使广播操作支持单维度超过 2^31 的大 tensor。
devPR:#79439
是否引起精度变化
否