想象你是一家生意火爆餐厅的主厨。店里的招牌菜是“老配方红烧肉”(我们叫它模型A),熟客们闭着眼都能点这道菜,每天雷打不动地卖出上千份。某天你潜心研究出“新配方红烧肉”(模型B),据说更嫩、更香、更入味。你当然想把它端上桌,可万一客人觉得口感不对,招牌砸了怎么办?聪明的做法从来不是“全换”或“不换”,而是先让一小部分客人试吃:70% 的客人仍然吃老配方,30% 的客人尝新配方,同时悄悄记录两拨人的反应——谁吃得更开心、谁抱怨得更少、厨师的出菜速度有没有变化——最后根据这些真实反馈,决定新配方是一炮而红全线推广,还是赶紧撤下来回炉重造。这就是模型A/B部署的一天。它不是什么高深魔法,而是一套“让新模型在真实流量里安全试吃”的工程方法:流量切分决定谁尝新、请求镜像复刻真实订单给新后厨试做、结果收集记录口碑、部署监控盯着后厨别起火、自动切换在数据说话时果断拍板。

岗位关联

对 AI 从业者而言,A/B 部署是模型上线前的“安全气囊”——掌握它,你就能让模型带着真实流量验证价值,同时把事故风险压到最低,这是 MLOps 工程师的核心竞争力。

目录
  1. 可视化演示
  2. AB测试部署概览
  3. 基础设施搭建
  4. 流量切分路由
  5. 请求镜像复制
  6. 结果收集分析
  7. 部署监控告警
  8. 自动切换回滚
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

交互展示模型A/B部署的工程化实现:流量切分、请求镜像、监控对比和自动切换机制。

本节配有交互动效(进入视口后自动加载,约 3KB)。

流量切分:请求路由 负载均衡器将请求按比例分配到模型A和B 支持加权随机、轮询等多种切分策略 Request → Router → Model A/B 流量切分实现灰度发布和对比测试

AB测试部署概览

假设你是一家餐厅的运营负责人,正面临“要不要把招牌菜换成新配方”的抉择。你心里清楚:数据不会说谎,但拍脑袋会。于是你定下规矩——用真实顾客的反应来做决定。你要先想清楚三个问题:想验证什么(新配方是不是更好吃)、在什么场景验证(哪些菜可以冒险)、怎么验证(让多少人试吃)。这就是 A/B 部署的起点。在机器学习世界里,模型 A 是当前线上稳定运行的“老配方”,模型 B 是你的候选“新配方”,A/B 部署的目标就是在真实请求流量中,为两个模型搭建一个公平的“试吃擂台”。

从工程视角看,A/B 部署不是简单的“两个服务都跑起来”,而是一套完整流程:切流(把部分请求引给 B)、镜像(复刻请求给 B 练手)、收集(记录两边表现)、监控(盯住别出事)、切换(用数据拍板)。常见的部署模式有两种:影子模式(shadow mode)指 B 只接收镜像流量,结果不对外返回,相当于后厨试做、不端上桌;金丝雀模式(canary)指 B 真实承接一小部分线上流量,相当于真让一小桌客人尝新菜。两种模式可以衔接使用:先影子热身,再金丝雀小范围试吃。

无论哪种模式,都必须守住一条铁律:切给 A 和 B 的流量加起来要等于总流量。一个请求要么去 A 要么去 B,绝不能“凭空蒸发”或“两边都算”。

T=TA+TBT = T_A + T_B

1. 总流量 T:单位时间内进入系统的全部线上请求数,是整个实验的“分母”,相当于餐厅当天接待的全部顾客。

T=TA+TBT = T_A + T_B

2. A 版本流量 T_A:被切分到旧模型 A 的请求量,继续服务绝大多数熟客。

TA=pA×TT_A = p_A \times T

3. B 版本流量 T_B:切给新模型 B 的请求量,在真实压力下接受检验。

TB=pB×TT_B = p_B \times T

4. 守恒约束:二者之和必须等于总流量,任何“凭空消失”的请求都意味着切分逻辑有 bug。

pA+pB=1p_A + p_B = 1

回到餐厅的比喻:你定下“七成老配方、三成新配方”的规矩后,服务员就要保证每一位进门的顾客都能吃到菜,且只吃一种配方。流量守恒,是这场试吃实验的底线。

基础设施搭建

要给新配方红烧肉试吃,光有配方不够,你得先保证后厨能同时开工:老灶台不停火,新灶台也架起来,两套锅碗瓢盆不能互相污染。在 MLOps 里,这叫基础设施搭建——让 A、B 两个模型版本在同一套服务架构里“和平共处”。

首先是服务架构。A 和 B 通常是同一个服务进程的两个不同版本,它们共享上层的 API 网关和下游的数据库,但模型推理部分各自独立。其次是服务注册发现:A 和 B 启动后都要到注册中心“签到”(比如 Consul、Nacos),网关才能知道此刻有哪些版本可用、各自的地址是什么。第三是配置管理:切流比例、镜像采样率这类参数不能写死在代码里,而要放到配置中心(比如 Apollo),改配置就能动态调权重,就像餐厅改菜单上的供应比例,不用重新装修厨房。

还需要环境隔离。新模型可能是实验性代码,它读的配置、写的日志、占用的资源,都不能和老版本纠缠不清。工程上通常用独立的命名空间、独立的日志目录、独立的指标上报通道来隔离。否则一旦 B 模型写坏了共享的状态,A 模型会被无辜连累——这就像新厨师的备用锅,绝不能和主厨的锅混着刷。

基础设施稳不稳,有一个硬指标:可用性。所谓可用性,就是系统在统计周期内“正常营业”的时间占比,可用性越高,顾客吃闭门羹的概率越低。

A=1−DTA = 1 - \frac{D}{T}

1. 总时间 T:统计周期内承诺提供服务的时间长度,可以是一天、一月或一年。

T=统计周期T = \text{统计周期}

2. 故障时长 D:服务不可用(宕机、超时、拒绝服务)的累计时间。

D=∑故障时间片段D = \sum \text{故障时间片段}

3. 可用性 A:1 减去故障占比,得到“正常营业”的比例。

A=1−DTA = 1 - \frac{D}{T}

4. SLA 目标:A = 0.999 表示 99.9% 可用,一年最多允许约 8.76 小时宕机。

A≥0.999A \geq 0.999

基础设施就像后厨的灶台和管道,顾客看不到,但一旦漏水全店遭殃。把 A、B 两个版本的注册、配置、隔离做好,你的“双灶台”才能稳稳支撑后面的切流实验。

流量切分路由

试吃规则定好了,接下来服务员要面临一个实际问题:怎么把客人引到两个窗口?流量切分,就是模型 A/B 部署里最关键的“领位员”。它决定了哪些请求去 A、哪些去 B,以及各自占比多少。

切分方式有三类。第一类是按权重随机:每个请求来的时候,生成一个随机数,比如小于 0.7 去 A,否则去 B,最终比例会在大数定律下收敛到 70/30。这种方式最简单,但同一个用户两次请求可能被分到不同版本,体验不一致。第二类是按用户维度路由:以用户 ID 的哈希值做切分,保证同一个用户始终被分到同一版本,避免用户“上一秒吃新配方、下一秒吃老配方”的错乱感。第三类是按规则路由:比如只切分白名单用户、只切分特定地域的请求、只切分 1% 的边缘流量,用于小范围灰度验证。

具体实现时,最常用的技术是一致性哈希。它把用户 ID 映射到一个哈希环上,A 和 B 各占据环上的一段弧,请求落在哪段就路由到哪个版本。这样当你要调整切分比例时,只需要移动环上的节点位置,绝大多数用户的归属不会变化,只有边界上的少量用户会被“搬移”。权重策略的本质,是把每个版本配置的“权重值”换算成真正的流量比例:

Pi=wi∑j=1nwjP_i = \frac{w_i}{\sum_{j=1}^{n} w_j}

1. 版本序号 i:当前要计算比例的版本,比如 A 或 B。

i∈{A,B}i \in \{A, B\}

2. 权重 w_i:每个版本配置的流量权值,相当于后厨给新窗口安排的服务员人数。

wA=7,wB=3w_A = 7, \quad w_B = 3

3. 权重总和:所有版本权值相加,得到分母。

∑j=1nwj=wA+wB\sum_{j=1}^{n} w_j = w_A + w_B

4. 归一化概率:权值除以总和,得到真正的流量比例。

PA=710=0.7,PB=310=0.3P_A = \frac{7}{10} = 0.7, \quad P_B = \frac{3}{10} = 0.3

回到餐厅:权重随机就像门口随机发号,哈希路由就像按桌号固定窗口,规则路由就像“只让会员试新菜”。无论用哪种方式,核心原则都是——让两边承接的流量既真实、又可解释、还能随时调整。

请求镜像复制

试吃还有一个更稳的玩法:与其让真客人冒险吃新配方,不如在老配方的每一道菜上桌之前,顺手多盛一小勺端给新后厨试做。新后厨做完不端给顾客,只是自己练手并且记录“如果是我做,我会做成什么样”。这就是请求镜像(request mirroring)——把真实请求复制一份发给模型 B,B 的结果只用于评估,不返回给用户。

镜像原理很简单:网关收到请求后,除了正常转发给 A,再异步复制一份同样的请求发给 B。对用户来说,线上响应完全由 A 决定,体验不受任何影响;对 B 来说,它却在拿最真实的生产数据练兵。流量复制有两种实现:一种是在网关层旁路复制(比如 Nginx mirror、Envoy shadow),另一种是在服务内部订阅消息队列里的请求流,把符合条件的请求再投递一份给 B。

采样策略决定复制多少:不是所有请求都要镜像,否则 B 后厨会累垮。采样率就是“每几个请求复制一个”:

r=NmNtr = \frac{N_m}{N_t}

1. 镜像请求数 N_m:复制出来发给模型 B 的请求份数。

Nm=被复制的请求数量N_m = \text{被复制的请求数量}

2. 总请求数 N_t:进入系统的全部真实请求数。

Nt=全部线上请求数量N_t = \text{全部线上请求数量}

3. 采样率 r:二者相除,得到复制的比例。

r=NmNtr = \frac{N_m}{N_t}

4. 取值含义:r = 1 表示全部复制,r = 0.2 表示每 5 个请求复制 1 个。

r=0.2⇒Nm=0.2×Ntr = 0.2 \Rightarrow N_m = 0.2 \times N_t

镜像请求也要注意数据隔离:镜像产生的推理日志、指标数据要打上独立的标记(比如 is_mirror=true),不能被当成真实流量统计,否则会把 A/B 的结果比较搅浑。这就像试做菜的记录要单独记在一本小本子上,不能混进顾客的结账单里。请求镜像的价值,在于让 B 模型零风险地接触真实数据分布,在正式上场前先摸清敌人火力。

结果收集分析

试吃搞了几天,你必须回答一个问题:新配方到底赢没赢?这就靠意见反馈系统了。你得在顾客吃完后收集反馈:给了多少小费、有没有加菜、有没有皱眉。对应到模型部署里,就是结果收集与指标分析,它是整个 A/B 实验的“记账本”。

埋点设计是第一步。每个请求从进入系统到返回结果,都要记录三段信息:版本标识(这个请求由 A 还是 B 处理)、请求上下文(用户 ID、请求参数、耗时)、业务结果(是否下单、是否点击、是否转化)。这些数据通常通过日志或消息队列(Kafka)上报,再落入数据仓库。指标定义要提早想清楚,常见的线上指标有:响应延迟 P99、错误率、QPS,以及业务侧的转化率、点击率、成单率。业务指标才是最终裁判——模型再准,如果用户不买账也白搭。

数据收集上来后,要通过聚合计算得到可比较的结论。最常用的指标是提升度(lift):它衡量 B 相对 A 的效果增幅。比如 A 的转化率是 5%,B 是 6%,提升度就是 20%。

lift=pB−pApA\text{lift} = \frac{p_B - p_A}{p_A}

1. 转化率 p_A:A 版本处理请求后产生业务转化的比例,是实验的基线。

pA=A 版本转化次数A 版本请求总数p_A = \frac{\text{A 版本转化次数}}{\text{A 版本请求总数}}

2. 转化率 p_B:B 版本同样口径下的转化比例。

pB=B 版本转化次数B 版本请求总数p_B = \frac{\text{B 版本转化次数}}{\text{B 版本请求总数}}

3. 分子差值:B 减去 A,得到绝对增幅。

pB−pAp_B - p_A

4. 相对提升度:差值除以基线 p_A,得到相对增幅,便于对比不同基线下的实验。

lift=pB−pApA\text{lift} = \frac{p_B - p_A}{p_A}

最后是存储与检索:指标要按时间维度聚合,画成趋势曲线,让工程师一眼看出 B 是否稳定优于 A。这就像餐厅把每天的试吃意见卡录入系统,按天、按周、按窗口维度对比。结果收集做得越细,后续的“切换决策”就越有底气——数据从来不会骗人,前提是你真的把它记下来了。

部署监控告警

新配方试吃进入第二周,你不可能 24 小时守在店里盯着每一位客人的表情,你需要一套监控系统替你盯着:后厨有没有起火、出菜有没有变慢、顾客有没有掀桌。部署监控,就是 A/B 实验里“不眨眼的值守人”。

健康检查是最基础的一层:每隔几秒对 A、B 两个服务发送探活请求(比如 HTTP /health 接口),检查进程是否存活、依赖的数据库是否连得上。如果 B 挂了,网关要自动把它从路由候选里摘除,避免把流量打进黑洞。性能监控则关注更细的指标:响应延迟的 P50/P95/P99、QPS 变化、内存与 GPU 利用率。B 模型如果推理特别慢,即使效果再好也不能全量上——就像新菜再好吃,出菜要等一小时,客人也会掀桌。

阈值告警是监控的“警报器”。每个指标都要设上下限,比如错误率超过 5% 立即告警、P99 延迟超过 800ms 立即告警。告警一旦触发,值班工程师的钉钉、电话就会被轰炸。错误率是其中最敏感的指标:

E=FTE = \frac{F}{T}

1. 失败请求数 F:返回 5xx 错误、超时或异常的结果数量。

F=错误响应请求数F = \text{错误响应请求数}

2. 总请求数 T:监控周期内进入服务(或某个版本)的全部请求数。

T=监控周期内请求总数T = \text{监控周期内请求总数}

3. 错误率 E:失败请求占总请求的比例。

E=FTE = \frac{F}{T}

4. 告警阈值:E 超过阈值(如 0.05)触发告警,此时应暂停向该版本切流量。

E>0.05⇒触发告警E > 0.05 \Rightarrow \text{触发告警}

最后把所有指标汇聚到可视化面板(如 Grafana),A、B 两组数据并排展示,切换比例的旋钮旁边就是实时健康状态。这就像餐厅主管的巡店平板:大厅翻台率、后厨出菜速度、顾客差评数一目了然。监控不是给人看的,是给“决策”看的——没有监控的 A/B 实验,等于蒙着眼睛过马路。

自动切换回滚

试吃一个月,数据终于说话了。现在你要做最后一步决策:新配方能不能全面上线?自动切换机制,就是那个“敢拍板、又能反悔”的餐厅总经理。

评估决策不能只看 lift 是正数就切,还要看样本量够不够、指标是否显著、B 的错误率和延迟是否达标。工程上通常设定一组条件,全部满足才允许切换:提升度超过某个阈值 θ、错误率低于安全上限、延迟不劣化。如果有任何一条不满足,就继续保持灰度,或直接回滚。

自动切换是指通过控制面(配置中心)一键修改路由权重,将 B 的流量从 30% 逐步放大到 50%、80%、100%。这个过程叫灰度发布:每次放量后停留一段时间,观察监控面板,确认安全后再继续放量。最后一步是快速回滚:上线后如果发现问题,必须能在几分钟内把流量全部切回 A。这要求旧版本始终保活——A 的服务节点不能因为 B 全量上线就被立刻销毁,至少要保留足够算力兜底。切换的决策逻辑可以写成:

S=(lift>θ)∧(EB<Eth)S = (\text{lift} > \theta) \wedge (E_B < E_{th})

1. 提升度条件:B 相对 A 的提升度必须显著超过设定的门槛 θ(如 θ = 0.1)。

lift>θ\text{lift} > \theta

2. 错误率条件:B 版本自身的错误率必须低于可接受的安全上限 E_th。

EB<EthE_B < E_{th}

3. 逻辑与操作:两个条件用逻辑与(∧)连接,缺一不可。

S=(条件一)∧(条件二)S = (\text{条件一}) \wedge (\text{条件二})

4. 决策结果:S = 1 表示可以全量切换,S = 0 表示继续灰度或回滚。

S∈{0,1}S \in \{0, 1\}

回到餐厅:新菜连续一个月好评率碾压老配方、而且后厨没出过任何差错,你才敢把菜单全面换新;换新后如果有三位客人同时投诉,你立刻翻出老配方重新开火。自动切换的本质不是“相信新模型”,而是“留好退路”。能随时回滚的上线,才是真正安全的发布。

AI 应用场景

电商推荐系统的双版本模型 A/B 部署

阶段:模型上线与灰度发布

解决的问题:推荐团队训练了新的排序模型 B,但不确定它能否在真实流量中胜出。传统做法是直接全量替换,结果一旦 CTR 下降,损失惨重且难以及时排查。通过 A/B 部署,将 10% 的推荐流量切给 B,并全量收集两组的点击、下单数据,用提升度决定是否放大流量。

具体案例:某电商平台要上线“双十一”新推荐模型。团队在流量入口配置一致性哈希路由,将新模型承接的流量从 5% 逐步提升到 30%,同时镜像 50% 的请求给新模型做离线评测。两周后数据显示新模型 CTR 提升 15%、延迟持平,团队通过配置中心一键将流量切到 100%,并保留旧模型节点观察三天后回收。

金融风控模型的灰度发布与自动回滚

阶段:模型监控与自动运维

解决的问题:风控模型误杀率每升高 0.1%,都会造成大量用户投诉和业务流失。新模型必须在不影响稳定性的前提下逐步承接流量,一旦错误率飙升必须秒级回滚。

具体案例:某支付平台升级反欺诈模型 B。部署团队将 B 以金丝雀模式上线,承接 5% 的真实交易检测流量,监控面板实时对比 A、B 的误杀率和响应延迟。上线首日 B 的误杀率异常升高,触发阈值告警,控制面自动将流量全部切回 A,整个过程不到 3 分钟,业务无感。

语音识别服务的请求镜像离线评估

阶段:模型评估与数据回流

解决的问题:新语音识别模型推理耗时较高,直接承接线上流量有风险。团队采用影子模式,把生产环境全部请求按 20% 采样率镜像给 B,B 的结果只写入离线评估库,不返回用户,从而在零风险下评估新模型在真实口音、噪声分布上的表现。

具体案例:某智能客服的语音转写服务想升级模型。平台在网关侧配置 mirror = 0.2,每日自动复制约 10 万条真实录音请求给 B 模型做识别,结果与 A 模型的转写文本离线对比 WER(词错误率)。一个月后 B 在 WER 上领先 8%,团队才放心让它承接真实流量。

Python 演示

python64 行
"""Model A/B deploy demo: traffic split, request mirror, result collect."""import random  class ABDeploy:    """Simulate an online A/B deployment service."""     def __init__(self, weight_a=0.7, mirror_rate=0.3):        self.weight_a = weight_a        self.mirror_rate = mirror_rate        self.count_a = 0        self.count_b = 0        self.mirror_count = 0        self.latency_list_a = []        self.latency_list_b = []        self.error_count = 0        self.total = 0     def receive_request(self, latency_a, latency_b):        """Receive one request and route it by weight, then mirror."""        self.total += 1        if random.random() < self.weight_a:            # route to version A            self.count_a += 1            self.latency_list_a.append(latency_a)            # mirror a copy to version B with sampling rate            if random.random() < self.mirror_rate:                self.latency_list_b.append(latency_b)                self.mirror_count += 1        else:            # route to version B            self.count_b += 1            self.latency_list_b.append(latency_b)        # simulate small error probability for monitoring        if random.random() < 0.01 and self.total > 0:            self.error_count += 1     def collect_result(self):        """Aggregate metrics from A and B for decision making."""        avg = lambda xs: sum(xs) / len(xs) if xs else 0.0        return {            "traffic_a": self.count_a,            "traffic_b": self.count_b,            "mirror_count": self.mirror_count,            "latency_a": round(avg(self.latency_list_a), 4),            "latency_b": round(avg(self.latency_list_b), 4),            "error_rate_a": round(self.error_count / max(self.total, 1), 4),        }  def main():    deploy = ABDeploy(weight_a=0.7, mirror_rate=0.3)    for _ in range(2000):        # B model is assumed faster: latency_b < latency_a        deploy.receive_request(latency_a=0.15, latency_b=0.10)    result = deploy.collect_result()    for key, value in result.items():        print("{}: {}".format(key, value))    if result["latency_b"] < result["latency_a"]:        print("decision: B latency is lower, expand B traffic")  if __name__ == "__main__":    main()
目的:用 2000 个模拟请求演示 A/B 部署的完整闭环——权重切分、镜像采样、指标聚合,最后给出“是否扩大 B 流量”的决策建议。
  1. def __init__(self, weight_a=0.7, mirror_rate=0.3):初始化时配置 A 的流量权重和镜像采样率,这是部署的“旋钮”。
  2. if random.random() < self.weight_a:流量切分的核心逻辑:随机数小于权重就去 A,否则去 B。
  3. self.count_a += 1版本 A 的请求计数器,对应结果收集中的“版本标识”。
  4. if random.random() < self.mirror_rate:请求镜像:按采样率把 A 的请求复制一份给 B。
  5. self.mirror_count += 1统计镜像请求数量,用于评估镜像带来的算力开销。
  6. avg = lambda xs: sum(xs) / len(xs) if xs else 0.0聚合计算平均延迟,对应监控中的性能指标。
  7. "error_rate_a": round(...)错误率指标,用于触发告警和切换决策。
  8. if result["latency_b"] < result["latency_a"]:评估决策环节:用收集到的结果判断是否扩大新版本流量。

常见误区

误区我以为把 A 和 B 两个服务进程都启动,然后让它们各监听一个端口,A/B 测试就算搞好了。用户请求具体打到谁,听天由命。

正解A/B 部署的核心不是“两个服务在跑”,而是“流量可控地分流”。你必须通过网关或路由层明确定义切分策略——权重多少、按什么维度切、谁被切到 B,并且要能动态调整。就像餐厅里不是你同时开两个厨房就行,而是要有领位员按规矩把客人分到对应窗口,还能随时改规矩。

为何常见:因为“部署”两个字容易让人只想到把进程拉起来,忽略了“流量调度”才是 A/B 实验真正的灵魂。

误区既然镜像能帮新模型收集真实数据,那就把所有请求都复制一份给 B,数据越多评估越准,反正 B 的结果不返回用户。

正解全量镜像会让 B 的算力消耗翻倍,而且镜像数据高度冗余——同一时段的请求分布高度相似,增加的数据量对评估精度的边际贡献很小。工程上通常用采样率控制成本,比如 20% 或 50%。另外镜像数据必须打标隔离,否则统计口径混乱反而毁掉实验。

为何常见:人天然觉得“数据越多越好”,忽略了算力成本和统计上的边际递减效应。

误区我让开发在服务里 print 了几行日志,把请求参数和响应结果打出来,就算完成结果收集了,后面要分析时再翻日志。

正解A/B 结果收集需要结构化的埋点体系:每个记录必须包含版本标识、请求上下文、业务结果、时间戳,并且通过消息队列实时上报、按指标口径做聚合。零散打日志既无法保证版本标识正确,也无法支撑实时的指标计算和告警。就像顾客意见卡如果只随便扔进一个盒子,月底根本没法统计新配方到底赢没赢。

为何常见:写日志看起来最省事,让人误以为“记录了”就等于“可分析了”,实际上没有结构和口径的日志是一堆数字垃圾。

误区系统都“自动”了,指标达标会自动切流量,出错会自动回滚,那运维工程师可以完全放手了。

正解自动切换的决策逻辑(阈值、统计显著性、切换时机)本身必须由人精心设计,而且切换后还需要人工确认灰度放量的节奏、观察业务指标的波动。自动化的作用是“快速执行既定策略”,而不是“替人做判断”。更重要的是,任何自动切换都必须配套一键人工回滚的逃生通道。

为何常见:人们对“自动”二字有天然的信任感,却忘了自动化系统本身也需要被监控和治理。

面试题

Q1. 模型A/B部署中,流量切分的第一步通常是什么?

单选题 · easy

  • A.立即将全部流量切换到新模型
  • B.确定切分比例和路由策略← 正确答案
  • C.删除旧模型的所有服务节点
  • D.停掉监控和告警服务

答案:B。A/B 部署的第一步是设计切分方案——定权重、选路由维度(随机/哈希/规则)。B 与 A 的关系类似“让多少客人试新菜”,要先定好规矩再执行。A 选项直接全量切换违背了灰度原则;C 删除旧节点会使回滚失去退路;D 关闭监控是在实验中“蒙眼开车”,都不对。

Q2. 请求镜像最恰当的比喻是什么?

单选题 · easy

  • A.把客人全部赶到新窗口
  • B.让老顾客顺便尝一口新菜← 正确答案
  • C.撤掉老菜单换成新菜单
  • D.厨师辞职换新人来掌勺

答案:B。请求镜像复制真实请求给新模型,但结果不返回用户——就像给老顾客上老菜时,多盛一小勺到新后厨试做,顾客实际吃的仍是老菜。A 是全量切换、C 是彻底替换、D 是换人,都不符合“复制一份但不影响线上”的核心特征。

Q3. 一致性哈希在流量切分中的主要作用是什么?

单选题 · mid

  • A.随机打散请求使分布更均匀
  • B.保证同一用户总被路由到同一版本← 正确答案
  • C.直接提高模型推理的准确率
  • D.减少请求镜像的存储开销

答案:B。一致性哈希把用户 ID 映射到哈希环,按环上区域路由版本,因此同一用户总是命中同一版本,避免体验漂移。A 是随机切分的特征而非哈希的目的;C 与模型精度无关;D 与镜像存储无关。可以理解为:同一桌客人每次都由同一位服务员接待。

Q4. 模型A/B部署中,结果收集通常需要记录哪些信息?

多选题 · mid

  • A.每个请求的响应延迟← 正确答案
  • B.请求对应的版本标识← 正确答案
  • C.用户的密码和身份证号
  • D.业务转化结果的埋点← 正确答案

答案:ABD。结果收集要记录版本标识(每个请求由 A 还是 B 处理)、性能指标(响应延迟)、业务结果(转化/点击埋点),这样才能按版本聚合比较。C 涉及用户敏感信息,与 A/B 实验无关且违反数据合规要求,不应该也不允许被采集。

Q5. 权重切分中,给 B 模型分配 30% 流量意味着什么?

单选题 · mid

  • A.B 版本真实承接三成的线上请求← 正确答案
  • B.B 版本只在每天晚上接收请求
  • C.A 版本停止接收任何线上流量
  • D.所有请求同时发给 A 和 B

答案:A。30% 权重表示约三成真实线上请求直接由 B 处理并返回结果,这是金丝雀模式下的真实承接。B 是时间维度切分,不是权重含义;C 是全量切换的结果;D 是请求镜像的特征。类比餐厅:30% 的客人真的吃到了新配方,而不是所有客人都得到新配方试吃装。

Q6. 触发全量自动切换前,通常需要确认哪些条件?

多选题 · hard

  • A.B 的业务指标显著优于 A← 正确答案
  • B.B 的错误率低于安全阈值← 正确答案
  • C.B 的响应延迟符合 SLA 要求← 正确答案
  • D.A 版本的服务节点被完全销毁

答案:ABC。自动切换是多重条件的“与”:提升度达标(业务指标胜出)、错误率达标(稳定性安全)、延迟达标(性能不劣化),三者缺一不可。D 恰恰相反——旧版本 A 必须保活以便随时回滚,全量切换后也不能立刻销毁。

Q7. 部署监控中,健康检查主要检测什么?

单选题 · mid

  • A.模型服务是否返回正常响应← 正确答案
  • B.用户的消费偏好是否改变
  • C.训练数据的标注质量是否合格
  • D.代码仓库的分支数量是否过多

答案:A。健康检查通过定时探活接口判断服务进程、依赖组件是否正常,保证路由不会把流量打入不健康的节点。B 是业务分析范畴,C 是数据质量范畴,D 是代码管理范畴,都与部署监控中“服务存活”的直接目的无关。

Q8. 新版本指标出现劣化时,快速回滚成功的关键前提是什么?

单选题 · hard

  • A.旧版本的服务部署仍然完整保留← 正确答案
  • B.重新标注全部训练数据
  • C.删除新版本的所有日志记录
  • D.立刻修改新版本的模型参数

答案:A。回滚的本质是把流量快速切回旧版本,这要求旧版本 A 的节点始终保活、配置可用,才能做到分钟级恢复。B 是数据修复动作,耗时长;C 删除日志反而破坏排查能力;D 在线改参数既慢又危险。类比餐厅:老配方灶台不能因为新菜试吃就熄火。

Q9. 关于请求镜像与真实流量切分的区别,下列说法正确的是?

多选题 · mid

  • A.镜像请求的结果不返回给用户← 正确答案
  • B.镜像请求同样会消耗模型算力← 正确答案
  • C.镜像请求通常使用采样率控制成本← 正确答案
  • D.镜像请求必须全量复制才能生效

答案:ABC。镜像请求复制到 B 后,推理结果只用于评估、不返回用户(A 对);B 的推理会消耗 GPU/CPU 算力(B 对);常用采样率如 20% 控制开销(C 对)。D 错误,镜像可以是部分采样,全量复制不是必须条件。镜像只是“试做”,不代表真实上桌。

Q10. A/B 测试中“提升度(lift)”表示什么?

单选题 · easy

  • A.新版本相对旧版本的效果增幅← 正确答案
  • B.服务器内存的占用比例
  • C.训练集样本的规模大小
  • D.模型参数量占存储的比例

答案:A。提升度 = (p_B - p_A) / p_A,衡量新模型 B 相对老模型 A 在业务指标(转化率、点击率等)上的相对增幅。B 是资源指标、C 是数据规模、D 是模型结构指标,都不属于 A/B 实验效果评估中的“提升度”定义。