一个采样设置,如何让LLM的JSON输出合格率从75%暴跌到4%

一个no_repeat_ngram_size设置让Llama 3.2 1B的JSON合格率从75%跌至4%。
作者在真实浏览器中用Transformers.js和WebGPU测试了三款小模型(SmolLM2 360M、Qwen2.5 0.5B、Llama 3.2 1B),共432次JSON生成。消融实验显示,temperature基本无害,repetition_penalty仅轻微下降,而no_repeat_ngram_size: 3单独启用就把Llama的schema合法率从75%打到4%,因为它把提示词也计入历史,禁止了JSON必需的重复token序列,模型只能绕路写出带空格、单引号或改名的键。四步解析阶梯可把解析率从29%提升到44%,但修不了截断循环和根本没有JSON的回复。示例提示词还导致21/288条回复泄漏示例值。作者建议结构化调用关闭这两个参数、用贪心解码、加schema校验与重试,最优解是约束解码。
一个采样设置,如何让LLM的JSON输出合格率从75%暴跌到4%
如果你正在用开源小模型做结构化输出(比如让模型返回JSON),这篇实测可能会帮你省下几个小时的调试。一位开发者在真实浏览器环境中,用三款小模型跑了432次JSON生成,发现一个常见的防重复设置 no_repeat_ngram_size: 3 会把Llama 3.2 1B的JSON合格率从75%直接打到4%。问题不在模型能力,而在采样器禁止了模型写出schema需要的token。

实验设置:432次回复,三个小模型,一个真实浏览器标签页
作者在自建的BEINGS聊天室中测试了三个模型:SmolLM2 360M Instruct、Qwen2.5 0.5B Instruct和Llama 3.2 1B Instruct(q4f16)。全部通过Transformers.js 4.2.0在WebGPU上以4-bit权重运行,和线上产品用的是同一套固定构建。
测试包含三个任务,每个任务8个输入:给聊天消息打上说话人、四种情绪之一和回复;从消息中提取姓名、邮箱和电话;把购物笔记转成带整数数量的物品列表。每个输入都有标准答案,因此可以分别评估三件事:能否解析、是否符合schema、答案是否正确。
对比两种采样配置:一种是聊天室线上用的配置(开启采样,temperature 0.75,top_p 0.9,top_k 40,repetition_penalty 1.12,no_repeat_ngram_size 3),另一种是纯贪心解码(关闭采样,repetition_penalty 1.0,no_repeat_ngram_size 0)。
Transformers.js、WebGPU 与 4-bit 量化
Transformers.js 是 Hugging Face 推出的 JavaScript 移植版推理库,让模型无需后端即可直接在浏览器中运行。它依赖 WebGPU 获得 GPU 加速,WebGPU 是浏览器提供的新一代图形与计算 API,相较 WebGL 更适合通用计算,但仍可能因驱动或显存问题触发「设备丢失」(device lost)。
4-bit 量化把模型权重从 16 位浮点压缩到 4 位整数(如 q4f16 表示权重 4-bit、部分计算用 fp16),可大幅降低显存占用与下载体积,使 1B 级模型能在消费级设备运行。代价是精度损失,可能影响指令遵循与格式稳定性。测试在真实浏览器标签页中进行,更贴近实际部署环境,但也引入了设备丢失等运行时不确定性,作者因此对失败批次做了重跑。
聊天设置下,72次回复无一符合schema
使用完整聊天设置和示例提示词时,三个模型没有一次回复符合schema:0/72。换成贪心解码加同样的提示词,25/72符合,仅Llama一个模型就从0/24提升到18/24。
作者随后在贪心解码基础上逐个打开聊天设置,每次只加一个参数,每组24次回复。结果如下:
| 添加的设置 | Llama 3.2 1B | SmolLM2 360M |
|---|---|---|
| 无(贪心) | 18/24 (75%) | 7/24 (29%) |
| 仅 temperature: 0.75 | 19/24 (79%) | 9/24 (38%) |
| 仅 repetition_penalty: 1.12 | 16/24 (67%) | 9/24 (38%) |
| 仅 no_repeat_ngram_size: 3 | 1/24 (4%) | 0/24 (0%) |
| 全部聊天设置 | 0/24 | 0/24 |
Temperature基本无害,甚至让Llama多对了一次。重复惩罚只造成轻微下降。真正的元凶是n-gram禁令,单独启用就足以摧毁JSON输出。
根因:no_repeat_ngram_size禁止了schema必需的token
no_repeat_ngram_size: 3 的规则是“永远不要产生任何已经出现过的3个token序列”。在Transformers.js的实现中,任何会构成重复3-gram的token,其分数会被设为负无穷,永远无法被选中。
关键在于 input_ids 包含了整个提示词。生成循环从完整提示词开始,新token不断追加。而JSON列表天然包含重复结构,比如 {"name": ..., "qty": ...}, {"name": ..., "qty": ...},同样的token组合反复出现。更糟的是,示例提示词已经写过一遍 "name": " 和 "qty":,这些3-token序列在模型开口之前就被禁掉了。
模型无法拒绝回答,只能选择次优路径——绕路。真实回复中出现了 " qty"(带前导空格)、".name"、单引号包裹的 'name'、用 "quantity" 代替 "qty"、用 "type" 代替 "name"。每一个都是绕过被禁3-gram的最短路径。危险的是,有些绕路后的JSON仍然能被解析,但你的代码读取 item.qty 时只会得到 undefined。
模型并非不擅长JSON,而是采样器禁止了schema要求的精确token,提示词中的示例让情况更糟。
n-gram 与 no_repeat_ngram_size 的机制
n-gram 指文本中连续出现的 n 个 token 序列。no_repeat_ngram_size: 3 意味着解码器在生成每个新 token 时,会检查「当前 token 与之前两个 token 组成的 3-gram」是否在已生成的历史中出现过;若出现过,该 token 的 logit 被置为 -inf,从而被硬性禁止。这与 repetition_penalty 的软性降权不同,是一种不可违反的约束。
问题在于,多数实现(包括 Transformers.js)统计历史时把整个 input_ids 都算进去,而 input_ids 包含提示词本身。因此提示词里出现过的任何 3-gram 都会在生成阶段被永久封禁。对于 JSON 这类高度模板化、键名与标点反复出现的格式,这种封禁直接切断了模型写出合法结构的路径。该参数原本用于抑制长文本生成中的复读循环,适合自由文本,却不适合结构化输出。
重复惩罚也会惩罚你的提示词
repetition_penalty 是更温和的机制,出自CTRL论文(Keskar等人,2019)。它会把已出现token的分数除以惩罚系数。Transformers.js的源码注释明确写道:对于decoder-only模型,考虑的token包括提示词。
这意味着schema中的每个引号、冒号和键名,再次出现的概率都会被轻微压低。1.12的系数是轻推而非禁止,数据也吻合:Llama从18/24降到16/24。但作者仍建议结构化调用时设为1.0,毕竟没有理由惩罚你明确要求的标点符号。
很多应用可能在共享配置中一次性设置了这些参数,所有结构化调用都默默继承了它们。值得在代码库里搜索一下。
解析阶梯能救回43条回复,但救不了全部
即使采样设置正确,小模型仍会把JSON包在散文或代码块里,JSON.parse 会直接抛错。作者设计了一个四步解析阶梯:
- 直接解析
- 去掉代码围栏后解析
- 提取第一个配对的
{...}或[...]后解析 - 修复常见语法问题后解析(丢弃
//注释、给裸键加引号、单引号换双引号、映射Python的True/False/None、去除尾逗号、闭合未关闭的括号)
在288条主运行回复中,严格 JSON.parse 接受了84条(29%),阶梯提升到127条(44%)。对Llama贪心解码加普通提示词的情况,从14/24提升到24/24。
但44%到100%之间的差距才是重点。阶梯能修复JSON的包装方式,却修不了那些根本没包含JSON的回复。
解析失败的真正原因:截断和根本没有JSON
在204条解析失败的回复中,代码围栏和“Here is your JSON:”这类散文只有43条。最大的两个原因无法靠解析器解决:
- 在token限制处截断(62条):主要不是长答案,而是循环。Qwen反复输出
"jars", "jars", "jars"直到耗尽token。 - 完全没有JSON(61条):纯散文、一个“提取联系信息”的Python函数、道歉,或者把输入原样返回。
Qwen2.5 0.5B值得单独说明:96次回复中0次直接解析成功,4次经阶梯成功,0次符合schema。贪心回复大多回显用户消息然后进入循环。作者强调这不一定是Qwen2.5的普遍问题,可能是这个量化构建的问题,但提醒要测试你实际部署的那个文件。
合法JSON不等于正确JSON
符合schema和答案正确是两个数字。Llama最佳配置(贪心+示例)给出18条schema合法回复,但只有16条正确。两类问题:
- 示例泄漏:被要求提取“Call me back on (555) 018-2299. Thanks, Tom Okafor”的联系方式时,Llama返回了示例中的名字和邮箱,配上了Tom的电话。在288条示例提示词回复中,21条包含从示例中抄来的值。普通提示词下144条中只有1条。
- 类型漂移:
"qty": "1"作为字符串,缺失的"email"键而不是null。JSON Schema验证器能抓住这些,JSON.parse永远不会。
示例提示词对Llama仍然是更好的选择(16正确 vs 普通提示词的12正确),但对SmolLM2更差(3 vs 7)。小模型倾向于复制示例而非从中学习。如果使用示例,让示例值明显是假的(比如 "NAME"、0),这样泄漏容易发现。
JSON Schema 验证与类型漂移
JSON.parse 只检查文本是否为合法 JSON,不检查结构是否符合预期。JSON Schema 是一种描述数据形状的规范,可规定必填键、字段类型、枚举取值等,配合验证器(如 Ajv)能在解析后进一步把关。
文中「类型漂移」指模型输出的结构看似正确但类型不符,例如数量被写成字符串 "1" 而非整数 1,或缺失字段用缺键代替 null。这类错误 JSON.parse 完全无法察觉,下游代码读取时才会静默失败。因此在解析阶梯之后增加 schema 校验,并检查类型而非仅键名存在性,是保证数据可用的必要环节。
对GPT、Claude和大模型意味着什么
这次测试是360M到1B参数模型在浏览器中的表现,失败率会比前沿API高。但作者认为采样器的发现对任何自行运行开源模型的人都适用:n-gram禁令是机械规则,不关心模型有多聪明。更大的模型只是找到更巧妙的绕路方式,这可能更糟,因为它们看起来是对的。
能跳过猜测的话就跳过。约束解码把每个下一个token限制在能保持输出符合语法或schema的范围内,从构造上杜绝格式错误。OpenAI Structured Outputs、llama.cpp GBNF语法和Outlines都提供这种能力。如果不能使用,解析阶梯加schema校验加一次重试是实际的底线。
约束解码(Constrained Decoding)
约束解码是在每一步采样时,把候选 token 限制在「能使输出继续符合给定语法或 JSON Schema」的集合内。实现方式通常是把 schema 编译成有限状态机或上下文无关文法,再据此动态屏蔽非法 token。这样模型从构造上就不可能生成格式错误的输出,无需事后解析修补。
常见方案包括 OpenAI 的 Structured Outputs、llama.cpp 的 GBNF 语法文件、以及 Outlines 库。其局限在于:只保证语法与结构合法,不保证语义正确(如字段值是否准确),也不解决截断或模型拒绝回答的问题;复杂的嵌套 schema 可能拖慢解码速度。对自行部署开源模型的团队,这是比调采样参数更根本的解决方案。
今天就能在项目里做的事
- 搜索
no_repeat_ngram_size和repetition_penalty。如果结构化调用从共享配置继承了这两个参数,覆盖它们:no_repeat_ngram_size: 0、repetition_penalty: 1.0。 - 对JSON使用贪心解码或低温度。温度在这里没造成伤害,但贪心让失败可复现,便于修复。
- 用阶梯解析。记录哪一步被用到;第4步比例上升是早期预警。
- 解析后对照schema验证,检查类型而非仅键名。
- 失败后带错误信息重试一次。两大失败类型(循环和无JSON)是解析器修不了的。
- 让示例值明显是假的,并测试泄漏。
- 为列表提高token上限。截断回复是唯一应该闭合括号的场景,但你会静默丢失条目。
关键数字汇总
| 指标 | 数值 |
|---|---|
| 生成并评分的回复 | 432(288主运行+144消融) |
| Llama 3.2 1B schema合法,贪心+示例 | 18/24 (75%) |
| 同上,仅添加 no_repeat_ngram_size: 3 | 1/24 (4%) |
| 三个模型,聊天设置+示例提示词 | 0/72 |
| 288条主回复直接解析成功 | 84 (29%) |
| 经四步阶梯后解析成功 | 127 (44%) |
| Llama贪心+普通提示词经阶梯 | 24/24 |
| 泄漏示例值的回复 | 288条示例提示词回复中21条 |
| Qwen2.5 0.5B schema合法 | 0/96 |
一个诚实的说明:Chrome的WebGPU设备丢失过两次,作者重跑了Llama的整批并让脚本在设备丢失时重载模型重试,所有数字都来自完整的数据集。
对于BEINGS的下一步,作者计划让生物动作以JSON返回,为结构化调用使用独立的贪心、无惩罚配置,而聊天保留防重复设置。75%能否在真实房间流量中保持,值得关注。
如果你手头正好有一条坏掉的LLM回复,可以试试作者提供的免费JSON修复工具,它运行同样的四步阶梯,并告诉你哪一步修好了它。
相关推荐

2026年AI编程工具横评:Claude、ChatGPT、Gemini谁最强?
2026年五款主流AI编程工具横评:Claude Code、ChatGPT Codex、Gemini、DeepSeek和GitHub Copilot在真实项目、定价与代码质量上的全面对比,帮你决定每月20美元该花给谁。

ChatGPT Codex克隆任意软件?实测风险与真相
YouTube短视频宣称用ChatGPT Codex或Claude Code即可克隆Adobe、Excel、Figma等软件并终身免费使用。本文拆解其技术可行性、法律风险与私信引流套路,说明AI编程工具真正适合的用法。

Kilo Code免费安装但API收费:成本估算与配置指南
Kilo Code 开源 AI 编程代理安装免费,但接入的 AI 模型按 token 收费。本文详解 VS Code 扩展与 CLI 配置步骤,并用假设用量演示 API 成本估算方法,对比按量付费与订阅制的选择逻辑。