TGI 多模型部署时的内存预算,核心是把模型权重和 KV cache 拆成两个维度去算。权重属于静态占用,加载后基本不变;KV cache 属于动态占用,由并发请求数、序列长度和模型结构共同决定。明确了这两类内存的边界,才能判断“共享权重”到底能省多少,以及每个模型独立 KV cache 需要预留多少空间。
共享权重通常只能省下模型权重那一份静态内存,KV cache 仍要按照每个模型的峰值并发和最大序列长度独立计算。预算时先按最差并发叠加每个模型的 cache 上限,再核对框架是否真的复用同一份权重组。部署后必须用真实负载监控峰值显存,来校准预留值。
内存构成:权重与 KV cache 分开估算
模型权重内存 = 参数量 × 每个参数的字节数。比如 FP16 通常约 2 字节,INT8 约 1 字节,具体数值由加载精度决定。多个模型如果各自独立加载,权重内存按模型数量累加;如果确认共用同一份权重组,则只需计入一份。
KV cache 内存则独立于权重,每个请求都会占用自己的缓存空间。TGI 这类推理服务会把 KV cache 统一管理,但多模型部署时,如果每个模型有独立的 cache 池,就需要为每个模型分别计算峰值占用。粗略估算时,KV cache 大小与层数、KV 头数、头维度、序列长度和并发请求数成线性关系,可以先用公式“2 × 层数 × KV 头数 × 头维度 × 序列长度 × 并发数 × 每元素字节数”得到下限,再根据实际实现叠加额外开销。
共享权重的落地边界
“共享权重”是否能真的省内存,取决于部署方式。如果多个模型是同一个基础模型加不同的 LoRA/Adapter,那么基础权重可以只加载一份,每个模型只额外占用自己的 Adapter 权重,KV cache 仍然按各自的请求量独立分配。此时内存预算就是“基础权重 + 所有 Adapter 权重 + 所有模型的 KV cache 峰值”。
如果多个完整模型参数恰好相同,但通过多进程或多实例分别加载,那么即使权重文件相同,进程间通常也是各自持有一份,不会自动去重。要避免这种情况,只能把多个模型放到同一个进程内,或者依赖操作系统的共享内存机制,但这不是 TGI 默认行为。因此预算时不能默认“同一份权重就自动只占一份内存”,必须确认进程内部是否真正复用了同一份参数。
内存预算估算模板
可以先按下面的思路建立估算脚本,把具体参数换成自己模型的结构数据。注意这里只给骨架,不是可直接运行的生产配置。
def estimate(weights_per_model_bytes, model_count, cache_per_seq_bytes, max_concurrent, max_seq_len):
# 如果确认共享权重,model_count 视为 1;否则按实际加载份数
weights = weights_per_model_bytes * model_count
kv_cache = cache_per_seq_bytes * max_concurrent * max_seq_len
# 预留部分需要结合部署环境调整,不要直接使用固定比率
overhead = (weights + kv_cache) * 0.1 # 仅示例,需用真实压测校准
return weights + kv_cache + overhead
执行时,先分别估算单个模型的权重和 KV cache,再按上文规则叠加。如果使用 TGI,需要重点核对以下参数:max_input_tokens、max_total_tokens、并发上线和模型加载精度。这些参数直接决定 KV cache 的上限,设置过小会限制单个请求的长度,设置过大则可能让显存提前打满。
验证清单:用实际负载校准预算
- 启动一个 TGI 实例,分别记录加载模型前后的显存占用,确认权重是否只加载一份。
- 用少量请求测试,观察 KV cache 的占用变化,与估算公式对比。
- 逐步提高并发和输入长度,记录显存峰值,确认是否触碰上限。
- 如果同一个部署内有多个模型,分别触发每个模型的峰值请求,观察是否存在单模型 cache 拖垮整体的场景。
- 用
nvidia-smi或容器监控工具持续采样,覆盖低峰和高峰时段,再根据采样结果调整预留空间。
需要留意的是,显存碎片、CUDA context、激活值还会额外占用空间,这部分很难从公式直接算准,通常只能通过压测来确认。预留过大会浪费资源,预留过小会直接 OOM,所以首次上线前至少在测试环境跑一次峰值负载。
最后,不要把 swap 或临时缓存当作扩容手段。显存不足时,这些方案会影响延迟,甚至导致服务不稳定。若预算远超单卡容量,应优先考虑拆分为多个部署,或者降低每个模型的并发上限和最大序列长度,而不是依赖显存之外的次级存储。