cannot find tensor embeddings.word_embeddings.weight
主线位置:推理主线 safetensors 那条路,
BertModel::load按名字取权重。BERT 的结构和这次加载的完整过程见 BERT,rename_f的机制见 VarBuilder。代码在 candle-transformers 的models/bert.rs。
flowchart TB load["BertModel::load"] --> a["试 embeddings.*"] a -->|失败 记下 err| b["试 bert.embeddings.*"] b -->|"失败 LayerNorm.weight"| ret["返回第一次的 err"] ret --> msg["word_embeddings 找不到"] b -->|rename_f 之后| ok["加载成功"]
实验 crate exp-candle/pb-pit,bin bertgb,本地的 bert-base-chinese(snapshot 8f23c25b),CPU,debug,M2 Max、macOS 27、rustc 1.99.0。
现象
照着英文 BERT 的例子加载:
let vb = unsafe { VarBuilder::from_mmaped_safetensors(&[format!("{DIR}/model.safetensors")], DType::F32, &Device::Cpu)? };
let _ = BertModel::load(vb, &cfg)?;
--- plain
Error: cannot find tensor embeddings.word_embeddings.weight
exit=1
文件里明明有词嵌入,名字是 bert.embeddings.word_embeddings.weight。于是自然地补上前缀 vb.pp("bert"):
--- prefixed
Error: cannot find tensor bert.embeddings.LayerNorm.weight
exit=1
这次才说到真正缺的东西。文件里 bert.embeddings 下的名字是:
['bert.embeddings.LayerNorm.beta', 'bert.embeddings.LayerNorm.gamma', 'bert.embeddings.position_embeddings.weight', 'bert.embeddings.token_type_embeddings.weight', 'bert.embeddings.word_embeddings.weight']
bert-base-chinese 是从 TensorFlow 转过来的老文件,LayerNorm 的参数叫 gamma / beta,candle 的 layer_norm 要 weight / bias。
出处
BertModel::load 为了同时支持「有 bert. 前缀」和「没有前缀」两种文件,先后试两次:
let (embeddings, encoder) = match (
BertEmbeddings::load(vb.pp("embeddings"), config),
BertEncoder::load(vb.pp("encoder"), config),
) {
(Ok(embeddings), Ok(encoder)) => (embeddings, encoder),
(Err(err), _) | (_, Err(err)) => {
if let Some(model_type) = &config.model_type {
if let (Ok(embeddings), Ok(encoder)) = (
BertEmbeddings::load(vb.pp(format!("{model_type}.embeddings")), config),
BertEncoder::load(vb.pp(format!("{model_type}.encoder")), config),
) {
(embeddings, encoder)
} else {
return Err(err);
}
bert-base-chinese 的 config.json 有 "model_type": "bert",所以第二次试的是 bert.embeddings.*。BertEmbeddings::load 依次取 word_embeddings、position_embeddings、token_type_embeddings、LayerNorm:第一次在第一个名字上就失败了;第二次前三个都找到,失败在 LayerNorm.weight。但第二次的错误被 if let 丢掉,返回的是第一次的 err,于是报的是一个其实存在(只是前缀不同)的名字。
手动加 pp("bert") 时只试一次(加了前缀再拼 bert.bert.embeddings 的第二次也失败),错误才是真实的那一个。
修法
用 rename_f 把模型要的名字翻译成文件里的名字,传根 vb(不加 pp("bert"),让 load 自己的回退去找 bert. 前缀):
fn gamma_beta(vb: VarBuilder<'static>) -> VarBuilder<'static> {
vb.rename_f(|name: &str| {
if let Some(p) = name.strip_suffix("LayerNorm.weight") {
format!("{p}LayerNorm.gamma")
} else if let Some(p) = name.strip_suffix("LayerNorm.bias") {
format!("{p}LayerNorm.beta")
} else if name == "cls.predictions.decoder.weight" {
// tied to the word embeddings
"bert.embeddings.word_embeddings.weight".to_string()
} else if name == "cls.predictions.decoder.bias" {
"cls.predictions.bias".to_string()
} else {
name.to_string()
}
})
}
let _ = BertModel::load(gamma_beta(vb()?), &cfg)?;
let model = BertForMaskedLM::load(gamma_beta(vb()?), &cfg)?;
// predict the [MASK] in "北京是中国的[MASK]都。"
BertModel::load with rename_f: ok
北京是中国的[MASK]都。 -> 首
- 前两条改 LayerNorm 的名字,
BertModel就够了。 - 后两条只有
BertForMaskedLM需要:它的输出层要cls.predictions.decoder.weight/.bias,bert-base-chinese 里输出层和词嵌入共享权重、偏置存在cls.predictions.bias(详见 BERT)。 - 句向量看不出权重有没有加载对,掩码预测可以:「首」是对的,说明 LayerNorm 和输出层都接上了。
怎么确认没中招
- 报
cannot find tensor时,不要只信报错里的名字:先列出文件里的名字(cannot find tensor 的修法),对着模型代码要的名字逐个看。 - 老的 BERT 系文件(从 TensorFlow 转换的)普遍用
gamma/beta;在名字列表里搜一下gamma就知道要不要改名。 - 加载成功后做一次能检验输出的推理(掩码预测、和 Python 版本比一个向量),不要只看「没报错」。
小结
- bert-base-chinese 的 LayerNorm 参数叫
gamma/beta,candle 要weight/bias,必须用rename_f改名。 BertModel::load先试无前缀、再试bert.前缀,失败时返回第一次的错误,所以报的是一个前缀不对、本身存在的名字。- 传根
vb加rename_f;做掩码预测时还要把输出层指到词嵌入上。