很多团队第一次把 LLM 接进生产环境时,预算表上通常只写了“一张显卡多少钱”。真正上线后才发现,模型权重只是成本的一个角,KV Cache、推理框架选型、并发扩容、接口限流、日志存储、GPU 空转,每一项都在持续吃掉预算。
这篇文章把 LLM 生产化过程中的真实成本拆开讲。不劝你买某块卡,也不劝你无脑上云 API,而是给出一套可以自己估算的维度:模型参数量和精度决定权重显存,上下文长度和并发决定 KV Cache,推理框架决定吞吐上限,再往下还有存储、带宽、运维和合规成本。
如果你是做后端、做 AI 平台化,或者正在给团队选型 LLM 推理方案,这篇可以直接收藏。文末会给一个可按业务场景复制的成本评估模板。
1. LLM 生产部署核心成本速览
先看一张总表,把成本分成固定和动态两类,后面所有讨论都围绕这张表展开。
| 成本维度 | 性质 | 主要构成 | 可控性 |
|---|---|---|---|
| 模型权重显存 | 固定 | 参数量、精度、模型架构 | 高,可通过量化降低 |
| KV Cache 显存 | 动态 | 上下文长度、并发数、层数 | 中,受业务上下文长度约束 |
| 推理计算资源 | 固定+动态 | GPU、CPU、内存、带宽 | 中,框架优化空间大 |
| 存储成本 | 动态 | 模型文件、日志、输入输出 | 中,需要压测和清理策略 |
| 推理框架与运维 | 持续 | 节点部署、监控、故障恢复 | 高,框架选型影响明显 |
| 数据合规与安全 | 持续 | 脱敏、审计、权限管理 | 必须预算,不能省 |
这里有一个容易忽略的点:权重显存是“一次性”的,模型加载完就固定了;KV Cache 才是随业务流量实时变化的。一个并发只有 10、上下文开到 32K 的服务,KV Cache 就可能吃掉比模型权重还大的显存。后面第三节专门算这笔账。
2. 模型选型:参数量和精度是第一个成本锚点
2.1 参数量的乘法效应
参数量直接决定权重显存下限。以常见开源模型量级来看:
- 7B/8B 级别:单卡可以尝试,适合做垂直场景的底座。
- 13B/14B 级别:单卡高显存或双卡,效果和成本相对平衡。
- 32B/70B 级别:基本进入多卡推理或强量化区间。
- 百亿参数以上:接近多节点部署范畴,运维复杂度明显上升。
这里说的“单卡可以尝试”只是权重层面。真正能不能跑,还要看上下文长度、并发数、是否量化,以及推理框架是否做了显存管理。下面给出不同参数量在不同精度下的权重显存估算表,注意这是简化计算,实际模型会因为 embedding、LM head、MoE 结构等差异略有浮动。
| 精度 | 字节/参数 | 7B 权重 | 13B 权重 | 70B 权重 |
|---|---|---|---|---|
| FP32 | 4 | 28GB | 52GB | 280GB |
| FP16/BF16 | 2 | 14GB | 26GB | 140GB |
| INT8 | 1 | 7GB | 13GB | 70GB |
| INT4 | 0.5 | 3.5GB | 6.5GB | 35GB |
这里最核心的结论是:参数量每翻一倍,权重显存就翻一倍;精度从 FP16 降到 INT4,权重显存能降到四分之一。所以量化是成本优化的第一手段,而不是扩容。
2.2 精度选择:FP16、BF16、FP8、INT8、INT4
生产环境里精度选择不是“越低越好”,而是“质量还能接受的前提下尽量低”。
- FP16/BF16:默认选项。质量损失最小,但显存占用最高。要注意硬件支持差异,BF16 在 A100、H100 等新卡上支持良好,部分老卡只有 FP16 支持完整。
- FP8:新一代硬件上的折中方案,显存比 FP16 少一半,质量损失相对可控。
- INT8:显存收益明显,推理速度通常也有提升,但需要好的校准数据集。
- INT4:显存最低,消费级显卡跑大模型的常见方案。如果校准不当,复杂任务上会明显掉点。
从成本视角看,精度每降一档,等于用少量质量损失换一次硬件规模缩减。生产环境里必须做量化前后的效果对比,不能只盯着显存数字。
2.3 开源自建 vs 云 API
这是生产选型里最容易算错账的地方。
云 API 的成本是线性增长的:按 token 付费,调用量越大费用越高,但省掉了 GPU 采购、运维、监控、故障恢复的人力成本。自建模型是固定成本:卡买了或租了,不管用不用都在花钱,但调用量大之后单 token 边际成本会明显下降。
所以核心问题不是“哪个更便宜”,而是“你的模型利用率有多高”。如果一天只有几百次调用,自建大概率不划算;如果是日均千万 token 的稳定流量,自建的性价比会逐渐体现。
3. 显存成本:权重只是入门,KV Cache 才是大头
3.1 显存里的三件套
一个 LLM 推理服务在 GPU 上占用的显存大致由四块构成:
- 模型权重。
- KV Cache。
- 激活值(activation)。
- 框架临时 buffer 和 CUDA context。
很多人只算了第一块,结果服务启动后动不动 OOM,就是因为忽略了 KV Cache。KV Cache 的大小和输入输出长度、并发数强相关,而且是在服务运行中动态变化的。
3.2 权重显存估算
权重显存最简单:
def weight_memory_gb(params_b, bytes_per_param): return params_b * 1e9 * bytes_per_param / 1e97B 模型用 BF16,大约 14GB;70B 模型用 BF16,大约 140GB。这个数值在选卡时可以直接用来做“权重下限”判断。
3.3 KV Cache 估算
KV Cache 的公式:
KV Cache 字节数 = 2(K 和 V) × 层数 × KV头维度 × 上下文长度 × 并发数 × 精度字节这里“2”是 K 和 V 各一份。注意很多模型用了 GQA/MQA,KV 头数可能小于 Q 头数,所以实际 KV Cache 会比“按所有头计算”更小。
写成一个估算函数:
def kv_cache_memory_gb(layers, kv_head_dim, ctx_len, batch_size, bytes_per_param=2): bytes_per_sequence = 2 * layers * kv_head_dim * ctx_len * bytes_per_param return bytes_per_sequence * batch_size / 1e9以一个简化的 7B 模型为例,假设 32 层、KV 头维度 128,估算结果如下:
| 上下文长度 | batch=1 | batch=16 | batch=64 |
|---|---|---|---|
| 2048 | 约 33MB | 约 536MB | 约 2.1GB |
| 8192 | 约 134MB | 约 2.1GB | 约 8.6GB |
| 32768 | 约 536MB | 约 8.6GB | 约 34.4GB |
从这张表能直观看出为什么长上下文是成本放大器:上下文长度翻 4 倍,KV Cache 翻 4 倍;并发翻 4 倍,KV Cache 又翻 4 倍。两者同时增长,显存压力是乘数级的。
3.4 为什么长上下文和并发是成本放大器
生产环境里,业务侧经常提出“上下文越长越好”“并发越高越好”,但每个需求最终都会换算成显存和 GPU 数量。
- 上下文从 8K 提到 32K,KV Cache 变成 4 倍。
- 并发从 16 提到 64,KV Cache 又变成 4 倍。
- 前两项乘起来,显存需求变成 16 倍。
这就是为什么很多团队换了更大的模型后,反而把上下文从 32K 降回 8K,或者限制单实例并发。容量规划时,建议留出至少 10% 到 20% 的显存余量,给激活值和临时 buffer。
4. 推理框架选型:同样的卡,吞吐能差几倍
4.1 主流推理框架对比
| 框架 | 定位 | 适合场景 | 是否适合直接上生产 |
|---|---|---|---|
| vLLM | 高吞吐推理服务 | 预置 API、高并发、多用户 | 是 |
| TensorRT-LLM | GPU 深度优化推理 | 追求极致吞吐和低延迟 | 是 |
| TGI | HuggingFace 官方推理服务 | 与 HF 生态集成 | 是 |
| SGLang | 高性能结构化推理 | 复杂采样、长上下文 | 是 |
| llama.cpp | 单机/边缘推理 | 小模型、CPU/GPU 混合 | 小型服务可用 |
| Ollama | 本地开发体验 | 开发机、原型验证 | 一般不建议直接上生产 |
框架选型不是“哪个火用哪个”。vLLM 的优势在于连续批处理和显存管理,适合高并发在线服务;TensorRT-LLM 优化更彻底,但模型编译和部署链路更重,适合固定模型长期运行;llama.cpp 适合资源受限的边缘场景。
4.2 连续批处理是关键
传统推理服务按静态 batch 处理请求,batch 里只要有一个请求生成长文本,其他短请求也得等它结束,GPU 利用率上不去。
连续批处理(Continuous Batching)的思路是:每个 token 生成完成后立刻把完成的请求移出 batch,再补入新请求。这样短请求不会被长请求拖住,GPU 利用率会明显提升。
PagedAttention 这类显存管理机制,则把 KV Cache 分成物理块管理,减少显存碎片,提高可用 batch 数。这就是为什么同一个模型,vLLM 能同时服务更多请求,显存占用却更稳定。
4.3 用 vLLM 起一个 OpenAI 兼容服务
下面是生产环境常见的一种启动方式,以 vLLM 为例。注意路径、端口和模型名按实际环境修改:
python -m vllm.entrypoints.openai.api_server \ --model /data/models/llama-3-8b-instruct \ --tensor-parallel-size 1 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --port 8000启动后可以用 curl 验证:
curl http://127.0.0.1:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "/data/models/llama-3-8b-instruct", "messages": [{"role": "user", "content": "用一句话解释 KV Cache"}], "max_tokens": 128 }'返回 JSON 里的choices数组就是模型输出。这个接口是 OpenAI 兼容格式,很多现有代码可以少改甚至不改就接进来。
4.4 框架选型的隐性成本
框架本身的切换成本经常被低估。换一个推理框架往往意味着:
- 模型格式可能要从 HuggingFace 格式转 TensorRT Engine。
- 服务化代码要改动,超时、流式、错误码逻辑都不同。
- 监控指标要重新适配,比如 vLLM 暴露的指标和 TensorRT-LLM 不同。
- 团队要重新积累故障排查经验。
我的建议是:小流量阶段就固定一个主推框架,压测通过后不要频繁换。框架调优带来的吞吐收益,通常远大于“再换一个试试”的短期快感。
5. 自建推理 vs 云 API:按真实负载算账
5.1 云 API 侧成本
云 API 按 token 计费,公式很简单:
单月 token 消耗 = QPS × 每次请求平均 token 数 × 月秒数 API 成本 = 单月 token 消耗 / 100万 × 每百万 token 单价写成一个估算函数:
def estimate_api_cost(days, qps, tokens_per_req, price_per_million_tokens): secs_per_month = days * 24 * 3600 total_tokens = qps * tokens_per_req * secs_per_month cost = total_tokens / 1_000_000 * price_per_million_tokens return total_tokens, cost云 API 还隐藏了两个问题:一是 rate limit,业务突发流量会被限流;二是长上下文调用价格远高于短文本,因为模型计费通常按输入和输出 token 合计。生产环境建议把输入缓存、Prompt 压缩做起来,否则同样的任务量,费用可能差出好几倍。
5.2 自建侧成本
自建不是只有 GPU 费用,完整账单包括:
- GPU 实例月租或一次性采购摊销。
- CPU 内存、系统盘、数据盘。
- 公网带宽或内网流量费。
- 日志存储、监控系统、对象存储。
- 运维人力,包括模型更新、框架升级、故障恢复。
估算模板:
def estimate_self_hosted_cost(gpu_instances, price_per_instance_month, storage_gb, price_per_gb_month, ops_cost=0): return gpu_instances * price_per_instance_month + storage_gb * price_per_gb_month + ops_cost这里不写死云厂商具体价格,因为实例定价和带宽费用变化很快,而且不同区域差异很大。实际评估时把所在云厂商的报价填进去就行。
5.3 自建更划算的几个典型条件
- 月调用 token 量很高,且趋势稳定。
- 延迟敏感,云 API 的网络开销和排队时间不可接受。
- 数据不能出企业内网,必须私有化部署。
- 原本就有 GPU 资源或长期闲置算力池。
反过来,如果是新产品验证阶段、调用量波动大、团队没有 GPU 运维经验,先用云 API 跑通业务逻辑反而更省钱。等到月成本稳定了,再决定要不要自建。
6. 批量任务的成本与并发控制
6.1 典型离线批量场景
LLM 生产化里大量工作其实是离线批量任务:批量摘要、批量文本分类、批量翻译、批量结构化抽取。这类任务的特点是量大、无实时要求、需要稳定吞吐。
批量任务最容易踩的坑是同步 for 循环调用 API,一个请求超时就把整个任务拖住。正确做法是异步并发控制:限制最大并发数,失败有限次重试,记录成功 ID 以便断点续跑。
6.2 并发控制示例
import asyncio import aiohttp async def process_item(sem, session, item): async with sem: payload = { "model": "your-model-name", "messages": [{"role": "user", "content": item["text"]}], "max_tokens": 512, } async with session.post("http://127.0.0.1:8000/v1/chat/completions", json=payload, timeout=aiohttp.ClientTimeout(total=120)) as resp: data = await resp.json() return item["id"], data["choices"][0]["message"]["content"] async def run_batch(items, concurrency=8): sem = asyncio.Semaphore(concurrency) async with aiohttp.ClientSession() as session: tasks = [process_item(sem, session, item) for item in items] results = await asyncio.gather(*tasks, return_exceptions=True) return resultsconcurrency的取值要按 GPU 显存和模型并发能力压测得出。并发设得太大,会让服务端 OOM 或延迟飙升;设得太小,GPU 利用率上不去。批量任务阶段应该专门做一次“并发-延迟-成功率”三点测试。
6.3 Prompt 前缀复用
很多批量任务有固定前缀,比如系统提示词、用户指令模板。如果推理框架支持 Prefix Caching,相同前缀的 KV Cache 可以复用,能明显降低计算成本。
对于不支持前缀缓存的框架,可以在 Prompt 层面压缩:把固定指令模板精简,减少重复 token。大批量任务里,每省一个 token 都是钱。
6.4 失败重试与幂等
批量任务必须设计重试:
- 网络超时:指数退避重试,重试次数不要超过 3 次。
- 服务端限流:等一段时间后重试。
- 模型输出格式异常:不要盲目重试,先检查是不是 Prompt 本身的问题。
- 已完成任务要记录,防止进程崩溃后重复处理。
批量任务的稳定性,往往比单次延迟更有价值。
7. 生产环境部署与运维成本
7.1 基础设施与版本管理
生产环境建议把 LLM 服务容器化,用 Docker 或 Kubernetes 管理。重点是把 CUDA、PyTorch、推理框架版本固定下来,不要随便升级。GPU 驱动和 CUDA 版本不一致,是常见的启动失败原因。
模型文件建议单独放对象存储或模型仓库,启动时按版本拉取,而不是每次都重新上传几百 GB 文件。加载模型很慢时,先检查磁盘 IO 和网络带宽。
7.2 必须盯住的五个指标
| 指标 | 含义 | 关注原因 |
|---|---|---|
| TTFT | 首 token 延迟 | 用户等待体感 |
| TPOT | 生成每个 token 的耗时 | 输出速度 |
| TGS | 每秒生成 token 数 | 系统吞吐 |
| QPS | 每秒成功请求数 | 容量评估 |
| GPU 利用率 | GPU 计算资源使用率 | 成本有效性 |
TTFT 偏高,通常说明排队或前缀匹配慢;TGS 偏低,说明 batch 数不够或模型较大;GPU 利用率和吞吐不成正比时,要考虑框架 overhead 和显存瓶颈。
7.3 容易忽略的成本陷阱
- GPU 空转:请求量波动大,但 GPU 一直满规格运行。冷热流量明显时,不如用弹性伸缩或混部。
- 日志膨胀:每个请求都打完整输入输出,日志收集和存储费用会非常夸张。建议只记录关键元信息和采样率。
- 冷启动恢复:模型加载几十秒甚至几分钟,扩容节点时服务恢复慢。有容灾要求的服务,需要长期保留热备节点。
- 多模型重复加载:一个服务同时挂多个模型,每个模型占一份显存。要按业务优先级控制同时加载的模型数量。
8. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动后直接 OOM | 模型权重、KV Cache、buffer 总和超显存 | 看启动日志和nvidia-smi显存占用 | 降低并发、缩短 max-model-len、量化模型 |
| 模型加载特别慢 | 磁盘 IO 或网络带宽瓶颈 | 观察加载耗时和磁盘速率 | 模型放本地 SSD,或预热模型缓存 |
| 并发一高 TTFT 就涨 | 请求排队、Prefix Cache 未命中 | 看服务端队列长度和 GPU 利用率 | 增加副本、优化 batch 策略、做负载均衡 |
| 量化后效果明显变差 | 校准集不合适或量化粒度太粗 | 对比量化前后同一批测试用例 | 换量化方法,或回到更高精度 |
| API 调用频繁超时 | 客户端超时设置过短或服务端排队 | 看服务端指标和客户端错误码 | 超时设为 120 秒以上,增加重试 |
| GPU 利用率低但吞吐不高 | 小 batch 或 CPU 与 GPU 传输瓶颈 | 看 batch 大小和 TGS 指标 | 开启连续批处理,增大并发请求 |
| 多卡负载不均衡 | 张量并行配置或请求偏差 | 看每张卡利用率 | 调整并行策略,均匀分发请求 |
排查顺序建议是:先看日志,再看指标,最后才动配置。不要一遇到 OOM 就调低并发,先判断是权重问题还是 KV Cache 问题。
9. 一套可执行的成本评估流程
9.1 小流量压测模板
正式采购或上云之前,先做一个小规模压测:
- 选定 1 到 2 个代表模型,确定精度和上下文长度。
- 用推理框架启动单副本服务。
- 固定一个典型 Prompt 长度,逐步提高并发。
- 记录 TTFT、TGS、GPU 显存、QPS、错误率。
- 找到“显存刚好够用且错误率低”的并发上限。
- 用这个上限反推需要几个副本。
9.2 月度成本模板
def monthly_cost_report(qps, tokens_per_req, days=30, gpu_instance_price=0, extra_cost=0): total_tokens = qps * tokens_per_req * days * 24 * 3600 gpu_cost = gpu_instance_price * days * 24 return { "total_tokens_month": total_tokens, "gpu_runtime_cost": gpu_cost, "extra_cost": extra_cost, "suggested_budget": gpu_cost + extra_cost }压测得到的 QPS 上限,决定了你需要多少个 GPU 实例副本。QPS 翻倍,副本数可能翻倍,KV Cache 和带宽也会翻倍。建议把这份报告按月留存,用来观察调用量增长和成本增长是否匹配。
9.3 优化优先级
通常按这个顺序做成本优化:
- 第一次优化:量化模型精度。
- 第二次优化:压缩 Prompt 长度,开启前缀缓存。
- 第三次优化:调整并发和 batch 策略,提高 GPU 利用率。
- 第四次优化:做模型路由,简单请求走小模型。
前两步通常能在不换硬件的情况下省下最多成本。动辄扩容反而是最贵的选择。
10. 最佳实践与合规边界
最后给几条工程化建议。第一,第一次部署先用最小配置跑通,小模型加低并发,把指标监控和服务日志搭起来,再逐步放大。第二,模型文件、输入素材、输出结果分目录管理,生产环境要控制访问权限,防止敏感数据外泄。第三,批量任务要加日志和失败重试,避免一条坏数据拖垮整个队列。第四,涉及人脸、声音、版权素材、个人数据时,必须确认授权和合规边界,不能只考虑技术成本。
LLM 生产化的真实成本,不是买一张显卡那么简单。模型选型、精度、上下文长度、并发、推理框架、批量任务、运维监控,每一个环节都在影响最终账单。最值得做的事,是先跑通一套小模型加量化的最小系统,用压测数据算清楚单 token 成本,再做扩容或上云的决策。这样每一步花钱,都有据可依。