tinygrad 优化器为何不能只存权重:zero_grad 置 None

作者:

在

结论:tinygrad 的优化器是训练期参数更新器,不是车端规划器或控制器;把它接进车端推理链没有意义。有两个事实最容易被忽略。第一,zero_grad() 是把每个参数的 .grad 设为 None,不是对梯度缓冲填零,下一轮 backward() 才重新生成梯度。第二,step() 的执行包含参数、动量/矩和偏置幂三组状态的同批 realize,只序列化权重而遗漏优化器状态,不能无损续训。此外 FUSE_OPTIM 与 TRAINING 默认都是 0,它们是 tinygrad 的上下文/构建配置,不是车辆安全授权。以下为固定快照的静态分析,未运行完整优化器矩阵或实车测试,本地没有模型实体。

公共生命周期

Optimizer.__init__() 拒绝负学习率,只把 is_param 张量作为可更新参数并按对象去重,非参数进入 buffers;无可更新参数会断言。device 默认取第一个参数的设备,OPTIM_DTYPE 默认 float32,学习率 dtype 至少为 float32。_new_optim_param() 为动量/矩状态创建零张量:普通模式逐参数创建,设备为 tuple 时使用 zeros_like,融合模式只创建一条展平状态,长度由 pos_params 决定。

调度与执行上,schedule_step() 要求 TRAINING=1:融合模式拼接参数与梯度、执行一次 _step() 再按原形状拆回,普通模式逐参数更新;它调用 assign() 排定状态和参数写入,返回额外状态、参数和 buffer 的实现列表,梯度缺失则通过 unwrap() 报错。step() 把 schedule_step() 返回的张量交给 Tensor.realize();_apply_update() 将更新量搬到参数设备并从 detach() 后的参数中减去。由于 schedule_step() 已有 assign 副作用,不应视为只读计划 API。

入口 实现及外部契约
Optimizer.__init__() 拒绝负学习率;仅 is_param 张量可更新并按对象去重;OPTIM_DTYPE 默认 float32。
_new_optim_param() 创建零状态张量;融合模式只建一条展平状态,长度由 pos_params 决定。
zero_grad() 把 .grad 设为 None,不是填零;下轮反传重新生成。
schedule_step() 要求 TRAINING=1;融合拼接后一次 _step() 再拆回;有 assign 副作用。
step() / _apply_update() 前者把张量交给 realize();后者在参数设备上从 detach() 参数减去更新量。
OptimizerGroup 汇集多优化器的参数/buffer/排定更新;无跨组参数去重,也未调用基类构造器。

优化器族与状态布局

优化器族大致分为 LARS 家族与 LAMB 家族。SGD() 是 LARS(tcoef=0, pre_wd=True) 的包装;LARS._step() 可按参数/梯度范数计算 trust ratio,支持两种动量与 Nesterov,权重衰减和学习率在 classic 分支的作用次序不同。Muon() 复用 LARS 更新主干,先对梯度应用 Newton-Schulz,再做学习率及后置权重衰减,并明确拒绝 fused=True。

优化器 实现要点 边界
SGD() LARS(tcoef=0, pre_wd=True) 包装 复用 LARS 主干与权重衰减次序。
LARS._step() 按参数/梯度范数算 trust ratio;两种动量与 Nesterov 权重衰减与学习率次序在 classic 分支不同。
Muon() 先 Newton-Schulz 再学习率与后置权重衰减 拒绝 fused=True;形状与数值稳定性不能由接口名保证。
Adam() 包装 LAMB(adam=True),trust ratio 固定为 1 不加权重衰减。
AdamW() 同 Adam 包装 默认衰减为 0.01。
LAMB._step() 更新幂状态与一、二阶矩,做偏置修正与 eps 分母稳定,可选算 trust ratio 梯度与状态设备不同则先搬至状态设备;更新量 shard_like(t) 在参数设备应用。

状态布局的关键是 OPTIM_DTYPE 默认 float32、FUSE_OPTIM 默认关闭;融合时会把参数和梯度展平拼接,切换融合配置或设备布局不能直接复用旧状态形状。学习率 dtype 至少为 float32。

状态机与调用顺序

典型训练调用为:loss.backward() 前先 zero_grad(),随后 loss.backward()、step()。step() 的执行包含参数、动量/矩和偏置幂的实现;仅序列化权重而遗漏优化器状态,不能无损续训。FUSE_OPTIM 默认 0、TRAINING 默认 0,这两个开关是 tinygrad 的上下文/构建配置,不是车辆安全授权。OptimizerGroup 的交叉参数重叠、状态文件版本兼容、多设备搬运以及异常发生在部分 assign() 之后的恢复语义,均应列入训练插件测试矩阵。

开关 / 状态 默认值 含义
OPTIM_DTYPE float32 优化器状态张量的默认 dtype。
FUSE_OPTIM 0(关闭) 融合时参数与梯度展平拼接为单条状态。
TRAINING 0(关闭) schedule_step() 要求其为 1;是构建/上下文配置,非安全授权。
优化器状态集合 — 参数 + 动量/矩 + 偏置幂,step() 时同批 realize。

ONNX 与车端关系

OnnxRunner._init_from_graph() 通过图中训练域节点设置 is_training,OnnxRunner.__call__() 在该标志下设置 Context(TRAINING=...)。ONNX 的 Adam() 借用 TinyAdam,但把外部 V/H 及学习率装入临时优化器,并依据步数初始化偏置幂——这属于 ONNX 训练域功能,不表明 openpilot 车端执行在线学习。

车端一侧,openpilot 的 compile_model() 在构建时把 ONNX 编译为捕获的模型工件;运行期 ModelState.run() 调用 run_warp/run_model 并解析输出。当前本地源码未见该路径实例化 tinygrad.nn.optim 或调用 step();由于缺少模型实体,只能确认可见源码的调用边界,不能断言模型图内部的全部节点。

硬件与插件重构建议

把优化器、梯度、训练域 ONNX 和权重写入归入离线训练/构建插件;车端推理插件只接受已验证、版本绑定、只读的模型工件,不要给训练插件 CAN 发送、panda 安全模式或控制消息发布权限。模型工件契约应记录 ONNX/opset、tinygrad 版本、dtype、输入输出 shape、目标设备、权重哈希和参考数值;优化器状态另行版本化,fused、参数顺序和设备分片都需一致。硬件替换只接管相机帧、时间戳、缓冲拷贝/映射和推理执行端,保持 modelV2 等输出 schema、延迟预算和健康信号稳定,不可通过更换优化器绕开感知、规划、车型协议与 panda 固件安全门。

常见问题

zero_grad() 是把梯度清零吗?

不是。它把每个参数的 .grad 设为 None,而不是对已有梯度缓冲填零;下一轮 backward() 会重新生成梯度。

只保存权重就能无损续训吗?

不能。step() 的执行包含参数、动量/矩和偏置幂三组状态的同批 realize;只序列化权重而遗漏优化器状态,无法无损续训,优化器状态需要另行版本化保存。

tinygrad 优化器与 openpilot 车端推理是什么关系?

没有直接关系。优化器是训练期参数更新器;openpilot 在构建时把 ONNX 编译为捕获的模型工件,运行期 ModelState.run() 只运行该工件,当前可见源码未见车端实例化 tinygrad.nn.optim 或调用 step()。

FUSE_OPTIM 或 TRAINING 打开会影响车辆安全吗?

不应这样理解。这两个开关默认都是 0,是 tinygrad 的上下文/构建配置,不是车辆安全授权;训练数值测试也不等于整车安全验证。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/TINYGRAD_OPTIMIZER_TRAINING_STATE_INFERENCE_BOUNDARY_DEEP_DIVE.md(取用了优化器族与状态表、zero_grad 置 None、三组状态同批 realize、ONNX 训练域与车端推理边界)。

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

更新记录

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

评论

发表回复

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