大语言模型部署实战:生产环境怎么做高并发、监控、限流与故障恢复?
前一篇把本地部署这件事拆开讲了:
- Ollama 更适合快速本地验证
- vLLM 更像服务化推理基础设施
- SGLang 更适合复杂推理编排
max_model_len、gpu_memory_utilization、tensor_parallel_size、max_num_seqs这类参数决定了服务上限,而不只是"能不能启动"
但很多团队真正上线时,遇到的问题很快就不再是"模型能不能跑",而是:
- 为什么单用户体验很好,一上并发就开始排队甚至超时?
- 为什么 GPU 利用率看起来很高,但用户还是觉得慢?
- 为什么日志里没报错,服务却偶发性卡住?
- 为什么同样的模型、同样的显卡,测试环境稳定,生产环境却经常抖?
- 为什么一次流量高峰之后,尾延迟会拖很久才恢复?
这些问题背后,本质上都不是"模型不够强",而是推理服务进入生产后,系统目标变了。
本地阶段看的是:能不能跑、效果好不好。
生产阶段看的是:
- 高峰时能不能扛住
- 延迟能不能预测
- 故障能不能隔离
- 出问题能不能定位
- 模型服务能不能像一个工程系统一样被运营
这篇就集中讲这个问题。
我想把大模型生产服务拆成四层来看:
- 高并发怎么扛:吞吐、排队、批处理、实例规模怎么平衡
- 监控怎么做:哪些指标真正能反映推理服务健康度
- 限流怎么做:什么时候该拒绝请求,而不是把系统拖死
- 故障恢复怎么做:模型服务抖动、OOM、节点异常后怎么尽快止损
如果你已经从"本地能跑"走到了"要给业务长期提供 API",这一篇基本就是分水岭。
一、先把问题说透:生产环境里的核心矛盾,不是模型推得快,而是请求来得不均匀
很多人一开始做推理服务,会默认用单次请求的速度来判断系统好不好。
比如:
- 首 token 延迟 1 秒以内
- 每秒能生成 40 token
- 某个 benchmark 跑得不错
这些都重要,但还不够。
因为生产环境不是一条请求匀速进入系统,而是:
- 有时空闲
- 有时瞬间冲进来一批请求
- 有的请求很短
- 有的请求上下文很长、输出也很长
- 有的请求还要求结构化输出、工具调用、多轮重试
所以线上最关键的问题不是:
单条请求快不快。
而是:
流量波动时,系统还能不能维持稳定、可预期的响应。
这会直接把几个概念拉到前台:
- 吞吐量(throughput)
- 首 token 延迟(TTFT)
- 请求总耗时
- P95 / P99 尾延迟
- 排队长度
- 实例利用率
很多团队的问题就出在这里:
把推理服务当成"更贵一点的 Web 接口",却没有按排队系统去设计。
而大模型服务一旦进入排队系统视角,你会发现策略完全变了。
二、高并发不是靠"更大的卡"自动解决,而是靠调度策略和容量规划
GPU 更大当然有帮助,但不是决定性答案。
真正决定系统是否能扛并发的,通常是三件事:
1)单位时间能处理多少 token
大模型服务的核心资源,不是请求数本身,而是 token 处理能力。
因为一个请求的成本主要来自两段:
- prefill:把输入上下文编码进去
- decode:逐 token 生成输出
这两段的资源特征不同:
- prefill 更像一次性计算高峰,输入越长越重
- decode 更像持续占用,输出越长越拖系统
所以生产上别只看 QPS。
更稳妥的是同时看:
- 输入 token/s
- 输出 token/s
- 平均每请求输入长度
- 平均每请求输出长度
否则两个"同样 10 QPS"的业务,系统压力可能完全不是一个量级。
2)高并发本质上是"长短请求混跑"问题
最麻烦的不是高并发本身,而是不同类型请求同时进来。
比如一个实例上同时出现:
- 200 token 输入、100 token 输出的轻请求
- 8k token 输入、1k token 输出的重请求
- 需要 JSON 结构化输出、失败还会重试的复杂请求
如果没有调度隔离,系统就会出现非常典型的问题:
- 轻请求被重请求拖慢
- 排队时间不可预测
- 尾延迟迅速升高
- 用户体感比平均值差很多
所以线上不要只做"统一模型服务",还要做请求类型分层。
常见做法包括:
- 短请求和长请求分开路由
- 普通聊天和结构化抽取分开实例
- 长上下文能力做成专门服务池
- 高优任务和低优任务使用不同队列
很多系统明明算力够,却因为所有请求混在一起,最后把体验做坏了。
3)并发上限不是拍脑袋设的,而是靠压测反推
很多参数名字看起来像"容量开关",但其实只是你对风险的选择。
比如:
max_num_seqsmax_model_lengpu_memory_utilizationmax_tokens- batch 上限
这些值配大一点,表面上看实例能接更多请求。
但真实情况往往是:
- 显存更紧张
- 长请求更容易把队列拖长
- P99 更难看
- OOM 风险更高
所以容量规划的正确顺序通常是:
- 先定义目标流量模型
- 再用真实输入长度和输出长度做压测
- 观察 TTFT、吞吐、P95/P99、OOM 情况
- 反推出单实例安全并发上限
- 再决定实例数和扩容策略
不是先把参数拧到最大,再希望系统自己稳定下来。
三、在线推理里最重要的工程问题之一,是把 prefill 和 decode 分开理解
很多系统调优总是陷入一个误区:
"模型慢了,就继续加机器。"
但如果不区分 prefill 和 decode,很多优化其实会失焦。
1)prefill 为什么经常在长文档场景里成为瓶颈
当用户一次塞进来大量上下文时,系统首先要把整段输入编码进 KV Cache。
这一步的典型特征是:
- 算力消耗集中
- 对输入长度非常敏感
- 可能在流量高峰时造成瞬时拥堵
所以 RAG、长文档问答、长对话续写这类场景,经常不是 decode 慢,而是 prefill 先把前面堵住了。
这也是为什么很多团队后来会做:
- chunk 控制
- 上下文截断
- 检索条数限制
- prompt 模板瘦身
这些动作看起来像"提示词优化",其实本质上是在做系统负载控制。
2)decode 为什么更像持续性的系统占用
进入生成阶段后,每个请求会持续占着资源往前跑。
如果输出长度不受控,就会出现两个问题:
- 单个请求在系统里停留过久
- 队列后面的请求越来越多
所以线上一般不会让 max_tokens 无限制放开。
更实际的做法是:
- 按接口类型限制输出长度
- 摘要接口给较短上限
- 报告生成接口单独走重服务
- Agent 任务按阶段拆开,而不是一次生成到底
本质上,max_tokens 不只是内容参数,它同时也是调度参数。
3)为什么很多团队会把长上下文和普通问答拆成两套服务
原因很简单:
- 长上下文实例需要更多显存预算
- 它更容易被长 prefill 拖慢
- 它会压缩普通问答的并发承载能力
如果所有业务都共用一套长窗口实例,结果通常是:
- 普通请求成本变高
- 吞吐下降
- 用户体感变差
所以更合理的方案往往是:
- 普通 4k / 8k 服务池
- 长文本 32k / 64k 服务池
- 必要时再做高优先级路由
这比"一套实例打天下"更工程化。
四、真正能扛住高并发的系统,通常都做了队列、批处理和分级路由
推理服务只要一进生产,就几乎绕不开这三件事。
1)队列不是可选项,而是系统缓冲层
很多人对队列有误解,觉得一排队就是系统不行。
其实不是。
没有队列的系统,遇到突发流量时更容易直接失控。
队列的作用主要有三个:
- 吸收突发流量
- 给调度器留出合并请求的时间窗口
- 让系统知道自己当前有多拥堵
但队列不是越长越好。
如果队列无限堆积,只是把失败从"立即失败"变成"延迟很久后失败"。
所以工程上要配合两个策略:
- 最大排队长度
- 最大等待时间
超过阈值时就应该主动拒绝,而不是继续拖。
2)连续 batching 的收益,不只是更高吞吐
像 vLLM 这类框架的价值,很大一部分就在于能持续把请求合并进 batch。
这样做的好处是:
- 提高 GPU 利用率
- 降低空转
- 在并发场景下比单请求串行更划算
但连续 batching 也有代价:
- 调度更复杂
- 长短请求混跑更容易互相影响
- 某些极端请求会拉坏整体尾延迟
所以很多成熟系统不会只依赖框架默认行为,而是会在上层再做:
- 按请求类型拆队列
- 按优先级拆队列
- 给结构化任务单独池子
3)分级路由比"统一入口统一处理"更现实
如果你的业务已经比较复杂,建议尽早做分级路由。
比如至少区分:
- 普通对话
- 长文档问答
- 结构化抽取
- Agent / 工具调用
- 离线批处理任务
这些任务的资源特征、延迟目标、失败代价都不一样。
把它们全部塞给同一层实例,短期看省事,长期看一定会让排障和容量规划越来越痛苦。
五、监控不是多加几个 dashboard,而是要能回答"系统为什么慢、慢在哪、会不会更慢"
很多团队说自己做了监控,其实只是画了几张显卡使用率曲线。
这远远不够。
生产环境里的监控,核心不是"看起来很多数据",而是能支撑判断和行动。
至少要分成四层。
1)资源层监控:机器到底是不是在硬件上出问题
这一层至少要看:
- GPU 利用率
- GPU 显存占用
- GPU 温度与功耗
- CPU 利用率
- 内存占用
- 磁盘 IO
- 网络吞吐
为什么这一层仍然重要?
因为很多"模型变慢",根本不是模型本身的问题,而是:
- 容器被别的进程抢资源
- 磁盘抖动导致加载异常
- 节点网络问题引发上游超时
- 驱动或 CUDA 相关异常造成性能退化
2)服务层监控:接口有没有在健康提供能力
这一层建议重点看:
- QPS / 请求数
- 成功率
- 4xx / 5xx 比例
- 平均响应时间
- P95 / P99 响应时间
- 排队长度
- 请求超时数
- 主动拒绝数
这里有个常见误区:
平均延迟很漂亮,不代表服务真的好。
因为用户最容易感知的是慢请求,而不是平均值。
所以尾延迟必须长期盯住。
3)推理层监控:模型服务到底卡在 prefill 还是 decode
这是大模型服务区别于普通 API 的关键层。
建议至少拆出:
- TTFT
- 每秒输出 token 数
- 平均输入 token 长度
- 平均输出 token 长度
- prefill 耗时
- decode 耗时
- batch 大小分布
- 每实例活跃序列数
- OOM 次数
有了这些指标,你才知道:
- 是输入过长导致 prefill 被拖慢
- 还是输出过长导致 decode 占住资源
- 是 batch 太小没有吃满卡
- 还是 batch 太大反而引起抖动
4)业务层监控:系统快不快,不等于业务真的可用
很多时候模型服务本身没挂,但业务已经不可用了。
比如:
- 工具调用成功率下降
- JSON 结构化输出失败率升高
- 答案截断率上升
- 某类任务重试暴增
这些问题只看底层 GPU 指标是看不出来的。
所以生产系统最好再加一层业务观测:
- 按任务类型统计成功率
- 按模型版本统计质量波动
- 记录典型失败样本
- 给关键链路做 canary 请求
这才是真正能发现"系统还活着,但已经不好用了"的办法。
六、限流不是保守,而是保护系统和保护用户体验
很多团队一开始排斥限流,觉得限流是在损失业务。
但不做限流,通常损失更大。
因为推理服务一旦进入过载状态,常见结果不是"大家都慢一点",而是:
- 排队迅速膨胀
- 尾延迟失控
- 超时增多
- 上游重试放大流量
- 节点开始 OOM 或抖动
- 最终整片服务不可用
所以限流本质上是:
用局部拒绝换整体可用。
1)最基础的限流:实例级并发上限
每个实例都应该有明确的安全承载边界。
超过之后,不要继续盲接。
否则系统只是把失败推迟。
常见控制点包括:
- 最大活跃请求数
- 最大排队请求数
- 最大 token 预算
- 单请求最大输入长度
- 单请求最大输出长度
2)更实用的限流:按租户、按业务、按优先级分层
如果系统服务多个业务方,统一限流通常不够。
更合理的做法是分层:
- 核心业务优先级更高
- 内部测试流量优先级更低
- 离线批处理不要抢在线流量
- 大客户或关键租户保留配额
这样即使系统进入紧张状态,也不至于所有业务一起掉。
3)限流要和超时、熔断、重试一起设计
如果只有限流,没有配套超时与重试策略,也会出问题。
典型坏案例是:
- 模型服务慢了
- 上游超时后立即重试
- 下游继续接收更多重复请求
- 整个系统雪崩
所以一套完整的保护机制通常包括:
- 明确超时阈值
- 有上限的指数退避重试
- 失败快速返回
- 熔断可疑实例
- 必要时降级到小模型或缓存答案
限流从来不是一个孤立开关,而是整套保护链的一部分。
七、故障恢复的关键,不是"永不出错",而是"出错后别扩散"
大模型服务进入生产后,故障几乎一定会发生。
真正重要的是:
- 能不能快速发现
- 能不能把影响范围限制住
- 能不能自动恢复到可接受状态
1)最常见的几类故障
生产里最常见的大模型推理故障,通常包括:
- 显存耗尽导致 OOM
- 某个长请求拖死实例
- 推理进程卡住但没有直接退出
- 模型权重加载失败或重启后恢复很慢
- 节点网络异常导致上游误判实例存活
- 某个版本上线后尾延迟大幅升高
这些故障很少是"彻底挂掉"这么简单。
更麻烦的是半故障状态:
- 还能接请求
- 但明显变慢
- 错误率开始上升
- 上游还在继续打流量
这类状态最容易把整片系统拖坏。
2)健康检查要能区分"活着"和"可服务"
很多系统的 health check 只是:
- 进程在不在
- 端口通不通
这只够判断"机器没死"。
但推理服务更需要判断:
- 是否还能正常返回结果
- 平均耗时是否异常飙升
- 是否已经连续 OOM
- 是否排队过长
所以更靠谱的健康检查通常会结合:
- 轻量探针请求
- 最近窗口错误率
- 最近窗口尾延迟
- 当前队列积压程度
只有这样,流量调度层才能把真正不健康的实例摘掉。
3)恢复策略要分层,而不是全部靠重启
重启当然有用,但不是唯一手段。
更成熟的恢复链路一般会分层:
- 请求级恢复:超时、取消、重试、降级
- 实例级恢复:摘流、清队列、重启进程
- 节点级恢复:迁移流量、重建容器、切换节点
- 系统级恢复:回滚版本、关闭高成本功能、启用降级模型
如果所有问题都只能靠人工 SSH 上去重启,说明系统还没有准备好进入长期运营阶段。
4)降级机制常常比"硬扛"更重要
当系统处于高压状态时,降级比坚持完整能力更有价值。
常见降级方法包括:
- 把长上下文请求改走慢队列
- 暂时降低
max_tokens - 关闭高成本结构化模式
- 从大模型切到小模型
- 对非关键功能返回缓存或模板化结果
降级的目标不是让体验完美,而是让系统在异常时期仍然"可用"。
这在生产里非常关键。
八、生产部署时,几个关键参数应该怎么理解
下面把线上最常见的几个参数,再用生产视角解释一遍。
1)max_model_len
含义:
- 实例允许处理的最大上下文长度
生产影响:
- 决定 KV Cache 预算
- 影响单实例并发能力
- 直接影响长请求对系统的拖拽程度
建议:
- 不要默认全量实例都开超长上下文
- 长文本需求单独建池通常更合理
2)max_num_seqs
含义:
- 实例同时活跃处理的序列上限
生产影响:
- 决定系统并发承载方式
- 会影响 batch 行为、显存压力和尾延迟
建议:
- 用压测反推,不要追求数字越大越好
3)gpu_memory_utilization
含义:
- 推理框架可使用的 GPU 显存比例
生产影响:
- 直接影响 OOM 风险与波峰容错空间
建议:
- 生产值通常比实验值更保守
- 给偶发峰值留安全边界
4)max_tokens
含义:
- 单请求最大生成长度
生产影响:
- 决定 decode 占用时长
- 影响整体吞吐和队列恢复速度
建议:
- 按接口分类设置,而不是给所有接口同一大值
5)请求超时参数
含义:
- 请求在系统里最多允许等待和执行多久
生产影响:
- 决定异常请求会拖系统多久
- 直接影响上游重试行为
建议:
- 区分排队超时和执行超时
- 超时后要有明确返回码和降级策略
九、一套更接近现实的生产架构,通常长什么样
如果把一个相对健康的大模型生产系统抽象一下,常见结构会是:
- API 网关层:认证、路由、配额、限流
- 调度层:队列、优先级、实例选择、熔断
- 推理层:vLLM / SGLang / 其他推理服务
- 监控与日志层:指标、日志、trace、告警
- 模型治理层:版本管理、灰度发布、回滚
在这个架构里,推理引擎只是中间一层。
真正决定生产可用性的,不只是模型框架本身,而是它前后两端有没有补齐:
- 前面有没有做入口保护
- 后面有没有做观测和恢复
如果只有推理引擎,没有队列、限流、监控和回滚,系统很容易停留在"能跑的 demo 服务"阶段。
十、三条最实用的生产建议
如果你准备把模型服务正式供业务使用,我觉得最值得先做的是这三件事。
建议一:先按请求类型拆池,而不是先追求一个万能大池
普通问答、长文档、结构化输出、Agent 调度,不要混成一锅。
一旦拆池,很多延迟和容量问题会立刻变得更容易解释。
建议二:先把尾延迟和拒绝率看稳,再谈极限 GPU 利用率
一个 GPU 利用率很高但 P99 很差的系统,通常不是"更高效",而是"更脆"。
生产优先级应该是:
- 稳定性
- 可预测性
- 可恢复性
- 然后才是极限吞吐
建议三:一定做带真实流量特征的压测
不要只测短 prompt。
至少要覆盖:
- 短输入短输出
- 长输入短输出
- 长输入长输出
- 高峰期并发突增
- 上游重试叠加
- 结构化输出和工具调用场景
只有这样,监控、限流和恢复策略才不是纸上谈兵。
十一、最后总结:生产环境里的大模型服务,本质上是一个排队系统加一个脆弱的高成本计算核心
很多人做大模型部署,前半程关注的是模型,后半程才发现真正难的是系统。
因为只要开始对外提供服务,核心问题就会变成:
- 流量波动怎么吸收
- 长短请求怎么隔离
- 什么指标能真正说明系统健康
- 什么时候该拒绝请求保护整体可用
- 节点出问题后怎么快速恢复
所以这篇最核心的结论其实可以浓缩成一句话:
生产环境的大模型服务,不是"把模型放到 GPU 上",而是"把推理能力变成一个可观测、可控、可恢复的系统"。
如果你把这件事做好了,大模型服务才算真正从"能跑"走到了"可运营"。