Rust in Everything
基于 candle-core 0.11.0

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;做掩码预测时还要把输出层指到词嵌入上。

💬 关联讨论

发起讨论
加载中...