Together AI 推出端点级 LLM A/B 实验功能
A/B test models in production
Together AI 在平台上推出端点级 A/B 实验功能,实验挂在端点上,声明一个 control 和一个或多个 variant,各自指向一个部署并设置百分比,总和须为 100%。
原文给出端点级 A/B 实验的完整机制与实测流量数据,可据此判断生产环境模型灰度对比的落地方式。
在生产环境中为 LLM 实施 A/B 测试
迟早每个团队都会想回答同一个问题:与当前模型相比,新模型对我们的用户真的更好吗?不是基准测试上的更好,而是在留存率、点赞率、任务完成率,或者你的产品实际衡量的任何指标上的更好。
影子流量无法回答这个问题。影子测试只能告诉你候选模型在运维层面是可靠的——延迟、错误、吞吐量都没问题,但它的响应会被丢弃;没有任何用户会基于这些响应采取行动。质量问题需要让最终用户真正接触到,让一部分用户使用模型 B,然后比较发生了什么。
通常团队会在应用层自己构建这套机制,使用以下一些组合:
- 在客户端代码中使用功能开关或对用户 ID 进行 hash-mod-100。
- 客户端在两个端点(或两个硬编码的模型字符串)之间切换。
- 某个地方放着一张电子表格,解释 A 组与 B 组的含义。
这能行,但它以伤害后续工作的方式将实验与基础设施纠缠在一起:路由逻辑随应用一起发布,随着客户端缓存决策,队列划分可能会漂移,而且即使在实验“结束”之后,分支代码仍会长期存在,因为没人确定删除它是否安全。
Together AI 平台允许你在端点级别运行 A/B 实验逻辑。
工作原理
一个 A/B 实验附加到一个端点上,并声明成员,其中恰好有一个对照组和一个或多个变体,每个都指向一个部署,每个都有一个 percent 设置,这些设置之和必须为 100,控制流量路由。

端点路由器的工作方式是,每当基础流量向对照组发送请求时,实验会在各分支之间重新采样并重新分配,使得 95% 留在对照组,5% 流向变体。
准确说明其机制:实验会细分对照组在基础流量拆分中所占的份额。路由首先通过权重拆分解析请求;当胜出者是一个 A/B 实验的对照组时,请求会按实验各分支的百分比在它们之间重新采样。由于对照组是拆分成员百分比中的唯一入口,因此这些百分比就是绝对流量份额。同样值得注意的是,拆分权重为零的对照组不会给实验留下任何可细分的内容,因此整个实验将不会收到任何流量。

重要的是,变体部署不得出现在端点的流量拆分中,平台要求变体权重为零;只有对照组存在于基础拆分中。实验将完全拥有路由到变体的流量;其百分比就是其流量份额。如果变体也能从拆分中获取按容量加权的流量,你的测量结果就会在不知不觉中出错。一种理解方式是,你应该像设置影子部署那样设置变体:创建、READY、权重为零,然后让实验百分比设置将流量路由到它。
这里的另一个重要点是,A/B 百分比是真正固定的流量份额,总和为 100%,并且与副本数量无关。我们特意让它与流量拆分权重(按就绪副本计算并随容量变化)不同。实验是一种测量工具;你希望在你测量时拆分保持恒定,而不是随自动扩缩容而漂移。
创建一个 95/5 实验:
tg beta endpoints ab my-org/candidate-model --control $CONTROL_DEPLOYMENT --percent 5
你的客户不会注意到这个实验,因为从表面上看,相同的端点名称、API 和密钥都保持不变。在后端,现在将有 5% 的请求由变体候选者来应答。
底层机制:逐步放量、度量、结束
逐步放量就是重新发送成员集合
没有单独的“放量”API,一次更新会替换整个成员列表,这保持了心智模型的简单(实验始终恰好等于其成员所声明的内容),并使每次放量都成为一次显式、可审查的变更:
# Week 2: candidate looks good at 5% —> go to 20%
client.beta.endpoints.ab_experiments.update(
id=experiment_id,
endpoint_id=endpoint_id,
update_mask="members",
etag=experiment.etag, # a teammate's concurrent ramp gets rejected, not overwritten
members=[
{"deployment_id": control_dep, "percent": 80, "role": "AB_EXPERIMENT_MEMBER_ROLE_CONTROL"},
{"deployment_id": variant_dep, "percent": 20, "role": "AB_EXPERIMENT_MEMBER_ROLE_VARIANT"},
],
)
更新受 etag 保护,因为如果在你编写更新时队友对实验进行了放量,你的更新将被拒绝,而不是静默覆盖他们的更新。

有了这个 API 设计,你仍然需要做出常见的流量暴露选择:将多少流量路由到 B 组:
| 拆分 | 信号速度 | 风险 | 适用场景 |
|---|---|---|---|
| 95/5 | 慢(需要流量/时间) | 最小 | 新模型,首次真实暴露 |
| 80/20 | 中等 | 可控 | 候选者通过了 5%;你想要更显著的读数 |
| 50/50 | 最快 | 你一半的用户 | 两个已知良好选项之间的后期确认 |

最多支持 20 个变体成员,你还可以运行多路测试,比如说你想尝试一个全精度端点以及另外三个量化变体(V1、V2、V3)。只要百分比总和仍为 100% 且恰好有一个对照组,这就会按预期工作。

度量:将平台指标与你的产品指标关联起来
每个请求都由特定的部署来服务,而每个平台指标都按部署提供——因此,比较的基础设施侧(每个队列的延迟、错误、吞吐量)只是一个筛选条件,而不是一个项目。产品侧则归你负责:记录每个响应由哪个部署服务(它在响应元数据中),并将其与你的质量信号——评分、重试、任务完成——一起记录,而关联键就是部署 ID。平台刻意不去猜测你的质量指标;它让归因变得轻而易举,这样你的分析系统就能做出评判。
结束:提升,然后删除
假设实验显示变体胜出。结束它分为两步:
- 通过 rollout 提升。从对照组部署到变体部署运行一次蓝绿 rollout。健康门禁、传播等待和回滚安全措施都适用。
- 删除实验。所有实验路由都会消失,100% 的流量随后遵循端点的基础流量拆分,而在 rollout 之后,该拆分指向你的胜出者。

而如果变体落败,你只需删除实验,流量就会完全回到对照组。在后端,变体部署会缩容到零或被删除。
tg beta endpoints rm abx_abc123 # delete the experiment; 100% returns to the base split
tg beta endpoints rm dep_variant123 # or delete the variant — auto-unwinds experiment + split
边缘情况
变体在实验中途性能下降。影响范围有多大?
仅限其队列。部署是独立监控和独立自动扩缩的,这意味着一个表现不佳的变体不会拖垮对照组。要解决这个问题,你可以重新发送不包含该故障变体的成员集合,其用户将在一定的传播时间后回到对照组。这也是从 5% 开始是个好主意的原因。
观察到的份额真的与配置的百分比相符吗?
在有意义的流量规模下,是的!要了解实际运行的例子,请查看下面的实验。在较小的窗口内,你可以预期会有采样噪声:1,000 个请求中的 5% 份额是一个小样本。如果你观察到的份额偏差很大并且持续偏差,请检查上面的设置规则。
A/B 实验和 rollout 可以在同一个端点上运行吗?
是的,组合顺序定义为:路由首先解析基础拆分,然后是 A/B 实验(细分对照组的份额),接着是 rollout(在源部署与其 rollout 目标之间重新采样)。平台仍然对每个端点强制实施一个活动 rollout,但这些阶段被设计为在重叠时能够组合。
队列是按用户固定的吗?
分配使用请求的采样键,例如请求体中的顶层 prompt_cache_key 或 user 字段,因此携带相同键的请求会一致地路由,给定用户可以在整个会话中保持在一个测试分支中。没有键的请求实际上按请求随机分配(我们下面的实验测量使用了无键流量,这就是为什么观察到的份额与百分比如此接近)。如果按用户一致性对你的研究很重要,特别是如果你有多轮质量比较,请发送一个稳定的 user 字段。
拆分下的自动扩缩容会发生什么?
每个成员部署根据自己的策略进行扩缩容,按其自身的流量份额确定规模。一个 5% 的变体,边界为 1-2,一个 95% 的对照组,边界为 2-8,是完全正常的形态。你应该独立观察每个队列的副本数,这就是我们在下图中捕获的内容。
展示一个 A/B 实验,从开始到结束
我们在一个实时端点上运行了完整的生命周期,我们以 95/5 创建,逐步调整到 80/20,再调整到 50/50,然后删除。我们这样做时保持稳定的 3 RPS 流,每个请求都归属于服务它的部署。下图显示了变体上看到的流量,绿色点捕获了变体流量份额:

以下是每个阶段配置的与观察到的流量份额:
| 配置(对照组 / 变体) | 观察到的 | 请求数 |
|---|---|---|
| 95 / 5 | 95.3 / 4.7 | 1,330 |
| 80 / 20 | 79.2 / 20.8 | 1,348 |
| 50 / 50 | 50.2 / 49.8 | 1,348 |
| 已删除 | 100.0 / 0 | 360 |
运行中值得指出的三个细节:
- 每次调整实际上只是一次调用,你可以用当前的
etag重新发送完整的成员集。etag 在两次调整中推进1 → 2 → 3;过期的 etag 会被拒绝,而不是静默覆盖队友的更改。 - 传播很快但不是即时的。我们在每次更新后等待约 75 秒再进行测量;路由层以与流量拆分更改相同的 30–60 秒时间尺度获取实验更改。
- 删除:移除实验后,我们发送了 360 个连续请求,它们全部落在对照组上。除了删除留下的需要展开的内容外,没有遗留的队列逻辑。
以下是控制台在端点的流量测试选项卡上显示的实验(A/B 测试和影子测试共享该页面):

自己试试!
你需要一个带有正在服务流量的对照组部署的端点,加上一个候选部署(已创建,READY,不在流量拆分中)。然后:
- 以 95/5 创建实验。
- 让它运行直到你有足够的量——5% 的全部意义在于在你收集数据时保持低曝光。
- 当数据表明时,用一次更新进行调整。当结果决定时,用 rollout 进行提升。完成后删除——没有其他需要清理的。
📚 文档:专用模型推理 → A/B 测试
来源:Together AI Blog · together.ai