想象你是一位顶级餐厅总厨,耗尽心血研发出一道招牌菜,评委们在封闭厨房里试吃后给出了 9.9 的高分。可评委只有三位,而排在你餐厅门口的客人有三千位。菜谱存在你的笔记本里,客人们却已经在门外翘首以盼——你不可能把三千位客人请进实验室,看你在白板上推演配方。你需要做的,是把这道菜的完整工艺变成一套标准化的出餐流程:每个分店的灶台都能炖出同一锅汤,每个窗口都能在三十秒内出菜,某个厨师请假时其他窗口照常运转,换新菜单时老客人点单不受影响。这套“把厨房变成营业门面”的工程能力,就是在线推理服务(Online Inference Service)。 在 AI 工程的世界里,训练好的模型就像那道被评委夸赞的秘制菜——它很值钱,但它还不能被顾客享用。从“模型在训练脚本里跑出好数字”到“模型在线上每时每刻服务真实用户”,中间隔着部署、加载、接口、扩容、容灾、升级、监控这一整条生产链路。本文会用“开一家永不打烊的 AI 餐厅”这个比喻,带你走完这条链路:从备料入锅(模型加载),到设计点菜单(API 接口),再到开多个窗口应对排队(高可用架构),最后让你学会如何让这家餐厅永远稳定营业。

岗位关联

掌握在线推理服务的工程方法,是 AI 工程师从“能在实验室跑通模型”走向“能把模型变成可靠产品”的分水岭。这会直接影响你在 MLOps 团队中的核心价值与职业天花板。

目录
  1. 可视化演示
  2. 推理服务概述:从实验报告到营业门面
  3. 模型加载:把秘方备进灶台
  4. API接口设计:客人怎么点单
  5. 高可用架构:多扇窗口的底气
  6. 健康检查:永不熄灭的营业灯
  7. 优雅升级:换菜单不关店
  8. 性能优化:让队伍流动起来
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示在线推理服务架构设计、健康检查机制与优雅升级流程,支持三种交互模式理解模型部署到生产的核心工程能力。

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

推理服务架构 客户端请求负载均衡 模型加载推理响应 高可用API服务架构

推理服务概述:从实验报告到营业门面

你研发出了全城最好吃的牛肉面秘方,评委打了 9.9 分。但评委只来三位,客人有三千位。秘方写在总厨的笔记本里(模型权重保存在训练脚本中),客人们已经在门外排队。你不可能把三千位客人请进后厨看你怎么调火候——这恰恰是在线推理服务要解决的第一件事:把模型从“研究环境”搬到“营业环境”,变成一个随时能点单、随时能上菜的稳定门面。模型再厉害,如果不能被外部系统稳定调用,它的价值就停留在 PPT 里。

在 AI 工程中,训练(Training)像是研发菜品:离线进行、反复实验、以精度为核心目标;而推理(Inference)像是出菜:在线执行、低延迟、高吞吐、每分每秒都要稳定输出。训练可以花几个小时跑一个 epoch,推理却必须在几百毫秒内响应一个真实用户的请求。在线推理服务正是承接训练产出、对外提供预测能力的工程层:它加载模型、暴露 API、管理资源、保障可用性,让任何一个业务系统都能像调用普通接口一样使用 AI 能力。

一个典型的推理服务流程并不复杂:客户端发起预测请求 → API 网关接收与鉴权 → 服务加载模型执行前向计算 → 返回预测结果。但“简单流程”背后的工程难点在于:训练是异步的、可重试的,推理是同步的、有超时上限的;训练吃批量数据,推理面对单个用户。服务化让模型可以被任何团队、任何语言调用,让模型可以独立升级扩容,也让业务方完全无需理解模型内部细节。

就像餐厅把总厨的秘方固化成标准出餐流程,在线推理服务把模型封装成了稳定、可复用、可扩展的 API 门面。评委的 9.9 分只是开始,真正的考验是让三千位客人在高峰期也能吃上同样水准的面——这就是我们从模型到生产的关键一跃。

模型加载:把秘方备进灶台

汤好不好喝,取决于牛肉是不是提前炖好。如果客人点了面你才开始泡发牛肉,这家店一定倒闭。模型加载就是这个“提前备料”的过程:服务启动时,必须把训练好的模型文件从磁盘读进内存,完成初始化,让模型随时处在“可以开火”的状态。备料备得越充分,出菜才能越快;模型加载做得越好,服务上线后的首请求才能越快返回。

在技术层面,模型加载(Model Loading)是指将训练产出的权重文件加载到内存并完成运行时初始化的过程。常见的模型格式包括 pickle、Safetensors、ONNX、TensorRT engine 等,不同格式的加载速度与内存占用差异巨大。加载的生命周期通常有三个阶段:读取磁盘文件、反序列化还原参数、构建可执行的计算图。加载完成后,模型常驻内存,实现“一次加载、多次推理”。

推理服务的内存规划直接决定了副本密度和部署成本。一个非常重要的估算公式是模型内存占用等于参数量乘以每参数占用的字节数:

备料不是把牛肉买回来就行,还得切好、腌好、放在顺手的位置。模型加载就是把“训练好的权重”这道主料,提前炖成“随时能出菜”的高汤,稳稳放在灶台边。如果你在客人点单后才开始加载模型,那第一单的超时就是必然的代价。

M=N×b\displaystyle M = N \times b
模型内存 ≈ 参数量 × 单参数精度字节数。70 亿参数(7B)模型用 FP16(2 字节)存储时,仅权重就需要约 14 GB 内存,还没算上激活值和中间缓存。
  1. 参数量 N模型中可学习权重的总个数。比如 7B 模型就是 70 亿个参数,参数越多模型容量越大、内存需求越高。
    N=7×109N = 7 \times 10^9
  2. 精度字节数 b每个参数用多少字节存储。FP32 是 4 字节,FP16 是 2 字节,INT8 量化后是 1 字节,精度越低内存越省但可能损失效果。
    b=2 (FP16)b = 2 \ (\text{FP16})
  3. 内存计算把参数量与单参字节数相乘,得到模型权重的基本内存需求,部署前必须据此规划机器规格。
    M=7×109×2=14 GBM = 7 \times 10^9 \times 2 = 14 \ \text{GB}

API接口设计:客人怎么点单

客人进店后怎么点单?口头喊、勾菜单、扫码自助——点单方式不同,餐厅的接待能力截然不同。推理服务对外同样需要一套明确的“点菜单”,这就是 API 接口设计。它是模型与外部世界之间的契约:怎么传数据、怎么拿结果、出错怎么办,都要在这张“菜单”上写得清清楚楚。菜单设计得好,客人(调用方)用得顺手,后厨(模型引擎)也能高效出餐。

目前业界两套主流“点单方式”:REST 与 gRPC。REST 基于 HTTP + JSON,直观易调试,任何语言都能轻松调用,适合对外提供能力、跨团队协作、开发调试友好的场景;gRPC 基于 HTTP/2 + Protobuf,接口强类型、传输走二进制,延迟更低、带宽占用更小,特别适合高并发微服务内部调用。选哪个不完全是技术偏好,而要看你服务对象是谁:给外部开放平台用,REST 更亲民;内部服务间高频调用,gRPC 更高效。

除了协议本身,批处理策略也是推理 API 设计的关键决策。批处理(Batching)是将多个请求合并成一次前向计算:GPU 擅长并行处理矩阵运算,一次性算 32 条样本往往比逐条算 32 次要快得多。但批处理会引入排队延迟——早到的请求要等后面的请求凑齐一批才能出发。所以批处理适合吞吐优先、延迟容忍度较高的场景;对延迟极其敏感的交互式请求,宁可牺牲吞吐也要逐条即时响应。

点单方式决定了餐厅能服务多少客人。菜单写得太复杂,客人看不懂;写着“扫码下单”却没人维护,客人更生气。选对协议、设计好批处理策略,等于把菜单设计得既清晰又好用——客人点得顺畅,后厨出得高效,这才是一家稳定营业的 AI 餐厅该有的样子。

高可用架构:多扇窗口的底气

一家只开一个窗口的餐厅,主厨一请病假,全店歇菜。高可用的推理服务,就像开了三个窗口的连锁分店:一个窗口的厨师临时有事,另外两个窗口照常接客,排队的客人都能吃到面,只是某一扇窗前暂时没人排队。可用性(Availability)的本质,就是系统在面对部分组件故障时,仍然能对外提供持续服务的能力。

高可用架构有三个核心支柱。第一是多副本部署:同一模型部署 N 个实例(容器或 Pod),每个实例独立加载模型、独立运行,互为冗余;第二是负载均衡:由负载均衡器(如 Nginx、云负载均衡 ALB)按轮询、最少连接数等策略,把请求均匀分发给各个健康实例,避免单点过载;第三是故障转移:当某个实例无响应或返回错误时,负载均衡自动把后续请求转发给其他健康实例,并对异常节点进行隔离。

多副本带来的可用性提升可以用一个简洁的公式刻画:假设单个实例的故障率为 p,那么 N 个副本同时故障(即整体不可用)的概率是 p 的 N 次方,因此系统整体可用性为:

多扇窗口给了餐厅底气:不是所有窗口都永远健康,但只要还有一个窗口亮着灯,客人的面就能端上来。高可用架构追求的不是“永不故障”这个不切实际的目标,而是“故障发生时客人无感知”——这份底气,来自副本的冗余、均衡的分流和果断的转移。

A=1−(1−a)n=1−pn\displaystyle A = 1 - (1 - a)^n = 1 - p^n
单个实例可用性 a = 99%(故障率 p = 1%),3 副本时系统不可用概率为 0.01³ = 0.0001%,即整体可用性达到 99.9999%。副本越多,冗余越强。
  1. 单实例故障率 p单个副本在一定周期内发生故障的概率。假设每实例有 1% 的概率故障,p = 0.01。
    p=0.01p = 0.01
  2. 全部副本同时故障N 个独立副本同时故障的概率等于各自故障率的乘积,即 p 的 N 次方,这是冗余设计的数学基础。
    pn=0.013=0.000001p^n = 0.01^3 = 0.000001
  3. 整体可用性系统可用的概率为 1 减去全部故障概率。3 副本时整体不可用概率从 1% 降至 0.0001%,可用性飞跃到 99.9999%。
    A=1−0.000001=99.9999%A = 1 - 0.000001 = 99.9999\%

健康检查:永不熄灭的营业灯

餐厅门口那盏“营业中”的灯,不能只是灯亮着就算数,它得真的在营业。想想看:橱窗里灯光明亮,但后厨的灶还没热、面还没备好,客人推门进来却什么也点不了——这比关门更伤害口碑。健康检查就是这盏营业灯背后的“检查团”:既确认后厨没有起火、伙计还活着(进程存活),也确认灶台已经热了、面条已经备好(模型就绪可以接单)。

在 Kubernetes 等容器编排体系中,健康检查被拆成两类探针。存活探针(Liveness Probe)回答的是“进程还活着吗”——如果服务进程崩溃或死锁,存活探针失败,容器会被杀掉并重启;就绪探针(Readiness Probe)回答的是“服务准备好接收流量了吗”——如果模型还没加载完、依赖的下游服务还没连通,就绪探针失败,该实例会从负载均衡中摘除,不再转发请求给它。一个管“活不活”,一个管“能不能干活”,职责完全不同。

具体实现上,推理服务通常暴露一个 /healthz 端点用于健康检查:返回 200 表示健康,返回非 200 表示异常。但仅仅写死返回 200 是典型的“形式主义营业灯”。对加载模型较慢的服务,正确的就绪探针应当检查“模型加载完成 + 能成功执行一次真实推理”,确保流量进来时真的能出结果。存活探针失败触发重启,就绪探针失败只摘流量不重启——两者配合,既避免服务长时间不可用,也避免重启风暴拖垮系统。

营业灯不是装饰,是客人敢不敢进门的信号。健康检查让“营业中”这三个字名副其实:灯亮着,是因为后厨真的在冒热气,灶台真的能出菜。对于推理服务而言,健康检查是把“看似在线”变成“真正可用”的关键闸门——每一次请求分发之前,系统都要确认这扇窗口真的能接单。

优雅升级:换菜单不关店

餐厅要升级菜单,但门口排着三百位客人。最粗糙的做法是宣布打烊,等新菜单研发好再重新开业——客人早走光了。优雅的做法是滚动更新:先让一扇窗口开始用新菜单,其余窗口继续用旧菜单,一扇扇替换,全程店门不关。对推理服务来说,模型版本迭代是常态,如何在替换版本时不中断在线请求,就是优雅升级(Graceful Deployment)要解决的问题。

优雅升级有三个关键环节。第一是滚动更新:新版本实例逐个启动,每个新实例就绪后才接入流量,同时逐个摘除旧版本实例,保证任意时刻都有可用副本在服务。第二是双阶段退出:旧实例收到终止信号(如 SIGTERM)后,不立即退出,而是先停止接收新请求(从负载均衡下线),再在宽限期内处理完正在进行的请求,最后才优雅退出——这就像先挂出“暂停接单”的牌子,把已上桌的菜做完再关火。第三是服务注册发现:实例上线时自动注册到注册中心(如 Consul 或 Kubernetes Service),下线时自动摘除,负载均衡只把流量分发给注册表中的健康实例。

这三者环环相扣:滚动更新是策略,双阶段退出是单实例的优雅谢幕,注册发现是让这一切自动化的“通讯录”。没有注册发现,负载均衡可能把请求发给正在退出的实例;没有双阶段退出,进行中的请求会被硬生生掐断;没有滚动更新,新版本上线就意味着全局闪断。生产环境里最怕的,就是为了“上新模型”这个目标,牺牲了“服务永不中断”这条底线。

换菜单是一门艺术:让老客人喝着旧汤,看着新菜一盘盘端上来,全程没人被赶出门。优雅升级的全部意义,就是在模型迭代这场“菜单革命”中,让用户毫无感知,让服务永不打烊。版本可以一天换三次,但客人的面,不能有一次上迟。

性能优化:让队伍流动起来

高峰期的餐厅,最怕的不是人多,而是出菜太慢导致整个队伍卡死。前厅排队、后厨压单、传菜员在过道里撞在一起——餐厅的崩溃从来不是一瞬间,而是“流动”被卡住后的连锁反应。推理服务的性能优化,就是让“排队→点单→上菜→离店”这条流水线持续流动:每个请求的等待有上限,每份餐的出品有节奏。

衡量推理性能需要同时盯住三个核心指标:QPS(每秒处理请求数)、延迟(单个请求从发出到返回的耗时)、并发(同时在途的请求数)。这三者之间存在一条基本规律,由排队论中的 Little's Law 描述——并发数等于 QPS 与平均延迟的乘积:

除了用 Little's Law 做容量规划,性能优化还有几个经典实战手段。冷启动优化:模型加载耗时较长,容器刚启动时第一次推理会很慢,常用方案是预热——启动完成后主动跑一次假请求,让模型计算图和缓存全部“热身”。超时控制:为每个推理请求设置超时上限(如 500ms),避免某个慢实例把请求队列拖垮,超时的请求快速失败、快速重试到其他实例。监控告警:持续跟踪 QPS、延迟分位数(P50/P99)、错误率,设定告警阈值,在性能劣化影响用户之前就发出预警。

队伍流动起来,每个客人的等待才有上限。性能优化的最高境界,不是把某一单做到最快,而是让整个系统在任意流量冲击下都可预期:出菜快、队伍稳、没人被饿跑。对推理服务而言,性能不是实验室里的压测数字,而是高峰时段每一秒都在发生的真实体验。

L=λ×W\displaystyle L = \lambda \times W
L 是系统内的平均并发请求数,λ 是每秒到达的请求数(QPS),W 是每个请求的平均处理时间(秒)。若 QPS 为 1000、平均延迟 0.2 秒,则任意时刻系统内约有 200 个请求同时在途。
  1. 到达率 λ单位时间进入系统的请求数量,也就是 QPS。假设每秒有 1000 个请求到达。
    λ=1000\lambda = 1000
  2. 平均延迟 W每个请求从进入系统到完成处理的平均耗时。假设平均延迟为 200 毫秒,换算成秒为 0.2。
    W=0.2 sW = 0.2 \ \text{s}
  3. 并发数 L系统内同时在途的请求总数,等于两者乘积。这决定了你需要多少并发线程、多少显存缓冲来支撑目标流量。
    L=1000×0.2=200L = 1000 \times 0.2 = 200

AI 应用场景

金融风控:毫秒级实时反欺诈

阶段:在线推理服务部署与高可用保障

解决的问题:信用卡交易每秒产生成千上万笔,每笔交易必须在几百毫秒内完成欺诈风险评估。传统规则引擎误报率高、无法捕捉复杂隐蔽的欺诈模式。将反欺诈模型部署为在线推理服务后,每笔交易实时调用风险评分 API,结合多副本高可用架构与健康检查,确保高峰时段任何单点故障都不会让交易审核停摆。

具体案例:某支付平台将 GBDT 反欺诈模型部署为 3 副本推理服务,负载均衡按最少连接分发请求。某实例因内存泄漏被就绪探针摘除流量,其余副本无缝承接,P99 延迟稳定在 80ms 以内,欺诈拦截率提升 45%,且整个故障期间用户交易无感知。

电商推荐:A/B 实验的优雅切换

阶段:模型版本迭代与优雅升级

解决的问题:推荐系统需要频繁上线新模型做 A/B 实验,传统做法是直接替换模型,一旦新模型效果不佳会导致全量用户体验下降。利用优雅升级的滚动更新策略,新模型实例逐个上线、逐步接入流量,配合注册发现实现新旧版本平滑过渡,全程无请求中断。

具体案例:某电商平台首页推荐模型从 v2.1 升级到 v3.0,采用 10% → 50% → 100% 的三阶段灰度放量。每阶段通过监控点击率与延迟对比新旧模型,确认无异常后继续放量。升级全程用户无感知,推荐点击率提升 12%。

内容安全:多模型协同的实时审核

阶段:推理服务 API 设计与批处理优化

解决的问题:短视频平台每天新增数百万条内容,需同时经过色情识别、暴力识别、广告导流识别等多个模型审核。单个模型逐条推理吞吐严重不足。通过设计统一的推理 API 网关,将多个模型封装为独立服务并启用动态批处理,显著提升 GPU 利用率。

具体案例:某内容平台将三个审核模型部署为独立推理服务,API 网关按内容类型路由到对应模型。对延迟容忍度较高的离线队列启用批大小为 32 的动态批处理,GPU 利用率从 35% 提升到 78%,审核吞吐提升 3 倍,同时在线高优内容仍保持单条即时推理。

Python 演示

python84 行
import jsonimport mathfrom http.server import BaseHTTPRequestHandler, HTTPServer  class SimpleModel:    """模拟一个已训练好的二分类模型"""     def __init__(self):        self.w = [0.5, -0.3, 0.8]        self.b = 0.1     def predict(self, features):        z = self.b + sum(w * x for w, x in zip(self.w, features))        prob = 1.0 / (1.0 + math.exp(-z))        return prob  class InferenceService:    """推理服务:负责模型调用与请求计数"""     def __init__(self):        self.model = SimpleModel()        self.requests_seen = 0     def handle_predict(self, data):        self.requests_seen += 1        features = data.get("features")        if not features or len(features) != 3:            return {"error": "features must be a list of length 3"}        prob = self.model.predict(features)        label = 1 if prob >= 0.5 else 0        return {            "label": label,            "prob": round(prob, 4),            "total_requests": self.requests_seen,        }  class RequestHandler(BaseHTTPRequestHandler):    server_version = "InferenceDemo/1.0"    service = InferenceService()     def do_GET(self):        if self.path == "/health":            self._send_json(200, {"status": "ok", "model_ready": True})        else:            self._send_json(404, {"error": "not found"})     def do_POST(self):        if self.path != "/predict":            self._send_json(404, {"error": "not found"})            return        length = int(self.headers.get("Content-Length", 0))        raw = self.rfile.read(length)        try:            data = json.loads(raw)            result = self.service.handle_predict(data)            self._send_json(200, result)        except Exception as exc:            self._send_json(400, {"error": str(exc)})     def _send_json(self, code, payload):        body = json.dumps(payload)        self.send_response(code)        self.send_header("Content-Type", "application/json")        self.send_header("Content-Length", str(len(body)))        self.end_headers()        self.wfile.write(body.encode())     def log_message(self, fmt, *args):        pass  def main():    server = HTTPServer(("127.0.0.1", 8080), RequestHandler)    print("推理服务已启动: http://127.0.0.1:8080")    print("健康检查: GET /health")    print("预测接口: POST /predict {'features': [0.5, 1.2, -0.8]}")    server.serve_forever()  if __name__ == "__main__":    main()
目的:演示一个最小可用的在线推理服务架构——启动时模型加载进内存,/health 端点供健康检查探针探测,/predict 端点接收 JSON 特征并返回预测结果,完整复现正文讲述的“模型加载 + API 接口 + 健康检查”三大环节。
  1. class InferenceService:推理服务的核心类,内部持有模型实例和请求计数器,对应正文中“模型常驻内存、一次加载多次推理”的设计。
  2. self.model = SimpleModel()服务启动时即完成模型加载与初始化,相当于餐厅开门前就把牛肉炖好。
  3. self.requests_seen = 0记录累计处理请求数,方便观察服务运行状态。
  4. def handle_predict(self, data):处理单个预测请求:校验输入 → 调用模型 → 返回结构化结果。
  5. prob = self.model.predict(features)执行前向计算,得到样本属于正类的概率值。
  6. label = 1 if prob >= 0.5 else 0将概率转为具体的类别标签,这是二分类推理的标准输出格式。
  7. server_version = "InferenceDemo/1.0"自定义 HTTP 响应头中的服务标识,便于排查调用方。
  8. def do_GET(self):实现 GET 方法处理逻辑,/health 返回健康状态供探针检查。
  9. if self.path == "/health":健康检查端点,对应正文中“就绪探针通过 /healthz 确认服务可接单”。
  10. data = json.loads(raw)解析请求体中的 JSON 数据,REST 接口的典型处理方式。
  11. result = self.service.handle_predict(data)委托推理服务处理请求,业务逻辑与 HTTP 层解耦。
  12. print("推理服务已启动...")打印启动信息,方便确认服务的监听地址与可用端点。

常见误区

误区我觉得模型离线评测 AUC 很高,部署上线后效果肯定没问题,线上表现不好一定是算法的问题,不是我的部署问题。

正解模型精度只代表“这道菜配方好”,不代表“餐厅能稳定出菜”。线上推理还面临延迟约束、并发冲击、特征缺失、数据分布漂移等训练时没有的问题。一个 AUC 0.98 的模型如果推理延迟 2 秒,用户在移动端早走了。上线前必须做压测:QPS 能扛多少、P99 延迟多少、内存是否稳定。好配方只是起点,出餐系统的稳定性才是线上口碑。

为何常见:算法工程师的日常工作集中在离线评测,容易把“模型好”等价于“服务好”,忽略了从模型到服务之间的大量工程环节。

误区我的服务加了健康检查端点,进程没挂就返回 200,K8s 也显示 Running,这样应该没问题吧?

正解进程活着不代表能接客。典型场景:容器启动了但模型还在从磁盘加载,此时进程存活、服务端口已监听,但收到推理请求必然超时。如果只配存活探针,流量照样打进来,请求全部超时,最终触发重启,陷入“重启→流量进来→超时→再重启”的恶性循环。正确做法是区分存活探针(查进程)与就绪探针(查模型加载完成 + 能成功跑一次推理),就绪探针不通过时先把实例摘出负载均衡。

为何常见:很多人从传统 Web 服务迁移过来,传统服务启动即就绪,而推理服务有“模型加载”这个额外的准备阶段,这个差异最容易被忽视。

误区新模型要上线了,我把旧服务停掉,部署新版本,然后再启动服务。中间有几秒的服务不可用,应该可以接受吧?

正解对内部工具可能可以接受,对生产环境就是事故。直接停旧版本意味着所有在途请求被掐断,用户看到的是“请求失败”和“服务异常”。正确做法是滚动更新:先启动新版本实例,等它通过就绪检查、成功接入流量后,再逐个摘除旧版本。配合双阶段退出(先停接收新请求、处理完存量再退出),全程用户无感知。这正是“换菜单不关店”的工程实现。

为何常见:本地开发时服务重启只要几秒,容易形成“停旧起新”的惯性思维,没有意识到生产环境每时每刻都有真实流量。

误区GPU 并行能力强,我把所有请求攒起来一次性推理,吞吐肯定更高,延迟也会更低。

正解批处理提升的是吞吐(单位时间处理更多请求),但会牺牲单请求延迟。早到的请求必须等批凑满才能出发,排队时间直接加到了每个请求的延迟上。对延迟敏感的场景(如实时风控),多等 100ms 可能就超时了;对延迟不敏感的场景(如离线批量审核),批处理是最优选择。业界常用动态批处理:在最大延迟预算内尽量凑批,兼顾两者。

为何常见:训练时的经验是“batch 越大效率越高”,不少人把这个经验直接搬到推理服务,忽略了训练和推理对延迟的容忍度完全不同。

面试题

Q1. 在线推理服务中,模型加载完成后模型权重通常存放在哪里?

单选题 · easy

  • A.磁盘缓存文件
  • B.内存中常驻← 正确答案
  • C.数据库备份表
  • D.浏览器本地缓存

答案:B。模型加载的核心目标就是把权重从磁盘读入内存并常驻,实现“一次加载、多次推理”。磁盘缓存每次推理都要重新读盘,数据库和浏览器缓存更不是推理服务的存储位置。就像牛肉要提前炖好放在灶台边,而不是等客人点了再去冰箱拿。

Q2. 就绪探针(Readiness Probe)检查失败时,编排系统应该怎么做?

单选题 · easy

  • A.立即重启容器
  • B.从负载均衡摘除流量← 正确答案
  • C.清空容器日志
  • D.丢弃所有已存数据

答案:B。就绪探针回答“能不能接客”,失败说明服务(如模型还在加载)尚未准备好接收流量,正确动作是从负载均衡摘除,等就绪后再恢复接入。存活探针失败才触发重启。就像餐厅门口挂出“暂停接单”的牌子,而不是把后厨直接炸掉。

Q3. 以下哪种协议适合高并发、低延迟的微服务内部调用场景?

单选题 · easy

  • A.REST
  • B.gRPC← 正确答案
  • C.SMTP
  • D.FTP

答案:B。gRPC 基于 HTTP/2 与 Protobuf,二进制传输、强类型、支持长连接,性能和带宽表现优于 REST。REST 适合调试友好和跨组织的场景,SMTP 是邮件协议,FTP 是文件传输协议。就像内部传菜用高效的小推车(gRPC),对外给客人看菜单才用印刷精美的册子(REST)。

Q4. 三个副本的推理服务,每个副本故障率为 1%,系统的整体不可用概率是多少?

单选题 · mid

  • A.0.0001%← 正确答案
  • B.0.001%
  • C.0.01%
  • D.1%

答案:A。多副本同时故障的整体不可用概率等于各副本故障率的乘积,即 0.01³ = 0.000001 = 0.0001%。冗余设计让整体可用性从 99% 提升到 99.9999%。三个窗口同时坏掉的概率,比一个窗口坏掉的概率低得多。

Q5. 根据 Little's Law,QPS 为 1000、平均延迟为 200ms 时,系统内的平均并发数是多少?

单选题 · mid

  • A.50
  • B.100
  • C.200← 正确答案
  • D.500

答案:C。L = λ × W = 1000 × 0.2 = 200。并发数等于每秒到达的请求数与平均处理时间的乘积。这决定了你要预留多少并发线程、多少显存缓冲。200ms 的延迟意味着每秒钟有 200 个请求正在路上。

Q6. 优雅升级中,旧版本实例收到终止信号后,第一步应该做什么?

单选题 · mid

  • A.立即停止进程
  • B.停止接收新请求← 正确答案
  • C.重启容器
  • D.清空缓存数据

答案:B。双阶段退出的第一步是“停止接新单”:先从负载均衡摘除,保证新流量不再进入,然后在宽限期内处理完进行中的请求,最后才退出。立即停止会掐断在途请求,重启和清缓存都与优雅退出无关。就像先挂出“暂停接单”,把手头的菜做完再关火。

Q7. 容器启动后第一次推理请求特别慢,最有效的缓解手段是什么?

单选题 · mid

  • A.调大批处理
  • B.启动时执行预热← 正确答案
  • C.提高超时时间
  • D.关闭日志输出

答案:B。慢的原因是模型加载和计算图初始化未完成,预热(启动后主动跑一次假请求)能让模型在接客前完成热身。调大批次与首请求延迟无关,调高超时只是掩盖问题,关日志更是文不对题。就像餐厅开市前先开火试锅,而不是等第一位客人来了才现烧水。

Q8. 构建高可用推理服务架构的常见手段包括哪些?

多选题 · mid

  • A.多副本冗余部署← 正确答案
  • B.负载均衡分发流量← 正确答案
  • C.健康检查摘除异常节点← 正确答案
  • D.升级时直接停止旧版本

答案:ABC。多副本提供冗余,负载均衡实现分流,健康检查保证流量只发给健康实例,三者共同支撑高可用。直接停旧版本会造成服务闪断,是优雅升级中要避免的做法。多扇窗口互相备份,才是餐厅不关门的底气。

Q9. 与 REST 相比,gRPC 的优势包括哪些?

多选题 · hard

  • A.接口强类型定义← 正确答案
  • B.二进制高效传输← 正确答案
  • C.基于 HTTP/2 长连接← 正确答案
  • D.天然支持跨域调试

答案:ABC。gRPC 用 .proto 文件定义强类型接口,Protobuf 二进制序列化更紧凑,HTTP/2 支持多路复用长连接,因此高并发下性能更优。跨域调试友好反而是 REST 的优势,不是 gRPC 的强项。强类型菜单、高效传菜通道、保持长连接的传菜电梯,是 gRPC 的三板斧。

Q10. 关于推理服务的批处理策略,下列说法正确的有哪些?

多选题 · extreme

  • A.批处理一定会降低单个请求的延迟
  • B.批处理能提升吞吐但可能增加排队延迟← 正确答案
  • C.批处理适合延迟不敏感的批量场景← 正确答案
  • D.批处理可以完全消除冷启动的影响

答案:BC。批处理把多个请求合并计算,提升吞吐,但早到的请求要等批凑满,排队延迟反而增加,所以 A 错、B 对;也因此它更适合延迟不敏感的场景,C 对。冷启动是模型加载问题,批处理无法消除,D 错。攒一车菜一起炒效率高,但第一个点单的客人要多等一会儿。