ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

模型推理GPU资源调度实战:从显存分配到集群优化

模型推理GPU资源调度实战:从显存分配到集群优化 模型部署上去之后GPU 利用率一直在 20%~30% 徘徊响应却慢得让人怀疑是不是用 CPU 在跑。换一台机器想多塞几个并发请求结果 OOM 直接崩掉。这不是单个模型的问题而是整个推理链路上 GPU 资源调度没有理顺。“AI 模型推理 GPU 资源调度”听起来像运维同学的活但实际干过的人都知道做算法、做应用、做后端的人每天都会被它绊一跤显存怎么分、并发开多大、为什么多卡利用率上不去、为什么换了 GPU 反而更慢。这篇文章把我这几年在推理部署、模型微调、多用户共享 GPU 这些场景里攒下的调度经验和踩坑记录完整写出来从单块显卡到集群调度、从 Ollama 本地推理到 PyTorch 微调每个环节都会说清楚“为什么这么做”也会给出可以直接照着操作的验证方法。1. GPU 推理资源调度先把问题定义清楚提到 GPU 资源调度很多人第一反应是“排队跑任务”其实推理场景和训练场景完全是两码事。训练任务可以等推理任务等不起这就决定了调度策略的根本差异。1.1 一句话说清“调度”到底在调度什么很多人以为 GPU 调度只跟显存有关这是最常见也最危险的误解。GPU 上能被调度的资源至少分四类显存容量、计算单元SM、显存带宽、以及并发执行上下文。显存决定能同时装下多少个模型副本和多少条并发请求计算单元决定算力峰值能跑到多高显存带宽决定数据搬运速度而并发上下文比如 CUDA stream、MPS 的并发通道决定多个任务能不能真正并行而不是互相阻塞。我见过一个生产事故某服务部署了 4 个模型副本每份显存占用 4GB一块 16GB 的卡刚好塞下。表面上显存利用率接近 100%但四个副本在同一个 GPU 上争抢 SM实际吞吐比单副本只高了 40%延迟反而涨了 3 倍。原因就是只算了显存够不够没算 SM 份额够不够。调度的时候这四个维度必须一起算否则就会得到“看起来满载实际很慢”的诡异结果。1.2 推理调度的核心约束延迟敏感训练调度的目标函数是“吞吐最大化”任务排队多久无所谓GPU 利用率越高越好。推理的约束完全不同它的目标函数是P99 延迟达标的前提下尽量提升吞吐。打个比方训练就像货运火车装得越多越好晚点一两小时可以接受推理就像外卖骑手每一单都有时限 30 分钟哪怕只有一单超时用户就能感知到。这个差异会直接反应到调度策略上。训练时可以大胆把 batch size 拉到 64、128让 GPU 吃得越满越好推理时如果 batch size 拉得太大单个请求处理时间暴涨P99 延迟就守不住。所以推理场景通常会在服务端做动态 batching——把一小段时间窗口内的请求攒起来凑成一个 batch既保证延迟不超标又尽量抬高 GPU 利用率。这个窗口设多长、batch 最多攒到多大就是推理调度里最常见的调优项。2. 单机推理显存分配、CUDA Context 与并发上限单机推理是一切调度的基础。先把一块卡上的资源算明白再谈多卡和集群。2.1 模型推理的显存占用到底怎么估算很多人用“模型参数量 × 精度字节数”来估显存这个算法只对了一半。比如 7B 模型半精度是 7×10^9 × 2 字节 ≈ 14GB确实是对的但这只是权重占用的显存。实际部署时还要算上三笔额外开销。第一笔是CUDA Context每个进程初始化 CUDA 环境时都会固定占掉约 300MB~500MB 显存多进程部署多个模型时这笔开销会被放大。第二笔是KV Cache它随并发请求数和序列长度线性增长。以 7B 模型为例每个并发请求的 KV Cache 大约是几百 MB 到 1GB 不等取决于上下文长度。第三笔是临时激活值推理时前向计算产生的中间张量虽然比训练时小得多但 batch 变大时也不能忽略。我常用的快速估算公式是显存需求 模型权重 × 精度系数 并发数 × 单请求 KV Cache 大小 常量开销驱动 CUDA Context 框架缓存假设 7B FP16 模型并发 32单请求 KV Cache 0.4GB那至少需要 14 12.8 1 27.8GB 显存。一块 24GB 的 4090 就不够需要 48GB 的 A6000 或者 L40S。很多人在 24GB 卡上跑 7B一压并发就 OOM就是这个账没算明白。2.2 为什么“GPU 利用率低”不一定是坏事接着开头那个 30% 利用率的问题说。我用nvidia-smi看到的利用率指的不是显存占用率而是 SM 上有活跃 warp 的时间占比。推理服务如果延迟敏感通常不会把 batch 拉到很大SM 一直喂不满利用率低其实是正常的。判断推理型服务的 GPU 是否“够用”不能只看利用率。更靠谱的指标链是P99 延迟是否达标 → 吞吐是否满足业务峰值 → 显存是否有余量。链路全绿哪怕利用率只有 20% 也没问题链路中有任何一项超标才需要去排查是不是 GPU 资源被卡住了。真想分析 GPU 到底在忙什么单靠nvidia-smi不够我一般会用nsys抓一下时间线看 kernel 之间有没有大量空隙。如果 kernel 之间空隙很大说明数据搬运或者 CPU 侧预处理成了瓶颈这时就算把 GPU 换成顶配也白搭。2.3 PCIe 与显存带宽容易被忽略的隐形瓶颈热词里反复出现“PCIe”“GPU 对称内存”这类词说明很多人已经遇到带宽瓶颈了。推理时权重要从显存搬进 SM 里的计算单元每算一次都要搬一遍所以显存带宽对推理延迟的影响经常超过算力。6550 这类数据中心卡显存带宽高达 3.35TB/s而普通消费级卡往往只有几百 GB/s差距非常可观。另一个被忽略的是PCIe 带宽。如果数据要在 CPU 和 GPU 之间来回搬运PCIe 4.0 x16 的理论带宽约 32GB/s实际有效带宽只有一半左右。比如视觉模型要做 CPU 侧预处理再拷到 GPU图像分辨率一大这份拷贝时间就能吃掉整个推理延迟的一半。我的经验是能用 GPU 算子完成的预处理就尽量在 GPU 上做减少跨 PCIe 的搬运次数。3. Ollama 本地推理最常见的 GPU 调度实战场景Ollama 是本地跑大模型时使用率最高的工具之一但它对 GPU 的调度逻辑藏在环境变量后面很多人装完发现模型在跑却没意识到它根本没用上 GPU。3.1 如何确认 Ollama 到底用没用到 GPU判断方法很简单跑一个模型后执行ollama ps输出结果里能看到进程列表。关键是看PROCESSOR列显示 100% GPU 说明权重全在显卡上显示 GPU/CPU 混合说明有一部分层被卸载到了内存显示 100% CPU 说明压根没启用 GPU。我自己在第一次装 Ollama 时就踩过坑ollama run llama3之后响应速度尚可但ollama ps显示 100% CPU原来是因为 CUDA 驱动没配对Ollama 静默回退到了 CPU 推理。它不会报错只会慢如果不主动查进程列表根本发现不了。3.2 显存不足时的调度策略层卸载与并发控制当模型权重大于显存容量时Ollama 会把一部分 transformer 层放在 CPU 上GPU 只跑剩下的层这就是默认的“层卸载”策略。OLLAMA_NUM_GPU环境变量可以控制放到 GPU 上的层数。比如 32 层的模型默认全放 GPU如果显存不够就设OLLAMA_NUM_GPU20只放 20 层到 GPU剩下 12 层在 CPU 上算。但这里有个容易忽略的点推理是串行的每一层都要按顺序计算。只要有一层在 CPU 上整条链路的速度就被这一层拖住。所以层卸载只是“能跑”的方案不是“快”的方案。如果模型是 14B、32B 级别显存不够我的建议是优先考虑量化版本比如 Q4_K_M把权重压到 5GB 以内整模放 GPU远好过硬塞 FP16 然后卸载一半层到 CPU。并发层面的调度Ollama 默认单请求独占多请求排队。OLLAMA_NUM_PARALLEL环境变量可以开并行比如设成 4 就能同时处理 4 个请求。但注意并行数开太高每个请求分到的显存和 SM 份额都会下降OOM 和延迟飙升都可能在开大的瞬间出现。我实测 7B Q4 模型在 24GB 卡上并行 4 是甜点并行 8 延迟翻倍且收益很小。3.3 非 NVIDIA GPU 的处理差异Intel、AMD 与国产加速卡Ollama 不只有 NVIDIA 生态可以用。Intel 平台走 SYCL 后端运行前设置OLLAMA_INTEL_GPU1并安装 Intel 官方 GPU 驱动AMD 显卡走 ROCm 后端需要装 ROCm 运行时并且 Ollama 对 ROCm 版本有比较严格的配对要求。国产加速卡比如昇腾 CANN 生态也在陆续适配 Ollama但需要把后端编译成 CANN 版本不能直接拿官方包跑。这些非 NVIDIA 生态最麻烦的地方在于驱动、运行时、框架三者的版本匹配。NVIDIA 的 CUDA 已经是事实标准装错了大不了重装ROCm 和 SYCL 的文档相对零散版本兼容矩阵要自己试。实在要踩我的建议是先看官方 Docker 镜像里锁定的版本照着镜像里的依赖版本在宿主机上复现能省一半时间。4. WSL 环境 GPU 被系统阻止完整排查链路近几年在 Windows 上做 AI 开发的人越来越多WSL 成了标配但“failed to initialize nvml: gpu access blocked by the operating system”这个报错频繁出现在各种论坛里。这类问题本质上不是 Ollama 或 PyTorch 的问题而是 Windows 侧 GPU 虚拟化通道没打通。4.1 这个报错的根因是什么WSL 2 里访问 GPU依赖 Windows 宿主机的 GPU 驱动通过 dxgkrnl 虚拟化协议把 GPU 能力暴露给 Linux 内核。所以链路是Windows GPU 驱动 → WSL 内核的 GPU 虚拟化模块 → Linux 用户态的 CUDA 库 → 应用程序这段链路里任何一环断了或者版本不匹配就会出现“gpu access blocked”或者“NVML 初始化失败”。最常见的是三种根因Windows 侧驱动太老WSL 内核没有更新用户态 CUDA 库和驱动版本不匹配。4.2 从现象到定位三步排查法第一步在Windows 侧打开终端执行nvidia-smi确认驱动能识别 GPU、显示的是正常状态而不是“不可用”。如果 Windows 侧都识别不了优先更新 GE Force Experience 或企业版驱动。第二步进入WSL 终端执行nvidia-smi。注意看输出的驱动版本和 CUDA 版本正常情况下应该和 Windows 侧一致。如果 WSL 里提示找不到命令说明没装 CUDA 工具链sudo apt install nvidia-cuda-toolkit可以解决但我不推荐直接装系统包管理器里的版本版本太老。第三步检查 WSL 内核是否支持 GPU 直通。在 WSL 里执行uname -r如果内核版本太老跑wsl --update把 WSL 本体更新到最新版。这一步是很多人忽略的WSL 内核和 Windows 系统是分开更新的光更新驱动不更新 WSL 内核GPU 虚拟化模块就不会加载。4.3 为什么“更新驱动”不一定能解决问题我踩过一次很深的坑驱动装的是最新版WSL 也更新了但报错依旧。最后发现问题是WSL 里装的 PyTorch 是 CPU 版它内部调用 CUDA 的路径根本没有所以去初始化 NVML 的库和驱动不是同一个 CUDA 版本。这种报错不是“被系统阻止”而是用户态的 CUDA 库和 Windows 驱动里的 CUDA 运行时版本错配。解决方法是统一基线Windows 驱动是 572.61对应的驱动内 CUDA 版本是 13.0WSL 里的 CUDA 工具链也要用 13.x 或向下兼容的 12.x不能在 WSL 里单独装一个 CUDA 11 的 PyTorch 包。建议直接用 PyTorch 官方命令装对应 CUDA 版本比如pip install torch --index-url https://download.pytorch.org/whl/cu121装完用torch.cuda.is_available()验证。还有一个容易中招的场景是VMware Workstation Pro里跑 GPU 推理。虚拟化软件的 GPU 直通方案vGPU 或 PCIe Passthrough对虚拟硬件版本有要求如果虚拟机是默认的旧硬件版本GPU 直通功能根本不会生效。解决办法是把虚拟机硬件版本升级到支持 GPU 直通的版本并且在虚拟机设置里显式指定 GPU 资源。5. 多用户与集群场景GPU Operator、资源池化与租用单机搞定之后面对多卡、多用户、甚至多机集群时调度复杂度会上一个数量级。这里最核心的问题是怎么把几块 GPU 按需切给不同的人、不同的任务互不干扰。5.1 从裸机到资源池化容器化是基础裸机上的多用户 GPU 调度非常痛苦A 用户的任务显存泄漏能把整块卡打挂B 用户毫无办法。容器化是最基础的隔离手段。在 Kubernetes 里跑 GPU 任务如果只是resources: nvidia.com/gpu: 1这么简单写你会发现调度器根本不知道该去哪个节点找 GPU——这就是 GPU Operator 存在的理由。GPU Operator 做的事情可以理解为一个“插线板管理工具”它在 Kubernetes 集群里装上 NVIDIA 驱动、CUDA 运行时、容器运行时插件和监控组件让 Kubernetes 能够感知每台节点上的 GPU 资源并且把 GPU 以标准资源的方式暴露给调度器。部署它之前节点上不能预先装 NVIDIA 驱动否则会有冲突。5.2 GPU Operator 的调度链路与验证GPU Operator 装上后调度的链路是用户提交 Pod声明 nvidia.com/gpu: 1→ Kubernetes 调度器发现节点有余量 → 节点上的 Device Plugin 把 GPU 设备挂载进容器 → 容器里 nvidia-smi 正常识别验证调度是否生效最快的方法就是部署一个临时 Pod 跑nvidia-smi确认容器里看到的显卡编号和宿主机对得上。我遇到过的常见坑是 Pod 启动报Failed to initialize NVML多数是 Device Plugin 和驱动版本不匹配升级 GPU Operator 版本即可解决。5.3 共享 GPU 的调度策略时间片、MIG 与显存隔离很多场景下一人独占一张卡太浪费但几组人同时用又要防止互相干扰。这就得靠共享 GPU 调度。目前主流方案有三类方案隔离粒度优点缺点时间片Time Slicing按时间轮转配置简单兼容所有卡SM 争抢严重延迟不稳定MIGMulti-Instance GPU硬件级切分显存和SM隔离最彻底延迟稳定仅 A100/H100 等新卡支持显存SM 限制算子级软件限制灵活支持消费级卡实现复杂需配合专用调度器中小团队最常用的是时间片方案配置很简单但要注意它对延迟的破坏。我在一个语音识别服务上试过时间片切分两个任务的 GPU 利用率都拉满时P99 延迟从 80ms 涨到 450ms完全不可用。后来改用 MIG 把一张 A100 切成 3 个实例每个实例独享 SM 和显存分片延迟彻底稳定下来。如果你的 GPU 支持 MIG优先用 MIG 而不是时间片。5.4 云上 GPU 租用的调度经验“GPU 租用”本身就是一种资源调度把算力需求放到云服务商的 GPU 实例上跑。这里最容易踩的坑是实例规格选择和弹性策略。推理服务最好选按量付费实例搭配弹性伸缩不要选包年包月因为推理负载的波峰波谷非常明显。另一个经验是不同云厂商的 GPU 实例CPU 和内存配比差异很大。有的 24GB 显存卡只配 8 核 CPU跑大模型的 tokenizer 和预处理时 CPU 会成瓶颈。选型时一定看 CPU 核数和内存大小而不仅仅看 GPU 型号。6. 微调场景的显存调度与优化从 PyTorch 到生产标题虽然重点在“推理”但“GPU 微调大模型”在热词里反复出现而且微调的显存调度和推理是强关联的——很多推理坑KV Cache、序列长度的影响在微调阶段就已经埋下了。微调时显存不足本质上也是调度问题怎么把几十 GB 的计算量塞进一块 24GB 的卡里。6.1 PyTorch GPU 环境先通过版本匹配这道关PyTorch 安装教程类的问题常年居高不下核心原因是版本匹配。pip install torch默认装的是 CPU 版跑torch.cuda.is_available()永远返回 False。正确做法是到 PyTorch 官网看 CUDA 版本矩阵然后装对应索引源的包。我目前用的稳定组合是驱动 572.xxCUDA 13.0 PyTorch cu121 编译版向后兼容没问题。选 cuda 版本的时候不用盯着最新装得能跑比装得最新重要得多。驱动版本向下兼容 CUDA 运行时但反过来不行——老驱动撑不住新版 CUDA 编译出来的 PyTorch。6.2 微调显存峰值估算为什么一张 24GB 卡经常爆微调阶段显存占用比推理多得多。除了权重本身还要存梯度、优化器状态Adam 要存一阶动量和二阶动量FP16 下是权重的 4 倍、以及前向传播留下的激活值。以 7B 模型为例项目显存开销权重FP1614GB梯度FP1614GBAdam 优化器状态FP3242GB激活值随 batch 和序列长度变化数 GB 到数十 GB所以 7B 全参数微调在 24GB 卡上根本跑不动。全参微调建议 80GB 以上显存。24GB 卡要跑 7B 微调必须做显存调度优化。6.3 显存调度优化三板斧混合精度、梯度检查点、CPU offload第一板斧是混合精度AMP权重和激活用 FP16/BF16优化器状态保持 FP32。这一步就能把优化器状态的 42GB 砍到约 20GB 左右。第二板斧是梯度检查点gradient checkpointing不保存所有前向激活值而是在反向传播时重算。激活占用的显存可以从几十 GB 降到几 GB代价是训练速度慢约 30%。对 24GB 卡的微调用户来说这个代价值得付。第三板斧是 CPU offload把优化器状态和梯度卸载到内存GPU 只保留权重和激活。这是 ZeRO-Offload 的核心思路。24GB 卡 64GB 内存的组合用 DeepSpeed ZeRO-Offload 能跑 7B 的 LoRA 微调甚至有人拿它跑全参微调只是慢得感人。这三板斧的合理组合我实测过7B 模型在 409024GB上做 LoRA 微调开混合精度 梯度检查点 batch size 1显存稳定在 18GB 左右训练能跑通。再多开一个 CPU offload显存能压到 12GB但每步训练时间涨了接近 50%所以能不开就不开优先想办法减小 batch 和序列长度。6.4 微调完转推理时显存调度要重算一遍微调结束后的模型做推理部署显存估算要重新按推理逻辑算不能用训练时的数据。我遇到过同事把微调时的显存峰值当成推理占用结果部署时预留了 3 倍冗余白白浪费了一张卡的成本。推理部署参照第 2.1 节的公式重新算就行这里要提的重点是微调时用的 LoRA 权重推理时可以先合并进主模型省掉运行时加载 LoRA 适配器的开销能省约 0.5~1GB 显存也能降低加载耗时。7. 从一块卡到一片卡调度意识比工具更重要最后分享一个我个人的实操体会GPU 资源调度这件事工具和参数固然重要但真正拉开差距的是“先算账再动手”的习惯。就算拥簇一堆 GPU Operator、MIG、Ollama 环境变量如果不先算清楚显存账、带宽账、延迟账配置出来的调度策略大概率是看着合理、跑起来崩盘。一个小技巧收尾日常排查 GPU 调度问题时我习惯常驻三个终端命令——watch -n 0.5 nvidia-smi看实时占用nsys profile --statstrue抓 kernel 时间线ollama ps验证本地推理的真实使用情况。三张图拼起来问题基本就水落石出了。GPU 调度的本质是资源的生意每一块显存、每一个 SM、每一份带宽都要花在能带来实际收益的地方。先把账算清楚再选择合适的调度手段这块卡才算真正被用透了。
返回列表