A weight is negative, too large or not a valid number
主线位置:推理主线的最后一步,logits 交给
LogitsProcessor取下一个 token。各种Sampling怎么取样见 取样。代码在 candle-transformers 的generation/mod.rs。
flowchart TB cli["温度 0 来自命令行"] --> fs["from_sampling TopK t=0"] fs --> div["logits 除以 0"] div --> inf["正数变 inf"] inf --> nan["softmax 里 inf 减 inf 得 NaN"] nan --> rand["WeightedIndex::new 报错"] cli --> fix["t 不大于 0 映射成 ArgMax"]
实验 crate exp-candle/pb-pit,bin temp0,SmolLM2-135M Q8_0 GGUF 对「What is Rust?」prefill 之后的真实 logits,CPU,release,M2 Max、macOS 27、rustc 1.99.0。
现象
命令行有 --temperature 和 --top-k 两个参数,用户想要确定性的输出,传了 --temperature 0:
/// What a CLI typically does with `--temperature` and `--top-k`.
fn sampling(temperature: f64, top_k: Option<usize>, guard: bool) -> Sampling {
if guard && temperature <= 0.0 {
return Sampling::ArgMax;
}
match top_k {
None => Sampling::All { temperature },
Some(k) => Sampling::TopK { k, temperature },
}
}
let mut lp = LogitsProcessor::from_sampling(42, sampling(0.0, Some(40), false));
let next = lp.sample(&logits)?;
Error: A weight is negative, too large or not a valid number
exit=1
报错里没有「temperature」,也没有 candle 的任何字样。再看看中间发生了什么,以及温度要多小才出事(probe):
49152 logits: 48582 positive, max 31.949
logits / 0.0: max inf; softmax: 49152 NaN of 49152
All { temperature: 1e-3 }: Ok(66)
All { temperature: 1e-7 }: Ok(66)
All { temperature: 1e-30 }: Ok(66)
All { temperature: 1e-38 }: Err("A weight is negative, too large or not a valid number")
LogitsProcessor::new(42, Some(0.0), None): Ok(66)
- 除以 0 之后最大值是
inf,softmax 先减最大值,inf - inf是 NaN,整行 49152 个概率全是 NaN。 - 很小但不是 0 的温度没事,直到
31.9 / 1e-38超出 f32 的范围(约 3.4e38)又变成inf。 - 老接口
new传Some(0.0)不出错,返回 66(「R」,和贪心一样)。
出处
sample_f 对 All、TopK、TopP、TopKThenTopP 都先算:
let prs = |temperature: f64| -> Result<Vec<f32>> {
let logits = (&logits / temperature)?;
let prs = candle_nn::ops::softmax_last_dim(&logits)?;
let mut prs = prs.to_vec1()?;
f(&mut prs);
Ok(prs)
};
没有检查温度。NaN 的概率一路传到 sample_multinomial,rand 0.9 的 WeightedIndex::new 返回 InvalidWeight,Error::wrap 原样包起来,显示的就是 rand 的那句话。TopK 先排序截断再取样,排序用 total_cmp,NaN 不会让它 panic,照样走到 WeightedIndex。
温度 0 的保护只在 LogitsProcessor::new 里:
pub fn new(seed: u64, temperature: Option<f64>, top_p: Option<f64>) -> Self {
let temperature = temperature.and_then(|v| if v < 1e-7 { None } else { Some(v) });
let sampling = match temperature {
None => Sampling::ArgMax,
from_sampling 原样收下 Sampling。只有 GumbelSoftmax 在 candle-nn 的 gumbel_softmax 里自己处理了 temperature <= 0.0(直接 argmax)。而 new 造不出 TopK,要用 top-k 就只能走 from_sampling。
#848(「Use softmax-last-dim in the quantized example.」,2023-09)给 LogitsProcessor::new 加了 v < 1e-7 就当作没有温度的判断,那时 sample 只看 self.temperature: Option<f64>,None 走 argmax,所以所有入口都受保护。半年后 #1923(「Add topk sampling.」)把两个字段换成了 Sampling 枚举,新增 from_sampling(seed, sampling),new 改成先做同样的判断、再构造 Sampling 交给 from_sampling。判断留在了 new 里,from_sampling 和枚举本身都不检查,TopK 又只能从 from_sampling 进来。#1923 提交时工作区版本是 0.4.2,之后的 #2009 把版本号改成 0.5.0,所以首次发布应是 0.5.0(本地仓库没有 0.5.0 的 tag,能查到的最早包含它的 tag 是 0.5.1)。两个提交都只有标题。
修法
在构造 Sampling 的地方自己把不大于 0 的温度映射成 ArgMax(上面 sampling 函数的 guard):
for (t, k) in [(0.0, Some(40)), (0.0, None), (0.8, Some(40))] {
let mut lp = LogitsProcessor::from_sampling(42, sampling(t, k, true));
let next = lp.sample(&logits)?;
println!("temperature {t}, top_k {k:?}: next token {next} {:?}", tok.decode(&[next], false).map_err(E::msg)?);
}
temperature 0, top_k Some(40): next token 66 "R"
temperature 0, top_k None: next token 66 "R"
temperature 0.8, top_k Some(40): next token 66 "R"
candle 自带的 candle-examples/examples/quantized/main.rs 也是这样写的:temperature <= 0. 时用 Sampling::ArgMax。阈值用 <= 0.0 还是 new 的 < 1e-7 都行;上面的实验说明 1e-7 到 1e-30 之间并不会出错,只是和贪心没有区别。
怎么确认没中招
- 搜
from_sampling(,看每处构造Sampling的温度能不能是 0;命令行、配置文件、HTTP 请求里来的温度都要过一遍这个映射。 - 看到
A weight is negative, too large or not a valid number,先打印传进去的Sampling,再检查 logits 里有没有 NaN / inf(模型本身算出 NaN 时也是这句话)。 - 写一个温度为 0 的单元测试:取样结果应当等于
logits.argmax(0)。
小结
from_sampling不检查温度;温度 0 让 logits 变成无穷、softmax 变成整行 NaN,rand的WeightedIndex报A weight is negative, too large or not a valid number。- 只有
LogitsProcessor::new把小于 1e-7 的温度换成ArgMax,但它造不出TopK;GumbelSoftmax自己处理了温度 0。 - 构造
Sampling时把不大于 0 的温度映射成Sampling::ArgMax。