PyPTO Tune Tool 使用指南:自动化 Tile 参数搜索与 XGBoost 性能调优
【免费下载链接】pyptoPyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。项目地址: https://gitcode.com/cann/pypto
PyPTO(Parallel Tensor/Tile Operation)项目内置了一款自动化调参工具(Tune Tool),它能够把一组候选参数(如 cube/vec 的 tile 形状、pass 配置等)逐一注入你的 kernel 源码,自动运行并测量性能,最终输出按耗时排序的对比表格。本文围绕 tools/scripts/tuner/README_en.md 展开,结合 tuner.py、xgb_tuner.py 与 config.json 的源码实现,完整讲解配置编写、运行方式、输出解析,以及基于 XGBoostRegressor 的智能搜索模式,帮助你快速找到性能最优的 tiling 组合。
Tune Tool 是什么
Tune Tool 是一个"自动换参数跑性能"的脚本工具。你不需要手改代码、手动运行、手动记录耗时,只需要:
- 在 config.json 中声明要改哪个文件的哪一行、把哪个参数换成哪些候选值;
- 运行脚本;
- 得到一张按耗时(微秒)排序的结果表,以及每次运行的详细日志。
从源码看,整个工具由两个入口组成:
- tuner.py — 穷举式(exhaustive)搜索:生成所有参数组合的笛卡尔积,逐个在真实设备上运行并测量;
- xgb_tuner.py — 模型辅助搜索:用 XGBoostRegressor 预测性能,只对部分组合做真实运行,大幅减少设备占用时间。
两个脚本都通过--json_path参数接收配置文件,用法完全一致:
python tools/scripts/tuner/tuner.py --json_path tools/scripts/tuner/config.jsonpython tools/scripts/tuner/xgb_tuner.py --json_path tools/scripts/tuner/config.json典型场景:调优 matmul 的 tile 形状
官方 README 给出了最典型的用法:你在编写自己的 kernel,想知道哪种 tile 形状性能最好。
第 1 步:定位源码中设置 tile 的行
假设你的 kernel 文件为/path/to/my/file,其中第 29 行调用了pypto.set_cube_tile_shapes:
# my_kernel.py 27 #some your code 28 ... 29 pypto.set_cube_tile_shapes([tile1, tile2], [tile2, tile3], [tile4, tile5]) 30 #You can tune not only cube_tiles, vec_tiles supported too just specify it config.json 31 res = pypto.matmul(matrix_a, matrix_b, datatype) 32 ....需要说明的是,被替换的行不一定是set_cube_tile_shapes,任何一行源码都可以被替换——tile 形状只是最常见的调优对象。set_cube_tile_shapes的定义位于 python/pypto/_controller.py:它接收三个长度均为 2 的列表m、k、n(分别代表 m/k/n 维度的 tile 形状,两个元素对应 L1 与 L0 两级缓存),以及可选的enable_split_k(是否在 GM 中累加 matmul 结果,默认 False)。向量计算侧对应的pypto.set_vec_tile_shapes(*shapes)接收不定长整数参数,用于设置向量计算各维度的 tile 形状。
第 2 步:编写 config.json
修改 tools/scripts/tuner/config.json,把你的文件路径、行号、格式字符串和候选值填进去:
"files": [ { "/path/to/my/file": [ { "line": 29, "string": "pypto.set_cube_tile_shapes([{mm1[0]}, {mm1[1]}], [{mm1[2]}, {mm1[3]}], [{mm1[4]}, {mm1[5]}])", "mm1": [ [1, 1, 512, 1024, 16, 16], [1, 1, 512, 1024, 32, 32], [1, 1, 512, 1024, 64, 64] ] } ] } ]关键点:
line是源码中待替换行的行号;string是一个 Pythonstr.format()风格的格式字符串,其中的{mm1[0]}、{mm1[1]}等占位符会被替换为参数mm1对应下标的值;mm1是参数名,其值为候选列表——这里枚举了 3 组 tile 形状,每组 6 个整数分别填充string中 6 个占位符。
string的格式字符串机制在 tuner.py 的run_values_to_run_params中实现:工具用line["string"].format(**run_value)把每个候选值填入格式串,生成实际要插入的源码行。由于是标准format()语法,占位符支持下标索引(如{mm1[0]})、嵌套字典等任意 Python 格式表达式,因此可以调优不同类型、不同结构的参数。
第 3 步:在 jit 装饰器中开启 debug 模式
工具测量性能依赖运行时输出的bubble_analysis.log(详见下文"性能如何测量"),因此需要在 kernel 的pypto.frontend.jit中开启 debug 模式:
@pypto.frontend.jit(runtime_options={ debug_options={"runtime_debug_mode": 1} })jit装饰器定义于 python/pypto/frontend/parser/entry.py,支持host_options、codegen_options、pass_options、runtime_options、verify_options、debug_options等配置入口。runtime_debug_mode的取值含义见 python/pypto/config.py:0关闭;1一键开启执行期相关配置(如泳道图 swimlane graph,即 tuner 依赖的性能数据来源);2启用 AICORE_MODEL 仿真;3开启运行时依赖校验数据导出;4开启 GM 越界检查等更严格的调试。tuner 场景使用1即可。
如果不想手动改 kernel 文件,也可以把 debug 配置直接放进 config.json 作为一个"固定替换行"(不带参数,仅按行号插入),仓库自带的 config.json 就是这么做的:
{ "line": 116, "string": "debug_options={\"runtime_debug_mode\": 1}," }凡是不含调优参数(只有line和string)的配置项,工具会原样把string插入到指定行,而不会参与组合枚举——从 tuner.py 的逻辑可以看出,只有同时包含line、string和至少一个参数名的配置项才会被展开成组合。
第 4 步:运行并查看结果
python tools/scripts/tuner/tuner.py --json_path tools/scripts/tuner/config.json脚本会按组合逐个执行。从源码实现看(tuner.py),main()会先创建以 UTC 时间命名的结果目录(格式%d_%b_%H_%M_%S,如26_Mar_18_57_43),然后由ExhaustiveGenerator生成所有组合,逐一遍历。运行结束后输出目录结构如下:
measurements └── 26_Mar_18_57_43 ├── combination_0 <- result of run for one combination │ ├── combination_params.json <- combination description │ └── tiling.log <- logs of run ├── combination_1 │ ├── combination_params.json │ └── tiling.log ├── combination_2 │ ├── combination_params.json │ └── tiling.log ├── combination_3 │ ├── combination_params.json │ └── tiling.log └── config_perf.csv(README 目录树中该文件写作configs_perf.csv,实际源码 tuner.py 写入的文件名为config_perf.csv,以源码为准。)
combination_params.json记录了该组合具体使用了哪些参数值,例如:
[ { "file": "/path/to/my/file", "lines": { "207": "pypto.set_vec_tile_shapes(1, 1, 160)", "163": "pypto.set_cube_tile_shapes([1, 1], [512, 1024], [16, 16])" } }, { "name": "measurements/31_Mar_10_45_35/combination_0" } ]config_perf.csv汇总了所有组合的结果并按耗时升序排列:
| combination | amax1 | mm1 | time(us) |
|---|---|---|---|
| combination_0 | [1,1,160] | [1,1,512,1024,16,16] | 47.3 |
| combination_1 | [1,1,160] | [1,1,512,1024,16,16] | 58.3 |
| combination_2 | [1,1,256] | [32,32,128,128,256,256] | 62.4 |
| combination_3 | [1,1,256] | [32,32,128,128,256,256] | 68.8 |
注意:表中amax1、mm1两列的列名正是你在 config.json 中声明的参数名——这正是 README 强调"参数名必须在整个 config.json 内唯一"的原因,因为它们会成为结果表的列名。
源码注入与还原机制
一个值得注意的细节:脚本会直接修改你的源码文件,但在每次运行结束后把文件恢复为初始状态。这一机制由 tuner.py 的Patcher类实现:
__enter__阶段对每个待修改文件执行_make_backup,复制出<path>.backup备份;apply_changes按行号从大到小排序后逐行插入(倒序插入可避免行号错位),插入时会保留原行的缩进;若原行以冒号结尾,还会追加一个制表符,以适配字典/函数体内部的缩进层级;__exit__阶段执行_restore_backup,用备份覆盖回原文件。
例如原文件第 29 行是pypto.set_cube_tile_shapes([tile1, tile2], [tile2, tile3], [tile4, tile5]),工具会在其后插入:
29 pypto.set_cube_tile_shapes([tile1, tile2], [tile2, tile3], [tile4, tile5]) 30 pypto.set_cube_tile_shapes([1, 1], [512, 1024], [16, 16]) #inserted by tool所有组合跑完后,源码文件恢复原状,不会污染你的代码。因此请确保待修改文件所在目录可写,且不要与 tuner 并发编辑同一个文件。
config.json 完整字段说明
仓库自带的 config.json 展示了全部顶层字段:
{ "build_folder": "pypto-tune_tool", "test_name": "/path/to/my/file", "results_folder": "measurements", "save_best_k": 100, "repeats": 1, "files": [ { "/path/to/my/file": [ { "line": 116, "string": "debug_options={\"runtime_debug_mode\": 1}," }, { "line": 206, "string": "pypto.set_vec_tile_shapes({amax1[0]}, {amax1[1]}, {amax1[2]})", "amax1": [[1, 1, 160], [1, 1, 256]] }, { "line": 162, "string": "pypto.set_cube_tile_shapes([{mm1[0]}, {mm1[1]}], [{mm1[2]}, {mm1[3]}], [{mm1[4]}, {mm1[5]}])", "mm1": [[1, 1, 512, 1024, 16, 16], [32, 32, 128, 128, 256, 256]] } ] } ] }各字段含义如下:
| 字段 | 含义 |
|---|---|
test_name | 被测脚本路径。Execution._run_test(tuner.py)会以python3 {test_name}方式子进程执行它,并把标准输出/错误重定向到tiling.log;返回码非 0 视为运行失败(对应结果为Error) |
results_folder | 所有结果的存放根目录,每次运行在其下再按 UTC 时间戳创建子目录 |
save_best_k | 只保留性能最好的 k 个组合的运行文件夹。从 tuner.py 可见,当组合数超过该值时,remove_worst_combination会删除当前最差组合的目录,从而节省磁盘空间;config_perf.csv则始终保留全部结果 |
repeats | 每个组合重复运行的次数。measure_perf(tuner.py)会收集repeats次运行耗时并取中位数作为该组合的最终性能,以抵抗运行抖动;若某次运行失败则整体记为Error |
files | 需要替换的文件列表。每个文件对应一个对象{"文件路径": [行配置, ...]},支持同时修改多个文件、每个文件的多个行。每个行配置必须给出line(行号)与string(格式字符串),可选一个或多个调优参数 |
行配置中的调优参数说明:
line:要修改的源码行号。定位方法是:用文本编辑器打开 kernel 文件,找到以pypto.set_vec_tile或pypto.set_cube_tile开头的行,其行号即为所需值;string:Pythonstr.format()格式字符串,占位符对应参数名。借助格式字符串机制,可以替换任意类型的参数(整数、列表、字典、字符串等);- 参数名:为你的参数起名,它同时用于格式字符串和最终性能表的列名。每个参数给出一个候选值列表,工具会生成所有候选值的笛卡尔积组合。
重要约束:参数名必须在整个 config.json 内全局唯一。这一检查由 tuner.py 的check_json完成:它会收集所有行配置中除string、line之外的参数名,若发现重复则抛出NameError。原因是参数名会直接成为config_perf.csv的列名,重复会导致结果表字段冲突。
另外注意files中的行配置有两种形态:
- 含调优参数的行(
line+string+ 参数名)→ 参与组合枚举,每个组合插入不同值; - 仅含
line+string的行(如仓库示例中插入runtime_debug_mode的那一行)→ 作为固定注入,每个组合都会原样插入。
启发式 tile 自动生成(Heuristic Tiles)
如果不想手写候选 tile 列表,可以请脚本针对指定的 matmul 形状自动生成 tiling。只需把参数值写成一个特殊命名字符串Matmul_<datatype>_<m>_<k>_<n>:
{ "line": 384, "string": "pypto.set_cube_tile_shapes([{mm1[0]},{mm1[0]}],[{mm1[1]},{mm1[2]}], [{mm1[3]},{mm1[4]}])", "mm1": "Matmul_int8_48_1536_24576" }上述写法表示:对形状为m=48, k=1536, n=24576、数据类型为 int8 的 matmul 自动生成 tile 候选。
生成逻辑位于 tuner.py 的HeuristicTile类,分三步:
解析命名:
preproc_line_conf把Matmul_int8_48_1536_24576按_拆分为操作、数据类型、m、k、n;数据类型字节数按int(datatype 中的数字) // 8估算(int8 → 1 字节,fp16 → 2 字节,fp32 → 4 字节等);枚举候选:
generate_tiles在 2 的幂组成的 m/k/n 网格上枚举 tile(并附带 k、n 翻倍的变体),用is_good_tiling过滤掉超出片上缓存的组合。默认缓存参数为l1_size=131072(128KB)、l0a_size=65536(64KB)、l0b_size=65536(64KB)、l0c_size=524288(512KB),output_dt_bytes=4。合法条件(tuner.py):- k 大块能被 k 整除且 n 大块不小于 n;
m * n * output_dt_bytes <= L0C;n * k * input_dt_bytes <= L0B;m * k * input_dt_bytes <= L0A。
也就是说,生成的 tiling 保证不超出 L0A、L0B、L0C、L1 各级缓存容量;
打分排序取 Top-5:
get_score_for_tiling从三个维度给每个候选打分——tile 等于整个 shape(一次性搬入)加分、L0A/L0B/L0C 利用率越高越好(取三者几何平均)、m:k / k:n / m:n 的比例越接近 1 越好(不均衡会扣分)。get_best_k_tiles按得分降序去重后选出前 5 个最佳 tiling。
最终这 5 个候选会替换原来的mm1参数参与后续组合枚举,与手写候选的流程完全一致。
XGBTune:用机器学习加速搜索
当参数多、候选多时,穷举所有组合(笛卡尔积)可能非常耗时。xgb_tuner.py引入 XGBoostRegressor 回归模型:模型在真实运行过程中持续训练,用预测值代替部分真实运行,从而大幅减少设备占用。
python tools/scripts/tuner/xgb_tuner.py --json_path tools/scripts/tuner/config.json从源码看其工作方式(xgb_tuner.py):
- 依赖 requirements.txt 中的
xgboost与scikit-learn,运行前需先安装; RandomParameterMutator(xgb_tuner.py)把每个参数表示成候选列表的下标向量,每次迭代随机挑选一个参数,在候选索引上前进/后退一步,生成新组合;MeasurePerf.__call__(xgb_tuner.py)按real_rate节奏决定当前组合是真实运行还是模型预测:idx % real_rate == 0(或模型尚无训练数据)时真实运行并用结果增量训练模型(self.model.fit),否则直接self.model.predict([vector]);- 主流程(xgb_tuner.py)以
real_rate=10、真实运行上限real_perf_limit=100,在温度序列[10, 5, 1, 0.25]上执行scipy.optimize.basinhopping(盆地跳跃)全局搜索,最终打印最优耗时与对应参数值。
因此,搜索结果表中会多出一列is_real:
true:该结果是通过在设备上真实运行得到的;false:该结果由 XGBoost 模型估算得到。
示例(含is_real列的config_perf.csv):
| combination | amax1 | mm1 | is_real | time(us) |
|---|---|---|---|---|
| combination_0 | [1,1,160] | [1,1,512,1024,16,16] | true | 47.3 |
| combination_1 | [1,1,160] | [1,1,512,1024,16,16] | false | 47.3 |
| combination_2 | [1,1,256] | [32,32,128,128,256,256] | true | 62.4 |
| combination_3 | [1,1,256] | [32,32,128,128,256,256] | false | 68.8 |
重要限制:xgb_tuner.py 要求 config.json 中的参数值必须是整数向量。因为模型以整数向量为输入(组合被编码为下标向量后送入 XGBRegressor),字符串或字典形式的候选值无法参与模型推理。错误写法与正确写法对比如下:
错误的写法(字符串候选,无法用于模型):
"string": "\"cube_l1_reuse_setting\" : {l1_reuse_118}", "l1_reuse_118": [ "{-1: 2}", "{-1: 8}", "{-1: 16}" ]正确的写法(整数向量候选):
"string": "\"cube_l1_reuse_setting\" : {{ {l1_reuse_118[0]}: {l1_reuse_118[1]} }}", "l1_reuse_118": [ [-1, 2], [-1, 8], [-1, 16] ]注意这里格式字符串内出现了双层花括号{{ ... }}:外层{{/}}是str.format()的字面转义(最终输出单个{/}),内层{l1_reuse_118[0]}、{l1_reuse_118[1]}才是真正的占位符,把[-1, 2]这样的整数向量拆成键与值两个部分填入。字典{-1: 2}由此被表达为向量[-1, 2]。
实战用例:调优 L1 Reuse 参数
除了 tile 形状,pass 配置类参数同样可以调优。以下示例调优cube_l1_reuse_setting(L1 缓存复用设置,位于 pass options 中)。
第 1 步:定位 kernel 中的参数
你的 kernel 文件第 113~119 行可能形如:
# my_kernel.py 113 @pypto.frontend.jit( 114 runtime_options={"device_sched_mode": 1, 115 "stitch_function_max_num": 128, 116 pass_options={ 117 "cube_l1_reuse_setting": {-1: 2}, 118 } 119 )这里要替换的是第 117 行的"cube_l1_reuse_setting": {-1: 2},,即行号为 117(README 示例中为 119,以你的实际文件为准)。
第 2 步:编写 config.json
"/path/to/my/file": [ { "line": 119, "string": "\"cube_l1_reuse_setting\" : {{ {l1_reuse_118[0]}: {l1_reuse_118[1]} }}", "l1_reuse_118": [ [-1, 2], [-1, 8], [-1, 16] ] } ]候选值[-1, 2]、[-1, 8]、[-1, 16]分别对应{-1: 2}、{-1: 8}、{-1: 16}三种 L1 reuse 设置。该写法同时兼容tuner.py与xgb_tuner.py——若只使用穷举版tuner.py,也可以直接用字符串候选,但为了能切换到 XGBTune 加速,建议统一采用整数向量形式。
性能如何测量
config_perf.csv中的time(us)来自运行日志解析,而非简单的墙钟计时。Execution._get_execution_time(tuner.py)会:
- 在当前目录下的
output/中找出最新的子目录; - 读取其中的
bubble_analysis.log; - 用正则匹配
[AIC_x|AIV_x] Execute task num:... Core Total Work Time: ...这类逐核工作量记录,收集每个 AIC/AIV 核的Core Total Work Time; - 取所有核工作时间的最大值作为本次运行耗时。
这就是为什么必须开启runtime_debug_mode=1(泳道图/性能分析相关配置):没有它,就不会产出bubble_analysis.log,测量将无法进行。该耗时还会经过repeats次运行取中位数,最终按升序写入config_perf.csv;运行失败(返回码非 0)的组合统一记为Error,排序时被放到表尾(tuner.py)。
使用前提与注意事项
- 运行环境:需要可执行 PyPTO kernel 的环境;
xgb_tuner.py额外需要安装 requirements.txt 中的xgboost、scikit-learn,以及tuner.py自身依赖的scipy; - 源码保护:脚本通过
<文件>.backup备份并在结束后恢复原文件。运行期间请不要手动编辑被测文件;如果进程被强制终止导致备份残留,可手动用.backup文件恢复; - 参数名唯一:所有调优参数名必须全局唯一,否则
check_json会直接抛NameError; - 行号精确:
line必须在修改前核对准确(建议以编辑器实际行号为准),因为注入是按行号插入的; - XGBTune 输入约束:使用
xgb_tuner.py时参数候选必须是整数向量;is_real列用于区分真实运行与模型估算结果,对估算结果请谨慎采信; - 结果目录:每次运行都会在
results_folder下新建 UTC 时间戳目录,多次运行的结果互不覆盖;save_best_k只影响组合文件夹的保留数量,不影响config_perf.csv的完整性。
总结
PyPTO Tune Tool 提供了一条从"手工试参数"到"自动化搜索 + 模型加速"的完整调优链路:tuner.py负责穷举并真实测量所有组合(取中位数、按耗时排序输出),HeuristicTile可以针对 matmul 形状自动生成满足 L0/L1 缓存约束的候选 tile,xgb_tuner.py则在参数空间较大时用 XGBoostRegressor 预测代替部分真实运行。理解 tuner.py 的注入/备份/测量机制、config.json 的字段语义以及runtime_debug_mode的作用,你就能把这套工具套用到自己的 kernel 上,系统化地找到最优 tiling 与 pass 配置。
【免费下载链接】pyptoPyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。项目地址: https://gitcode.com/cann/pypto
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考