Llama::load 和 forward 会 panic
主线位置:推理主线第二步「建模块」和第三步「前向」,把 candle 的 llama 放进一个长时间运行的服务里,指望所有错误都是
Err。
版本:PyTorch 2.15.0 / transformers 5.22.0(CPU)、candle 0.11.0(CPU,accelerate)
flowchart TB cfg["config"] --> ld["Llama load"] ld -->|"层数比文件多"| p1["unwrap panic"] ld -->|"flash 为 true"| ok["加载成功"] ok --> fw["forward"] fw -->|"没开 feature"| p2["unimplemented"] fw -->|"位置 8192"| e3["narrow Err"]
实验代码:PyTorch 侧 llama_py.py、kv.py,candle 侧 crate pitb-exp 的 bin p23_panics(release)。模型是本地的 SmolLM2-135M-Instruct 权重,f32。平台 M2 Max、macOS 27、rustc 1.99.0。panic 行里 cargo 缓存的路径前缀换成了 ...。
PyTorch 的写法
三种情况在 transformers 里的样子:
cfg = AutoConfig.from_pretrained(model_dir, num_hidden_layers=31)
m = AutoModelForCausalLM.from_pretrained(model_dir, config=cfg, dtype=torch.float32)
print("31 layers loaded:", len(m.model.layers))
try:
AutoModelForCausalLM.from_pretrained(model_dir, attn_implementation="flash_attention_2")
except Exception as e:
print(type(e).__name__, str(e)[:200])
(PyTorch 的输出里有一段加粗用的终端控制符,这里去掉了;MISSING 一共 9 行,中间略去。)
[transformers] LlamaForCausalLM LOAD REPORT from: ...
Key | Status |
------------------------------------------------+---------+-
model.layers.30.self_attn.o_proj.weight | MISSING |
model.layers.30.self_attn.q_proj.weight | MISSING |
...
model.layers.30.mlp.gate_proj.weight | MISSING |
Notes:
- MISSING: those params were newly initialized because missing from the checkpoint. Consider training on your downstream task.
31 layers loaded: 31
ImportError FlashAttention2 has been toggled on, but it cannot be used due to the following error: the package for FlashAttention2 doesn't seem to be installed.
位置超过 max_position_embeddings(kv.py):
far = model(ids[:, :1], position_ids=torch.tensor([[8192]])).logits
position 8192: (1, 1, 49152) finite True
- 缺层:随机初始化,打一份报告,模型照样能用(输出是错的)。
- 没装 flash-attn:
from_pretrained当场抛异常,try接得住。 - 位置 8192:照算,因为 cos / sin 每次按
position_ids现算。
没有一种会让 Python 进程退出。
搬到 candle 的现象
p23_panics 按模式改 config 或参数,再加载、前向:
let mut raw: serde_json::Value = serde_json::from_slice(&std::fs::read(format!("{MODEL}/config.json"))?)?;
if mode.starts_with("layer31") {
raw["num_hidden_layers"] = 31.into();
}
let cfg: LlamaConfig = serde_json::from_value(raw)?;
let cfg = cfg.into_config(mode == "flash");
let model = Llama::load(vb, &cfg)?;
println!("loaded");
let mut cache = Cache::new(true, DType::F32, &cfg, &dev)?;
let (ids, pos): (&[u32], usize) = if mode.starts_with("far") { (&PROMPT[..1], 8192) } else { (&PROMPT, 0) };
let l = model.forward(&Tensor::new(ids, &dev)?.unsqueeze(0)?, pos, &mut cache)?;
== layer31
thread 'main' (9129703) panicked at .../candle-transformers-0.11.0/src/models/llama.rs:524:75:
called `Result::unwrap()` on an `Err` value: cannot find tensor model.layers.30.self_attn.q_proj.weight
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
exit 101
== flash
loaded
thread 'main' (9129725) panicked at .../candle-transformers-0.11.0/src/models/llama.rs:258:5:
not implemented: compile with '--features flash-attn'
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
exit 101
== far
loaded
Error: narrow invalid args start + len > dim_len: [8192, 32], dim: 0, start: 8192, len:1
exit 1
前两种是 panic,退出码 101,main 里的 ? 接不住;在服务里,panic 时持有的 std::sync::Mutex 会被毒化。第三种是正常的 Err,但 transformers 那边根本不报错。
原因
缺层。 Llama::load 建 30 个 Block 用的是 .unwrap():
let blocks: Vec<_> = (0..cfg.num_hidden_layers)
.map(|i| Block::load(vb.pp(format!("model.layers.{i}")), cfg).unwrap())
.collect();
词嵌入、model.norm、lm_head 缺了是 Err,层里缺了是 panic。为什么这样写、在服务里怎么兜住,见源码卷的坑 cannot find tensor。
flash-attn。 into_config(use_flash_attn) 只是把一个 bool 放进 Config,加载时不看。没开 flash-attn feature 时编进来的 flash_attn 是:
#[cfg(not(feature = "flash-attn"))]
fn flash_attn(_: &Tensor, _: &Tensor, _: &Tensor, _: f32, _: bool) -> Result<Tensor> {
unimplemented!("compile with '--features flash-attn'")
}
函数签名返回 Result,函数体却是 unimplemented!。这个 feature 依赖 CUDA 的 candle-flash-attn,Mac 上本来就开不了。
位置。 Cache::new 按 max_position_embeddings(SmolLM2 是 8192)一次建好 cos / sin 表,apply_rotary_emb 用 narrow(0, index_pos, seq_len) 取行,越界就是 narrow invalid args。transformers 每次现算,没有上限。
origin/main 上 llama.rs 和 0.11.0 相同,三处都没有改。前两处我认为是上游的问题:同一个文件里别的错误都走 Result。
改法
缺层:加载前自己核对每一层的权重都在,把 panic 变成 Err:
fn check_layers(vb: &VarBuilder, cfg: &Config) -> Result<()> {
for i in 0..cfg.num_hidden_layers {
for w in ["self_attn.q_proj", "self_attn.k_proj", "self_attn.v_proj", "self_attn.o_proj",
"mlp.gate_proj", "mlp.up_proj", "mlp.down_proj", "input_layernorm", "post_attention_layernorm"] {
let name = format!("model.layers.{i}.{w}.weight");
if !vb.contains_tensor(&name) {
bail!("config has {} layers, file has no {name}", cfg.num_hidden_layers);
}
}
}
Ok(())
}
== layer31-check
Error: config has 31 layers, file has no model.layers.30.self_attn.q_proj.weight
exit 1
VarBuilder::contains_tensor 只查名字,不查形状;形状不对仍然会在 Llama::load 里 panic。要连形状一起兜住,用 catch_unwind 包住 Llama::load:
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| Llama::load(vb, &cfg)));
r.map_err(|p| anyhow::anyhow!("Llama::load panicked: {:?}", p.downcast_ref::<String>()))??
thread 'main' (9187338) panicked at .../candle-transformers-0.11.0/src/models/llama.rs:524:75:
called `Result::unwrap()` on an `Err` value: cannot find tensor model.layers.30.self_attn.q_proj.weight
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Error: Llama::load panicked: Some("called `Result::unwrap()` on an `Err` value: cannot find tensor model.layers.30.self_attn.q_proj.weight")
exit 1
panic 消息仍然打到 stderr(默认的 panic hook),但进程以 Err 退出(退出码 1),调用方能处理。不写 AssertUnwindSafe 编译不过(E0277,dyn SimpleBackend 和 Metal 设备句柄不是 RefUnwindSafe);panic 后这个 VarBuilder 不再使用,所以这样写是安全的。release 配置里写了 panic = "abort" 时 catch_unwind 接不住,只能用 contains_tensor 那种预检。
flash-attn:Mac 上一律 into_config(false);要在 CUDA 机器上打开,写成 into_config(cfg!(feature = "flash-attn")),让 feature 和参数绑在一起。
位置:在调用 forward 之前检查:
if pos + ids.len() > cfg.max_position_embeddings {
bail!("position {} past max_position_embeddings {}", pos + ids.len() - 1, cfg.max_position_embeddings);
}
== far-check
loaded
Error: position 8192 past max_position_embeddings 8192
exit 1
到了上限之后怎么办(截断上下文、滑动窗口)是应用的决定;candle 的报错其实替你画出了模型训练长度的边界,transformers 不画。
怎么检查
RUST_BACKTRACE=1跑一遍加载和一次前向,看 panic 是否出自 candle-transformers:模型文件里的unwrap、unimplemented!、todo!都会这样出现。grep -n "unwrap()\|unimplemented!" llama.rs能先看一眼。- 服务里把模型加载放在启动阶段,失败就让进程退出;请求处理里的
forward用catch_unwind或单独的任务隔离。 - 生成循环的长度上限取
min(max_new_tokens, max_position_embeddings - prompt_len)。
小结
- 层数比文件多:transformers 随机初始化并打报告;candle 在
Llama::load里unwrap,panic(llama.rs:524)。加载前用contains_tensor逐层核对。 attn_implementation="flash_attention_2"没装时 transformers 抛ImportError;candleinto_config(true)加载成功、第一次前向unimplemented!panic。Mac 上传false。- 位置到
max_position_embeddings:transformers 照算;candle 报narrow invalid args ... start: 8192。调用前自己检查。 origin/main的llama.rs三处都没变。