长上下文与 MoE 让 Kimi K 系列、GLM 等模型具备很强的推理能力,也把显存压力推到了工程团队面前。Cloudflare Workers AI 团队分享的实践表明,通过 KV cache 量化和权重压缩等手段,推理吞吐量可以提升 41%,成本降低 30%。这组数字背后的关键不是让 GPU 算得更快,而是减少每个请求占用的显存,从而把空出来的容量交给更高的并发。
显存为什么会成为长上下文推理的瓶颈
部署大模型时,GPU 显存主要被三类数据占用:模型权重、运行过程中的激活,以及随上下文长度增长的 KV cache。
对于 MoE 模型,单个 token 通常只激活部分专家,但完整或大部分专家权重仍需要驻留在设备或可快速访问的内存中。因此,MoE 可以降低每个 token 的计算量,却不一定同步降低模型的存储压力。
KV cache 的问题则更直接。自回归生成需要保存历史 token 的 Key 和 Value,避免每生成一个 token 都重新计算整个上下文。其占用可以粗略写成:
KV cache ≈ 请求数 × token 数 × 层数 × KV 头数 × head_dim × 2 × 每元素字节数
其中的 2 代表 Key 和 Value。上下文越长、并发请求越多,KV cache 增长得越快。即使 GPU 算力尚有余量,显存不足也会迫使服务降低批大小、拒绝新请求,或者频繁换入换出缓存。
这也解释了为什么优化目标不应只盯着单请求延迟。在线推理更值得观察的是:固定延迟目标下能容纳多少并发请求,以及每块 GPU 每秒能够完成多少 token。
KV cache 量化:压缩随请求增长的那部分内存
KV cache 通常使用 FP16 或 BF16 保存。将其改为 FP8,可以把理论上的元素存储宽度从 2 字节降到 1 字节。实际节省比例还会受到元数据、内存对齐、临时缓冲区和框架实现影响,但方向很明确:上下文越长,收益越容易显现。
这项优化的价值通常体现在三个方面:
- 同一块 GPU 可以保留更多请求的 KV cache。
- 调度器可以组建更大的连续批次,提高 GPU 利用率。
- 服务更不容易触发缓存驱逐、CPU offload 或请求排队。
风险也不能忽略。KV cache 量化会引入数值误差,误差可能随长序列传播。评估时不能只跑短问答,应覆盖目标业务中的最大上下文、长文本生成、代码补全和需要精确引用的任务,并比较输出质量与尾延迟。
权重压缩:让大模型先装得下,再谈吞吐量
权重压缩针对的是相对固定的模型常驻内存。把 FP16/BF16 权重压缩成 INT8、FP8 或 INT4,可以显著降低模型加载后的显存占用。对于参数规模很大的 MoE 模型,这可能决定部署需要多少张 GPU,也可能为 KV cache 留出更多空间。
不过,压缩后的位宽并不直接等于端到端加速比例。真实收益取决于 GPU 是否具备对应的低精度计算能力、推理框架是否提供融合内核,以及量化格式是否需要频繁反量化。如果内核支持不理想,模型虽然更省显存,单 token 计算反而可能变慢。
因此,KV cache 量化与权重压缩解决的是两个不同维度的问题:前者压缩随流量和上下文增长的动态内存,后者压缩模型本身的静态内存。二者叠加后,释放的显存可以转化为更大的批处理空间,这才是吞吐量提升的重要来源。
可以这样实践:用 vLLM 做一组可复现对照
下面是一个通用实验方案,不代表 Cloudflare 的具体生产配置。假设机器已经安装兼容 CUDA 的 vLLM,并且准备了同一模型的原始版本与 AWQ 预量化版本。不同 vLLM 版本和 GPU 对 FP8 KV cache 的支持不同,运行前应检查版本文档。
先启动 BF16 权重与自动 KV cache 类型的基线服务:
export BASE_MODEL=/models/your-bf16-model
vllm serve "$BASE_MODEL" \
--host 0.0.0.0 \
--port 8000 \
--dtype bfloat16 \
--max-model-len 32768 \
--gpu-memory-utilization 0.90
完成基线测试后停止该进程,再启动权重压缩与 FP8 KV cache 版本。QUANT_MODEL 必须指向已经按 AWQ 格式量化、且能被当前 vLLM 版本加载的模型目录:
export QUANT_MODEL=/models/your-awq-model
vllm serve "$QUANT_MODEL" \
--host 0.0.0.0 \
--port 8000 \
--quantization awq \
--kv-cache-dtype fp8 \
--max-model-len 32768 \
--gpu-memory-utilization 0.90
可以使用下面的 Python 脚本向 OpenAI 兼容接口发送并发请求。它只依赖 Python 标准库,修改 MODEL、请求数量和提示词长度后即可运行:
import concurrent.futures
import json
import os
import statistics
import time
import urllib.request
URL = os.getenv("API_URL", "http://127.0.0.1:8000/v1/chat/completions")
MODEL = os.environ["MODEL"]
CONCURRENCY = int(os.getenv("CONCURRENCY", "8"))
REQUESTS = int(os.getenv("REQUESTS", "32"))
PROMPT = "请用三点说明 KV cache 的作用。" * 200
def send_request(index):
payload = json.dumps({
"model": MODEL,
"messages": [{"role": "user", "content": PROMPT}],
"max_tokens": 128,
"temperature": 0,
}).encode("utf-8")
request = urllib.request.Request(
URL,
data=payload,
headers={"Content-Type": "application/json"},
)
started = time.perf_counter()
with urllib.request.urlopen(request, timeout=300) as response:
result = json.load(response)
elapsed = time.perf_counter() - started
tokens = result.get("usage", {}).get("completion_tokens", 0)
return elapsed, tokens
started = time.perf_counter()
with concurrent.futures.ThreadPoolExecutor(max_workers=CONCURRENCY) as pool:
results = list(pool.map(send_request, range(REQUESTS)))
wall_time = time.perf_counter() - started
latencies = sorted(item[0] for item in results)
total_tokens = sum(item[1] for item in results)
p95_index = max(0, int(len(latencies) * 0.95) - 1)
print(f"requests={REQUESTS} concurrency={CONCURRENCY}")
print(f"wall_time_s={wall_time:.2f}")
print(f"completion_tokens_per_s={total_tokens / wall_time:.2f}")
print(f"mean_latency_s={statistics.mean(latencies):.2f}")
print(f"p95_latency_s={latencies[p95_index]:.2f}")
运行时让 MODEL 与服务实际暴露的模型名保持一致:
MODEL=/models/your-bf16-model CONCURRENCY=8 REQUESTS=32 python benchmark.py
对照实验中应固定模型语义版本、提示词集合、输出 token 上限、GPU 型号、并发数和服务参数。除吞吐量外,还要记录峰值显存、首 token 延迟、每 token 延迟、P95/P99 延迟、错误率以及任务质量。只比较一次短压测,很容易把预热、缓存或调度抖动误判成优化收益。
落地时不要直接照搬 41% 和 30%
Cloudflare 报告的 41% 吞吐量提升和 30% 成本下降说明这套组合在其工作负载上有效,但它们不是所有模型与 GPU 的固定系数。短上下文、低并发服务可能几乎用不到节省下来的 KV cache;不支持相应低精度内核的 GPU,也可能无法兑现权重压缩带来的计算收益。
生产采用前可以按以下顺序推进:
- 用真实请求分布建立 BF16/FP16 基线,而不是只使用统一长度的合成提示词。
- 单独开启 KV cache 量化,确认长上下文质量、显存和尾延迟变化。
- 再替换为压缩权重,区分静态显存收益与计算性能变化。
- 组合两项优化,逐步提高并发,找到满足延迟目标的最大吞吐量。
- 用每百万 token 的实际 GPU 成本计算收益,并把失败请求、重试和质量回退纳入成本。
真正值得复制的不是某一个百分比,而是这条工程路径:分别测量权重和 KV cache 的显存占用,通过低精度格式释放容量,再把容量转换成可验证的并发与成本收益。