不少团队真正把AI能力落到生产环境时,都会遇到同一个问题:模型在实验环境里跑得好好的,一上生产就垮。要么延迟高得无法接受,要么显存爆掉,要么并发一上来直接OOM。这个问题不是某个框架的bug,而是“AI模型推理系统架构设计实践”这件事没做扎实。我做了几年模型推理系统的架构和优化,踩了不少坑,也总结出一套相对稳定的整体设计思路。这篇文章不打算讲特别玄的理论,就是把我在实际项目中真实用到的方法、选型和排查过程写清楚,给正在设计自己推理系统的同学做一个参照。
先把范围说清楚:这里讲的推理系统,主要面向大语言模型以及类似需要长时间占用GPU资源的深度模型,包含硬件方案、推理引擎、服务调度、资源监控和线上稳定性几个层面。无论你是准备自己部署开源模型,还是想优化已有服务的吞吐和延迟,都可以参考。我会尽量把每个决策背后的理由说透,包括当时为什么选A方案而不是B方案,以及事后踩了什么坑。
1. 从“调API”到“自建推理”:我是在哪一步决定动手的
1.1 调用外部API的隐性成本
很多业务启动时为了快,直接调用厂商提供的模型API。这个方向没有错,但等流量上来之后会暴露出几类问题。
第一是成本,按Token计费在大规模调用下非常可观。我们当时有过统计,一个支持多轮对话的线上服务,单用户日均消耗几千Token,一旦日活过万,仅模型调用费就是一笔不小的开支。更麻烦的是,那个费用不是线性增长的——很多业务场景下,每轮对话都会重复把历史上下文塞给模型,实际消耗远大于“有效内容”的Token数。
第二是延迟和限额不可控。厂商的API服务通常会有并发限制,高峰时段排队严重。虽然可以通过重试缓解,但会把业务侧的单次请求延迟拉得很高,且这个延迟波动不是你能通过代码优化的。
第三是数据控制权。用户输入会经过第三方服务,很多业务场景有数据合规要求,不允许把数据送到外部。即使技术上可行,也需要走冗长的审批流程。这个点往往是自建推理系统最强有力的理由,甚至比成本更优先。
我当时决定动手,是因为同时踩中了成本增长和限流两个问题。当然,动手前要先想清楚自己的技术储备和运维资源。推理服务不是部署一个脚本就完事,它涉及GPU服务器、推理引擎、监控告警、弹性扩缩容,如果团队完全没有这块经验,建议先小规模试水,不要一开始就做一个“能支撑百万并发”的宏大系统。
1.2 自建推理解决的不仅是成本,更是调度和控制力
自建推理系统最直接的收益确实是可以把边际成本压下来。以我们常用的7B至13B参数模型为例,一张40GB显存的A100/A800在连续批处理下能同时服务几十路并发请求,硬件成本平摊到每百万Token上,比按量付费API通常低一个数量级。但硬件的另一半是运维,这是不少人容易忽略的。
更核心的价值其实是控制力。你可以自主决定模型的量化方式、上下文长度、批处理策略、排队顺序,甚至可以为不同业务单独部署一套推理引擎。举个例子,低优先级的数据清洗任务和对延迟敏感的在线对话任务,最好物理隔离到不同实例上,否则会发生“一个跑批任务把在线服务的显存吃光”的事故。这个在API模式下是无法控制的。
我的建议是,如果你的业务满足下面任意两条,就可以考虑自建:
- 模型调用量稳定在一个较高水位,每月成本超过一台GPU服务器租用价格。
- 有强数据合规要求,不允许数据出域。
- 需要深度定制推理逻辑,例如接入私有的向量检索、特殊的采样策略。
- 希望把P99延迟降下来,做到相对稳定的SLA。
满足条件之后再进入系统设计阶段,下面的内容就是围绕这个阶段来展开的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 硬件底座:GPU怎么选、显存和CPU内存如何配比
2.1 大模型推理对显存的真实需求
GPU选型看两个硬指标:显存大小和显存带宽。显存决定你能装下多大的模型以及单卡能同时处理的并发数,显存带宽决定解码阶段每秒能生成多少Token。
一个非常粗略但实用的估算公式是:部署模型所需的显存大约等于参数规模乘以精度字节数,再加上KV Cache等运行时开销。以13B模型用FP16部署为例,权重本身需要约26GB显存;如果使用INT8量化,权重降到13GB左右;用INT4则更小。但KV Cache会随着并发请求数和上下文长度增长,这部分不能忽略。假设单请求上下文长度为4096,KV Cache可能占用数百MB到数GB不等,具体取决于模型层数和注意力头数。所以选卡时不能卡着模型权重尺寸买显存,要给KV Cache和碎片化预留30%至50%余量。
对于大多数团队,我比较推荐先租用A10、L20或同等规格的卡来跑通流程,确认推理引擎的吞吐表现后再考虑购买或长期包机。这里有一个容易忽视的事实:不同GPU的“单Token生成速度”差异没有想象中那么悬殊,真正的差距主要体现在显存带宽和并发吞吐上。也就是说,你从T4换成A100,单条请求变快可能只提升两三倍,但整体吞吐和并发用户数会提升一个量级。
显存带宽这个参数经常被忽略。比如A100 40GB的显存带宽约1.6TB/s,而一些低端卡的带宽只有它的四分之一甚至更低,这会直接拉低Token生成速度。如果业务面向在线对话,务必选择高带宽的卡。
2.2 容易被忽略的CPU、内存和磁盘瓶颈
很多人把注意力全放在GPU上,但推理系统是个整体,CPU、内存、磁盘都可能成为瓶颈。
CPU主要承担数据预处理、Token化、采样、调度和协议解析。模型比较大时,如果每秒请求量很高,CPU核数不够会先成为瓶颈。尤其是在使用Python为主的控制面时,GIL已经不算大问题,因为推理引擎通常用C++做核心计算,但Python很容易在数据进出时把CPU跑满。
内存方面,最大的坑是模型权重文件的加载。一个13B模型大约有26GB权重,如果从磁盘读完后还要在内存和显存之间做转换,内存至少要有权重的1.5到2倍。我见过一台6核32GB内存的服务器,GPU是24GB,加载一个13B模型时内存直接被打满,然后触发Swap,加载时间从几十秒涨到好几分钟。后来把内存升级到128GB,这个事情才彻底解决。
磁盘方面,模型加载速度会受限于磁盘IO。最好把模型文件放到NVMe SSD上,瓶颈就从磁盘转移到PCIe带宽。如果是多卡并行加载同一个模型,还要考虑多路SSD的并发能力。
2.3 验机时我要跑的几条命令和指标
拿到一台GPU服务器后,不要急着部署,先确认几件事。
用 nvidia-smi 查看GPU是否正常被识别,同时注意驱动和CUDA版本是否和推理引擎匹配。用 lscpu 或者 uname -a 确认系统架构是x86_64还是ARM64,很多公司内部脚本在这个地方会踩坑,直接下载了错误的安装包。
再做一个简单的GPU浮点性能或带宽测试,比如用 bandwidthTest 看显存带宽是否符合预期,用 stress-ng 简单压一下CPU稳定性。另外一个容易被骗的点是“虚拟化GPU”,如果服务器是云主机/虚拟化平台,nvidia-smi 看到的卡信息可能是虚拟卡,性能折扣很大,这一点尤其要在采购时确认清楚。
我当时在验机时最常用的命令组合是:
bash复制nvidia-smi --query-gpu=name,memory.total,memory.used,temperature.gpu,power.draw --format=csv
这条命令能直观看到每张卡的显存占用、温度和功耗。为了看实时功耗曲线,可以加 -l 1 参数循环输出,例如观察模型加载时的瞬时功耗:
bash复制watch -n 1 nvidia-smi
3. 推理引擎选型:vLLM、Triton、TensorRT-LLM,我最后留了什么
3.1 三个引擎各自的定位
推理引擎是整套系统中的核心软件层。当前主流选择有三个:vLLM、NVIDIA Triton Inference Server、TensorRT-LLM。它们不是同一个层面的东西,先分清楚再选。
vLLM是一个专门面向大语言模型的高性能推理框架,最大的贡献是把PagedAttention和Continuous Batching做到开箱即用。它特别适合快速部署OpenAI兼容的Chat接口,生态里对主流模型的适配很全,社区活跃,改起来也方便。它的缺点是灵活性稍弱,如果你要接入自定义算子或者多模型组合流水线,需要自己动手改框架。
Triton Inference Server是一个更通用的模型服务框架,不只是服务大模型。它支持多种后端:ONNX、PyTorch、TensorRT等,还支持Ensemble多模型编排,适合做同时包含多个模型推理任务的服务。它的资源管理很强,可以在一张卡上加载多个模型,并设置不同模型的显存分配和优先级。
TensorRT-LLM是NVIDIA专门为自家GPU优化的LLM推理库,性能上限很高,特别是在用NVIDIA卡时,通过图融合、FP8量化等手段能够压出很高的吞吐。但它的接入成本也高,需要模型转换和编译,且强依赖CUDA生态和NVIDIA硬件,迁移到非NVIDIA平台时要重做。
3.2 我的选型组合和理由
在实际项目中,我的组合并不是只选一个引擎,而是根据请求类型做了分层。
对于在线对话、文本生成这类典型大模型任务,我倾向于直接把vLLM作为主力引擎。原因有三个:一是PagedAttention对KV Cache的管理非常高效,显存利用率高;二是Continuous Batching能显著提升并发吞吐;三是它原生兼容HuggingFace权重和OpenAI协议,接入成本低,团队上手快。
如果系统中还有常规的CV或NLP模型,比如嵌入模型、目标检测模型,我会把这些小模型放到Triton上。因为小模型数量多,如果每个都单独起一个vLLM实例,开销太大。Triton的Model Ensemble可以把多个小模型串联起来,时延和资源管理上也更可控。
TensorRT-LLM我没有作为主力,主要在单卡性能调优时拿来做对比基准。如果团队对性能有极致追求、并且有足够的时间做定制,可以尝试,否则不要轻易把它作为线上默认引擎,因为它带来的额外维护成本并不低。
3.3 引擎部署时的两个基础配置
部署vLLM时,有两个配置在初期就必须认真对待。
第一个是 --max-model-len,它决定单个请求允许的最大上下文长度。这个值不是越大越好。如果你设置为32768,但绝大多数请求只有2048,那么KV Cache会被预分配为32768的长度,显存浪费严重,并发度也上不去。比较好的做法是基于业务实际需求设置,同时在前端做Token截断,不要无脑追求长上下文。
第二个是 --gpu-memory-utilization,它控制引擎最多占用多少比例的显存。默认值通常是0.9,但如果你在同一张卡上还要部署第二个模型,或者希望留一点显存给其他进程,必须手动调低。我通常在线场景会设置为0.85,留出一点余量给碎片和临时峰值。
这里还要提一下部署时建议开启的 --enable-prefix-caching 参数。在多轮对话场景下,公共前缀的KV Cache可以被复用,对吞吐和延迟都有明显改善。但不是所有场景都适合开启,如果请求前缀随机性很强,这个功能反而会增加额外管理开销。
4. 服务架构与上线拓扑:网关、K8s、多副本与故障转移
4.1 一张我画在文档里的部署拓扑
推理系统不只是“GPU上跑一个服务”这么简单。我习惯把线上服务拆成四层:接入网关层、业务服务层、推理服务层、基础设施层。
接入网关层负责统一收口所有外部请求,做鉴权、限流、路由和灰度。业务服务层负责把用户的输入转成模型请求,并做业务逻辑编排,比如调用检索、生成提示词、处理流式输出。推理服务层就是一个或多个vLLM/Triton实例,真正的模型计算在这里完成。基础设施层包括日志、监控、告警和弹性伸缩组件。
在各层之间,我建议用队列解耦,而不是直接同步调用。尤其在推理服务能力不足时,队列可以起到削峰填谷的作用。例如在线对话场景,如果短期峰值超过容量,服务端可以先返回“排队中”,让前端的体验不至于直接超时失败。
4.2 K8s中推理服务的特殊之处
部署推理服务到K8s时,有几个点跟普通Web服务差异很大。
GPU资源要单独声明为 nvidia.com/gpu,同时建议在Pod级别设置 nodeSelector 或 affinity,把含有GPU的节点和普通应用节点隔离。不要把GPU和CPU应用混部,否则一个普通的日志采集容器都可能把宿主机的CPU争抢掉,间接拖累推理性能。
推理Pod的存活探针不要只做TCP检查。因为模型加载时间很长,探针很容易在启动阶段就误杀Pod。我通常会把探针改成HTTP请求,在服务内部暴露一个 /health 接口,只有模型真正加载完成之后才返回200。这样K8s的滚动更新才能安全工作。
另外,推理服务的优雅终止非常关键。vLLM在收到SIGTERM后会处理完当前正在执行的请求再退出,但K8s默认等不到那么久。你需要配置 terminationGracePeriodSeconds,并确保容器镜像里能正确处理信号。我在线上曾经因为Pod滚动更新,把正在进行的对话全部打断,后来把优雅终止时间调到120秒,问题才解决。
4.3 网关层要做对限流和重试
网关层是保护推理服务的第一道防线。这里最容易犯的错误是无脑重试。
当后端推理服务已经过载时,客户端反复重试只会加重负载,甚至引发雪崩。正确做法是采用带退避和抖动(jitter)的重试策略,并且针对不同错误码区别对待。比如429应该做指数退避,5xx可以有限重试,而4xx请求参数错误就不要重试。
限流方面,不要在网关只按QPS限流,因为LLM处理的消耗是每个请求的Token数决定的。一个Token数很大的请求,其代价远高于几十个小请求。更合理的做法是做Token级别的配额控制:估算每个请求预计消耗的Token数,再按总Token吞吐量对用户或业务方进行限流。这个逻辑类似API网关中的“流量包”,只是配额单位从次数变成Token。
这里有一张我在容量规划时常用的对照表,可以直观表达不同并发下的预估情况:
| 并发请求数 | 平均输入Token | 平均输出Token | 估算显存开销 | 说明 |
|---|---|---|---|---|
| 8 | 2048 | 512 | 约4-8GB | 小规模试运行,延迟低 |
| 32 | 2048 | 512 | 约16-32GB | 中等压力,需注意KV Cache |
| 64 | 4096 | 1024 | 约48-96GB | 高并发场景,需要多卡 |
| 128 | 8192 | 2048 | 超过单卡 | 必须多副本或模型并行 |
当然这只是一个粗略估算,实际数值与具体模型结构强相关。造这张表的目的在于提醒团队在压测前就留足显存余量,而不是等OOM再处理。
5. 延迟优化和吞吐提升:动态批处理、连续批处理和投机采样
5.1 Prefill和Decode是两回事
理解大模型推理的延迟,必须先看懂推理过程的两阶段:Prefill(预填充)和Decode(逐Token生成)。
Prefill阶段一次性处理完整的输入提示词,生成KV Cache,GPU利用率很高,计算密集。Decode阶段则是逐Token生成,每次迭代只生成一个Token,GPU利用率很低,且严重依赖显存带宽。简单理解就是:Prefill更像一次性的“计算冲刺”,Decode像多次“跑步送信”。
这两个阶段的资源消耗差异很大。在架构设计上,如果你的应用是长输入短输出,瓶颈会在Prefill;如果是短输入长输出,比如写文章、聊天多轮,瓶颈集中在Decode。针对不同的侧重,优化手段也不同。
5.2 动态批处理/连续批处理怎么提升吞吐量
传统批处理是把多个请求收集到固定批大小后一起推理,批大小固定,会导致一个问题:如果当前批里某个请求早早生成完了,而其他请求还在解码,那么已经完成的请求也要等整个批结束后才能返回,白白浪费吞吐。
vLLM等引擎推广的Continuous Batching(连续批处理)改变了这个逻辑:每个请求在生成完成或到达停止条件时,立刻从当前批中移除,同时把排队中的新请求加入。批大小是动态的,GPU始终在处理那些还在活跃的请求,利用率因此高得多。
既然动态批处理是引擎层面的事,架构层面还需要做点什么?我的经验是:尽量让上游把请求集中送到同一批,但不要强行为了等批处理而增加排队延迟。要充分利用动态批处理,可以使用引擎提供的队列参数,控制最大等待时间和最大批大小。
例如vLLM中通常不需要手动控制批大小,它内部会根据等待请求数和当前显存自动调度。但如果你在业务层自己实现了连接池和批量抓取,注意不要把一条请求的逻辑拆得太碎,否则长请求始终占着资源,批处理效率反而下降。
5.3 投机采样(Speculative Decoding)的适用条件
投机采样(Speculative Decoding)是近几年比较热的加速方案,思路是先用一个小模型快速生成多个候选Token,再由大模型并行验证。由于大模型一次可以验证多个候选Token,生成速度可以在特定条件下提升两三倍。
但它不是万能的。这个方案对“小模型和大模型的词表一致性”有要求,且候选序列的接受率高度依赖小模型与大模型的分布对齐程度。如果小模型猜不准,验证会反复回退,速度不仅不快,反而更慢。
我建议先不要在生产环境盲目开启投机采样,而是先做A/B测试。如果业务的文本模式比较集中,比如代码补全、特定格式输出,这个方案有不错的收益;如果是开放式创意写作,接受率往往不高,收益有限。尤其要注意,投机采样会额外占用显存来加载小模型,显存本来就不充裕时,优先保证其他基础能力。
除投机采样外,还有两种非常实用的优化:量化(Quantization)和KV Cache量化。GPTQ、AWQ等4bit量化可以将模型权重压缩到原来的四分之一左右,但精度损失需要业务场景去评估。KV Cache量化更激进,直接把缓存从FP16压缩到FP8甚至INT4,能显著降低显存需求并提升并发度。代价是可能带来细微的精度下降,在把线上的一些关键结果和未量化版本做对比验证后再上。
6. 容量评估和监控:我从“看CPU”转向“看Token”的整个过程
6.1 监控指标体系怎么定
推理系统的监控和普通Web服务有本质差异。只看CPU、内存、QPS是不够的,要把监控单位从“请求次数”切换到“Token吞吐量”。
我梳理过一套监控指标体系,分为三层。后端层要监控GPU利用率、显存占用、显存带宽利用率、温度、功耗;推理层要监控排队请求数、批大小、Prefill耗时、Decode耗时、每Token生成耗时、Token吞吐量;业务层则关注P50/P95/P99延迟、首Token延迟、流式输出间隔以及请求成功率。
这里面最容易误导人的是GPU利用率。nvidia-smi 里显示的GPU利用率是相对时间片的,不代表计算单元真正饱和。你可能看到一个推理进程GPU利用率很高,但延迟依然很差,因为瓶颈可能在显存带宽和KV Cache管理上。所以不要单独看GPU利用率,要结合Decode阶段每秒生成Token数来判断。
我常看的“黄金指标”是 <Tokens/s> 和 Inter-token Latency。如果Tokens/s稳定,说明引擎调度正常。或者从业务视角看“首Token时间”和“Token间隔”这两个指标,它们能更直接反映用户感知到的流畅度。流式输出场景下,Token间隔不稳定会导致前端打字效果一顿一顿,体验很糟。
6.2 容量压测的简单方法
容量测试不要用并发压测工具无脑打。大模型推理压测之前要明确一个概念:相同的并发数下,不同输入Token长度和输出Token长度对系统负载的影响差别非常大。
压测时要定义好场景矩阵。比如一个在线对话场景,可以拆成三档:轻量(输入512、输出256)、常规(输入2048、输出512)、重度(输入4096、输出1024)。分别压测得到每个场景下的最大可支撑并发,然后估算业务中不同场景的占比,得到加权综合容量。
我通常用Python写一个异步压测脚本,模拟不同长度、不同并发的请求,采集延迟分布和Token吞吐。这里有个小技巧:如果引擎实现了OpenAI协议,压测脚本可以直接用 openai 库来发请求,代码量很少。对于流式输出,要明确统计的是 首Token延迟 和 全部完成时间,而不是只统计总耗时。
python复制import asyncio
import time
from openai import AsyncOpenAI
client = AsyncOpenAI(base_url="http://inference-server:8000/v1", api_key="not-needed")
async def one_request(prompt_blocks=4, max_tokens=512):
prompt = "".join(f"用户{idx}的问题" for idx in range(prompt_blocks))
start = time.perf_counter()
stream = await client.chat.completions.create(
model="local-model",
messages=[{"role": "user", "content": prompt}],
max_tokens=max_tokens,
stream=True,
)
first_token_time = None
last_time = start
token_count = 0
async for chunk in stream:
token_count += 1
now = time.perf_counter()
if first_token_time is None:
first_token_time = now - start
last_time = now
total_time = time.perf_counter() - start
return {
"first_token": first_token_time,
"total": total_time,
"tokens": token_count,
"tps": token_count / total_time if total_time else 0,
}
async def main():
tasks = [one_request() for _ in range(16)]
results = await asyncio.gather(*tasks)
for r in sorted(results, key=lambda x: x["first_token"]):
print(r)
asyncio.run(main())
压测过程要持续足够长的时间,比如10到20分钟,因为短时间压测捕捉不到显存碎片、KV Cache膨胀和热积累导致的性能退化。压测过程中要同时监控显存占用随时间的变化,如果显存一直在涨,就要怀疑有泄漏。
6.3 扩容阈值和队列长度的关系
推理系统的自动扩缩容不建议只按CPU使用率或QPS来触发。因为GPU服务在没请求时利用率很低,有请求时又会瞬间打满,直接简单阈值很容易抖动。
更合理的扩容信号是“排队请求数”。如果你在推理服务前面挂了队列,队里等待的请求数增多,说明当前实例已经无法及时消化流量。我们可以设置一个阈值,比如每个实例平均排队请求超过N个,就扩容。缩容时要更谨慎,因为GPU实例卸载模型很慢,缩得太快会造成服务闪断。我通常的做法是:缩容前先进入“排空”状态,即不再接收新请求,等现有请求跑完再移除实例。
从容量规划的角度看,有两个估算公式对做预算很有用:
单实例可支撑的并发请求数 ≈ 显存减去模型权重后的可用空间 / 单请求平均KV Cache开销。单实例每秒Token吞吐 ≈ 批量大小 × 每批生成Token速度 / 平均请求长度。
用这些数据乘以实例数,就能估算出全集群的容量。这些数字在不同型号的GPU上差异巨大,需要在本地实测后才能确定,不要直接照搬别人的基准值。
7. 上线后踩过的坑:显存泄漏、上下文溢出和模型热更新的处理
7.1 显存泄漏排查
我在上线早期遇到过一个很头疼的问题:推理服务的显存占用随运行时间不断上涨,运行一两天后就会出现OOM,然后进程崩溃重启。最麻烦的是复现困难,每次重启后又正常,但几小时后再次上涨。
排查的第一步是找出显存增长曲线。当时我写了一个小脚本,每隔30秒记录一次 nvidia-smi 中对应进程的显存使用量,同时在服务日志里记录请求的Token数和批次信息。对比后发现问题集中在特定请求模式——有些请求携带了很长的system prompt,而且并发高时,KV Cache碎片增长明显,长期运行后碎片化越来越严重。
这类问题不完全等同于“内存泄漏”,很多是引擎在申请显存块时无法利用碎片空间,导致有效显存逐渐变小。解决方式有几种:一是升级推理引擎版本,后续版本往往优化了显存分配算法;二是降低 --gpu-memory-utilization,把显存余量留足;三是周期性对服务做预重启,虽然治标不治本,但在业务低峰期做滚动重启也能减少影响。
7.2 上下文长度与并发度的矛盾
长上下文请求和并发度之间的矛盾,是我在架构设计时低估最严重的一点。业务方总希望上下文越长越好,但从资源角度讲,KV Cache占用随上下文长度线性增长,并发高时显存消耗会成倍放大。
我之前遇到过业务方要求上下文长度支持32K,并发也要高。从单卡看,这个组合几乎不可能在一个显存合理的实例上同时满足。要满足长上下文,就要大幅降低并发;要满足并发,就要把上下文限制到合理范围。这是个数学问题,不是调参能绕过去的。
架构层面的解法是做上下文分级。把高频短对话和长文档分析拆成两条独立的推理链路,分别部署在不同规格的实例上。短对话面向实时交互,控制并发和延迟;长文档任务可以放到离线批处理队列,支持更长的上下文但可以等待更长时间。不同链路之间互不影响,资源也更容易规划。
7.3 模型热更新导致的连接断开
推送新模型版本时,我遇到过线上连接大量断开的问题。原因是滚动更新时,vLLM实例开始加载新模型,而K8s的Service在LoadBalancer模式下把新Pod纳入流量转发后,新Pod还不能接受连接,或者旧Pod被终止时没有等当前请求完成就直接断开。
解决方案是调整两个参数:一是新Pod的就绪探针,必须等模型完全加载后再返回Ready,K8s才能开始把流量转发过来;二是Pod被终止时,要延长优雅终止时间,让引擎有机会处理完正在进行的请求。另外还要把Service设置为在摘除节点前检查连接,避免把流量打到正在销毁的Pod上。
现在我在滚动更新时还会加一个“金丝雀”步骤,先让新Pod接收小部分流量,确认无异常后再全量切换。推理模型的错误不像普通Web服务那样能在几秒内暴露,可能需要一段时间的观察,金丝雀发布是性价比最高的防护手段。
7.4 经验小结
推理系统的架构设计不存在一套适用于所有团队的标准答案,但有几个原则我认为是通用的:尽量让推理引擎做它擅长的事,不要在上层重复调度逻辑;认真对待显存预算,尤其要算上KV Cache和碎片开销;监控和告警要围绕Token设计,而不是只盯着QPS和CPU;最后,任何优化都要用压测数据说话,不要凭感觉调参。
我在实际项目中还有一个体会:推理系统上线后,不用急着追求极限吞吐,稳定运行比峰值性能更值钱。很多优化是逐步加进去的,每加一步都要做A/B对比和压测。保持系统的可观测性和可回滚性,远比一次性把优化做满更重要。这套架构实践踩过的坑,相信很多做推理服务的团队也会遇到,希望这些经验能帮你少走几段弯路。
