一次大模型API请求背后:GPU推理与KV Cache优化实践 📅 发布时间:2026/9/10 5:53:45 👁 浏览次数: 做后端这几年我见过最多的大模型应用问题不是模型效果不好而是“请求发出去了却迟迟等不到结果”。很多人把大模型 API 当成普通 HTTP 接口来调觉得无非就是发请求、等返回但真正部署过模型的人都知道一次大模型请求背后涉及的链路远比想象中复杂客户端要过鉴权、服务端要排队、推理引擎要把文本变成 token、GPU 要同时跑前向计算还要为每个对话维护一份 KV Cache。任何一个环节卡住用户看到的就是一个转圈的 loading。这篇内容围绕“一次大模型 API 请求从发起到返回结果到底经历了什么”来展开重点拆解 Harness推理与评测框架、GPU 计算、并发调度、KV Cache 这四块核心机制也带上我在实际部署、压测和排障过程中总结的一些经验。适合正在做模型部署、API 服务开发、性能优化或者单纯想搞清楚“ GPU 到底在忙什么”的朋友参考。1. 整体链路一次 API 请求的“全景地图”1.1 从用户输入到模型输出的六个节点一次大模型 API 请求绝不是“请求进去结果出来”这么简单。我习惯把整条链路拆成六个节点定位问题的时候特别管用。第一个节点是客户端封装。无论是用 OpenRouter、DeepSeek 这类在线 API还是自己用 Ollama、llama.cpp 起的本地服务客户端总是先把用户输入的字符串加上 system prompt、历史对话、工具定义等拼成完整的消息结构然后通过 HTTP/SSE 发送给服务端。这个阶段最容易出问题的是 context 长度计算上游拼了太多历史消息还没到模型层就已经超限。第二个节点是鉴权与路由。服务端收到请求后先校验 API Key然后根据模型名把请求路由到对应的推理实例。热词里经常出现“login failed. check api token”这类报错实际上就是这一层的问题。第三个节点是请求排队与调度。模型服务不是来一个请求就立刻跑一个而是先进入调度器。调度器决定哪些请求可以进入 GPU 计算队列、要不要做批处理、优先级怎么排。并发高的时候调度策略比 GPU 算力更能决定整体吞吐。第四个节点是文本向量化与 prefill。用户的输入文本被 tokenizer 切成 token 序列然后一次性喂给模型做前向计算。这个阶段叫 prefill是计算密集型的特点是一次性处理整个输入序列速度非常快。第五个节点是逐 token 生成 decode。模型开始自回归地一个一个生成新 token每一步生成都要依赖之前所有 token 的 KV 缓存。这个阶段是访存密集型的也是整个请求中耗时最长的部分GPU 利用率往往并不高。第六个节点是流式返回与后处理。生成结果通过 SSE 流式返回给客户端同时服务端要对 logits 做采样、对特殊 token 做过滤最后释放该请求占用的 KV Cache 显存。这六个节点里面前两个是常规后端问题后四个才是真正跟大模型强相关的。很多人排障只看到“超时”“OOM”其实是没把问题定位到具体的节点上。1.2 Harness 到底是什么评测框架、推理服务还是调度层“Harness”这个词在热词里反复出现比如 DeepSeek Harness、Codex Harness。我第一次看到“harness”的时候也懵了一下因为不同语境下它指的东西完全不一样。在大模型社区里Harness 最早是指 lm-evaluation-harness也就是 EleutherAI 做的评测框架。它做的事是把各种 benchmark 数据集比如 MMLU、GSM8K、HumanEval加载进来让模型跑一批题目然后统计准确率、通过率等指标。你在热词里搜“deepseek harness 安装”大概率就是这类评测工具。它本身不直接提供 API 服务而是作为“测试架子”去驱动不同模型的推理后端。但到了工程场景里Harness 也经常用来指代推理服务的外层封装。比如你把 vLLM、TensorRT-LLM、llama.cpp 起起来之后外面套一层鉴权、路由、限流、日志采集这个外壳也能叫 harness。它的作用是让模型推理引擎对上提供统一的 API 接口对下屏蔽不同推理引擎的差异。这里有个容易混淆的点Harness 和 Agent 有什么区别。热词里也有人在搜“harness和agent区别”。简单来说Agent 是“用模型做决策的智能体”它决定下一步调用什么工具Harness 是“把模型跑起来的架子”它负责管理模型的生命周期、请求调度、评测流程。前者的核心是逻辑编排后者的核心是资源管理。你要是拿 Codex Harness 举例它其实是把代码补全模型包了一层接口让 IDE 能通过 API 调用模型核心还是推理服务的封装。所以理解 Harness不用纠结具体指哪个开源项目关键在于认识到它处于“模型引擎”和“业务方”之间承担了调用、评测、调度、转发这四类职责中的一部分或全部。2. GPU 上的推理显存、算子与计算流2.1 显存里到底放了什么权重、激活值、KV CacheGPU 显存不是无限大的部署大模型的第一道坎就是算显存放不放得下。我们用一张 NVIDIA 4090 24GB 跑 7B 模型就要精确计算每一块显存的用途。模型权重是最直观的一块。7B 参数如果是 FP16占用 14GB 显存如果量化到 INT4只需要大约 3.5GB。这就是为什么热词里到处是“量化”“GGUF”“GPTQ”因为显存不够用的时候第一个想到的就是压缩权重。激活值activation是第二块。前向计算过程中每一层都要保留中间结果用于反向传播但在推理阶段不需要反向传播所以激活值的显存消耗要小很多不过在长序列场景下依然不可忽视。prefill 阶段一次性处理几千个 token激活值会瞬间暴涨。第三块就是 KV Cache。简单说模型每生成一个新 token都要“回顾”之前所有 token 的 Key 和 Value。如果不做缓存每次都重新计算全部历史 token 的注意力分数算力开销是平方级别增长的。KV Cache 就是把历史 token 的 K、V 向量存在显存里让 decode 阶段每次只计算最新 token 的注意力。这三块里面KV Cache 的显存占用往往是容易被忽略的大头。很多人模型权重明明放得下跑起来却 OOM就是没给 KV Cache 留够空间。2.2 prefill 与 decode为什么生成是“逐 token 蹦出来的”大模型推理被分成两个语义完全不同的阶段prefill 和 decode。prefill 阶段模型拿到用户的完整输入比如一段 500 token 的文本一次性做并行前向计算得到每个 token 对应的 hidden state。这个阶段是典型的高算力需求GPU 的矩阵乘法单元几乎都在满负荷运转。我们实测过在 A100 上用 vLLM 跑 7B 模型处理 2K token 的输入prefill 大概只要几十毫秒。decode 阶段开始之后模型每生成一个 token就要做一次完整的前向传播但这个前向传播只算一个 token 的路径GPU 利用率很难拉满。真正耗时的恰恰是这个阶段生成 512 个 token就要做 512 次前向传播速度取决于显存带宽而非单纯的计算能力。这也是为什么 Mac 上跑小模型看起来很流畅因为显存带宽高但生成速度未必比得上数据中心 GPU。很多人在本地用 llama.cpp 跑 GPU 加速时发现“还是慢”根因就在这里。你看到的速度是“每秒生成多少 token”取决于 decode 阶段的优化程度包括 KV Cache 命中率、算子融合、显存带宽利用率等。单纯把模型塞进 CUDA 不代表就能跑得快还得看推理引擎有没有针对 decode 做优化。2.3 常见 GPU 部署形态单卡、多卡、量化实际部署时GPU 形态决定了请求能支撑到什么并发量级。单卡部署最简单比如 24GB 显存的 4090 或者 48GB 的 L20。这种形态适合跑 7B~14B 级别的量化模型并发量通常不会太高。我们在内部环境用 Ollama 部署 7B 模型单卡并发开 8 个请求每个请求平均生成 20 token/s已经能把显存带宽吃满。多卡部署主要是为了跑 70B 以上或者要做高并发。模型并行有两种主流方式张量并行TP把每一层的矩阵切到多张卡上流水线并行PP把不同层放到不同卡上。TP 的通信开销更大但负载更均衡。很多推理框架比如 vLLM 和 TensorRT-LLM 都支持自动切分你只要指定 tensor-parallel-size 就行。量化则是另一条路。把 FP16 权重压成 INT8、INT4显存占用直接砍半甚至砍到四分之一。代价是精度损失但实际测试中 8B 模型 INT4 量化后在大部分任务上的效果损失在可接受范围内。需要强调的是量化不仅影响权重还会影响 KV Cache后面第四章会专门展开。3. 并发当 100 个请求同时进来系统怎么扛3.1 排队、动态批处理与连续批处理在 GPU 推理的世界里并发不是“同时计算”而是“排队计算”。如果不做批处理100 个请求就得前一个跑完再跑下一个平均等待时间会非常长。所以现代推理引擎的核心优化方向之一就是提高批处理时的吞吐量。早期做法是静态批处理static batching等一批请求凑齐了或者等一段固定时间然后一起做前向计算。好处是实现简单坏处是太僵化有的请求已经生成完了还得等整个 batch 里最慢的那个算完才能释放资源。现在主流方案是连续批处理continuous batching核心思路是“有请求完成了就立即移出 batch同时插入新请求”。这个机制在 vLLM 里是默认行为配合 PagedAttention 的显存管理能把 GPU 资源利用率大幅提升。我们压测过一个场景不开启连续批处理时20 并发请求的平均完成时间是 40 秒开启后同样规格下压到 15 秒吞吐翻了一倍还多。你需要关注的是如果你在用某个 API 网关或者自研服务端转发大模型请求务必搞清楚底层推理引擎是否支持连续批处理。如果底层是不支持动态 batching 的简单实现再好的网关也优化不了 GPU 利用率。3.2 并发数、超时与重试压测时要看什么大模型 API 的并发参数和普通接口不太一样。普通接口压测主要看 QPS、响应时间、错误率大模型接口还要额外关注 throughput每秒生成多少 token、TTFT首 token 延迟和 TPOT每输出一个 token 的平均延迟。我实际压测时常用的工具是 Locust 配合自定义 SSE 客户端。压测指标包括请求成功率低于 99% 就要查是否 OOM 或超时TTFT 分布如果 p95 TTFT 超过 5 秒说明排队太严重生成速度单请求生成速度下降太多说明 batch 过大导致互相争抢显存带宽显存占用是否在压测过程中持续上涨可能是 KV Cache 没有及时释放超时和重试也是一个大坑。大模型生成本身是慢操作一个 500 token 的回答可能要跑 10 秒甚至更久。如果你按普通接口的标准设置 3 秒超时那基本必然失败。我建议把连接超时设为 5 秒读超时按 max_tokens 和生成速度估算比如目标 50 token/s、需要 512 token就设 15 秒以上。重试策略也要克制。大模型服务一旦进入高负载盲目重试只会加剧雪崩。推荐用指数退避加抖动初始间隔 1 秒最多重试 3 次。另外如果错误码是 429限流或者 503负载过高不要立即重试等 2 秒以上再试。3.3 并发和显存的博弈为什么 OOM 是最常见的崩溃大模型服务最经典的崩溃方式就是 CUDA OOM。原因在于每个并发请求都会占用独立的 KV Cache 空间而 KV Cache 的大小和输入输出长度强相关。举个例子一个 7B 模型默认配置下每请求预留 2048 token 的 KV Cache 空间。如果配置了最大并发 100那就需要预留 100 份 KV Cache显存算下来可能比权重还大。很多人的配置里只设置了 max_num_seqs100却没调大显存结果服务刚起来一点流量就 OOM。另一个常见坑是长上下文。热词里总有“maximum context length is 1048576 tokens”这类报错说明模型支持 1M 上下文但物理显存根本装不下 1M token 对应的 KV Cache。就算权重只占 14GB一个 1M 长度的请求可能直接把 80GB 显存打爆。解决方向有三条限制单请求最大序列长度比如服务端强制截断到 32K开启 KV Cache 量化把 FP16 压到 INT8显存直接减半使用支持 PagedAttention 的框架把 KV Cache 分页管理避免预分配浪费4. KV Cache让模型“记住上下文”的那块缓存4.1 KV Cache 是怎么算出来的显存占用到底多大要理解 KV Cache先要弄清楚 Transformer 注意力机制里的 K 和 V 是什么。模型在计算注意力分数时对每个 token 都会生成一个 Query、一个 Key、一个 Value。其中 Key 和 Value 在后续生成过程中会被反复使用。假设模型有 32 层、hidden size 为 4096每个 token 在每一层都会产生一组 K 和 V。如果用 FP16 存储一个 token 的 KV 占用就是 2K 和 V 各一份乘以层数 32 乘以 hidden size 4096再乘以 2 字节等于 512KB。这个数字初看不起眼但如果序列长度是 4096那就是 2GB如果是 32K 上下文就是 16GB。更别说 batch 里还有多个并发请求。实际部署中要求精确计算 KV Cache 显存。我通常会把这个公式固化成一个 Python 脚本部署新模型时先按公式算一遍再决定要不要开量化或者限制并发。4.2 PagedAttention 与 KV Cache 的显存管理器既然 KV Cache 如此占显存就必须精细化管理。传统推理框架会为每个请求预分配一块连续显存存放 KV Cache就像操作系统里的连续内存分配。问题是预分配往往过量而且剩下来的显存碎片没法给小请求用。vLLM 提出的 PagedAttention 核心思路是分页paging。它把 KV Cache 切成固定大小的 block每个 block 存一定数量的 token 的 KV 数据多个请求可以像操作系统分页一样按需分配。这个机制直接解决了两个问题第一显存碎片被利用起来了第二多个请求可以共享已经计算过的 KV 块比如前缀相同的 prompt 可以复用公共的 KV。我自己调模型服务时最直观的感受是同样一张 24GB 显卡用不支持 PagedAttention 的框架跑 7B 模型只能同时处理 2 个长对话换成 vLLM 之后并发可以开到 8 个以上而且 TTFT 没有明显变差。如果你在选推理引擎支持 PagedAttention 或类似机制应该作为硬性要求。4.3 实操中的 KV Cache 参数max_length、cache 量化、复用接入 API 时常见一个参数叫 max_tokens它控制的是生成的最大长度但不直接控制 KV Cache 保留多少历史 token。真正跟 KV Cache 相关的是服务端的 max_model_len也就是模型最大序列长度。这个值开得越大预留给单个请求的 KV Cache 上限就越高并发能力就越低。调优时可以按这个顺序来先确认业务真实需要的上下文长度。如果平均对话只有 5 轮每条消息 200 token那 max_model_len 设为 4096 已经绰绰有余不必盲目拉到 32K再根据显存大小和期望并发数用 KV Cache 公式反推允许的最大 token 数最后开启 KV Cache 量化。INT8 量化通常对生成质量影响很小但显存减半收益很明显另外一个值得关注的点是提示词缓存prompt caching。很多 API 服务商会缓存系统提示词和常见前缀的 KV这样同一个用户发来的多轮对话只有新增部分需要重新计算 prefill。DeepSeek 官方文档里也有 cache 命中计费打折的机制本质就是这个原理。自建服务时也可以通过 vLLM 的 prefix caching 功能开启类似效果实测对多轮对话场景的首 token 延迟优化非常明显。5. 常见问题与排查技巧实录5.1 “请求超时了”先分清是排队慢还是生成慢这是出现频率最高的问题。超时不一定是因为模型慢也可能是请求在排队或者客户端和服务端超时配置不合理。排查顺序看服务端日志确认请求进入推理引擎的时间点看 TTFT 指标如果 TTFT 长说明排队严重或者 prefill 慢看生成速度指标如果 TTFT 正常但整体耗时高说明 decode 阶段慢可能是显存带宽瓶颈看客户端超时配置确认读超时是否覆盖了最大生成时间有个细节容易被忽略流式输出时客户端连接要保持打开很多网关默认会断开超过 30 秒的空闲连接就算模型还在生成 token服务端还没发数据客户端就已经收到 504 了。解决方法是让网关对 SSE 连接关闭空闲超时或者服务端每隔一段时间发送一个心跳注释行。5.2 “context length 超限”序列长度要分两层看API 报错里经常出现“maximum context length”之类的信息比如 1048576 tokens。这个报错表面上是提示词太长实际上可能是两种原因。第一种是客户端把所有历史对话一股脑全塞进去真实输入超过模型上限。这种情况是客户端封装逻辑问题解决办法是裁剪历史消息、只保留最近几轮。第二种是服务端配置的 max_model_len 小于模型真实能力上限。很多框架默认值是 2048 或 4096但模型本身支持 128K你需要主动把服务端的 max_model_len 调大同时确认显存能否支撑对应长度下的 KV Cache。之前遇到过一个案例模型明明是 32K 上下文但部署时忘了改参数用户一问长文就报超限改完参数后立竿见影。5.3 “GPU OOM”增加显存不是唯一解法OOM 问题并不总是“显卡不够大”很多时候是配置不合理。高频原因和对应措施并发数设置过大导致 KV Cache 总占用超过显存 → 调低 max_num_seqs单个长序列请求吃掉全部 KV Cache → 限制 max_model_len 或者改用流式截断模型权重 KV Cache 激活值三者叠加超限 → 对权重做 INT8/INT4 量化或对 KV Cache 做 INT8 量化显存碎片化严重 → 重启服务或更换支持 PagedAttention 的框架从运维角度看GPU 服务器上建议常驻一个显存监控脚本记录显存使用率的趋势。如果显存只涨不降大概率是 KV Cache 泄漏优先查推理引擎的垃圾回收配置。5.4 API Token 校验与鉴权失败常见但不是疑难杂症“login failed. check api token”这类错误经常被人当成大问题其实它就是普通的鉴权失败。排查思路跟普通后端一样先确认 API Key 是否正确再确认请求头格式是否符合服务端要求接着核对模型名是否在服务端注册。有一个大模型场景特有的大坑API Key 有权限范围。比如某个 Key 只能调用 7B 模型你却拿去请求 70B 模型服务端往往会返回 403 或者 404容易让人误以为是模型名写错。所以做多模型网关时一定要把“模型是否存在”“Key 是否有权调该模型”这两个错误区分开日志里打清楚否则排查起来非常痛苦。自建推理服务时建议在最外层加一层统一的鉴权中间件不要在推理引擎内部做鉴权逻辑。这样既方便更换引擎又能统一不同框架的鉴权行为。6. 最后分享一点部署体会回顾整个链路一次大模型 API 请求能跑通靠的不是某个单一组件厉害而是从 Harness 层的调度封装、GPU 层的显存管理、并发层的批处理策略到 KV Cache 的精细规划整个系统配合到位。我个人的项目实践中有三个体会可以分享给正在做部署优化的朋友。第一压测是必须的但不是用 curl 随便打几个请求就算数。要模拟真实的对话长度、并发模型和超时行为否则在低负载下永远发现不了显存和调度的问题。我自己会在上线前跑一轮持续 30 分钟的压测重点观察显存曲线和 TTFT 波动。第二KV Cache 相关的参数一定要在部署文档里写清楚。很多项目换一个人维护顺手把 max_model_len 从 4096 改成 32768结果某个深夜服务直接 OOM这就是因为 KV Cache 显存估算没有同步更新。第三调优不要一上来就换硬件或者换引擎。先看日志确定瓶颈在哪个节点是排队、prefill、decode 还是网络传输。很多时候调一个并发参数、开一个 KV Cache 量化效果比升级显卡明显得多。大模型 API 服务说到底是资源管理的问题GPU 是稀缺资源显存是稀缺资源KV Cache 是对显存无限欲望的缓存机制。理解了这层关系遇到问题就不会慌。