2986 字
约 9 分钟
15
S23-A02 · 推理服务架构:模型服务化、批处理与算力调度

推理服务不是"把模型包成 HTTP 接口",而是一组互相牵制的取舍:算力归谁、请求怎么排队、显存里放什么、延迟由谁兜底。同一个模型,POC 式与生产式部署的代码可能只差几十行,成本曲线却差一个量级。


1. 五个决策点

决策点 偏"自建/集群/异步/批/共享" 偏"API/单机/同步/不批/独占" 拍错的代价
自建还是调 API 量大且稳、数据不出域、需锁版本 流量波动、模型迭代快、无 GPU 运维 卡长期闲置,折旧吃掉预算
单机还是集群 单卡装不下、要多副本抗故障 单卡放得下、QPS 低 调度与网络开销超过收益
同步还是异步 单请求数十秒以上、可离线 交互式、需即时反馈 连接池被长任务耗尽
批还是不批 离线批量、延迟容忍高 首 token 即体验核心 TTFT 崩塌,交互不可用
共享还是独占 多模型、低负载、成本敏感 硬 SLA、显存紧、干扰不可接受 OOM 连锁,一模型拖垮整卡

判据一句话:先量化请求量与延迟目标,再决定形态。没有 QPS 与 TTFT 目标的架构讨论都是审美。

2. 成本结构:自建与托管 API 的对照

自建成本不是一个 GPU 单价,而是四项之和:算力折旧 + 闲置率 + 运维人力 + 机会成本。

成本项 自建(GPU 为主) 托管 API 反转点
计费单位 卡·小时(按厂商报价核对) 按输入/输出 token 月 token 跨阈值后自建更省
固定性 全额固定,闲时照付 随量线性,无请求即零 波动越大越偏 API
闲置率 典型量级 30%–60% 空转 不适用 过半则自建账面优势消失
运维人力 需专人管驱动、显存、调度 由厂商承担 团队小则人力超过差价
版本风险 升级自己重测、自己回滚 厂商升级即生效(也是风险) 需锁版本时自建可控
峰值能力 按峰值采购,峰后闲置 弹性,但可能限流 峰均比大于 2 偏 API

把它变成可算的式子,比拍脑袋可靠:

# 粗略估算(单价按厂商报价核对,不要用记忆中的数字)
gpu_month = GPU_HOUR_PRICE * 24 * 30 * replica_count     # 算力折旧
idle_loss = gpu_month * idle_ratio                       # 闲置浪费
ops_cost  = engineer_month_cost * ops_headcount_fraction # 运维分摊
self_host = gpu_month + idle_loss + ops_cost

tokens_month = qps_peak * avg_tokens_per_req * 3600 * 24 * 30 * duty_cycle
api_cost = tokens_month / 1e6 * price_per_million_tokens

结论通常不是谁更便宜,而是分层:稳定基座自建、峰值溢出走 API。混合结构把闲置率转嫁给厂商,代价是两套链路都要维护。

3. 吞吐与延迟的对立

连续批处理与 PagedAttention 解决同一类问题:不让 GPU 等请求。它们不是免费的。

手段 收益 代价 判据
静态批处理 简单,吞吐明显提升 整批等最慢一条,尾延迟拉长 离线、批内长度相近
连续批处理 每步重排,吞吐显著提升 调度开销,长度差异致抖动 在线默认
PagedAttention KV 按页分配,碎片大降 实现复杂,页表管理开销 并发高、长度方差大
前缀缓存 相同前缀免重算,TTFT 降 命中率依赖提示结构 系统提示长且共享
投机解码 单请求延迟降 换时间,吞吐可能降 低 QPS、延迟敏感

一条判据:批大小存在吞吐饱和点,越过之后吞吐不再涨、TTFT 继续恶化。该点只能实测,可先用下面参数逼近:

# 服务端参数骨架(数值按实测与厂商文档核对)
max_num_seqs: 256             # 同时在批请求数上限
max_num_batched_tokens: 8192  # 单步 token 上限,吞吐/延迟折中点
gpu_memory_utilization: 0.90  # KV 预分配比例,留碎片余量
enable_prefix_caching: true
enable_chunked_prefill: true  # 长 prompt 分块,改善 TTFT 抖动

gpu_memory_utilization 调到 0.95 以上看似榨干,实际把 OOM 从启动期推到运行期,后者代价高得多。

4. 四种部署形态

形态 延迟 隔离性 成本 判据
进程内 最低,无网络 无,与业务抢内存 最低 单模型、低 QPS、小模型
独立服务 一次往返 强,可独立扩缩 中 多语言客户端、需独立伸缩
边车 本机回环 中,随 Pod 生命期 中高,按 Pod 复制 每实例需专属模型
Serverless 冷启动不定 强 低频最优、高频最贵 突发、低频、可容忍冷启

边车最易误用:它把模型副本数与业务副本数绑死,业务扩容时显存跟着翻倍。判据是模型副本需求曲线是否与业务一致。

resources:
  limits:
    nvidia.com/gpu: "1"   # 整卡独占;共享需 device plugin 支持
  requests:
    cpu: "4"
    memory: "16Gi"

5. GPU 利用率的三层浪费

浪费层 表现 量化手段 处置
显存碎片 总量够但分配失败 reserved 与 allocated 差值 PagedAttention、统一预分配
batch 排不满 GPU 空转等请求 批大小分布、每步 token 数 连续批处理、调队列深度
KV 膨胀 长会话吃满显存 单请求 KV × 并发数 限上下文、前缀缓存、驱逐

显存估算不必精确到 MB,但要能算量级:

weights  ≈ params × bytes_per_param   # fp16 约 2,int8 约 1,int4 约 0.5
kv_cache ≈ 2 × layers × kv_heads × head_dim × ctx_len × concurrency × bytes
total    ≈ weights + kv_cache + activation_peak + 碎片余量(10%~20%)

长上下文场景里 KV cache 往往超过权重本身——"装得下"和"跑得动并发"是两件事。

6. 算力调度与公平性

策略 机制 公平性风险 适用
静态分配 每模型固定卡数 无争抢,闲置不可回收 有硬 SLA 的核心模型
队列调度 排队按容量消费 长请求饿死短请求(或反之) 异步任务、批处理
优先级抢占 高优抢低优 低优反复被中断,进度不可预期 在线优先、离线可断
多模型混布 共享卡,切时间片或显存 相互干扰,尾延迟不可控 多小模型、低负载

抢占的关键不是能不能抢,而是被抢的任务是否可恢复。离线任务可抢;长事务型请求被抢即失败,应走重试而非抢占。

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: offline-batch
value: 100
preemptionPolicy: PreemptLowerPriority

7. 多模型路由与降级链

路由依据只有三类:任务类型、成本上限、SLA。冲突时以 SLA 为准——SLA 违约可观测,成本超支往往月底才发现。按任务分流的风险在分类器自身会错,按成本升级的风险在判据难定,按 SLA 分层的风险在抖动传导。

routes:
  - match: intent == "chat"
    primary: MODEL_LARGE
    fallback:
      - on: [timeout > T1, error_rate > R1]
        use: MODEL_SMALL
      - on: [all_providers_unavailable]
        use: STATIC_TEMPLATE   # 终点必须是确定性输出

终点若还是另一个模型,只是延长故障,不是降级。

8. 容量规划与扩容滞后

容量规划回答三件事:峰值 QPS、TTFT 与 TPOT 目标、单副本并发上限。前两个业务给,第三个只能实测。

副本数 ≈ ceil( peak_qps × p99_latency / 单副本并发上限 )
单副本并发上限 ≈ min( 显存可容 KV 数, 调度器可排深度, CPU 预处理能力 )

扩容滞后来自三层:指标窗口(数十秒到数分钟)、调度与拉镜像(数十秒)、权重加载(数十秒到数分钟)。缓解依次是用队列长度而非 CPU 触发扩容、预热镜像、本地缓存权重。就绪探针要等到能出 token 再放流量。结论:扩容只能应对缓变,突发必须靠预留容量或排队削峰。

9. 可观测性与演进路径

指标 回答的问题 异常信号
排队时长 算力不够还是调度慢 排队占总延迟一半以上
批大小分布 吞吐是否被排不满拖累 长期低于阈值
KV 使用率 显存被谁占着 持续贴近上限即 OOM 前兆
OOM 重启次数 容量是否真够 任何非零值都要查

演进不要跳级:单卡试点(先钉质量基线)→ 多副本(负载均衡与健康检查)→ 批处理服务(队列、优先级、可中断)→ 混布调度(隔离与抢占,接受尾延迟上升)。每级都该有触发条件,而非按计划表推进。

10. 排错

现象 可能原因 处置方向
首 token 慢但总时长正常 排队或 prefill 拥塞 看排队时长与 prefill 占比
吞吐低、GPU 利用率低 批排不满或 CPU 预处理瓶颈 看批大小分布、tokenize 耗时
偶发 OOM 集中在高峰 KV 预分配比例过高 降 utilization、限上下文、加驱逐
混布后尾延迟变差 共享卡相互干扰 改独占或加时间片隔离
扩容后延迟没改善 冷启动慢、流量没打到新副本 查就绪探针与负载均衡
成本超预算但 QPS 未涨 长输出、重试放大、缓存未命中 按 token 维度拆账

11. 边界

场景 不该做的事 更合适的做法
QPS 很低、请求稀疏 建集群、上调度器 单进程或托管 API
单卡放得下且负载平稳 分布式推理 单卡多副本,简单可靠
延迟不敏感(离线) 优化 TTFT 把预算花在吞吐与成本
任务强确定(规则可判) 用模型兜底 规则优先,模型处理长尾
无 GPU 运维能力 自建集群 托管 API + 峰值混合

过度设计的信号很一致:说得出每一层,却说不出它解决了哪个已观测到的问题。


12. 自测

  1. 判断"自建还是调用 API"时,哪两个量化指标必须先有?
  2. 闲置率对自建成本的影响,为什么比 GPU 单价更关键?
  3. 连续批处理提升吞吐,为什么仍可能让尾延迟变差?
  4. gpu_memory_utilization 设到 0.95 以上,风险为什么落在运行期?
  5. 降级链的终点为什么不能是另一个模型?
  6. 扩容滞后里哪一层通常最大,它对"抗突发"意味着什么?
S23-A02 · 推理服务架构:模型服务化、批处理与算力调度
https://www.clxhxhhr.top/posts/6050/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。