Skip to content

[Cherry-Pick] Support iteration space beyond INT32_MAX in GPU index_elementwise_put kernels(#79810) - #79811

Open
omoYang wants to merge 3 commits into
PaddlePaddle:release/3.4from
omoYang:cherry-pick-79810-release3.4
Open

omoYang wants to merge 3 commits into
PaddlePaddle:release/3.4from
omoYang:cherry-pick-79810-release3.4

Conversation

@omoYang

@omoYang omoYang commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

PR Category

Operator Mechanism

PR Types

Bug fixes

Description

dev PR: #79810

x[index] = value 这类 advanced indexing 的 setitem 在 GPU 上走 index_elementwise_put / index_elementwise_put_with_tensor 及其反向。当被索引区域的迭代空间 numel 超过 INT32_MAX 时:

  • 前向对 N = numel 做了无条件的 PADDLE_ENFORCE,与实际使用的 offset 位宽无关,即使调度端已选 64 位 offset 也会直接报 the value of N should be in [0, std::numeric_limits<int32_t>::max()]。实际触发场景是大 padding 率样本下 input_embeds[indices] = value,len(indices) * hidden_size 越界导致训练中断。
  • 反向没有这个检查,32 位实例化下 offset_calc.get() 的形参就是 uint32_t,线性下标被静默截断,写入落到错误地址。

改动内容:

  • 32 位实例化里发现 numel > INT32_MAX 时重新派发到 64 位实例化,位置在构造 offset calculator 之前(32 位的构造函数本身会对越界偏移抛错)。
  • 删掉前向的 numel 守卫,改为检查 grid 维度——线性下标放宽到 64 位后真正的硬上限是 gridDim.x,且 dim3 构造存在 int64_t 到 unsigned int 的隐式窄化。
  • 反向删掉两处冗余的 value_grad->numel() 守卫:OffsetCalculator 构造函数(signed_strides = true 下)的 CheckOffsetRange 已经逐操作数校验偏移是否装得进 offset 类型,且更精确。

本 PR 是 #79810 到 release/3.4 的等价 cherry-pick。#79731 合入后 release/3.4 的 index_elementwise 代码已与 develop 对齐(MakeOffsetCalculatorPut<3, true>、CheckOffsetRange、IsInInt32Range(..., IndexOperandByteSpan, StridedOperandByteSpan) 判据均在),因此三个 commit 无冲突原样应用,与 #79810 逐字节一致(仅 phi::SizeOf 命名空间限定符按各分支既有风格保留)。

复现

buf 取 [2, 1000000] float32(8 MiB,字节跨度远在 uint32 内)、索引取长度 rep 的 int64 张量、行 1 只出现在索引尾部时:

  • rep = 4075(迭代空间 4.075e9)正常;
  • rep = 4100(4.1e9)在未修复版本上直接 CUDA error(700) illegal memory access。

阈值低于 2**32:32 位实例化的线性下标走 IntDivider<unsigned int>::div(t = __umulhi(n, m1); return (t + n) >> shift;),其中 t + n 是 32 位加法,对最大 extent d 可用的下标上界是 2**32 * d / 2**ceil(log2 d),最坏情况接近 2**31。这也是重派发阈值取 INT32_MAX 的依据。

是否引起精度变化

否。改动只影响下标与偏移的计算位宽,以及守卫条件的作用范围,不改变任何计算逻辑。

@omoYang omoYang changed the title [Cherry-Pick] Support iteration space beyond INT32_MAX in GPU index_elementwise_put kernels (#79810) [Cherry-Pick] Support iteration space beyond INT32_MAX in GPU index_elementwise_put kernels Sep 22, 2026
@omoYang omoYang changed the title [Cherry-Pick] Support iteration space beyond INT32_MAX in GPU index_elementwise_put kernels [Cherry-Pick] Support iteration space beyond INT32_MAX in GPU index_elementwise_put kernels(#79810) Sep 22, 2026
wanghuancoder
wanghuancoder previously approved these changes Sep 24, 2026

@wanghuancoder wanghuancoder left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

The forward kernel rejected numel > INT32_MAX unconditionally, so a large
indexed region failed even on the 64-bit offset path. The grad kernel had no
check at all and silently wrapped its 32-bit linear index instead.

The offset type is chosen from the operands' byte spans, which does not bound
the iteration space: a small index tensor broadcast over a large input still
produces numel > INT32_MAX. Re-dispatch the 32-bit instantiation to the 64-bit
one in that case, and replace the numel guard with a grid-size check, which is
the real limit once the linear index is 64-bit wide. Also restrict the
value_grad numel guards in the grad kernel to the 32-bit instantiation.
…rload

GPUIndexElementwisePutWithTensorKernel was left out of the previous commit,
but it has the same exposure: the offset type is picked from the operands'
byte spans, and a broadcast `value` keeps its span small while the iteration
space can still exceed INT32_MAX, so the 32-bit instantiation would truncate
the linear index passed to OffsetCalculator::get(). Re-dispatch to the 64-bit
instantiation and add the grid-size check.
OffsetCalculator's constructor already validates every operand's byte reach
against the offset type it was instantiated with (CheckOffsetRange, called
whenever signed_strides is set), and that bound is both tighter and more
precise than comparing value_grad's element count to INT32_MAX: value_grad
here is always contiguous and input-shaped, so its byte reach is
(numel - 1) * sizeof(T).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants