常见问题#
kernel函数出参未写回导致计算不生效#
问题现象描述#
当前PyPTO框架用pypto.frontend.jit装饰的kernel函数,不支持有返回值,输出需要通过参数的形式传入并使用[:]等进行写回操作,如果直接使用等号赋值,无法将数据写入输出Tensor中。
示例代码:
@pypto.frontend.jit
def add_kernel(x, y):
pypto.set_vec_tile_shapes(4, 4)
y = x + 1 # 此处会创建新的Tensor y
torch.npu.set_device(0)
x = torch.ones(4, 4, dtype=torch.float32)
y = torch.empty(4, 4, dtype=torch.float32)
add_kernel(pypto.from_torch(x), pypto.from_torch(y))
print(y) # 输出torch.empty创建的未经初始化的随机值
输出数据:
tensor([[2.0703e-19, 7.1833e+22, 1.8502e+28, 6.8608e+22],
[4.8011e+30, 1.2123e+25, 4.7418e+30, 1.8465e+25],
[1.2122e+25, 4.6114e+24, 1.7836e+31, 1.7591e+22],
[1.1306e+24, 4.2245e-39, 6.8664e-44, 0.0000e+00]])
原因分析#
在add_kernel函数内部执行y = x + 1时,这里的y是函数的局部变量(相当于创建了一个新的变量y),它会覆盖传入参数y的引用。也就是说,这行代码只是让函数内的y指向了x + 1的新Tensor,并不会修改外部传入的Tensor y的内容。
解决措施#
通过全切片操作符[:],将计算结果写入函数参数y的原有内存空间
示例代码:
@pypto.frontend.jit
def add_kernel(x, y):
pypto.set_vec_tile_shapes(4, 4)
y[:] = x + 1 # 将x+1的结果写入函数参数y的原有内存空间
torch.npu.set_device(0)
x = torch.ones(4, 4, dtype=torch.float32)
y = torch.empty(4, 4, dtype=torch.float32)
add_kernel(pypto.from_torch(x), pypto.from_torch(y))
print(y) # 输出x + 1的结果
输出数据:
tensor([[2., 2., 2., 2.],
[2., 2., 2., 2.],
[2., 2., 2., 2.],
[2., 2., 2., 2.]])
其中y[:] = x + 1也可以替换为y.move(x + 1)或者y.assemble(x + 1, [0, 0])。
未设置执行算子的设备id#
问题现象描述#
在昇腾AI处理器上执行算子时,出现失败,报错信息如下。
2025-12-17 14:31:32.491 E | fail get device id, check if set device id
2025-12-17 14:31:32.492 E | RuntimeAgent::AllocDevAddr failed for size 20448
2025-12-17 14:31:32.493 E | RuntimeAgent::AllocDevAddr failed for size 20448
2025-12-17 14:31:32.493 E | aclmdlRICaptureGetInfo failed, return[100000]
可能原因#
用户定义的算子未使用@jit进行装饰,且未使用torch_npu接口显式设置当前算子执行的Device ID。
处理步骤#
在算子执行前设置Device ID,例如:
def test_onboard():
device_id = int(os.environ.get('TILE_FWK_DEVICE_ID', 0)) #从环境变量获取期望执行的device id
torch.npu.set_device(device_id) #显式设置device id
....
CANN包不兼容#
问题现象描述#
算子上板执行时出现如下报错字样:
ErrorTracking callback in, task_id = 0, stream_id = 3.
[ERROR] Exception Type: exception invalid error
taskid: 0, streamid: 3, tid: 6495, deviceid: 0, retcode: 507018
kernelName = (null)
ErrorTracking callback in, task_id = 1, stream_id = 3.
[ERROR] Exception Type: exception invalid error
taskid: 1, streamid: 3, tid: 6495, deviceid: 0, retcode: 507018
kernelName = (null)
且device日志中出现类似如下接口为空报错:
~/ascend/log/debug/device-0/device-6495_20251222194004973.log
[ERROR] CCECPU(5670,aicpu_scheduler):2025-12-22-19:40:01.899.541 [ae_kernel_lib_aicpu_kfc.cpp:105][CallKernelApi][tid:5680][AICPU_PROCESSER] Get KFC DynTileFwkKernelServerInit api success, but func is nullptr: (null)
[ERROR] CCECPU(5670,aicpu_scheduler):2025-12-22-19:40:01.902.745 [ae_kernel_lib_aicpu_kfc.cpp:105][CallKernelApi][tid:5681][AICPU_PROCESSER] Get KFC DynTileFwkKernelServer api success, but func is nullptr: (null)
原因分析#
PyPTO驱动包支持25.2.0以上版本,CANN包支持8.5.0以上版本。
解决措施#
可以查看驱动包安装目录下的version信息,如:
/usr/local/Ascend/driver/version.info
Version=25.3.rc1
ascendhal_version=7.35.23
aicpu_version=1.0
tdt_version=1.0
log_version=1.0
prof_version=2.0
dvppkernels_version=1.1
tsfw_version=1.0
Innerversion=V100R001C23SPC002B212
compatible_version=[V100R001C19],[V100R001C20],[V100R001C21],[V100R001C22],[V100R001C23]
compatible_version_fw=[7.0.0,8.9.9]
package_version=25.3.rc1
同样可以查看CANN包安装目录下opp包内的version信息,如:
/usr/local/Ascend/ascend-toolkit/latest/opp/version.info
Version=8.5.0.2.220
version_dir=8.5.0
timestamp=20251117_000024591
required_package_amct_acl_version="8.5"
按上述方法查看驱动包和CANN包是否满足版本要求,如不满足请升级对应版本。
TileShape与Tensor维度不匹配#
问题现象描述#
算子执行时出现如下报错:
2025-12-18 10:33:06.107 E | [ExpandFunction][Function][ERROR]: FUnction[TENSOR_b_loop_Unroll1_PATH0_hiddenfunc0] ExpandFunction failed: Tile shape size 1 is not matched the output shape size 2.
2025-12-18 10:33:06.107 E | Run pass [ExpandFunction] failed.
2025-12-18 10:33:06.107 E | Run pass <ExpandFunction> failed
可能原因#
某个操作的TileShape设置的维度过小,小于该操作的输出Tensor的Shape维度,导致出现错误。
处理步骤#
根据报错提示定位到相应的循环,如下所说,问题代码出现在b_loop循环中。
FUnction[TENSOR_b_loop_Unroll1_PATH0_hiddenfunc0]
找到对应的循环后,根据日志提示的错误维度以及代码逻辑,确定代码中TileShape的维度为1,而输出Shape的维度为2,将TileShape重新设置为2维即可。
view未传入valid_shape导致精度问题#
问题现象描述#
部分场景使用view时未传入valid_shape导致精度问题
问题原因#
当view接口的输入tensor没有一个正确的validshape时,框架无法正确推导出输出的validshape。
处理步骤#
当怀疑view部分的validshape推导有问题时,首先给view传入一个validshape,观察输出结果是否符合预期。
一个典型的需要传入validshape的场景:
当输入的validShape依赖别的tensor标识,必须传入dynValidShape。如下所示场景,q0的validshape curSeq来自于另外一个tensor,无法通过推导得到
# 输入 input [B, S, H]
# 输入 act_seqs [B]
# 输出 out [B, S, H]
# 计算过程 AddS
# 代码如下:
for b_idx in pypto.loop(B, name="b_loop", idx_name="b"):
cur_seq = act_seqs[b_idx]
a0 = pypto.view(input, [1, S, H], [b_idx, 0, 0], valid_shape=[1, cur_seq, H])
a1 = a0 + 1.0
out[b_idx:, :, :] = a1
set_xxx_tile_shapes最后一维没有32字节对齐校验报错#
问题现象描述#
with pypto.function("TENSOR_SUM_FP32", [x], [res]):
for _ in pypto.loop(1, name="LOOP_L0", idx_name="a_idx"):
pypto.set_vec_tile_shapes(4, 8)
res.move(x.sum())
通过pypto.set_xxx_tile_shapes设置TileShape大小最后一维需要32字节对齐,否则会校验报错。
C++ exception with description "ASSERTION FAILED: vecTile[lastDim] % alignNum == 0
Sum op: the tileShape of last axis need to 32Byte align!, func Sum, file reduction.cpp, line 374
libtile_fwk_interface.so(npu::tile_fwk::Sum(npu::tile_fwk::Tensor const&, int, bool)+0x620) [0xffff9ff2e090]
问题原因#
硬件指令限制处理的数据需要32字节对齐。
处理步骤#
通过pypto.set_xxx_tile_shapes设置TileShape大小需要将最后一维大小设置成32字节对齐的数,即TileShape[-1] * sizeof(dtype) % 32 == 0。
算子编译报堆栈溢出错误#
问题现象描述#
算子编译时出现类似如下报错字样:
error: stack frame size (*****) exceeds limit (32768) in function '*****'
在打屏日志中呈现样例如下:
error: stack frame size (47928) exceeds limit (32768) in function 'TENSOR_nLoop_Unroll1_PATH0_6_0_4503599627370496'
error: stack frame size (47928) exceeds limit (32768) in function 'TENSOR_nLoop_Unroll1_PATH0_6_0_4503599627370496'
2 errors generated.
terminate called after throwing an instance of 'npu::tile_fwk::Error'
what(): ASSERTION FAILED: ret == 0
CompileCCE failed. errCode = 256, cce file: output/output_20251111_175724_806073/kernel_aicore/TENSOR_nLoop_Unroll1_PATH0_6_17699850674043372772_0_aic.cpp
原因分析#
由于硬件限制,昇腾AI处理器核内的标量处理单元栈空间最大为32K。因此,在算子编译时,底层的毕昇编译器会对算子核内函数的栈使用情况进行分析和校验。如果函数实现较为复杂,例如变量较多或变量生命周期较长等因素,均可能影响分析结果。如果最终分析结果超出32K限制,毕昇编译器将拦截并报告该错误。
在PTO编程模型下,算子核内函数实现复杂的主要原因主要如下:
子图规模较大,导致变量过多。
子图计算逻辑复杂,导致变量的生命周期较长。
解决措施#
针对上述可能的原因,可以采取以下措施进行处理:
调整Tensor的Tiling切分策略,采用更大的Tile块进行切分。通过增加单个Tile块的计算数据量,在总数据量保持不变的情况下,可以减少子图所需的计算步骤,从而有效减小子图的规模。相关设置接口为:pypto.set_vec_tile_shapes和pypto.set_cube_tile_shapes。
对于矩阵乘(MATMUL)计算场景,建议用户对K轴进行多核切分。
修改控制子图大小的选项”cycle_upper_bound”,将单个子图的最大规模限制在指定范围内。相关设置接口为:pypto.set_pass_options
循环中使用Python print函数打印#
问题现象描述#
@pypto.frontend.jit
def add_kernel_0(a, b, c):
for i in pypto.loop(20):
print("i = ", i)
c[:] = a + b
>>>
i = 0
@pypto.frontend.jit
def add_kernel_1(a, b, c):
for i in pypto.loop(20):
print("i = ", i)
if pypto.cond(i == 0):
c[:] = a + b
else:
c[:] = a - b
>>>
i = 0
i = 1
可能原因#
用户算子描述的是构图过程,而非实际的执行逻辑。在构图阶段,Loop执行仅用于遍历所有执行路径。示例代码add_kernel_0中仅有一条执行路径,因此Loop仅执行一次。add_kernel_1中存在if/else两条路径,因此Loop执行两次。
处理步骤#
N/A
Loop 原理及其描述#
在编译阶段会将loop编译为控制流,将loop body转换为计算流,分别对应kernel_aicpu/kernel_aicore. kernel_aicpu 负责控制流的执行,创建kernel_aicore任务,包括使用到的内存分配以及执行参数准备, 同时分析输入输出的依赖将kernel_aicore任务合并成一个更大的调度单元,然后提交给调度单元进行调度
原型介绍#
def loop(start, end, step=1, name=None, idx_name=None,
unroll_list = [1], submit_before_loop=False):
def loop_roll(start, end, step=1, name=None, idx_name=None,
unroll_list = [1], submit_before_loop=False):
描述#
start, end, step 分别可以表示循环的起始值、结束值和步长,类型可以为 SymbolicScalar 或者 int, 和Python中的range语法基本保持一致
name 表示循环的名称,默认值为loop_{id}, 对实际使用运行效果无影响,仅用于调试和生成代码中注释信息
idx_name 表示循环索引的名称,默认值为loop_idx_{id}, 对于嵌套累的Loop使用相同的idx会产生覆盖行为, 目前会在前端进行检查报错
unroll_list 主要用于循环展开,产生更大的loop body, 降低调度开销。对于unroll_list=2, loop大概会产生如下代码
new_start = start for k in unroll_list: left = (stop - start) % k for idx in loop(new_start, stop - left, k): for i in range(k): body(idx) # 需要用户一次处理step=1的步长 new_start = stop - left
loop_unroll 大概会产生如下代码
new_start = start for k in unroll_list: left = (stop - start) % k for idx in loop(new_start, stop - left, k): body(idx, k) # 需要用户一次处理k的步长 new_start = stop - left
原则上如果可以一次处理多个i, 使用loop_unroll会更高效; 如果一次只能处理1个i, 则需要使用loop
submit_before_loop 表示是否在循环开始前提交任务,默认值为False。如果设置为True,则循环前的任务会先提交到调度队列中,等待后续任务完成后再开始执行, 过多的设置submit_before_loop会增加调度开销, 建议仅在必要时设置为True
unroll_list 对
pypto.cond影响, 通常一个循环中有一个pypto.cond, 会产生两个分支,当unroll次数为4次时,会产生 2 ** 4 16个路径分支,通常上每个分支都需要单独编译, 因此会大量增加编译时间和编译出的代码量. 为了支持关键算子FA的编译优化,提供了两个特殊的函数pypto.is_loop_begin()和pypto.is_loop_end()用于优化条件分支考虑到对外层loop进行loop_unroll不能提升loop body的大小, 当前仅支持最内侧循环进行unroll
为了写代码方便,可能有时看到前端算子并没有直接表达loop,是如何产生loop和loop body的呢. 实际是在构图阶段前端会隐式的在function开始的位置插入一个loop, 循环次数为1. 循环直到下一个循环开始前结束,举例如下
@pypto.frontend.jit def foo(a, b, c): c[:] = a + b # 等价于 @pypto.frontend.jit def foo(a, b, c): for i in pypto.loop(1): c[:] = a + b @pypto.frontend.jit def foo(a, b, c): t = a + 1 for i in pypto.loop(1): c[:] = t + b # 等价于 @pypto.frontend.jit def foo(a, b, c): for i in pypto.loop(1): t = a + 1 for i in pypto.loop(1): c[:] = t + b
框架当前不会自动进行loop(1)合并,因此在实际使用中,建议用户手动合并loop(1),以提高效率