系列第 8 篇 · 反直觉优化

本文性质说明:token 数、prefill 延迟、问答正确率全部本机真跑(Qwen2.5-0.5B-Instruct,纯 NumPy CPU,脚本 eng_repr_tokens.py 给全)。成本金额是「本机实测 token 数 × 公开挂牌单价假设」的外推,单价已标注为假设值。Skyvern 生产 A/B 的成功率数据作为外部佐证单独标注。

一句话结论

同一份订单数据,只改写法不改内容,喂给模型的开销差得离谱——而且省下的比例随数据量变大还在涨

明细条数 JSON HTML HTML 省 Markdown MD 省
1 77 58 24.7% 43 44.2%
3 138 99 28.3% 73 47.1%
10 351 242 31.1% 180 48.7%
30 940 631 32.9% 474 49.6%

但省 token 不等于能用。同一批问题问下来:JSON 3/3、HTML 3/3、Markdown 2/3——最省的 Markdown 答错了一题。

HTML 是甜点位:省 33% token、prefill 快 19%、正确率和 JSON 打平。

背景与痛点:JSON 是默认选项,也是最贵的选项

给模型喂结构化数据,几乎所有人第一反应是 json.dumps()。问题是 JSON 为机器解析设计,不为 token 效率设计:每个字段名都要重复一遍,加上引号、冒号、逗号、缩进——这些"标点税"你按 token 付费。

以 30 条明细的订单为例,JSON 里 "name""qty""price" 这三个键各重复 30 次,光键名就烧掉几百个 token,而它们没有携带任何信息——第一次出现就够了。

核心方法:三种表征怎么权衡

同一份订单,三种写法:

# JSON:字段名逐条重复,标点最多
{
  "order_id": "A1024",
  "customer": "张三",
  "items": [
    {"name": "机械键盘", "qty": 1, "price": 399.0},
    {"name": "鼠标垫", "qty": 2, "price": 29.9}
  ],
  "total": 458.8,
  "status": "paid"
}

# HTML/XML:属性紧凑,键名仍在但没有引号税+缩进税,模型预训练见得极多
<order id="A1024" status="paid">
  <customer>张三</customer>
  <item name="机械键盘" qty="1" price="399.0"/>
  <item name="鼠标垫" qty="2" price="29.9"/>
  <total>458.8</total>
</order>

# Markdown:最省,但字段语义被压平成自然语言
# 订单 A1024 (paid)
- 客户:张三
- 机械键盘 x1 = 399.0
- 鼠标垫 x2 = 29.9
- 合计:458.8

判断维度三个:信息密度(同内容多少 token)、模型熟悉度(预训练分布里见过多少)、字段保真度(键值对应关系是否还在)。JSON 保真度最高但最贵;Markdown 最便宜但把 status: paid 压成了标题里的 (paid);HTML 两头都不吃亏。

实验与数字(本机真跑,2026-08-19)

复现命令:

python eng_repr_tokens.py     # A: token 规模扫描  B: 理解力探针

环境:Qwen2.5-0.5B-Instruct,纯 NumPy CPU 推理引擎(本系列第 3 篇那套),模型加载 1.3s,全程 94s。

① token 规模扫描:省的比例会放大

关键代码——用真 tokenizer 数,不用字符数估:

def ntok(s):
    """内容 token 数(去掉 encode() 自动加的 BOS,避免虚增)"""
    return len(eng.encode(s)) - 1

for n in (1, 3, 10, 30):
    js, ht, md, _ = build(n)          # 同一份数据的三种序列化
    tj, th, tm = ntok(js), ntok(ht), ntok(md)
    print(n, tj, th, f"{(1-th/tj)*100:.1f}%", tm, f"{(1-tm/tj)*100:.1f}%")

结果就是开头那张表。HTML 的节省从 24.7% 涨到 32.9%,Markdown 从 44.2% 涨到 49.6%。

为什么会涨?因为固定开销(order_idcustomertotalstatus 这些只出现一次的字段)被摊薄了,而逐条重复的键名开销随条数线性增长——JSON 越长,冗余占比越高。这个规律很实用:你的 payload 越大,这个优化越值得做。反过来说,小 payload 上测出来"才省 24%",不要据此判断它不划算。

② 成本外推(token 实测,单价为假设)

按 30 条明细、100 万次调用、输入单价 $0.15 / 1M token(假设值,非本机产出)

表征 token/次 100 万次成本
JSON 940 $141
HTML 631 $95
Markdown 474 $71

仅 JSON→HTML 这一项,省 $46。这里的重点不是绝对金额(单价随模型和厂商差几十倍),而是比例结构:不换模型、不裁数据、不动 prompt 逻辑,只改序列化函数,账单直接砍三分之一。

③ 理解力探针:Markdown 翻车了

省 token 的前提是模型还读得懂。我用官方 chat 模板问了三个只能从数据里读出的问题:

表征 prompt token 客户名字 订单状态 鼠标垫数量 答对
JSON 182 张三 paid 2 3/3
HTML 143 张三 paid 2 3/3
Markdown 118 张三 已支付 2 2/3

Markdown 那一题很有意思:它并没有"不知道",而是答了已支付。数据里 paid 被我压进了标题 # 订单 A1024 (paid),失去了 "status": "paid" 这种显式键值绑定,模型于是把它当自然语言意译了。

这就是保真度损失的真实形态:不是幻觉,不是漏读,而是字段值被改写成语义等价但字符串不等的东西。如果下游代码要 if status == "paid",这条链就断了——而且断得很隐蔽,因为答案"看起来是对的"。

④ 附带发现:prefill 延迟跟着 token 一起降

每题的实际生成耗时(纯 CPU,prefill 占主导):

表征 三题耗时 平均 相对 JSON
JSON 12.3s / 12.7s / 12.8s 12.60s
HTML 10.1s / 10.2s / 10.2s 10.17s -19.3%
Markdown 8.3s / 8.3s / 8.2s 8.27s -34.4%

省 token 是双重收益:账单降,首 token 延迟也降。这条和第 2 篇(首 token 延迟拆解)直接呼应——prefill 成本正比于输入长度,砍输入就是砍 TTFT,比改推理参数省事得多。

⑤ 外部佐证:Skyvern 生产 A/B

指标 JSON HTML 变化
单任务成本 $1.22 $1.08 -11.4%
成功率 59.9% 63.8% +3.9%

来源:Skyvern 生产 A/B 报告(2026-06,约 1,100 个真实任务)。他们的成本降幅(11.4%)小于我本机的 token 降幅(33%),合理——真实请求里还有 system prompt、历史消息、输出 token 等不受表征影响的固定部分。

而"成功率反升 3.9%"和我本机 HTML 打平 JSON 的结果同向:HTML 更贴近模型预训练分布,噪声更少。至少可以说,省 token 不必然牺牲质量。

为什么重要

  • 这是极少数"纯赚"的优化:成本↓、延迟↓、质量不降。不像量化(省显存但掉精度)或换小模型(省钱但掉能力)那样要做权衡。
  • 改动量小到离谱:动的是序列化函数,几十行,不碰模型、不碰 prompt 逻辑、不碰架构。
  • payload 越大越划算,而生产里的 payload 通常比 demo 大得多——demo 上测出 24%,生产可能是 33%+。
  • 呼应第 1 篇(账单砍 79%):降本的杠杆多数不在模型上,在"你怎么喂它"
  • 反过来给了个警告:别无脑上 Markdown。它最省,但会压平字段语义,对需要精确字段值的下游是隐性风险。

读者可复用交付物:表征格式选型决策表

先问一句:下游要不要按字段精确取值?

要(写库 / 走 if 判断 / 触发流程)
  └─ 优先 HTML/XML 标签表征
       · 键值绑定完整(name="x" qty="1")
       · 省 25-33% token,量越大省越多
       · 本机实测正确率与 JSON 持平 3/3
  └─ 必须 JSON 的唯一场景:你要把模型输出直接 json.loads()
       (输入用 HTML、输出要 JSON —— 这两件事可以分开定!)

不要(摘要 / 分类 / 问答 / 语义检索)
  └─ 用 Markdown,省 44-50%
       · 但字段值可能被意译(paid → 已支付)
       · 只要下游不做字符串精确比较就无所谓

落地检查清单
  □ 用真 tokenizer 数 token,别用 len(str)/4 估
  □ 在你的真实 payload 规模上测,别用 1 条 demo 测
  □ 换格式后必须跑正确率回归(至少 20 题),别只看 token 降了就上线
  □ 重点回归"精确字段值"类问题(状态码、枚举、ID、金额)
  □ 输入表征和输出格式解耦:输入 HTML 省钱,输出仍可要求 JSON
  □ 长列表数据优先 HTML/MD;单条小对象差异不大,不用折腾

踩坑记录(复现时会遇到)

  1. encode() 不认特殊 token,chat 模板必须手拼 ID。 本系列踩过的老坑:<|im_start|> 走 BPE 会被切成碎片。必须直接拼 151644 / 151645

python def chat_ids(system, user): enc = lambda t: eng.encode(t)[1:] # 去掉自动 BOS ids = [IM_START] + enc("system\n" + system) + [IM_END] + enc("\n") ids += [IM_START] + enc("user\n" + user) + [IM_END] + enc("\n") ids += [IM_START] + enc("assistant\n") return ids

  1. 只解码新生成的 token。 如果 decode 整个 prompt + gen,模型会把整段输入回显进"答案",于是关键词永远命中,正确率假性 100%。第 10 篇的注入实验就是这么被坑过一次。

  2. 数 token 要减掉 BOS。 encode() 会自动加一个 BOS,三种表征各加 1 个,比例算出来会被稀释。早期我那版没减,同一份数据得到 95/78/59(省 17.9%),减掉后是 94/77/58(省 18.1%)——小 payload 上这个误差不能忽略。

  3. 别用字符数或 len(text)/4 估 token。 中文场景下这个经验公式误差极大:Markdown 的字符数比 HTML 少得有限,但 token 少得多,因为中文词在 tokenizer 里的切分和标点完全不是一个量级。

  4. HTML 表征别真去塞完整网页 DOM。 省 token 的是"标签化的紧凑表征",不是原始 HTML——真实 DOM 里的 classstyledata-* 属性比 JSON 还冗余。手工构造语义标签,或先做 DOM 精简。

今日可做的 3 件事

  1. 找到你最高频的那个 LLM 调用,把输入的 json.dumps() 换成标签表征,用真 tokenizer 数一下前后 token(10 分钟的事)。
  2. 按你真实 payload 的最大规模再测一遍。小样本会低估收益——我这儿 1 条时省 24.7%,30 条时省 32.9%。
  3. 跑一次正确率回归,专门挑"精确字段值"的问题(状态、枚举、ID)。如果你打算用 Markdown,这一步是必须的——我这儿正是在 status 上翻的车。

下篇预告

系列终篇:8 篇跑下来的硬规律汇总——每篇一句话结论、全部实测数据索引、可复用交付物清单,以及一个贯穿全系列的发现:工程收益的大头几乎从来不在"换个更强的模型"上


本文数据来源

  • 本机真跑:eng_repr_tokens.py(2026-08-19,Qwen2.5-0.5B-Instruct + 纯 NumPy CPU)
  • token 规模扫描:1/3/10/30 条明细,HTML 省 24.7%→32.9%,MD 省 44.2%→49.6%
  • 理解力探针:JSON 3/3、HTML 3/3、Markdown 2/3(status 被意译为「已支付」)
  • prefill 延迟:JSON 12.60s / HTML 10.17s(-19.3%)/ MD 8.27s(-34.4%)
  • 原始结果存 eng_repr_tokens.json
  • 成本外推:token 数为本机实测,输入单价 $0.15/1M token 为公开挂牌价假设值,非本机产出
  • 外部佐证(引用):Skyvern 生产 A/B 报告(2026-06,约 1,100 任务)——单任务成本 $1.22→$1.08(-11.4%)、成功率 59.9%→63.8%(+3.9%)
Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐