1.1B 的体量跑出 70B 的质量:Booking.com 那篇最佳论文到底做了什么

·阅读约12分钟·Evergreen Tools Team

2026 年 10 月 30 日,Amazon Science 把 AWS 与 Booking.com 的案例写成了完整复盘。这篇论文拿了 ACL 2025 工业赛道的最佳论文,从 421 篇投稿里选出来的,标题是《Speed Without Sacrifice》。它做的事情很朴素:把 Llama-3.1 的 70B 老师,蒸馏到一个 1.1B 的 TinyLlama 学生身上,再挂上 Medusa 投机解码头,最终质量追平老师,速度是老师的 10 到 20 倍,成本不到同级别开源模型的三分之一。三个功能都跑在 Booking.com 的真实流量上,不是离线数字。

服务器机架上的推理加速示意

蒸馏压缩每一次前向的成本,Medusa 压缩需要的次数——两个维度相乘。

一、这套流水线先解决了什么问题

旅行平台每天要回答几千万次查询:AI Trip Planner 要在不到一秒内从对话里抽出偏好,Smart Filters 要把一句自然语言实时变成结构化的筛选条件,住宿描述生成要按百万级房源批量产出。这些场景的共同点是:模型质量够用,但每次调用的钱和延迟都不够用。业界的做法通常是二选一——上便宜的小模型、接受质量下降,或者上大模型、把账全付了。这篇论文找的是第三条路:先把体量压下来,再把每次前向计算能吐出的 token 数提上去。

二、为什么两个技巧是乘的,不是加的

标准自回归解码的瓶颈不在算力,在显存带宽:每生成一个 token 都要把整个模型的权重过一遍。知识蒸馏动的是「每一遍有多贵」——模型小了,单位步长的成本就低;Medusa 动的是「需要多少遍」——一次前向并行吐出多个候选 token,再统一校验,接受的保留、拒绝的丢弃。两个维度被同时压缩,收益就近似相乘。这也是这篇论文最有价值的工程洞见:不要问「哪个技巧更好」,先问「我在压哪个维度」。如果你的瓶颈是排队,压模型大小没用;如果你的瓶颈是权重读取,堆解码头也没用。

# Step 1: build the distillation corpus from unlabelled production text.
# The teacher's cost is a one-off; the student's cost is every request forever.
import json

def label_with_teacher(prompts, teacher, out_path="distill.jsonl"):
    kept = 0
    with open(out_path, "w") as fh:
        for p in prompts:
            r = teacher.generate(p, temperature=0.7, max_tokens=512)
            if not r.text.strip():
                continue  # an empty teacher answer teaches nothing
            fh.write(json.dumps({"prompt": p, "completion": r.text}) + "
")
            kept += 1
    print(f"teacher labelled {kept} examples")

# Keep this step boring and reproducible: same prompt set, same sampling
# settings, versioned output file. Your student inherits every mistake here.

三、四步实验,每一步的账都公开

第一轮,只在任务数据上微调 TinyLlama,结果已经明显强于 few-shot 的 GPT-4o——但仍然追不上 70B 老师。第二轮加知识蒸馏,用人工标注和老师标注混合训练,把和老师之间的质量差距补上了 90% 以上,推理同时快了约 6 倍。第三轮挂 Medusa 头,在 6 倍的基础上再快 2 到 3.6 倍。第四轮把两者合在一个流水线里训练,得到的最终模型质量追平老师,速度随用例不同落在 10 到 20 倍之间。注意这个 6 倍:它不是「小模型自动快 6 倍」,而是这个特定任务上、这个特定体量比例下的实测值。

# Step 2: Medusa heads. Base weights frozen; each head predicts a token
# further ahead. Medusa-2 trains base and heads in one stage, which is why the
# authors preferred it for teams already juggling several production models.
import torch.nn as nn

class MedusaHead(nn.Module):
    def __init__(self, hidden: int = 2048, vocab: int = 32000):
        super().__init__()
        self.layers = nn.Sequential(
            nn.Linear(hidden, hidden),
            nn.SiLU(),
            nn.Linear(hidden, vocab),
        )

def build_heads(base, k: int = 4):
    for p in base.parameters():
        p.requires_grad_(False)      # freeze the distilled student
    return nn.ModuleList([MedusaHead() for _ in range(k)])

# At position t, head i is trained against token t+i+1. At inference you run
# the base once, verify the drafted tokens, and keep the longest accepted
# prefix. Speculation is cheap; verification is the bill.

四、Medusa-1 和 Medusa-2 的差别,是运维差别

两种变体测出来的加速差不多,也都没有可感知的质量损失。Medusa-1 是先微调基座、冻结权重、再单独训练解码头;Medusa-2 把基座和解码头放在一个阶段里训练。作者选了 Medusa-2,理由不在分数,在流水线:单阶段训练更简单,而对同时维护多个模型的团队来说,「简单」本身就是可运维性。这条经验比任何一个倍数都更值得抄——你最终要维护的不是一篇论文,是十几条这样的小流水线。

# Step 3: speedup is not a constant, it is a function of acceptance rate.
# Measure it on your own traffic before you promise anyone "10x".
def tokens_per_pass(accept):
    """accept[i] = P(head i accepted | tokens 0..i-1 accepted)."""
    total, prod = 1.0, 1.0          # the base model always commits one token
    for a in accept:
        prod *= a
        total += prod
    return total

for rate in (0.9, 0.8, 0.7, 0.6):
    print(rate, round(tokens_per_pass([rate] * 4), 2))

# 4 heads at 0.9 acceptance ≈ 4.1 tokens per base forward pass.
# At 0.6 it collapses to ≈ 2.3. That gap is why the same technique gave 2x in
# one Booking.com feature and 3.6x in another — and it is an upper bound: the
# verification pass is not free.

五、三个功能的真实结果

Smart Filters 是延迟最敏感的:99% 的查询响应快了 15 倍。AI Trip Planner 的用户参与度相对 GPT-3.5 提升 2.9%。住宿描述生成在成本不到 GPT-4 三分之一的情况下,"有帮助"评价多了 1.4%。这三个数字放在一起看才有意思:前两个买的是延迟,第三个买的是成本。同一个流水线,三个不同的收益方向——所以复现之前你要先确定,你买的是哪一个。

# Step 4: grade the student on the task metric, not on perplexity.
# Perplexity moving tells you the student is close. It does not tell you the
# feature got better.
import statistics

def evaluate(model, cases, metric):
    scores = []
    for c in cases:
        out = model.generate(c["prompt"], max_tokens=c.get("max_tokens", 256))
        scores.append(metric(out, c["expected"]))
    scores.sort()
    return {
        "n": len(scores),
        "mean": round(statistics.mean(scores), 3),
        "p10": round(scores[len(scores) // 10], 3),
    }

# Booking.com went further than offline metrics: the numbers that decided the
# rollout came from live A/B tests — engagement for the trip planner, and the
# share of "helpful" votes on generated accommodation descriptions.

六、想抄的话,按这个顺序来

第一步先量基线:每个功能的 p50、p95、每千次请求成本、以及一个真正和业务挂钩的质量指标。第二步确认瓶颈维度:是显存带宽(模型太大)还是串行步数(token 太慢)。第三步才是做蒸馏,用未标注的线上文本让老师打标,这一步的钱是一次性的。第四步挂解码头,同时把接受率当成一等指标持续监控,因为加速倍数几乎完全由它决定。第五步用线上 A/B 而不是离线相似度决定是否放量。

# Step 5: cost per thousand requests, then the break-even.
def cost_per_1k(tokens_in, tokens_out, in_price, out_price, requests=1000):
    return requests * (tokens_in * in_price + tokens_out * out_price) / 1_000_000

teacher = cost_per_1k(600, 220, 2.50, 10.00)
student = cost_per_1k(600, 220, 0.05, 0.15)
print("teacher", round(teacher, 2), "vs student", round(student, 2), "per 1k requests")
print("ratio", round(teacher / student), "x cheaper")

# Then add the one-off: teacher labelling + GPU hours, divided by monthly
# request volume. If the break-even lands past the feature's expected life,
# the honest answer is that you should not build it yet.
曲线图展示延迟与成本随接受率变化

同样的技术在一个功能上给 2 倍,在另一个上给 3.6 倍——差别全在接受率。

📌 常见问题 FAQ

这个 20 倍加速能直接套到我的场景吗?

不能。论文给的是 10 到 20 倍,具体取决于用例,而加速倍数主要由投机解码的接受率决定。接受率受任务的可预测性影响:结构化输出、模板化文案往往接受率高,开放式创作接受率低。你必须在自己的流量上量接受率,再推算加速,而不是抄一个倍数。

知识蒸馏会把老师的缺点一起学过来吗?

会。学生是在老师的输出上训练的,老师的偏好、格式习惯、甚至错误都会被继承。所以论文里的评测不只看与老师的相似度,还看线上真实指标——参与度和"有帮助"评价。复制这套流水线时,离线阶段至少要加一层人工抽检。

1.1B 的学生能替代 70B 老师做所有事吗?

论文的范围是三个明确定义的旅行场景任务,不是通用能力。蒸馏本质上是把能力压到特定任务分布上,任务之外的行为会明显退化。如果你的产品需要宽泛的开放域能力,正确做法是路由:把可预测的流量交给小模型,把长尾交给大模型。

为什么作者选 Medusa-2 而不是分数更高的 Medusa-1?

因为两者测出的加速相当、都没有可感知的质量损失,而 Medusa-2 是单阶段训练,流水线更简单。对同时维护多个模型的团队来说,少一个阶段、少一次权重冻结与切换,意味着更少的运维事故。

这篇论文是新的吗?为什么现在才看到复盘?

论文在 ACL 2025 工业赛道拿了最佳论文,AWS 在 2026 年 10 月 30 日通过 Amazon Science 发布了完整的生产复盘,把实验顺序、每个阶段的倍数、以及三个功能各自的线上结果都写了进去。对做推理成本优化的人来说,这份复盘比论文正文更好用。