一个推理服务有 6 个模型(每类任务一个),全都常驻——于是 24G 的卡"看着满了但 QPS 很低"。改成"按需加载"后,显存能放更多并发,而"每天第一次请求"要等 40 秒。
"常驻还是按需"本质上是"用显存换首请求时间"。而它有三个中间态:常驻、热备(部分常驻)、按需(每次加载)。这两个变量"相乘"才决定成本:"每天切 3 次 × 15 秒"与"每天切 300 次 × 15 秒",前者可以接受,后者必须常驻。
选择取决于"模型切换频率"与"首请求能容忍多久"。
1. 三种模式的代价对比
| 模式 | 显存 | 首请求 | 切换成本 |
|---|---|---|---|
| 全常驻 | 高(每模型都占) | 0 | 0 |
| 热备(MRU 缓存 N 个) | 中 | 0(命中)/加载(未命中) | 中 |
| 按需 | 低 | 加载时间 | 每次 |
第二项是"实践中最常选"的:**保留"最近用过的 N 个"**第二项的"参数 N"要按"显存"算,而不是按"业务有几个模型":3 个 7GB 的模型 + KV 余量,就要在 24G 卡上留出 2–3GB——算错就是"加载到一半 OOM"。
,其余淘汰——于是"高频任务"零等待,"低频任务"偶尔等一次。
2. "加载时间"由什么决定
| 因素 | 影响 |
|---|---|
| 模型体积 | 主要(10GB → 10–60 秒) |
| 存储介质 | SSD vs 网络存储(差数倍) |
| 量化/分片 | 分片可并行加载 |
| 引擎的加载方式 | mmap vs 读入内存 |
第一项的"实践含义"是"先把存储换成本地 NVMe":它比"优化加载代码"便宜得多,而收益可能 5 倍——而多数团队先去看"引擎参数"。
第一项是"最主要的":加载时间大致与"权重体积 / 存储带宽"成正比——于是"从网络存储加载 20GB"可能是"从本地 SSD 的 5 倍"。
第三项是"被低估的优化":分片(shard)可以并行加载第三项的"前提"是"存储能并行读":网络存储的"并发带宽"往往受限,于是"4 个分片并行"与"单文件"差别不大——分片只有在"本地多队列 SSD"上才明显。
,于是"20GB 的 4 分片"比"单文件"快 2–3 倍。
// 一个粗略的加载时间估算(含三类开销)
t_load ≈ 权重体积 / 存储带宽 + 初始化(图构建)+ 预热(首 token 编译/分配)
// 例:20GB / 2GB/s ≈ 10s,加初始化 3s、预热 2s ≈ 15s
// 若走网络存储(200MB/s)→ 100s + 5s ≈ 105s(差 7 倍)
3. "热备"的三个设计点
| 设计点 | 说明 |
|---|---|
| 缓存容量(N 个) | 按显存与模型体积算 |
| 淘汰策略 | LRU 或"按业务优先级" |
| 预加载时机 | 空闲时/按预测 |
这个"抢"的量化方式是"看加载期间的 P99":上升 20% 以上就说明"抢得太多"——此时该"限速"或"挪到低峰"。
第三项的"风险"是"预加载抢了正在服务的资源":加载 20GB 会占满存储带宽与部分显存,于是"正在推理"的请求变慢——所以要"限速加载"。
第三项是"把等待藏起来"的关键:在"空闲窗口"提前加载"下一个可能用的模型"——于是"用户等"变成"系统等"。
// 热备的三条配置(容量 / 淘汰 / 预加载)
model_cache:
max_models: 3 # 按显存算:3 × 7GB + KV 余量
eviction: priority_lru # 业务优先级优先,其次 LRU
prefetch:
trigger: idle_30s # 空闲 30 秒后
candidates: ["chat", "summary"] # 按"昨日调用频次"排
"加载"要能"限速",否则它会"抢掉服务资源":
// 三种"加载限速"的做法(按侵入性)
1 引擎侧参数:限制加载线程数与 IO 并发
2 系统侧:对"加载进程"做 cgroup IO 限速
3 调度侧:只在"低峰时段"触发预加载
// 判据:加载期间"在线请求的 P99"是否明显上升
4. "按需"的三个前提
| 前提 | 缺了会怎样 |
|---|---|
| 首请求可容忍(异步/排队) | 用户等 40 秒 |
| 有"加载中"的状态反馈 | 以为卡死 |
| 失败可重试 | 一次加载失败全挂 |
第一项是"产品"前提:如果"首请求"出现在"用户点击后立刻",40 秒不可接受第一项的"设计"是"把"冷启动"移到"用户看不到的地方"":定时任务在凌晨预热、或"用户提交后异步处理"——于是"等 40 秒"的是系统,不是人。
;如果它出现在"批处理任务"里,40 秒无所谓。
5. "混用"的三种做法
| 做法 | 说明 |
|---|---|
| 主模型常驻 + 其他按需 | 最常见 |
| 按"租户"分池(大客户常驻) | 商业化常见 |
| 按"时段"切换(白天常驻、夜里按需) | 省显存 |
第二项的"实现"是"两个池 + 路由":常驻池(固定 N 个模型)与按需池(共享显存)——而"路由"要按"租户 SLA"决定进哪个池。
第二项在"多租户"里很实用:大客户付费买"零等待",小客户共享"按需池"——于是"成本"与"SLA"分层。
"是否划算"可以算一个数:每次切换的等待 × 每小时切换次数,与"常驻多占显存导致的并发下降"比。
// 一个粗略的对比(同一张卡)
常驻 3 个模型:并发从 8 降到 4,QPS 约 -40%,首请求 0 等待
热备 1 个 :并发 8,每小时切 6 次 × 15s = 90s/小时 的等待
// 判据:若"等待"只影响 90s/小时,而"常驻"让 QPS 掉 40% → 热备更划算
6. 排错:五种"常驻/按需问题"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 显存满但 QPS 低 | 全常驻 | 改热备 |
| 每天首次很慢 | 按需 + 无预热 | 加预加载 |
| 偶发 40 秒超时 | 未命中缓存 | 加"加载中"状态 |
| 加载失败后不可用 | 无重试 | 加重试 + 降级 |
| 切换抖动 | 模型被反复抛弃 | 调 N 或改优先级 |
第五项在"两个模型交替使用"时出现:缓存只能放 1 个,于是每次都要换第五项的"识别"是"看"加载次数/小时"":接近"请求数"就说明"每次都加载"——此时"加 1 个缓存位"比"优化加载速度"收益大。
——而"换一次 15 秒",于是"两个任务都慢"。
"冷启动"的用户可见性,决定"要不要为它做产品设计":
// 三种"用户可见性"与对策
用户点击后立刻等(同步):不可接受 → 常驻或预热
用户提交后异步(后台处理):可接受 → 按需 + 进度反馈
定时任务(无人等待):完全可接受 → 按需最省
// 判据:这 40 秒"谁在等"
// 补充:即使"无人等待",也要有"加载失败"的重试——否则"批处理"会在载荷上撞到"模型没起来"
7. 边界:什么情况全常驻是对的
| 场景 | 为什么 | 做法 |
|---|---|---|
| 单模型服务 | 无"切换" | 常驻 |
| 延迟敏感(首请求 < 1s) | 不能等加载 | 常驻 |
| 显存充裕(占用 < 50%) | 无竞争 | 常驻 |
| 模型很小(< 1GB) | 加载几秒 | 常驻更简单 |
最后一条与"成本"有关的边界:"显存"是"固定成本","等待"是"每次发生"——于是"切得越多,越该常驻";而"每天只切一次"的任务,宁可等那一次。
判断口径一句话:"模型切换频率 × 每次加载时间"与"常驻多占的显存"哪个更贵。切换频繁(每小时多次)或延迟敏感,常驻;切换稀疏(每天几次)且显存紧,按需/热备。
8. 自测
- 三种模式的代价分别落在哪?为什么"热备"是实践中最常选的?
- "加载时间"主要由什么决定?"分片并行加载"为什么能快 2–3 倍?
- "预加载时机"为什么能把"用户等"变成"系统等"?
- "按需"的三个前提是什么?缺"状态反馈"会怎样?
- "两个模型交替使用"为什么会"都慢"?怎么缓解?
- 判断"常驻还是按需"的那个算式是什么?