常见坑
每一章对着一个报错或一个现象:先用最小的程序把它复现出来(输出是 M2 Max、macOS 27、rustc 1.99.0 上 candle 0.11.0 的原文,CPU 和 Metal 都试过),再到源码里找它从哪一行来,最后给出修法和检查办法。机制在前面几部分讲过的,这里只链过去。有八个坑是上游的问题,会悄悄算错或在不该 panic 的地方 panic,章里写明在 0.11.0 上复现。CUDA 上的情况只读了源码,章里另外标出。
- shape mismatch in add:
+不自动广播,用broadcast_*;matmul只比批维的乘积。 - device mismatch in add:CPU 和 Metal 混用;两次
new_metal(0)是两个设备,报错里却印得一模一样。 - unsupported dtype BF16 for op matmul:CPU 上 BF16 matmul、accelerate 下 F16 matmul、Metal 上的整数和 F64、整数
exp的todo!。 - Invalid matmul arguments:Metal 和 accelerate 对步长有要求,
softmax_last_dim和KvCache要连续输入。 - unexpected rank, expected: 0, got: 2:
to_scalar/to_vec1的秩不对;取样时 logits 多了一维。 - 错误里看不出是哪一行:默认没有 backtrace,打开它的代价和
matches!失效。 - RmsNorm 之后梯度是 None:融合算子都不记反向,训练悄悄停在一半参数上。
- 设了种子结果还是不一样:CPU 的
set_seed报错;Metal 小于 2^32 的种子被忽略。 - Metal 第一次运行要好几秒:运行期编译整个
.metal文件,macOS 的库缓存不一定命中;预热把代价挪到启动时。 - Metal 上计时几乎为零:不
synchronize量到的只是编码,GPU 时间落在下一次等待上。 - 改了 copy 出来的张量,原张量也变了:Metal 上
copy()共享缓冲区,用affine(1.0, 0.0)。 - Metal 上批量 matmul 结果不对:批维步长不能合并时静默算错,
Linear遇到非连续的 4 维输入也中招。 - from_vec 元素个数不对也不报错:CPU 之后 panic,Metal 读到缓冲区外。
- CPU 上 BF16 softmax 加起来不是 1:半精度累加,先转 F32 再归约。
- inplace_op2 卡死:第二个参数和第一个共享存储,读写锁自己等自己。
- cannot find tensor:
pp前缀不对、配置少了字段,Llama::load还会直接 panic。 - cannot find tensor embeddings.word_embeddings.weight:bert-base-chinese 的
gamma/beta,报出来的是另一个名字。 - index-select invalid index 512 with dim size 512:BertModel 不截断,Metal 上干脆不报错;分词器不转小写。
- cannot broadcast [13, 45] to [1, 9, 13, 13]:清了缓存却沿用旧的
index_pos;计数器在对话中途走错时静默乱码。 - A weight is negative, too large or not a valid number:
from_sampling里温度 0,要自己换成 ArgMax。 - 读 GGUF 头部要几十毫秒:
BufReader<File>上近十万次 lseek,读进内存再解析。 - attempt to divide by zero:
general.alignment = 0让读头部 panic;截断的文件到tensor()才报错。 - 同一个 GGUF,CPU 和 Metal 生成的文字不一样:CPU 先把输入量化成 Q8_0,
CANDLE_DEQUANTIZE_ALL=1能对齐。 - 量化后 NaN 变成了 0:Q8_0 遇到 NaN、inf、极小和极大值时的行为。
按出在哪里分:
| 出在哪里 | 坑 |
|---|---|
| 张量运算的检查 | 1 形状、2 设备、3 dtype、4 连续性、5 秩、13 元素个数 |
| 错误与调试 | 6 backtrace、15 死锁 |
| 自动微分与随机数 | 7 融合算子、8 种子 |
| Metal | 9 首次编译、10 计时、11 copy、12 批量 matmul |
| 数值 | 14 BF16 softmax、23 CPU 与 Metal 量化、24 Q8_0 边界值 |
| 加载权重 | 16 VarBuilder、17 BERT 参数名、22 GGUF 对齐 |
| 推理 | 18 BERT 长度、19 index_pos、20 温度 0、21 GGUF 读取 |
flowchart TB
q{"碰到了什么"}
q -->|返回 Err| er{"原文"}
er -->|"shape 或 rank"| p15["1 或 5"]
er -->|"device mismatch"| p2["2 设备"]
er -->|"unsupported dtype"| p3["3 dtype"]
er -->|"striding 或 contiguous"| p4["4 连续性"]
er -->|"cannot find tensor"| p1617["16 或 17"]
er -->|"cannot broadcast"| p19["19 index_pos"]
q -->|panic 或卡住| pn{"哪种"}
pn -->|"divide by zero"| p22["22 GGUF 对齐"]
pn -->|"进程不动了"| p15b["15 死锁"]
pn -->|"todo 或 swap_remove"| p32["3 或 2"]
q -->|结果不对但没报错| odd{"哪种"}
odd -->|"只在 Metal 上错"| pm["11 12 13 18"]
odd -->|"梯度是 None"| p7["7 融合算子"]
odd -->|"每次不一样"| p8["8 种子"]
odd -->|"概率和不是 1"| p14["14 BF16"]
odd -->|"CPU 和 Metal 不同"| p23["23 量化"]
q -->|慢| slow{"哪里慢"}
slow -->|"第一次几秒"| p9["9 首次编译"]
slow -->|"计时不可信"| p10["10 计时"]
slow -->|"读 GGUF"| p21["21 GGUF 读取"]
💬 关联讨论
发起讨论加载中...