功能调试#
PyPTO Pro采用SPMD执行模型,同一份Kernel代码由多个逻辑AI Core并行执行,各工作单元通过pypto_pro.language.get_block_idx()的全局逻辑索引确定数据分片。block_dim配置逻辑Block数;混合Kernel中AIC/AIV的实际逻辑核数还取决于两者比例。功能调试应围绕Kernel的编译、下发、多核切分、片上计算和结果写回逐层开展。
PyPTO Pro提供pypto_pro.language.printf、pypto_pro.language.dump_data、pypto_pro.language.pto_assert和pypto_pro.language.trap等Kernel级调试接口。当Kernel包含pypto_pro.language.printf或pypto_pro.language.dump_data时,JIT自动启用设备侧调试打印。
调试流程#
建议按以下顺序定位问题:
构造可稳定复现问题的最小用例,固定输入Shape、数据类型、TilingKey、
block_dim和随机种子。判断问题发生在编译阶段、Kernel下发阶段还是执行阶段。
先使用单核和小Shape验证单个数据块的计算逻辑,再恢复多核验证数据切分。
对照可信的CPU或NPU参考实现检查最终结果,并逐步比较中间结果。
修复后恢复完整Shape、全部TilingKey、全部数据类型和生产核数进行回归。
发布前移除调试打印、数据转储、调试断言和主动中止逻辑。
定位编译问题#
被@pypto_pro.language.jit()装饰的Kernel在首次调用时触发编译。首次调用失败时,应首先检查Python异常中的源码位置和错误信息,重点确认以下内容:
Kernel参数的类型标注、Shape和数据类型是否与实际输入一致。
Tile的Shape、数据类型和目标内存空间是否满足所调用API的约束。
pypto_pro.language.load、pypto_pro.language.move和pypto_pro.language.store的源、目的及偏移是否合法。TilingData字段的名称、类型和使用方式是否一致。
TilingKey是否已通过
@pypto_pro.language.jit(tiling_key=...)绑定,启动时传入的字段是否完整且通过有效性校验。pypto_pro.language.section_vector()和pypto_pro.language.section_cube()中的操作是否属于对应的执行域。Tile地址范围、内存占用和mutex编号是否存在冲突。
JIT编译产物的基础目录默认为当前工作目录下的./build/<kernel_name>__<arch>/。每个编译实例均使用独立的TilingKey子目录:使用TilingKey时为tk_<packed>/,其中<packed>是Key的十六进制打包值;未使用TilingKey时为tk_none/。使用datatype特化时,TilingKey子目录位于dt_<hash>/下;使用静态签名特化时,基础目录名称还会包含对应的签名后缀。
可检查以下生成文件辅助定位:
位置 |
文件 |
用途 |
|---|---|---|
当前编译实例的 |
|
检查生成的Device侧Kernel、参数类型、控制流和数据搬运。 |
当前编译实例的 |
|
检查Host侧参数打包和Kernel启动代码。 |
当前编译实例的 |
|
JIT调用的Host侧共享库;文件名包含12位内容哈希。 |
当前编译实例的 |
|
检查生成的TilingData C结构体布局。 |
基础目录 |
|
启用自动流水变换时,检查变换后的Kernel实现。 |
这些文件是编译器生成的诊断产物,不应直接修改。需要修复问题时,应修改Python Kernel或其调用代码后重新编译。
定位运行问题#
Kernel启动是异步操作。Host侧调用完成表示任务已下发,Kernel执行状态通过同步接口确认。调试时在Kernel启动后调用同步接口,使设备侧错误在当前调用点暴露:
kernel[None, block_dim](*args)
torch.npu.synchronize()
出现运行异常时,可按以下方式缩小范围:
将
block_dim临时设为1,以最小启动规模判断异常是否与多执行组的数据切分、重复写或范围覆盖有关。混合Kernel中该值表示一个CV执行组,组内仍可能包含多个AIC/AIV工作单元;完整语义参见Kernel函数。将输入缩小到一个或少量Tile,减少循环次数和尾块分支。
固定TilingKey和数据类型,只保留发生问题的编译期分支。
将复杂Kernel按“GM加载、片上计算、GM写回”拆分检查。
对动态偏移、循环边界、有效Shape和尾块长度增加标量打印。
对关键Tensor或Tile增加小窗口数据转储。
最小启动规模验证通过后必须恢复生产使用的block_dim,并检查所有数据是否恰好被一个工作单元处理。block_dim=1正确不能证明多执行组切分正确。
Kernel调试接口#
打印标量信息#
使用pypto_pro.language.printf打印Core编号、循环变量、偏移、Shape和分支标志等标量信息。当Kernel包含pypto_pro.language.printf或pypto_pro.language.dump_data时,框架自动开启设备侧调试打印,无需手动设置:
import pypto_pro.language as pl
@pl.jit()
def debug_kernel(out: pl.Tensor[[16], pl.DT_INT32]):
core_id = pl.get_block_idx()
block_num = pl.get_block_num()
if core_id == 0:
pl.printf("core_id=%d, block_num=%d\n", core_id, block_num, loc=True)
pypto_pro.language.printf的格式串必须是编译时常量,仅支持受限的C printf格式。其中%d和%i用于有符号整数,%u用于无符号整数,%x用于十六进制,%p用于指针,%f仅支持FP32。打印内容通过设备侧打印机制输出,具体查看位置由运行环境的日志配置决定。
多核Kernel中的打印会由每个Core分别执行,容易产生大量交错日志。通常应暂时使用单核,或通过pypto_pro.language.get_block_idx()仅打印指定Core的信息。pypto_pro.language.printf有显著运行时开销,不得用于性能测量。
转储Tensor或Tile#
使用pypto_pro.language.dump_data打印GM Tensor或片上Tile的数据。优先指定offsets和shapes转储小窗口,避免输出量过大:
# 打印二维Tensor左上角的8 × 8窗口
pl.dump_data(out, offsets=[0, 0], shapes=[8, 8], loc=True)
# 打印当前Vector Tile
pl.dump_data(tile_out)
使用时需满足以下约束:
offsets和shapes必须同时提供或同时省略,列表长度必须等于数据维数。Tensor窗口的最内维stride必须在编译期确定为
1。Vec、Left和Right Tile转储时无需用户提供
workspace。Acc(L0C)Tile必须通过
workspace传入GM Tensor作为中转空间,workspace的数据类型应与Tile相同,Shape不得小于待转储区域。多核场景应限制转储Core和转储窗口,避免不同Core的输出相互干扰。
pypto_pro.language.dump_data的内容通过设备侧打印机制输出,具体查看位置由运行环境的日志配置决定。该接口会改变Kernel的执行开销,仅用于功能定位。
检查运行条件#
使用pypto_pro.language.pto_assert检查动态偏移、循环边界和运行时标志。条件必须是BOOL标量:
pl.pto_assert(offset < total_length, "offset=%d, total=%d", offset, total_length, loc=True)
pypto_pro.language.pto_assert在条件不满足时通过设备侧打印机制记录信息,不会中止Kernel,也不会在Host侧抛出异常。因此,不能依赖pypto_pro.language.pto_assert阻止越界访问或保证后续代码安全。需要无条件停止执行时可使用pypto_pro.language.trap(),但该接口会直接中止Kernel,仅应在明确的调试分支中临时使用。
精度问题定位#
建立可复现的精度基线#
精度比对应保证测试输入、Shape、数据类型和布局一致。随机输入应固定随机种子,并保留问题输入。可使用torch.testing.assert_close比较实际结果和参考结果:
kernel[None, block_dim](*args)
torch.npu.synchronize()
torch.testing.assert_close(
actual.cpu(),
expected.cpu(),
rtol=rtol,
atol=atol,
)
rtol和atol应根据输入数据类型、累加数据类型、计算规模和算法误差确定,不应为了使失败用例通过而任意放宽阈值。比较前还应分别检查NaN、Inf、最大绝对误差、最大相对误差及首个错误位置。
逐层比较中间结果#
最终结果不一致时,可沿数据流逐层定位:
检查每个Core计算的首个和末个GM偏移,确认多核覆盖范围。
转储GM加载后的Tile,确认Shape、stride、offset和尾块填充值。
转储关键计算步骤后的Tile,确定首次产生误差的位置。
检查写回前的有效Shape和写回偏移,排除尾块越界或结果覆盖。
对Cube计算检查左右矩阵的布局、转置方式、L0A/L0B格式和累加数据类型。
应优先转储一个Core、一个Tile和少量元素。确认当前层正确后再向下一层移动,避免一次加入大量调试代码而改变问题表现。
常见问题检查#
现象 |
重点检查项 |
|---|---|
单核正确,多核错误 |
数据分片是否遗漏或重叠, |
仅部分Shape错误 |
Tile数量的向上取整、尾块有效Shape、补齐值、动态stride和边界条件。 |
仅某个TilingKey错误 |
启动时Key字段、Key有效性约束、编译期分支以及对应产物目录。 |
结果整体错位 |
|
误差随归约长度增大 |
累加数据类型、计算顺序、类型转换和舍入方式。 |
偶发错误或结果不稳定 |
Tile复用时的同步、mutex配置、流水间依赖以及跨核写冲突。 |
Kernel无Host异常但日志有断言失败 |
|
加入打印后性能显著下降 |
|
调试完成后的检查#
问题修复后,应移除或关闭所有pypto_pro.language.printf、pypto_pro.language.dump_data、pypto_pro.language.pto_assert和pypto_pro.language.trap调用,并关闭不再需要的设备打印编译选项。随后使用生产配置重新编译,覆盖全部目标Shape、数据类型、TilingKey和block_dim进行精度与稳定性回归。