news 2026/9/9 10:11:40

算力过剩但推理慢?大模型硬件调度才是关键

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
算力过剩但推理慢?大模型硬件调度才是关键

我经常在群里看到这种场面:有人晒出 8 卡 A100 的监控截图,显存占用不到一半,算力利用率只有百分之十几,然后配文“跑个 7B 模型,推理速度还是慢得离谱”。下面一群人讨论换卡、加节点、上更好的推理框架,但很少有人问一句:算力明明过剩了,为什么推理还是跑不快?

这个问题我琢磨了很久,也和不少做部署的朋友聊过。今天想把自己的思考整理一下,重点聊聊大模型推理场景里“硬件调度”这件事。先说明,我不是学术派,讲的都是自己实际部署和调优过程中踩过的坑、验证过的方法。如果你也在做大模型部署、推理服务优化,或者正准备买卡搭集群,这篇文章应该能帮你少走一些弯路。

我要表达的核心观点很简单:在大模型推理场景下,算力从来不是唯一的稀缺资源,甚至很多时候根本不是最稀缺的资源。真正决定服务快慢的,往往是调度策略——怎么把显存、带宽、算力、请求队列这些资源匹配好,才是关键所在。

1. 先拆问题:算力“过剩”的到底是什么

1.1 账面算力不等于有效算力

要聊清楚这个问题,先得理解“算力过剩”在说什么。以 A100 为例,FP16 算力高达 312 TFLOPS,一张卡的理论算力非常恐怖。但实际跑 Llama 7B 的推理服务时,GPU 利用率能到 30% 就算不错了,绝大多数时间都在 5%~20% 之间徘徊。

为什么?因为大模型推理不是一个“算得越猛就越快”的任务。它既吃算力,也吃显存带宽,还吃显存容量。模型权重、KV cache、中间激活、CUDA context 这些都要占显存,而每次生成一个 token 都要把整个模型的权重从显存里读一遍。

我做一个粗略的估算:FP16 格式的 7B 模型,权重大约是 14GB。即使按 2TB/s 的显存带宽算(A100 的水平),完整读一遍权重就需要大约 7 毫秒。这还只是读一次权重的时间,实际 decode 阶段还需要读写 KV cache,还有各种算子开销。换句话说,即使算力再高,显存带宽已经锁死了单个请求的生成速度上限。

所以这里说的“算力过剩”,其实是账面上的 FLOPs 远大于实际能消费掉的 FLOPs。推理服务跑不快,不是不缺算力,而是算力之外的约束条件先把路堵死了。

1.2 推理慢,到底慢在哪个环节

很多人在排查推理性能问题时,习惯性地先看 GPU 利用率。如果利用率不高,就觉得是“代码写得不行”或者“框架不够好”。但实际上,推理链路里有四个环节都可能成为瓶颈,单纯看利用率很难定位。

第一个环节是请求排队。服务端收到的请求多了,如果调度器来不及分配资源,请求就只能在队列里等着。我见过不少部署,GPU 明明很闲,但服务端因为配置了过大的 max_num_seqs,请求全部堆在进程里排队,整体响应时间惨不忍睹。

第二个环节是模型计算过程。prefill 阶段(处理输入 prompt)是计算密集型的,需要做大量的矩阵乘法,这个阶段确实吃算力;但 decode 阶段(逐个生成 token)是访存密集型的,绝大多数时间都花在从显存读权重和 KV cache 上。

第三个环节是设备间通信。如果模型切分到了多张卡,张量并行会引入通信开销。卡与卡之间走 NVLink 还好,一旦跨节点走 RDMA 网络,通信耗时可能比计算耗时还高。

第四个环节是显存不足导致的换入换出。显存不够时,框架可能会把 KV cache 或者在 CPU 内存和显存之间搬数据,这个开销极高,一旦触发,性能立刻断崖式下跌。

平时大家笼统说的“推理卡”,其实是这四个环节叠加的结果。其中任何一个环节处理不好,算力再高也白搭。

1.3 一个容易被忽视的“二八现象”

还有一个更隐蔽的问题,就是请求本身的“长短不均”。在真实业务里,用户输入和输出的长度差异极大。有的请求 prompt 很短、回答也很短,有的请求则要处理几万字的长文档。

这个过程里会出现一个典型的“二八现象”:少部分长请求会消耗掉绝大部分显存和算力,而短请求虽然数量多,占用的资源比例却很小。如果调度器没有区分长短请求,让短请求和长请求混在一个 batch 里,那么一次迭代的耗时就被最长的那个请求拖住了,所有人都得等它算完。

这种场景下,算力不是不够,而是被极端请求“绑架”了。这也是为什么现在调度器要专门做动态批处理、prefill/decode 分离之类的优化,目的就是把长短请求分开放,别让它们互相拖累。

2. 搞懂推理特征,才能理解调度为什么难

2.1 prefill 和 decode:同一模型,两个世界

要理解大模型硬件调度,必须先把推理过程拆开看。大模型生成回答的过程不是一口气算完的,它分两个交替的阶段。

prefill 阶段是“你的请求”到达服务端之后,先把整个 prompt 拿去并行计算,一次性算出每个位置上的 KV cache 和第一个输出 token。这个阶段计算量很大,因为要同时处理几千个 token 的注意力计算,但又因为可以充分并行,所以算力利用率可以做得很高。

decode 阶段则完全不同。模型一次只生成一个 token,然后把这个 token 追加到序列后面,再计算下一个 token。这个过程没法像 prefill 那样大规模并行,因为下一个 token 依赖前一个 token 的结果。更要命的是,每生成一个 token 都要把模型权重全部读一遍,所以 decode 的速度基本由显存带宽决定。

我做一个对比:prefill 阶段的算力利用率可以轻松跑到 70% 以上,而 decode 阶段往往连 20% 都不到。如果用生活中的例子来类比,prefill 就像是批发搬运,一次能扛很多箱货;decode 则像单件配送,跑得再快也得一家一家送。

很多人在调优时没有区分这两个阶段,给出的配置往往顾此失彼。比如拼命加大 batch size 想提高吞吐,结果 response 的首字延迟飙升;又比如为了压低延迟把 batch 设得很小,结果 prefill 的计算资源又被浪费了。调度在这方面做得好坏,直接影响服务的整体表现。

2.2 显存不只是“放模型”这么简单

接着说说显存。很多人都知道模型权重会占显存,但推理场景下,KV cache 的占用往往才是最大的变量。

KV cache 是 Transformer 在生成过程中缓存的历史 attention 信息,用来避免每次生成时重算前面的内容。它的大小和模型结构、batch size、序列长度直接相关。

我以 7B 模型为例,给个估算公式:KV cache 字节数 = 2(key 和 value) × 层数 × 头的数量 × 每个头的维度 × 序列长度 × batch size × 每个元素的字节数。

7B 模型一般是 32 层、32 个 head、每头维度 128。如果序列长度是 2048、batch size 是 8,那么 KV cache 的大小就是 2 × 32 × 32 × 128 × 2048 × 8 × 2(FP16),算下来是 4GB。如果序列长度涨到 8192,batch 也涨到 16,KV cache 就变成了 32GB,直接把显存吃光都有可能。

所以调度器的核心任务之一,就是管理好这块动态变化的内存池。谁分配多少显存给权重,多少给 KV cache,batch 最大能容纳多少请求,都需要动态调节。传统那种“跑模型前先把显存一次性占满”的思路,在大模型推理场景根本行不通,因为 KV cache 的大小是随着请求实时变化的。

2.3 请求到达是随机的,静态策略必然吃亏

本地部署一两个模型,可能体会不到调度的压力。但线上推理服务面对的是随机到达的请求流,这个“随机性”才是硬件调度最难的地方。

想象一下:某个瞬间,服务端同时收到 20 个请求,其中有两个是超长文档,剩下的是普通聊天。如果没有精细的调度,这批请求就会被一股脑塞进 batch。但它们的长度差异太大,导致整个 batch 的计算时间被最长的请求拉得很长。长请求还没算完,新的请求又到了,系统窗口期大量空转,算力被白白浪费。

这也是为什么早期静态批处理(static batching)在大模型推理里那么被动。静态批处理要求同一批请求同时开始、同时结束,任何一个请求拖后腿,整批都得等着。而现在的动态批处理、连续批处理(continuous batching)在实现上已经完全改变了思路:不把一个 batch 当作整体,而是逐个 token 粒度去调度。当 batch 里某个请求已经生成完一个 token,新到的请求可以立刻插进来,和大家一起继续下一步计算。

这种机制的本质,是在多个时间片上做“拼车”,谁准备好了就上车,谁到站了就下车。它靠的是实时调度,而不是预先规划。这也是为什么我坚持认为:在大模型推理语境下,硬件调度的核心矛盾不是“算力不够”,而是“资源匹配不上”。

3. 硬件调度的演进:从“人肉分配”到“实时编排”

3.1 早期阶段:单卡部署与人工切分

早几年做 AI 服务,GPU 调度基本是靠“人肉”。想上一个模型,先看单卡能不能装下。能装下就塞一张卡,装不下就想办法砍模型、砍 batch,或者手动把不同模型拆到不同卡上。

这种方式的缺陷非常明显。一切都是静态的,模型和卡的绑定关系写死之后,负载不均衡几乎无法避免。有的卡跑得满负载,有的卡闲置,谁也没有办法实时调配。而且人肉切分无法应对模型规模的增长。当 Llama 这种百亿参数模型出来后,单卡放不下,靠人工已经搞不定了。

当时行业里也出现过一些“土办法”,比如自己写脚本监控显存、动态地往显存有富余的卡上派任务。但这类方案只适合实验环境,一旦请求量上来,脚本本身就成了新的瓶颈。

3.2 并行策略:从“搬过来”到“拆开算”

到了大模型训练和推理都要用多卡的阶段,并行计算策略就成了核心话题。三个最基础的并行维度是:张量并行、流水线并行、数据并行。

张量并行把模型权重按层内切分到多张卡上,比如把一整层 attention 的矩阵运算拆成多份,每张卡算一部分。它的好处是能把超大规模模型放到多张卡上,但坏处是每算一步都要做卡间通信。在同一节点内走 NVLink 问题不大,跨节点就非常吃网络带宽。我的经验是:能不放跨节点就别放,通信开销往往是隐性杀手。

流水线并行则是按层切分,把模型的不同层放到不同卡上,数据像流水线一样逐层推进。它的好处是通信压力比张量并行小,但缺点是流水线有“气泡”——上下游速度不匹配时,部分卡会空转。

数据并行则是最简单的一种:每个卡上放完整模型,把不同请求分给不同卡各自推理。它适合低延迟优先的场景,但显存利用率低,模型一旦超过单卡容量就没法用了。

早期做推理的人直接把训练阶段的并行策略照搬过来,但效果并不好。原因是推理请求的到达是动态的,静态切分根本没有办法应对突发流量。合理做法是把并行策略和动态调度结合起来,比如多个副本之间做负载均衡,单副本内部再叠加张量并行或流水线并行。

3.3 调度器接管:连续批处理和显存页式管理

真正让推理性能上了一个台阶的,是调度器从“配角”变成了“主角”。vLLM 提出的 PagedAttention 和 Continuous Batching,在我看来是这个领域最有代表性的思路转变。

PagedAttention 解决的是显存碎片问题。它模仿操作系统虚拟内存的机制,把 KV cache 切成固定大小的块,按需分配,而不是预先给每个请求分配一块连续的大内存。这个设计让显存利用率提高了不少,尤其在长序列、高并发的场景下效果很明显。

Continuous Batching 解决的则是请求调度问题。它不再等一个 batch 整体跑完再调度下一批,而是在单个 token 粒度上做调度。当一个请求在 decode 阶段生成完当前 token,调度器就会决定下一步是把新请求加入当前批处理,还是继续让旧请求生成下一个 token。这么一来,整套系统的吞吐量提升非常显著。

我印象很深的是自己第一次把推理框架从静态批处理切到支持连续批处理的方案时,同样一台 A10,吞吐量翻了接近三倍,首字延迟还明显下降。当时我就意识到,硬件本身没变,只是调度策略变了,效果就完全不同。

这个阶段还有另外几个关键优化方向。比如 inflight batching(TensorRT-LLM 里的叫法),作用类似 Continuous Batching,不过对做动态批处理和抢占调度的支持更精细。还有 prefix caching,缓存相同 prompt 前缀的 KV cache,遇到相似请求可以跳过 prefill 阶段直接开始 decode,这个在 Agent 场景里非常实用。

3.4 异构混布与弹性调度:从“一人一卡”到“资源池”

再往后发展,调度不再局限在单机内的几张卡上,而是走向了“资源池化”。

异构混布就是把不同类型的 GPU(A 系列、V 系列、消费级卡)放到同一个资源池里,调度器按任务对显存、带宽、算力的需求,自动分配合适的卡。比如为了省成本,可以把轻量级模型的推理请求放到消费级卡上,把重型模型的请求放到 A100 上。

弹性调度解决的是“流量来去不定”的问题。上线一个模型后,不再固定占用多少张卡,而是按需扩容缩容。流量高峰时拉起更多推理副本,低谷时缩小副本规模。这种方案对平台型团队特别友好,但对基础设施的要求也高,要求推理服务和调度平台之间做很深的集成。

我自己的感受是,这种演进路径背后是思维方式的转变——从“每个人都守着自己的一亩三分地”,变成“算力是一池子水,按需取用”。GPU 不再属于某个模型或某条业务线,而是交给调度器统一编排。这也是当下 Serverless 推理、模型编排平台都在走的方向。

4. 实操中真正值得复用的调度调优思路

4.1 别拍脑袋,先看监控数据

网上有太多“三个月精通大模型推理”的帖子,上来就给出各种参数配置。但我想说的是,任何脱离了监控数据的调参都是自欺欺人。我每次接手一个推理服务,第一件事就是把下面几项监控盯起来:

  • GPU 利用率:看的是算力占用情况;
  • 显存占用:重点看是模型权重大还是 KV cache 大;
  • 内存带宽利用率:如果显存带宽被打满,说明 decode 阶段已经到头了;
  • 平均首 token 时延和生成速度:这是用户能感知到的核心指标;
  • 队列长度和排队等待时间:如果队列一直在涨,调度配置可能需要调大并发。

举一个我自己遇到的例子。之前调一个 7B 模型的部署,首 token 时延高得离谱,但 GPU 利用率只有 8%。一开始我以为是模型加载或者框架问题,折腾了半天,最后看监控才发现:请求到达速率很高,但服务端在等待 prefill 阶段释放显存,大量请求在队列里排队。问题根源是 KV cache 预留空间太小,调度器不敢同时处理多个长请求。把 gpu_memory_utilization 和 max_num_seqs 按实际请求长度调大后,首 token 时延立刻降了下来。

所以我的建议是:调优之前,先花一天时间把监控做全。没有监控数据,后面所有操作都是在赌。

4.2 一次完整的显存估算与 batch 配置过程

这里用一个实际案例,带大家完整走一遍显存估算和 batch 配置的思路。

假设我要在一张 24GB 显存的 RTX 3090 上部署一个 7B 模型,目标是尽可能提高并发吞吐。第一步是确定模型权重占用。如果直接加载 FP16 权重,大约需要 14GB。这就意味着留给推理中间状态的空间只剩 10GB。通常 CUDA context 和激活内存会占 1~2GB,那么 KV cache 最多只剩下 8GB 左右。

第二步是估算单请求的 KV cache 占用。假设模型是 32 层、32 个 head、每 head 维度 128,序列长度平均 1024,单请求的 KV cache 大约是 2 × 32 × 32 × 128 × 1024 × 2 字节 = 512MB。也就是说,在 8GB 的 KV cache 空间里,理论上最多容纳 16 个并发请求。但如果模型支持长上下文,用户经常用到 4096 序列,单请求就变成 2GB,并发能力立刻降到 4。

这时候就面临取舍:如果业务场景长请求占比高,就需要把 batch 和 KV cache 上限调低,避免显存溢出;如果短请求为主,则可以适当拉高并发。实际配置里,我会重点关注 max_num_seqs 和 max_num_batched_tokens 这两个参数。前者控制最多同时处理的请求数,后者控制单次迭代最多处理的 token 总数。我的经验是:把 max_num_batched_tokens 设为并发请求数和平均序列长度的乘积,再留一些余量,这样不容易出现显存峰值超限的情况。

4.3 prefill 和 decode 分开调度

如果说前面是基础配置,那么 prefill 和 decode 的分离优化就是进阶玩法。

为什么要把这两个阶段分开?因为它们对资源的需求完全不同。prefill 是计算密集型的,给它更多算力就能更快处理完;decode 是访存密集型的,并行加更多的请求反而能摊薄访存成本。如果混在一起跑,调度器很难做到两种需求的兼顾。

现在一些框架已经开始支持 prefill 和 decode 的独立调度,比如把 prefill 任务安排到专门的 GPU 上,decode 任务则在另外的 GPU 上执行,中间通过 KV cache 传递状态。这种设计的价值在于,可以根据两个阶段不同的负载特征分别做扩容。prefill 压力大就加 prefill 节点的卡,decode 压力大就加 decode 节点的卡,互不干扰。

这种拆分的代价是增加系统复杂度,需要管理两个阶段的 KV cache 传输和节点间的状态协调。对于单机部署,也可以退而求其次,在调度器上做优先级控制——比如当 prefill 任务堆积时,让给计算密集的请求先跑;当 decode 请求多时,则尽量合并它们做连续批处理。

4.4 别忘了网络和 CPU 这些“边角料”

还有一个常被忽略的点:硬件调度不只在 GPU 之间进行,CPU、内存、网络同样参与调度。

我见过一个项目,GPU 配置很好,但 CPU 核数太少。结果做 tokenization、预处理和 sampling 的 CPU 进程成了瓶颈,GPU 经常空转等着 CPU 喂数据。大模型推理不仅仅是 GPU 在干活,分词、采样、请求解析、结果返回这些操作都依赖 CPU。如果在跑多路推理服务,建议把 CPU 核数、内存大小和 GPU 卡数一起做预算。

多机部署时,网络更是硬约束。张量并行需要频繁通信,如果跨节点用普通万兆网而不是 RDMA 网络,通信延迟会直接把推理速度拖垮。我的建议是:能够单机放下的模型,尽量别做跨节点张量并行;实在要跨节点,优先考虑流水线并行而不是张量并行,因为它的通信频率要低很多。

5. 常见问题与排查技巧实录

5.1 一张实用的问题速查表

平时答疑时,我发现很多推理性能问题都能归到几个固定类型。下面这张表是我根据自己的排查经验整理的,不一定覆盖所有场景,但可以作为第一轮排查的参考。

现象最常见原因排查方向
GPU 利用率很高,但每秒生成 token 数很低decode 阶段访存瓶颈看显存带宽占用,确认是否已经打到上限
首 token 时延很长,后续生成正常prefill 排队或 prefill 节点过载检查请求队列长度和 prefill 阶段耗时
显存占用看起来不高,却报 OOM峰值显存超限(长序列或大 batch 导致)监控 KV cache 的峰值变化,降低并发上限
并发一高就超时服务端队列配置过小或批次过大调整 max_num_seqs 和 max_num_batched_tokens
重启时模型加载非常慢模型权重在 CPU 与 GPU 之间反复搬运检查加载逻辑,是否用了 mmap 或 safetensors 分片加载
多机推理比单机还慢跨节点通信开销过大检查网络配置,考虑改用流水线并行或减少跨节点张量并行

这个表的价值不在于“答案”,而在于帮你快速锁定方向。很多问题一旦方向对了,解决就只是时间问题。

5.2 一个典型 case:GPU 利用率高但吞吐上不去

我挑一个印象最深的 case 详细说说。有次帮朋友调优一个在线推理服务,现象是 GPU 利用率长期跑在 90% 以上,但服务吞吐却只有预期的一半。

第一反应是“GPU 在满负荷工作,那应该是算力不够吧”。但仔细看了一下 nvidia-smi 和 profiling 工具,发现显存带宽的利用率已经接近 100%,而 GPU 的 SM(流式多处理器)利用率其实只有 30% 左右。

这说明什么?说明 GPU 大量时间都在等待显存数据返回,也就是访存受限,而不是真正的计算受限。利用率高是因为显存带宽占满了,SM 算力却很空闲。

问题定位到这儿就清楚了:decode 阶段单个 batch 里跑太多请求,KV cache 读写的访存压力太大。每个请求都要读自己的 KV cache,batch 越大,访存压力越高,但计算量的增长并没有跟上。后来把 max_num_seqs 从 64 降到 32,同时开启 prefill/decode 分离策略后,单 token 生成速度明显提升,整体吞吐反而上去了。

这个 case 让我得到一个很重要的认知:GPU 利用率高不代表系统健康,先分清“算力利用率”和“访存利用率”这两件事,再决定是加卡还是调配置。

5.3 避免踩坑:几条实操避坑建议

最后分享几条比较“私房”的经验,算是给大家的避坑指南。

第一,不要盲目抄别人的配置。同一个模型,A100 和 4090 的最优 batch 配置差别很大;同一个硬件,长文本业务和短对话业务的最优参数也完全不同。任何人的经验都只能做参考,必须在自己的场景里压测验证。

第二,压测时别只用“平均延迟”做指标。平均值会掩盖大量问题。我看服务健康度时,更关注 P99 延迟和长尾请求的比例。在线推理场景,P99 抖动往往是用户体验崩坏的第一步。

第三,升级框架前先做回归测试。有一年我试着把框架从 A 版本升到 B 版本,结果模型的输出质量和生成速度都对,但在高并发下出现了随机性的卡死,最后排查发现是某个调度参数的默认值变了。框架升级不是换了就完事,必须拿线上流量回放做验证。

第四,显存估算永远要留 20% 的余量。别把显存算得刚刚好,因为真实业务中除了模型和 KV cache,还有采样、beam search、各种临时 buffer 都要占空间。我见过太多线上环境因为显存峰值超限直接 OOM 的。

最后说几句掏心窝的话

刚才讲的这些,实际上都指向一个共同的底层问题:你需要的不是更多算力,而是让已有硬件更“服帖”地服务于推理任务。我在实际配置和调优过程中最大的体会是,大模型推理的优化,本质上是一个“匹配”的过程——让模型的访存特征、计算特征和请求特征,匹配上 GPU 的算力、带宽和显存容量。这需要的是对细节的耐心,而不是简单地堆卡。

如果你正在为推理性能发愁,我建议你先别急着升级硬件。花一周时间,把监控做全,把请求特征摸清楚,把调度参数一项一项验证,大概率能在现有设备上得到不小的提升。最后再分享一个小技巧:每次调整完调度配置,只改一个变量,然后记录前后的对比数据。这样几轮下来,你会对线上服务的行为模式有远比“感觉流畅多了”要准确得多的理解。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 10:11:18

Ponytail:轻量级 CLI 技能插件化架构解析

1. 项目概述:Ponytail 不是发型,而是一个轻量级 CLI 工具链的代号最近在 GitHub Trending 和前端开发者社区里,“ponytail”这个词频繁出现,但它和马尾辫毫无关系——它是一套由德国开发者 Dietrich Giebert 主导构建的、面向现代…

作者头像 李华
网站建设 2026/9/9 10:10:12

微信小程序云开发实战:从零搭建宠物社区毕业设计全流程

想把宠物社区做成微信小程序毕业设计的同学,这篇可以帮你少走很多弯路。这个项目我从接到题目到跑通完整流程,前后花了大概三周,中间踩了不少坑,也总结出一套适合毕设阶段的实现思路。今天把这套系统从需求拆解到技术选型、从核心…

作者头像 李华
网站建设 2026/9/9 10:09:39

Java面试核心模块攻略:集合并发JVM框架一次讲透

最近帮几个准备跳槽的朋友做了几轮Java模拟面试,发现一个共同的问题:大家背了不少题,八股文张口就来,但一旦被追问“为什么这样设计”“你线上遇到这种情况怎么处理”,很多人就开始卡壳。这其实不怪大家,而…

作者头像 李华
网站建设 2026/9/9 10:05:41

Python嵌入式开发实战:MicroPython与ESP32快速上手避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华