tinygrad 内核生成链:优化、降级与两级编译缓存

作者:

在

结论:tinygrad 的内核生成是一条「优化 → 降级 → 线性化 → 渲染 → 编译」的多阶段重写链,而不是一次简单的源码翻译。apply_opts() 决定弱轴与访存形态,full_rewrite_to_sink() 负责展开、降级、屏障与控制流,最后才由 do_render()、do_assemble() 与后端编译器产出二进制。编译成功不等于数值一致,缓存命中也不等于本次重新做了重写诊断。本文为固定源码快照 c8fb906 的静态阅读,未运行目标设备推理。

入口与阶段顺序

道路模型的 ModelState.__init__() 加载已捕获图,分别对 _linear 调用 lower_and_compile()。该函数只收集仍为 SINK 或尚无二进制的 PROGRAM 调用,按 to_program_key() 去重;未缓存项再调用 to_program(),当存在多个任务且没有 Beam 搜索时可交给 worker pool。编译后的 PROGRAM 会被替换回调用图,后续执行与 HCQ 链仍由运行时处理。

座舱模型的 ModelState.__init__() 只装入 JIT 和 warp,并不在该构造函数里显式调用 lower_and_compile(),所以不能把道路模型的预编译时序直接推广到座舱模型。

do_to_program() 要求 SINK 携带 KernelInfo,先调用 full_rewrite_to_sink();若 renderer 走 ISA 路径,还会执行预指令选择与指令选择;最后用 pm_to_program 反复重写到带 SINK、线性指令、源码和二进制的 PROGRAM。若输入本身已是 PROGRAM,会跳过从 SINK 开始的完整优化,但仍进入后续程序重写链,所以它不是「任何输入都从头编译」。

阶段 关键函数 源码责任 对硬件移植的意义
入口 lower_and_compile() / to_program_key() 收集未完成的调用并去重,编译后替换回图 只编译仍缺二进制的调用,不是逐帧重建模型
优化 apply_opts() / Scheduler.apply_opt() 弱轴转全局轴,显式配置优先,再 Beam 或启发式 同一图在不同配置或搜索下会生成不同 launch 形态
降级 full_rewrite_to_sink() 展开、向量化、dtype 与运算分解、屏障、控制流依赖 后端不能只实现 render()
输出 do_linearize() / do_render() / do_compile() 线性化后输出源码或汇编,再交给后端编译器 CPU、Metal、QCOM、AMD 产物形态不同,不是共同 ABI

优化层:弱轴、tensor core 与启发式

apply_opts() 与 Scheduler.apply_opt() 把弱轴转为全局轴;显式传入的 opts_to_apply 优先,之后才可能进行 Beam 搜索,否则按条件调用手写启发式,并记录已应用的优化。hand_coded_optimizations() 会尝试 tensor core、图像向量化、matvec、归约分组和 upcast 等规则。

关键边界是:同一张图在配置、renderer 能力或搜索结果变化后,可以生成不同的 launch 与访存形态,因此不能只比较模型名称来判断两次编译是否等价。另外,tensor core 的支持声明与 dtype、维度门必须与目标设备一致,启发式本身不是硬件性能保证。

优化规则 作用 边界
显式 opts_to_apply 优先于自动搜索 决定最终 launch 形态,需随产物一起记录
Beam 搜索 可实际打开设备测候选 外层 ALLOW_DEVICE_USAGE=0 不构成「永不触碰硬件」的保证
hand_coded_optimizations() tensor core、图像向量化、matvec、归约分组、upcast 声明与维度门须与目标设备一致

降级与代码输出

expand_reduce() 展开归约与轴,add_gpudims() 按 renderer 最大维度把逻辑 range 映射到全局与局部索引;目标的线程维度、最大工作组和同步语义因此成为编译接口的一部分。memory_coalescing() 按操作、缓冲、基础索引、有效位和参数分组连续访存,跳过 volatile,并按 dtype 与 renderer 能力选择向量长度。

输出顺序不只取决于 AST 遍历。linearize() 与 CFGContext 对依赖图做带优先级的拓扑排序,给嵌套或相依的 range 增加执行顺序约束,调度或降级错误都可能改变载入、存储与循环语义。do_render() 输出源代码,ISA renderer 则经寄存器分配与汇编;do_compile() 与 Compiler.compile_cached() 再经后端编译器转成二进制。

函数 具体职责 硬件适配边界
expand_reduce() / add_gpudims() 展开归约与轴,映射逻辑 range 到全局/局部索引 线程维度、工作组上限、同步语义是编译接口的一部分
memory_coalescing() 分组连续访存,按能力选向量长度 对齐、掩码、越界语义需要数值与故障样例验证
linearize() / CFGContext 带优先级拓扑排序,约束嵌套 range 顺序 输出顺序不是 AST 遍历顺序
do_render() / do_assemble() / do_compile() 源码或汇编输出,再经后端编译器生成二进制 不应把一个后端的产物形态当作共同 ABI

两级缓存与配置边界

to_program_key() 包含 AST key、renderer 类型、目标以及 NOOPT、dtype 模拟、tensor core、IMAGE 等代码生成配置;进程内的 to_program_cache 是一级缓存。由于 SPEC、DEBUG 不在该 key 中,对已命中的内核切换这些诊断开关,不会重新执行 full_rewrite_to_sink() 的验证与调试打印。第二级是 Compiler.compile_cached():在后端编译器 cache key 非空时以源代码查询磁盘缓存。

两级缓存不是同一机制,新硬件验收应分别测试冷编译、内存命中、磁盘命中和配置切换。此外 full_rewrite_to_sink(optimize=False) 会跳过前段的 load、range 与调度优化,但仍执行展开、降级、分解、屏障与线性化准备,因此不能解释为「完全不编译」;do_to_program() 用 ast.tag is None 来决定该参数。

缓存/开关 键或范围 行为边界
进程内 to_program_cache AST key、renderer、目标、NOOPT、dtype 模拟、tensor core、IMAGE SPEC、DEBUG 不在键中,命中后不重跑重写诊断
磁盘 Compiler.compile_cached() 后端编译器 cache key 与源代码 与进程内缓存不是同一层
optimize=False 跳过前段优化 仍执行展开、降级、分解、屏障与线性化准备

车载硬件与插件验收

把内核生成链搬到新后端时,至少应固定模型产物哈希、tinygrad 版本、renderer 与编译器、目标架构和全部代码生成配置,并保存冷、热编译耗时及产物身份;再用同一输入张量在 CPU 基准与目标后端之间比较每层或可观测输出,覆盖掩码边界、非对齐形状、NaN/Inf、设备重连与缓存失效。内核编译或加载失败、执行超时、输出非有限值都应转换为明确的模型不可用状态,并验证 selfdrived 的控制退出。代码生成插件不应自行获得 sendcan 权限。

常见问题

tinygrad 编译成功是不是就等于数值正确?

不等于。编译只说明从 SINK 到 PROGRAM 的重写链与后端编译器走通。源码明确指出编译成功不等于数值一致,型号数据与设备时延也待实测;因此需要用同一输入在基准与目标后端之间做数值对比。

为什么改了 SPEC 或 DEBUG 开关也可能不重新编译?

因为这两个开关不在 to_program_key() 中,进程内 to_program_cache 命中后不会重新执行 full_rewrite_to_sink() 的验证与调试打印。需要区分「缓存命中」与「本次重新编译」。

optimize=False 是不是就等于完全不编译?

不是。它会跳过前段的 load、range 与调度优化,但仍执行展开、降级、分解、屏障与线性化准备。因此不能把它解释为「完全不编译」。

道路模型的预编译时序能不能直接套到座舱模型?

不能。道路模型的 ModelState.__init__() 会对捕获图调用 lower_and_compile();座舱模型的 ModelState.__init__() 只装入 JIT 和 warp,不在该构造函数里显式调用它。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/TINYGRAD_KERNEL_CODEGEN_DEEP_DIVE.md(取用了入口阶段顺序、优化与降级函数、两级缓存与配置边界、验收要求)。
  • 项目 02 —— openpilot-research/TINYGRAD_ENGINE_BACKENDS_DEEP_DIVE.md(关联的执行引擎与后端专题,用于区分内核生成与设备执行,本文未展开其实现)。

说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

更新记录

首次发布 2026-10-11 最近核对:2026-10-11

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注