/assign


已收到您的反馈,我们将会核实,请关注issue状态。


感谢反馈,也感谢您直接给出源码级定位、11 项回归用例和修复 PR。
结论:问题属实。这是 GE Python API(pyge)TensorDesc/Shape 维度归一化缺少 signed int64 范围校验导致的静默截断缺陷,属本仓(GE)责任田。我们在 master 225dd7adb 上逐条复核,与您在 develop ba12e592 基线上的判断一致。
先给一个便于对齐的事实:api/python/ge/ge/graph/tensor_desc.py 在这两个提交之间没有任何差异(git diff ba12e592 225dd7adb -- api/python/ge/ge/graph/tensor_desc.py 输出为空),因此下文行号在您的基线上同样适用。
核查证据
源码位置基于 main/ge(cann/ge master 225dd7adb)。
-
归一化只做类型校验,不做范围校验 ——
_normalize_dims[main/ge/api/python/ge/ge/graph/tensor_desc.py:75]if not isinstance(dims, list) or not all(isinstance(dim, int) for dim in dims): raise TypeError(f"{arg_name} must be a list of integers") # :79-80,仅类型 return list(dims) # :81,原值透传 -
三个入口共用同一条无校验路径,且都是先校验、后构造 ctypes 数组 —— 与您的描述一致:
TensorDesc.__init__:_normalize_dims[:119] →(ctypes.c_int64 * dims_num)(*dims)[:121] →GeApiWrapper_TensorDesc_Create[:122]set_shape[:195] →_set_dims[:333]:_normalize_dims[:334] →(ctypes.c_int64 * dims_num)(*normalized)[:336]set_origin_shape[:222] → 同一个_set_dims[:333]
-
截断发生在 Python 侧,C 侧无从校验 ——
GeApiWrapper_TensorDesc_Create[main/ge/api/python/ge/ge_api_c_wrapper/c_tensor_desc.cc:38] 与GeApiWrapper_TensorDesc_SetShape[同文件:59] 的形参已经是const int64_t *dims,BuildShape[同文件:21] 只判空指针后逐元素emplace_back,拿到的就是截断后的值。所以范围校验只能加在 Python 构造 ctypes 数组之前,您的修复落点判断正确。 -
Shape保留原值、描述符携带截断值,两者确实会分歧 ——Shape.__init__直接super().__init__(_normalize_dims(dims, "dims"))[:49],是纯 Python list,不经 ctypes,因此Shape([2**64+7])[0]仍是2**64+7;而desc.shape走_get_shape[:322] 从原生句柄读回,得到的是7。同一份维度在 Python 对象与原生描述符上不一致,就是您说的"描述符携带与调用方不同的实际维度"。
不依赖 NPU 的最小复核
在仓库根目录执行,只从生产文件里取出 _normalize_dims 本体,不加载 libgraph_wrapper.so、不分配任何张量(本机已实测):
python3 - <<'EOF'
import ast, ctypes
from typing import List, Optional
src = open("api/python/ge/ge/graph/tensor_desc.py").read()
fn = next(n for n in ast.parse(src).body
if isinstance(n, ast.FunctionDef) and n.name == "_normalize_dims")
ns = {"ctypes": ctypes, "List": List, "Optional": Optional}
exec(compile(ast.Module(body=[fn], type_ignores=[]), "<_normalize_dims>", "exec"), ns)
_normalize_dims = ns["_normalize_dims"]
for dims in ([2**64 + 7], [-2**63 - 1], [2**63 - 1], [-2**63], [0], [-1, -2]):
norm = _normalize_dims(dims, "shape") # 生产实现:无范围校验
arr = (ctypes.c_int64 * len(norm))(*norm) # 与 :121 / :336 同一构造
print(f"caller={str(dims):22} native={str(list(arr)):22} "
f"{'TRUNCATED' if list(arr) != norm else 'ok'}")
EOF
实测输出:
caller=[18446744073709551623] native=[7] TRUNCATED
caller=[-9223372036854775809] native=[9223372036854775807] TRUNCATED
caller=[9223372036854775807] native=[9223372036854775807] ok
caller=[-9223372036854775808] native=[-9223372036854775808] ok
caller=[0] native=[0] ok
caller=[-1, -2] native=[-1, -2] ok
第 2 行值得单独强调:-2**63-1 回绕成 2**63-1,是一个完全合法的巨大正维度,原生侧不会报错,只会在后续 shape 计算里体现,比截断成 7 更难被发现。您把"回绕为合法正数"单列一类用例是必要的。后四行也确认了您给出的预期行为边界:int64 两端边界值、0、动态维度 -1/-2 都必须继续放行。
影响范围
同类"只校验 isinstance(dim, int)、随后直接构造 c_int64 数组"的写法在 pyge 里不止 tensor_desc.py 一处,PR #5299 未覆盖的至少还有:
Tensor.__init__的 shape 校验 [main/ge/api/python/ge/ge/graph/tensor.py:56] →c_int64数组 [:119]、[:131]GeUtils.infer_shape:_normalize_input_shapes只判类型 [main/ge/api/python/ge/ge/utils/ge_utils.py:92] →flat_dims数组 [:43]TensorHolder.set_shape[main/ge/api/python/ge/ge/es/tensor_holder.py:186] → [:190]GraphBuilder建图入口的 shape [main/ge/api/python/ge/ge/es/graph_builder.py:210] → [:221],以及常量张量的 dims/values [:303]、[:351]、[:398]、[:445]、[:492]、[:520]
这些都不在本 issue 的复现路径上,是否随本次一并收敛由维护方评估,不需要您扩大 PR 范围。
另外确认了现有 UT 为何没拦住:test_shape_invalid [main/ge/tests/ge/ut/ge/graph/pyge_tests/tensor_desc_test.py:88]、test_set_shape_invalid [:146]、test_constructor_invalid [:153] 的参数集都是 "invalid" / [1, "invalid"] / [1.0, 2.0],断言的都是 TypeError,完全没有范围维度的用例。
后续
修复方向(在共享的维度归一化里加入 signed int64 范围校验,覆盖 Shape/TensorDesc 构造与两个 shape setter)我们认可,已关联 PR #5299,由维护团队按流程评审。评审时会一并关注三点:
- 异常类型取舍:您选的是
ValueError。现有实现对类型错误抛TypeError,而 Python 生态对数值溢出更常用OverflowError(struct.pack、array均如此)。两者都站得住,希望在 PR 里写明取舍理由,并保持 pyge 内部一致。 - 边界放行清单:
2**63-1、-2**63、0、-1、-2必须继续通过;Shape.get_shape_size[:51] 与is_unknown_shape[:66] 依赖-1/-2的动态维度语义,不能被新校验误伤。 - 回归用例落点:建议并入
tests/ge/ut/ge/graph/pyge_tests/tensor_desc_test.py,与既有test_shape_invalid/test_set_shape_invalid同层参数化,避免新增独立文件后与现有用例重复覆盖。
改动影响性评估、同类入口是否统一收敛、回归验证均由维护方承担。合入时间与版本由维护团队统一评估,有进展会在本 issue 更新。


问题描述与影响
TensorDesc([2**64+7]) 传给实际 C API 边界的维度为 7,Shape 仍保留原值;构造、set_shape 与 set_origin_shape 都经过同一无范围校验。
影响:描述符携带与调用方不同的实际维度,形状计算和原生推导可能错误。仅确认边界数值截断,不声称已复现设备越界。
基线
cann/ge:develop,提交ba12e592e8f84bc59709464bd3ad6ca010498d8b。任务直接从刷新后的上游建立。环境与复现
本地 Windows / Python 3.12.14;测试执行实际生产 Python 实现。实际 enum 类、ctypes 缓冲区和 Python getter/setter/Shape/TensorDesc;只在 graph C API 边界使用局部替身。未验证真实 ABI 或设备,未分配巨大张量。
触发示例/数据说明(完整构造与边界隔离见回归文件):
# 调用实际 TensorDesc([2**64 + 7]) 或 set_shape/set_origin_shape。 # 原版 ctypes.c_int64(2**64 + 7).value == 7,原生入口观察到错误维度 7。 # 预期在调用原生入口之前抛 ValueError,不需要分配巨大张量。预期行为
原生 int64 无法表示的维度在构造 ctypes 数组前抛 ValueError,设备入口不调用;int64 边界、0 和现有动态维度 -1/-2 保留。
根因与修改范围
实现
api/python/ge/ge/graph/tensor_desc.py。_normalize_dims 只检查 Python int 类型,ctypes.c_int64 接受大整数并按 64 位截断。
共享维度归一化加入有符号 64 位范围校验,覆盖 Shape/TensorDesc 的构造和两个 shape setter。
验证证据
11 项:构造、set_shape、set_origin_shape 三入口各覆盖过大/过小/回绕为合法正数的维度,Shape 入口拒绝及合法 signed-int64/动态标记控制。核对实际 ctypes 缓冲区。
同一最终回归在未修复生产实现:10 failed, 1 passed in 0.13s;应用修复:11 passed in 0.07s。依赖/收集失败没有计作缺陷复现。
本地组合验证:26 passed in 0.18s;组合对象:!5294: DataType write dispatch。未验证真实 CANN/NPU、全量 C++ UT/ST。远端完整 CI 以当前 PR 提交对应 run 的实际终态为准,本地通过不代表远端门禁通过。
等级建议、重复核查与关联
建议 L2,依据上述实际功能影响提出,最终请维护者/SIG 评定,不预先认定活动奖励等级。
核查全部作者、全部状态 Issue/PR 的标题正文,并读取涉及四个仓库的 443 个开放 PR 实际文件差异。
修复 PR:https://gitcode.com/cann/ge/pull/5299,目标
develop。AI 辅助说明
由 Codex 辅助源码核查、最小复现、修复、回归及文案整理;所有本地结果来自执行记录。未声称人工复核、真实设备或远端 CI 已通过。