已关闭
[Bug-Report|缺陷反馈]:aclDataBuffer 声明长度小于 OM 输入需求时GE读取到有效范围外数据 #510
nevermore创建于  8月11日关闭于  28 天前
nevermore
8月11日 创建

问题描述

在模型执行场景中观察到一个输入长度处理问题:模型输入在 OM 中需要 8 字节,但执行时传入的 aclDataBuffer.length 只有 4 字节。GE 后续执行过程中仍按 OM 的 DT_INT64[1] 读取 8 字节,导致读取到 aclDataBuffer.length 之外的高 4 字节残留数据。

本问题最终在 SequenceAt 算子里表现为 index 异常大,并报 input index is out of range。这里选择 SequenceAt 作为复现模型,是因为它会在日志中打印解析后的 index,便于确认 GE 实际读取到的输入值。从用户视角看,传入的低 4 字节 index 是 0;异常值来自同一 device 地址释放后重分配留下的高 4 字节残留。

复现中观测到的异常值如下:

低 4 字节: 0x00000000
高 4 字节残留: 0x000012c0
GE 按 INT64 读取后的值: 0x000012c000000000 = 20615843020800

32 位与 64 位口径说明

这里的“4 字节输入”指调用方本次实际提供的有效数据只有 4B,等价于低 32 位 index 值为 0。GE 后续 lowering 和算子执行使用 OM Data 节点描述;在复现日志中,CalcTensorSizeFromStorage 的 data type 为 9(INT64)。

这里需要关注的是 aclDataBuffer.length=4B 的有效边界语义。GE 已经拿到了该长度,后续仍按 OM 的 DT_INT64[1] 路径读取 8B,并把第 4 到第 7 字节的 device 残留数据当成本次输入的一部分。当前复现场景保持这个口径:OM 中 index 为 INT64[1],运行时只写入并声明低 4B 有效数据。

复现流程

构造了一个复现场景,走真实 ATC -> OM -> aclmdlLoadFromFile -> aclmdlExecute 路径。本次提单暂不附全量复现代码,下面列出关键流程和关键代码片段;如需要可继续提供完整复现工程。

1. 构造最小模型

ONNX 图只保留触发 index host 化和读取的最小链路:

X(float[2,1]) -> SplitToSequence -> SequenceAt -> Y(float[1,1])
index(int64[1]) ----------------------^

其中 index 在模型中声明为 INT64[1],因此 OM 逻辑上需要至少 8 字节输入数据。X 是正常输入,shape 为 [2,1],拆分后 sequence 长度为 2,所以 index 取 0 时应是合法访问。

2. 编译并加载 OM

复现路径使用 ATC 编译 ONNX,再通过 ACL 加载并执行 OM:

atc --framework=5 --model=sequence_at.onnx --output=sequence_at --soc_version=<soc_version>
aclmdlLoadFromFile(...)
aclmdlExecute(...)

3. 构造 device 内存释放后同地址重分配场景

复现场景中先申请一块 device 内存并写入特征值,随后释放;之后重新申请 index 输入内存,并确认 driver/allocator 返回刚释放的同一 device 地址。

复现场景中按这个顺序构造:

uint64_t pattern[8] = {};
pattern[0] = 0x000012c000000000ULL;

void *poison = nullptr;
aclrtMalloc(&poison, 64, ACL_MEM_MALLOC_NORMAL_ONLY);
aclrtMemcpy(poison, 64, pattern, 64, ACL_MEMCPY_HOST_TO_DEVICE);

uintptr_t released = reinterpret_cast<uintptr_t>(poison);
aclrtFree(poison);

随后立即重新申请 64 字节 index buffer,并确认地址复用:

void *index_device = nullptr;
aclrtMalloc(&index_device, 64, ACL_MEM_MALLOC_NORMAL_ONLY);

if (reinterpret_cast<uintptr_t>(index_device) != released) {
  // 当前环境未复用刚释放的地址,停止本次复现,避免误判。
  return SETUP_FAILED;
}

本地环境中第一次重申请即复用成功:

device 地址复用成功:释放地址=<device_addr>,重新申请地址=<device_addr>,尝试次数=1

4. 本次 index 输入只写入并声明 4 字节

地址复用成功后,本次输入只写低 4 字节 index=0,不再写高 4 字节:

uint32_t index = 0;
aclrtMemcpy(index_device, sizeof(index), &index, sizeof(index), ACL_MEMCPY_HOST_TO_DEVICE);

创建 dataset buffer 时,也只向 GE 声明 4 字节有效输入:

aclDataBuffer *buffer = aclCreateDataBuffer(index_device, 4);
aclmdlAddDatasetBuffer(input_dataset, buffer);

复现中还给该输入设置了用户侧 tensor desc:

int64_t dims[] = {1};
aclTensorDesc *desc = aclCreateTensorDesc(ACL_FLOAT, 1, dims, ACL_FORMAT_ND);
aclmdlSetDatasetTensorDesc(input_dataset, desc, index_input_index);

此时输入状态如下:

OM 输入描述: DT_INT64[1],逻辑需要 8B
用户侧有效数据: 4B,低 32 位 index=0
aclDataBuffer.length: 4B
本次写入内容: 只写低 4B
高 4B 内容: 来自释放前残留 0x000012c0

5. 执行模型

执行 aclmdlExecute 后,GE 日志中可以看到下游算子读取到的 index 是 20615843020800,与释放前残留拼接后的 8 字节值一致。

实际结果

runner 关键输出:

模型输入数=2,输出数=1
device 地址复用成功:释放地址=<device_addr>,重新申请地址=<device_addr>,尝试次数=1
index 输入:物理分配=64 字节,声明长度=4 字节,本次仅写入低 4 字节 index=0
index 高 4 字节来自释放前残留:复用地址=<device_addr>,释放前前 8 字节=0x000012c000000000
aclmdlExecute ret=500002

关键日志片段:

[sequence_at_op.cc:34] SequenceAtDoComputeExtend: SequenceAt handle = 1, index = 20615843020800
[sequence_at_op.cc:92] ValidateSeqIdx: input index 20615843020800 is not valid, sequence's size 2
[sequence_at_op.cc:47] SequenceAtDoComputeExtend: input index is out of range.

对照验证

为确认异常 index 来自 aclDataBuffer.length=4B 之外的高 4 字节残留,增加了一个对照场景。对照场景保持模型、输入声明、device 地址复用、本次只写低 4 字节 index=0 等条件不变,只在释放污染块前把同一块 device 内存刷 0。

对照场景关键差异:

uint64_t zero[8] = {};
aclrtMemcpy(poison, 64, zero, 64, ACL_MEMCPY_HOST_TO_DEVICE);
aclrtFree(poison);

对照场景 runner 关键输出:

模型输入数=2,输出数=1
device 地址复用成功:释放地址=<device_addr>,重新申请地址=<device_addr>,尝试次数=1
index 输入:物理分配=64 字节,声明长度=4 字节,本次仅写入低 4 字节 index=0
free 前已把污染块刷 0:复用地址=<device_addr>,重新申请后预期前 8 字节=0x0000000000000000
aclmdlExecute ret=0

关键日志片段:

[sequence_at_op.cc:34] SequenceAtDoComputeExtend: SequenceAt handle = 1, index = 0

同一个日志文件中未出现 input index is out of range 或 input index 20615843020800 is not valid。

两组结果对比:

释放前保留污染值: GE 读取到 index=20615843020800,aclmdlExecute ret=500002
释放前刷 0:      GE 读取到 index=0,aclmdlExecute ret=0

该对照结果说明当前执行路径会受到声明长度外高 4 字节内容影响。建议 GE 仍按 aclDataBuffer.length 对输入有效长度做边界校验,避免释放后残留数据参与本次模型计算。

期望行为

请 GE 在模型执行入口、host 化拷贝前,或算子读取输入前增加输入有效长度校验。

对于本例:

OM 要求: index 为 DT_INT64[1],至少需要 8B
实际传入: aclDataBuffer.length 为 4B
期望结果: 提前返回输入长度或输入描述不匹配错误

GE 不应把 aclDataBuffer.length 之外的 device 内存内容作为本次模型输入参与计算。

初步定位线索

从现象看,下面几个位置可能需要补充或确认长度校验逻辑。

  1. api/acl/acl_model/model/model_om2.cpp

PrepareOm2Tensor 会把 aclDataBuffer 的地址和长度写入 gert::TensorData:

tensor[i].MutableTensorData().SetAddr(dataBuffer->data, nullptr);
tensor[i].MutableTensorData().SetSize(dataBuffer->length);
tensor[i].SetDataType(tensorDesc[i].GetDataType());

这里 TensorData::GetSize() 已经记录了 4B,后续流程需要继续使用这个有效长度约束数据读取。

  1. runtime/v2/kernel/common_kernel_impl/memory_copy.cc

host 化拷贝路径里如果按 OM dtype/shape 推导出 tensor_size,建议在拷贝前确认 tensor_size <= tensor_data->GetSize():

rtMemcpyEx(host_block->GetAddr(), tensor_size, tensor_data->GetAddr(), tensor_size, RT_MEMCPY_DEVICE_TO_HOST);
  1. runtime/v2/engine/aicpu/converter/sequence/sequence_at_op.cc

算子侧根据 DT_INT64 解引用 index 前,也建议确认输入数据长度至少满足 sizeof(int64_t):

case ge::DT_INT64:
  index = *(static_cast<int64_t *>(index_data->GetAddr()));
  break;
likedislike
Sspring_yb成员
8月11日 将 yelongjian 设为负责人
yelongjian成员
8月11日 评论:

感谢您的提问,针对您的问题,我们进行了初步分析:
针对仅支持INT64的OM,进行INT32的输入,会有输入未被拒绝并补齐非预期的值进行执行的问题;当前现象以模型为基准转换为INT64也属于合理现象;但是从设计层面,这个现象是否正确,我们仍需要深入分析,我们将尽快给您最新答复。

likedislike
Yyelongjian成员
8月13日 将 wuzheng-hw 设为负责人
Yyelongjian成员
8月13日 移除了负责人 yelongjian
夏夏国正成员
8月26日 关联了pull request:feat(rt2): V2执行器增加用户输入buffer大小校验
Nnevermore
28 天前 issue状态由 待办的 改变为 已完成
Nnevermore
28 天前 关闭了 issue
CANN-robotCANN-robot成员
10 天前 添加了label:resolved
Wwuzheng成员
9 天前 删除了关联的pull request:feat(rt2): V2执行器增加用户输入buffer大小校验