模块
PyTorch 的模型是 nn.Module 的子类:__init__ 里把层赋给属性,__setattr__ 把它们登记下来,state_dict()、.to()、eval()、hook 都靠这份运行期登记表。candle 的模型只是一个持有张量的结构体,前向是 Module trait 的一个方法;权重在构造时由 VarBuilder 按名字从文件里取,名字是调用 pp 拼出来的字符串,和字段名无关。没有登记表,也就没有 named_parameters()、model.eval() 和 forward hook。
这一部分走推理主线的「建模块」一步,用 SmolLM2-135M 的真实权重:
- 定义一个模块:手搬 SmolLM2 一个解码层的后一半,
nn.Module对结构体 +Module+VarBuilder;pp拼出的名字就是state_dict的键(Llama::load要 272 个,比state_dict少一个绑定的lm_head.weight);名字错了在构造时就报;seq()的下标要跳过激活。 - 常用层:
Linear、Embedding、LayerNorm、RmsNorm、Conv1d/2d和 PyTorch 同参数顺序、同权重布局;x.gelu()是 tanh 近似,F.gelu要写gelu_erf,用错了 BERT 的隐藏状态差 1.93e-2;hidden_act字符串能解析哪些;Dropout是ModuleT,每次传train。 - 逐层对数:forward hook 对「抄一份前向、每层存进
Vec」;30 层逐层比,绝对误差随残差流涨到 2.1e-2,相对误差不超过 4.91e-6;单层误差和累积误差;故意写错 eps、RoPE,第 0 层就超标。
flowchart TB cfg["config.json"] --> ld["load vb cfg"] st["safetensors"] --> vb["VarBuilder"] vb -->|"pp 和 get"| ld ld --> m["结构体"] m -->|"Module forward"| y["输出"] m -->|"自己存中间结果"| chk["逐层对数"]
VarBuilder 内部怎么拼名字、交给哪个后端,见 candle 卷的 VarBuilder;层的实现见 candle 卷的常用层。和 burn 的做法对照,见 burn 那一卷的模型。
💬 关联讨论
发起讨论加载中...