news 2026/10/1 14:38:20

【torch报错处理】RuntimeError: probability tensor contains either `inf`, `nan` or element < 0:从定位到修复的完整排查路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【torch报错处理】RuntimeError: probability tensor contains either `inf`, `nan` or element < 0:从定位到修复的完整排查路

1. 报错现场还原:probability tensor 里到底混进了什么

RuntimeError: probability tensor contains either inf, nan or element < 0这个报错,第一次见的人基本都会懵:明明前面 loss 还在正常下降,怎么突然就炸了?而且它不像 shape mismatch 那样直接告诉你哪一维对不上,只丢一句“概率张量里有 inf、nan 或者负数”,剩下的全靠自己查。

先说清楚它是什么。这个报错来自 PyTorch 的torch.multinomial(以及内部调用它的torch.distributions.Categorical)。当你做文本生成采样、强化学习动作采样、或者任何“按概率抽一个索引”的操作时,PyTorch 会先检查传入的 probability tensor 是否合法:每个元素必须落在[0, 1]区间内,且整行求和不能是 0 或 nan。只要有一个元素是inf、nan,或者出现负数,就直接抛这个 RuntimeError。

它能做什么判断?其实它是一道“最后防线”。真正的问题往往发生在更早的地方:logits 溢出、softmax 数值不稳定、mask 填充把某些位置设成了-inf之后又参与了归一化、混合精度下float16表示范围不够导致上溢。这个报错只是把上游的数值污染暴露出来了。

适合谁看?如果你正在跑 HuggingFacegenerate()、自己写的自回归采样循环、PPO/DPO 里的动作分布、或者任何带multinomial/Categorical的代码,并且遇到了这个报错,那这篇就是给你写的。我试过在 batch size 只有 1、完全没有 padding 的情况下也触发它,所以别以为“我没用 pad 就跟我无关”。

先建立一个直觉:概率张量出问题,99% 不是采样那一步的错,而是它上游的 logits 已经烂了。所以排查方向永远是往上游走,而不是在multinomial那一行加 try/except。

下面给一个最小可复现的例子,你可以直接跑,感受一下报错长什么样:

import torch # 构造一个含 nan 的 logits logits = torch.tensor([[1.0, float('nan'), 3.0]]) probs = torch.softmax(logits, dim=-1) print(probs) # tensor([[0.2689, nan, 0.7311]]) torch.multinomial(probs, num_samples=1) # RuntimeError: probability tensor contains either `inf`, `nan` or element < 0

再构造一个负数场景,这个更隐蔽,因为 softmax 之后理论上不会有负数,但如果你手动拼了一个概率张量就会中招:

probs = torch.tensor([[0.5, -0.1, 0.6]]) torch.multinomial(probs, num_samples=1) # 同样报这个错

看到没,报错本身信息量很少,但它指向的是一整条数值链路。接下来我们先把环境准备好,再逐段拆解。

2. 前置准备:用 TaoToken 快速搭一个可调试的推理环境

排查这类数值问题,最怕的就是环境本身不稳定,一会儿 CUDA 版本不对,一会儿模型权重加载失败,把真正的 bug 淹没了。所以第一步是把推理环境固定下来,让每次跑出来的结果可复现。

我一般会用一个统一的 API 入口来跑模型对话和调试,这样不用在本地反复折腾权重和显存。TaoToken 的官网在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,它提供模型对话、Coding Plan、控制台和 API Keys 等入口。对于这篇的排查场景,我们主要用它来快速验证“同样的 prompt 在不同精度下会不会触发 nan”,省去本地加载大模型的等待时间。

具体来说,你需要拿到一个 API Key,然后就可以用统一的 Base URL 去请求模型。API 地址是 https://taotoken.net/api ,注意这个地址不带 UTM 参数,直接用于代码里的base_url。控制台和 Key 管理在 https://taotoken.net/console 和 https://taotoken.net/api-keys ,模型对话页面在 https://taotoken.net/chat ,接入文档在 https://taotoken.net/doc 。

为什么排查数值问题要先搭这个?因为你需要一个“对照组”。本地跑崩了,你可以立刻用同一个 prompt 走 API 跑一遍,如果 API 侧正常,说明问题出在你本地的精度设置或代码逻辑;如果 API 侧也崩,那可能是 prompt 本身触发了模型的极端输出。这种对照能帮你快速缩小范围。

安装依赖很简单:

pip install openai torch transformers

然后配置一个最小的调用脚本,把 base_url 指向 TaoToken:

from openai import OpenAI client = OpenAI( api_key="你的_TAOTOKEN_API_KEY", base_url="https://taotoken.net/api" ) resp = client.chat.completions.create( model="claude-3-5-sonnet", messages=[{"role": "user", "content": "how are you"}] ) print(resp.choices[0].message.content)

这段代码的作用是建立一个稳定的外部参照。当你本地generate()报probability tensor错误时,把同样的输入丢给这个脚本,看它是否正常返回。如果正常,那基本可以锁定是你本地精度或采样代码的问题。

另外,如果你要做长期的编码调试或者 Agent 相关的实验,可以考虑 TaoToken 的 Coding Plan,入口在 https://taotoken.net/coding-plan ,它更适合需要反复跑、反复改的场景。Claude Code 相关的接入文档在 https://taotoken.net/doc ,里面有 Anthropic 兼容的配置说明。

环境准备好之后,我们进入正题:怎么一步步定位到那个烂掉的概率张量。

3. 可复制配置:从 logits 到 probs 的数值稳定改造

这一节是核心,我会给你一套可以直接抄的配置和代码片段,覆盖最常见的三种触发场景:softmax 前未做数值稳定、logits 溢出、mask 填充异常。

先看第一种,也是最常见的:直接对原始 logits 做 softmax,然后送进 multinomial。问题在于,当 logits 里有很大的值时,exp()会溢出成inf,归一化之后就会出现inf/inf = nan。

import torch def unstable_sample(logits): probs = torch.softmax(logits, dim=-1) return torch.multinomial(probs, num_samples=1) logits = torch.tensor([[1000.0, 999.0, 998.0]], dtype=torch.float16) print(unstable_sample(logits)) # 大概率报 probability tensor contains inf/nan

修复方式是改用log_softmax配合multinomial的 log 概率输入,或者手动减去最大值。PyTorch 的torch.multinomial其实支持直接传 log 概率吗?不支持,它只接受概率。但torch.distributions.Categorical接受 logits,内部会做稳定处理。所以更稳的写法是:

def stable_sample(logits): # 减去最大值,防止 exp 溢出 logits = logits - logits.max(dim=-1, keepdim=True).values probs = torch.softmax(logits, dim=-1) # 再做一次 clamp,兜底 probs = torch.clamp(probs, min=0.0, max=1.0) return torch.multinomial(probs, num_samples=1)

第二种场景:混合精度下的 logits 溢出。float16的最大表示范围大约是 65504,而bfloat16的指数位和float32一样,范围大得多,但尾数精度低。如果你用float16跑大模型,logits 很容易超过 65504 变成inf。这时候可以在加载模型时指定dtype:

from transformers import AutoModelForCausalLM, AutoTokenizer model = AutoModelForCausalLM.from_pretrained( "meta-llama/Llama-2-7b-hf", torch_dtype=torch.bfloat16, # 用 bfloat16 替代 float16 device_map="auto" ) tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")

如果你用的是 HuggingFace 的generate(),可以在generate里传do_sample=True和temperature,但注意temperature太低会让分布过于尖锐,反而放大数值问题。实测下来,temperature=0.7到1.0之间比较稳。

第三种场景:mask 填充异常。当你用attention_mask或者手动构造-infmask 时,如果整行都被 mask 成-inf,softmax 之后整行都是nan。检查方法:

def check_mask(logits, attention_mask): # 检查是否有整行被完全 mask masked = logits.masked_fill(attention_mask == 0, float('-inf')) all_masked = torch.isinf(masked).all(dim=-1) if all_masked.any(): print(f"警告:第 {all_masked.nonzero().flatten().tolist()} 行被完全 mask") return masked

还有一个容易被忽略的点:tokenizer.pad_token的设置。有些模型默认没有 pad token,如果你手动设成[PAD]但模型词表里没有,可能会在 embedding 层产生异常值。可以尝试设成tokenizer.unk_token或者tokenizer.eos_token,具体看模型。

下面给一个完整的、可复制的采样函数,把上面几种防护都加上:

import torch def safe_multinomial(logits, attention_mask=None, temperature=1.0): """ logits: [batch, vocab] attention_mask: [batch, vocab] 或 None """ # 1. 温度缩放 logits = logits / temperature # 2. mask 处理,避免整行 -inf if attention_mask is not None: logits = logits.masked_fill(attention_mask == 0, float('-inf')) # 检查整行被 mask 的情况 all_masked = torch.isinf(logits).all(dim=-1) if all_masked.any(): raise ValueError("存在整行被 mask 的样本,请检查 attention_mask") # 3. 减去最大值,数值稳定 logits = logits - logits.max(dim=-1, keepdim=True).values # 4. softmax 得到概率 probs = torch.softmax(logits, dim=-1) # 5. 检测 nan/inf if torch.isnan(probs).any() or torch.isinf(probs).any(): raise ValueError("softmax 后出现 nan/inf,请检查 logits") # 6. clamp 兜底,防止极小负数 probs = torch.clamp(probs, min=0.0, max=1.0) # 7. 重新归一化,保证和为 1 probs = probs / probs.sum(dim=-1, keepdim=True) return torch.multinomial(probs, num_samples=1)

这套配置基本能覆盖 80% 的场景。如果你用的是transformers的generate(),它内部已经做了不少稳定处理,但如果你自己写了采样循环,就一定要把上面这些防护加上。

另外,如果你在本地反复调试精度问题很痛苦,可以用 TaoToken 的模型对话页面快速验证同一个 prompt 在服务端的表现,入口在 https://taotoken.net/chat ,这样能帮你判断是模型本身的问题还是你本地环境的问题。

4. 验证请求:用最小复现代码确认修复生效

配置写好了,怎么确认真的修好了?不能只看“不报错了”,还要看数值是否健康。这一节给你一套验证动作,包括 nan/inf 检测、clamp 前后对比、以及一个完整的端到端请求示例。

先写一个检测函数,专门用来在采样前拦截异常:

import torch def diagnose_probs(probs, name="probs"): print(f"--- {name} 诊断 ---") print(f"shape: {probs.shape}") print(f"dtype: {probs.dtype}") print(f"是否有 nan: {torch.isnan(probs).any().item()}") print(f"是否有 inf: {torch.isinf(probs).any().item()}") print(f"最小值: {probs.min().item()}") print(f"最大值: {probs.max().item()}") print(f"每行和: {probs.sum(dim=-1)}") # 检查是否有负数 if (probs < 0).any(): print(f"存在负数,位置: {(probs < 0).nonzero()}")

然后构造一个会触发问题的 logits,跑一遍修复前后的对比:

# 构造极端 logits logits = torch.tensor([[500.0, 400.0, 300.0]], dtype=torch.float16) # 修复前 probs_bad = torch.softmax(logits, dim=-1) diagnose_probs(probs_bad, "修复前") # 你会看到 inf 或 nan # 修复后 logits_stable = logits - logits.max(dim=-1, keepdim=True).values probs_good = torch.softmax(logits_stable, dim=-1) probs_good = torch.clamp(probs_good, min=0.0, max=1.0) probs_good = probs_good / probs_good.sum(dim=-1, keepdim=True) diagnose_probs(probs_good, "修复后") # 应该看到正常的概率分布

跑完这个对比,你就能直观看到“减最大值”这一步有多关键。修复前的概率张量要么是inf要么是nan,修复后是干净的[0,1]分布。

接下来做一个端到端的验证:用 HuggingFace 模型跑一次采样,确认generate()不再报错。

from transformers import AutoModelForCausalLM, AutoTokenizer import torch model_name = "meta-llama/Llama-2-7b-hf" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForCausalLM.from_pretrained( model_name, torch_dtype=torch.bfloat16, device_map="auto" ) prompt = "Explain the theory of relativity in simple terms." inputs = tokenizer(prompt, return_tensors="pt").to(model.device) with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=50, do_sample=True, temperature=0.8, top_p=0.9, pad_token_id=tokenizer.eos_token_id ) print(tokenizer.decode(outputs[0], skip_special_tokens=True))

如果这段代码能正常输出,说明你的精度设置和采样参数是健康的。如果还是报probability tensor错误,那就回到第 3 节,检查是不是 mask 或者 pad_token 的问题。

再给一个用 TaoToken API 做对照验证的脚本,确认服务端对同一个 prompt 的处理:

from openai import OpenAI client = OpenAI( api_key="你的_TAOTOKEN_API_KEY", base_url="https://taotoken.net/api" ) resp = client.chat.completions.create( model="claude-3-5-sonnet", messages=[{"role": "user", "content": "Explain the theory of relativity in simple terms."}], temperature=0.8 ) print(resp.choices[0].message.content)

如果本地崩、API 正常,那问题就在本地精度或代码;如果两边都崩,那可能是 prompt 触发了模型的极端输出,需要换 prompt 或者调整采样参数。

验证通过的标准很简单:连续跑 10 次不同的 prompt,都不再出现probability tensor报错,并且输出的文本语义正常。如果偶尔还有,那就把diagnose_probs挂到你的采样循环里,看是哪一步漏了防护。

5. 常见错排查:401、local proxy failed、reading choices、OAuth 对照表

排查过程中,除了probability tensor本身,你还可能撞上一堆周边报错。这些报错容易把人带偏,所以单独列出来对照。

先说你最可能遇到的:401 Unauthorized。如果你在用 TaoToken API 做对照验证,Key 填错了或者没带Bearer前缀,就会返回 401。检查你的api_key是否正确,以及base_url是不是https://taotoken.net/api。注意不要写成带 UTM 的地址,那个是给网页跳转用的,API 调用要用干净的/api。

第二个:local proxy failed。这个通常出现在你本地设置了网络代理,但代理不可用或者配置冲突。排查方法是检查环境变量HTTP_PROXY/HTTPS_PROXY是否指向了一个失效的地址。如果你在公司内网,可能需要走内网代理,具体问运维。注意,这里说的是正常的网络代理配置问题,不涉及任何绕过网络管理的手段。

第三个:reading choices相关的报错。这个一般出现在你解析 API 返回时,resp.choices为空或者结构不对。比如你用了错误的 model 名称,服务端返回了错误信息而不是正常的 completion,这时候resp.choices[0]就会 IndexError。检查方法是先打印完整的resp,看error字段有没有内容。

第四个:OAuth相关。如果你在接入 Claude Code 或者某些需要 OAuth 的工具,可能会遇到 token 过期或者 scope 不对的报错。这时候需要重新走一遍授权流程,确保拿到的 token 有正确的权限。TaoToken 的接入文档在 https://taotoken.net/doc ,里面有详细的配置说明。

为了让你更清楚,我列一个对照表:

报错关键词可能原因排查动作
401 UnauthorizedAPI Key 错误或缺失检查 key 和 base_url
local proxy failed本地代理配置失效检查 HTTP_PROXY 环境变量
reading choices返回结构异常打印完整 resp 看 error
OAuthtoken 过期或 scope 不足重新授权,检查权限
probability tensorlogits 数值污染按第 3 节加防护

另外,如果你在用 CC Switch、Cline MCP 或者 Codex 的auth.json,记得把三件套配全:Base URL、Key、Model ID。缺一个都会导致连接失败。比如 Codex 的auth.json里需要明确写base_url和api_key,Model ID 也要和请求里的一致。

还有一个坑:torch版本差异。有朋友反馈从 2.4 降级到 2.1 之后,同样的代码不报错了。这说明不同版本在softmax或multinomial的底层实现上可能有数值处理差异。但降级不是长久之计,更稳的做法还是在自己代码里加数值稳定处理,不要依赖框架版本的“运气”。

如果你在排查过程中发现某个 prompt 特别容易触发,可以把它单独拎出来,用diagnose_probs逐层打印 logits、softmax 后的 probs、以及 clamp 后的结果,定位到底是哪一步开始出现 nan。

6. 语义一致 CTA:把排查流程固化成你的调试习惯

到这里,probability tensor contains either inf, nan or element < 0的完整排查路径就讲完了。核心就一句话:这个报错是症状,不是病因,真正的病灶在 logits 的数值稳定性上。

把第 3 节的safe_multinomial函数保存下来,以后所有涉及采样的代码都走这个入口,基本能避免 90% 的同类问题。再配合第 4 节的diagnose_probs,在关键节点打印数值状态,出问题时能第一时间定位。

如果你需要快速验证模型行为、做对照实验,可以用 TaoToken 的模型对话页面,入口在 https://taotoken.net/chat ,API 调用统一走 https://taotoken.net/api 。Key 在 https://taotoken.net/api-keys 管理,接入文档在 https://taotoken.net/doc 。长期做编码和 Agent 实验的话,Coding Plan 在 https://taotoken.net/coding-plan 。

最后留一个实用技巧:在你的训练或推理循环里,加一个全局的数值检查钩子,每隔 N 步检查一次 logits 和 probs 的 min/max/mean,一旦发现异常就打印当前 batch 的输入和 mask 状态。这样你就不用等到multinomial抛错才发现问题,而是能在数值刚开始恶化的时候就介入。这个习惯比任何单次修复都值钱。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 14:37:00

初识PE结构:用汇编视角看懂验证逻辑并绕过

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 14:36:42

Codex 沙箱深度解析:从 Landlock 到 OS 级代码隔离的落地实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 14:34:37

Vibe 编程与 Happy Coding:用 TaoToken 统一 Key 打通 AI 编程工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华