跑一个AI模型推理服务,在今天已经不算什么门槛了。你装个PyTorch、拉个模型权重、起个FastAPI,一个能响应的接口就有了。但真到了生产环境,事情远没那么简单:GPU利用率上不去、多个模型抢显存互相OOM、半夜某个推理进程把显存吃满导致其他服务全挂、Windows上用Ollama加载模型怎么都调不起GPU……这些问题,本质上都是同一个——GPU资源调度没做好。
这篇就专门聊聊AI模型推理场景下的GPU资源调度。不是那种大而全的“Kubernetes从入门到精通”,而是我实际部署推理服务过程中真正踩过、填过、反复验证过的那些关于显存、算力、并发分配的经验。适合正在做模型服务化、算法工程化,或者自己租了GPU服务器跑模型但总觉得“哪里不对”的工程师参考。看完你会理解推理场景为什么比训练场景更吃调度,单机和集群分别怎么调度,以及那些“莫名其妙”的GPU问题到底是怎么排查出来的。
1. 为什么推理场景的GPU资源调度是个“真痛点”
1.1 训练和推理的资源使用逻辑完全相反
绝大多数人接触GPU是从训练开始的,训练场景下的资源使用逻辑其实特别简单:一个任务占一张卡,占了就独占,跑完就释放。你很少需要操心“谁跟谁共享这张卡”,因为深度学习框架默认就会把整张卡的显存都分配掉。
但推理完全不同。推理服务要常驻运行,一张A10或者4090上往往要同时塞好几个模型,每个模型的流量还会随时间波动——白天OCR模型被打爆,晚上可能换成一个推荐模型在扛高峰。如果按照训练的思路给每个模型独占一张卡,成本直接起飞;如果让大家挤在一起,又面临“谁把谁挤掉了”的调度问题。
而且推理请求是短任务、高并发、强实时的负载形态。训练可以容忍一个batch卡住但不报错,推理卡几秒钟用户就直接超时重试了。调度不好,GPU利用率看着挺高,但实际有效吞吐可能惨不忍睹。
1.2 推理负载的多样性让调度变成多目标优化
我在实际项目里经常遇到三类推理负载混在一组GPU上的情况:
- 小模型高频推理:比如文本分类、意图识别,单请求只需几毫秒GPU时间,但QPS可能上万。显存占用不大,瓶颈在算力和CPU到GPU的拷贝通道。
- 大模型低并发推理:比如7B、13B的LLM对话,显存大头全被权重占掉,同时并发数不高,每个请求都要做完整的prefill和解码循环。
- 批处理型推理:比如视频抽帧做检测,每秒处理几十上百张图,吞吐优先,延迟要求不严格。
这三种负载如果混在一起,简单按“显存大小”切分必然出问题。大模型虽然显存占用大,但实际算力利用率可能很低;小模型虽然显存占用小,却可能把整张卡的并行吞吐打满。经典的调度维度不能只看显存,得把显存占用、算力需求、延迟敏感度、峰值时段一起考虑,这已经不是一个简单的“谁大谁先分配”问题了。
1.3 调度做不好会带来哪些实际损失
说几个我真实见过的现象,你可以对照自己团队是否存在:
- 一张80GB显存的A100上跑了4个模型,看起来每个模型都分到了20GB,但训练类任务经常要“临时借显存”,结果在晚上高峰直接OOM,把4个推理服务一次性全带走。这种连锁事故比单个模型挂了更可怕。
- GPU利用率用
nvidia-smi看才15%,但已经有两个模型在报显存不足。原因是框架的显存预分配策略和实际请求量严重不匹配,资源虚占。 - 多机多卡集群上,模型调度全靠手工指定GPU编号,加一个模型就要改所有下游配置,上线一套服务折腾一个下午。
说白了,推理场景的GPU调度决定的不只是“能不能跑”,而是“同样的硬件能扛多少流量、稳定性有多高”。后面我会按单机到集群、从底层到框架把每个环节怎么调、为什么这么调讲透。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 理解GPU调度的三个核心维度:显存、算力、并发隔离
调度GPU之前先得弄清楚你到底在调度什么。很多人一上来就盯着显存,其实涉及到的资源至少有三个维度,任何一个没处理好都会出事。
2.1 显存:推理的硬约束
先说显存。模型能不能跑起来,第一道门槛就是显存。一个模型推理需要多少显存,可以按这个粗略公式估算:
code复制显存 ≈ 模型权重大小 × 1.2 + 激活值/KV Cache + 框架运行时开销
以目前最常部署的7B参数模型为例:
- 模型权重:FP16精度下,7B参数 ≈ 7 × 10^9 × 2字节 ≈ 14GB
- KV Cache:根据并发数和序列长度动态变化,8K上下文、同时处理几十个请求的话,约需 2~8GB
- CUDA上下文和运行时:通常占 0.5~1.5GB
也就是说,一个7B模型想推理跑起来,最低需要16GB左右显存,想跑得舒服、并发不拉胯,24GB比较保险。这个估算方法适用于你选显卡、租GPU、规划模型共存方案时的初步判断。
但显存的大小只是静态维度,真正麻烦的是显存的动态变化。PyTorch、TensorFlow这类框架默认是“按需分配、只增不减”的,进程启动就吃几GB上下文,推理过程中需要多少再申请多少,而且用完不还给系统,留在自己的缓存池里。后果就是:从nvidia-smi看显存一直满着,但实际空闲一大块。这个特性在下文排查OOM时会进一步展开。
2.2 算力:别让GPU利用率骗了你
nvidia-smi里的“GPU-Util”百分比,很多人把它等同于“GPU忙不忙”,其实它只代表采样时刻有CUDA核在执行计算的比例,不是“吞吐打满”的意思。
推理场景经常出现这样的尴尬:显存快满了,GPU利用率却只有20%。原因是推理请求是离散到达的,每个请求的算子尺寸又小,GPU大部分时间在等数据从显存搬到计算单元、等CPU喂数据,真正算起来只是一瞬。要让算力维度被有效利用,光靠“把模型塞进GPU”没用,得靠后面的两个东西:高并发请求 + 框架端的batching优化。调度时如果只盯着显存分配、不考虑模型本身的并发吞吐能力,很容易出现资源“被占住但没用透”的浪费。
2.3 并发与隔离:真正体现调度水平的地方
一台GPU上同时跑多个进程/容器,需要解决“彼此隔离、互不干扰”的问题。有三个级别的隔离技术,按粒度从粗到细分别是:
| 隔离方式 | 基本思路 | 适用场景 | 主要限制 |
|---|---|---|---|
| 进程/容器级 | 每个进程分配独立显存,通过CUDA_VISIBLE_DEVICES或K8s设备插件切分 |
绝大多数通用服务 | 只能整卡或按显存边界切割,共享同一张卡时存在算力争抢 |
| MPS(多进程服务) | NVIDIA官方支持,把多个进程的CUDA操作合并到同一上下文执行,提升算力利用率 | 小模型高并发、大量短任务混合 | 配置稍复杂,一个进程崩溃可能影响同卡伙伴 |
| MIG(多实例GPU) | 把物理GPU切分成多个硬件级实例,显存和算力严格隔离 | A100/A30/H100等高端卡 | 消费级卡不支持,切分后单实例显存和算力固定不可弹性调整 |
调度策略的选择,本质上就是在“隔离强度”和“资源利用率”之间取平衡。MIG隔离最强但弹性最差,容器共享弹性最好但互相干扰风险高。我个人的经验是:能用MIG的环境优先用MIG,毕竟算力隔离对推理服务的稳定性太重要了;如果卡不支持MIG,就用容器+显存配额兜底,同时从框架层限制并发数,避免一个模型把算力打满影响邻居。
3. 单机场景的GPU调度实操:从环境变量到显存限制
很多团队的AI服务刚起步时,就是一台GPU服务器上跑几个模型服务。这个阶段没有K8s,也不需要太复杂的编排,但该有的调度意识一样不能少。单机场景做好了,后面上集群只是换个平台执行同样的调度逻辑。
3.1 让每个服务“看到”正确的GPU:CUDA_VISIBLE_DEVICES
这是我见过被用得最乱、又最基本的一个手段。
CUDA_VISIBLE_DEVICES环境变量的作用,是在进程级别做“GPU编号重映射”。比如机器上有4张GPU,编号0到3,你希望模型A只用2号卡、模型B只用3号卡,可以这样启动:
bash复制# 模型A,只暴露2号卡给进程,进程内部看到的device是0
CUDA_VISIBLE_DEVICES=2 python run_model_a.py
# 模型B,只暴露3号卡给进程
CUDA_VISIBLE_DEVICES=3 python run_model_b.py
这个变量的好处是:不修改一行模型代码,进程内部依然是从cuda:0开始取设备,实际落到的物理卡却完全不同。配合systemd、docker-compose或者supervisor,可以实现“代码零侵入”的资源分配。
但要注意几个坑:
- 这个变量只控制进程能看到哪些卡,不控制显存上限。A服务被限制只看2号卡,但它如果跑了一个大模型,依然可以把2号卡32GB显存全吃光。
- 容器场景下,
NVIDIA_VISIBLE_DEVICES是另一个环境变量,作用类似但别混用。Docker启动时用--gpus '"device=2"',其实等价于在容器里设置NVIDIA_VISIBLE_DEVICES=2。 - 物理卡编号在驱动升级、插拔卡之后可能发生变化,生产环境建议通过
nvidia-smi -L先确认卡的UUID,再用UUID来锁定卡,避免重启后调度错乱。
3.2 显存配额限制:防止“一颗老鼠屎坏了一锅汤”
单机多个推理服务共享一张卡时,不限制显存上限,高峰期某个模型就会把显存占满,导致同卡其他服务OOM。NVIDIA容器运行时提供了一个很实用的环境变量:GPU_MEMORY_LIMIT,或者你可以在Docker启动时通过环境变量限制:
bash复制docker run --gpus '"device=0"' \
-e NVIDIA_VISIBLE_DEVICES=0 \
-e GPU_MEMORY_LIMIT=12GiB \
your_inference_image
注意,这个限制只对容器内的进程有效,而且本质上是限制CUDA可申请的最大显存量。但如果你是直接跑在宿主机上、没用容器,那可以用ulimit?不行,ulimit管不了显存。直接进程级的显存限制比较麻烦,业界常见的做法是给进程设置CUDA_MODULE_LOADING配合CUDA_LAUNCH_BLOCKING调试,但真正做硬限制,还是容器方案最可靠。
这里我还想强调一下PyTorch的显存缓存机制,因为它直接决定你限不限得住。PyTorch默认会用cudaMalloc申请一整块显存作为缓存池,之后所有张量都在池内分配。所以你在nvidia-smi里看到的显存占用,是框架的缓存池大小,不是实际张量大小。想查看进程内部真实张量占用,有两个API:
python复制import torch
print(torch.cuda.max_memory_allocated(device="cuda:0") / 1024**3, "GB") # 实际张量峰值
print(torch.cuda.max_memory_reserved(device="cuda:0") / 1024**3, "GB") # 框架缓存池峰值
理解这个机制后,你会明白为什么有时报OOM了,nvidia-smi一看还有几个GB空闲——因为那部分空闲是框架缓存池内部碎片,CUDA层面已经不给新分配了。这个线索对排查“假OOM”非常关键。
3.3 让显存真正“动态”起来:虚拟显存和按需释放
单GPU上调度多个模型,显存的“时间复用”也是必须考虑的问题。模型高峰期和低谷期交替时,如果每个模型都固守自己那一大块显存,整卡资源就浪费了。
一个思路是用GPU虚拟显存技术。这个领域目前比较成熟的开源方案是把显存调度拆成两级:物理显存不够时,把不常用的张量临时挪到共享内存或者NVMe盘上,用到时再调回来。PyTorch生态里有torch.cuda.memory的memcheck、以及一些第三方库做类似的事情,但坦白说,目前生产级方案还是偏有限。
更务实的做法是在应用层做“显存按需加载”:
- 服务启动时只加载小模型的权重,大模型用惰性加载,真正收到推理请求时才初始化到GPU。
- 闲置超过一定时间的模型,主动把权重从显存卸载到CPU内存,释放显存给高峰期的其他模型。
- 用vLLM、Triton这样的推理框架时,它们本身就支持“多模型按需加载、显存渐进释放”,这就是框架层面的调度能力了。
我在单机部署时通常的做法是:每个容器显存上限设为整卡显存的60%,预留一部分余量给突发请求和框架缓存碎片,剩下的空闲显存通过共享池的方式让给离线批处理任务使用。GC节奏用cron定时调用,把已经空闲很久的进程的显存缓存手动释放掉。
4. 多机集群场景的调度:从手工指定到平台化管理
规模一大,单机的“手工指定”玩法就撑不住了。你可能需要同时管几十张卡、上百个模型副本,还得随时应对某个GPU故障。这个阶段,调度核心从环境变量切换到了集群编排平台,最常见的底座就是Kubernetes加NVIDIA的设备插件体系。
4.1 Kubernetes + Device Plugin + GPU Operator:GPU资源纳入统一调度
Kubernetes本身不认识GPU。要让它能感知GPU资源并调度Pod,必须安装一个Device Plugin组件。NVIDIA官方更推荐直接上GPU Operator,它会帮你把驱动、Container Toolkit、Device Plugin、监控组件一起以容器方式装好,省掉很多跟操作系统环境和驱动版本斗智斗勇的时间。
安装完成后,你可以在Pod的资源配置里这样声明GPU需求:
yaml复制resources:
limits:
nvidia.com/gpu: 1
K8s会找到一张“当前空闲”的GPU卡,通过Device Plugin把它绑定给这个Pod,Pod内部的进程看到的就是一张独立的GPU。
这个方案的调度核心是“卡”粒度——即一个Pod绑一张卡,还是多个Pod共享一张卡?默认是前者。显存大于一张卡但小于两张卡的模型,会被K8s拒绝调度,这在实际生产里很浪费。所以现在很多团队开始用MIG + Device Plugin的组合,把A100切成多个1GB~8GB的“虚拟GPU”实例,让K8s按更小的粒度调度。
4.2 显存超卖与时间片:省显存还是抢算力?
K8s原生GPU调度只认显存总量,不管你实际用不用得完。于是有人提出GPU显存超卖:让10个Pod共享一张80GB卡,每个Pod声明20GB,但实际峰值可能只用到5GB。这么做能大幅提高卡上模型密度,代价是一旦某个Pod真的把显存吃满,同卡所有Pod一起OOM。
超卖能不能做,取决于业务模型是否“温顺”:确认过的模型、QPS可控的场景,超卖是可行的;带不确定性的外部请求、高峰期波动的模型,别超卖。
另一种思路是时间片共享。NVIDIA通过MPS把多个进程的计算操作合流,配合MIG的实例切分,多个模型可以轮流使用不同GPU实例而互不阻塞。这个方案对延迟敏感的小模型效果极好,但在算力争抢严重的场景,会导致每个模型的延迟都变得不稳定,需要对业务做压测验证。
4.3 云上GPU租用和自建集群如何选
热词里出现“gpu租用”“gpu服务器”,说明很多人纠结要不要自建。我给出的个人判断是:
- 业务QPS稳定、GPU利用率能长期保持在50%以上:自建或长期租用性价比高。
- 流量波动大、需要弹性扩容:按量付费租用更划算,而且不用承担硬件折旧,坏了换上就行。
- 团队没有专门的运维人员:序列化的容器服务和K8s托管方案优先,别碰自建集群。
我做过一次自建转云租用的迁移,核心模型不变,GPU数量从12张降到7张,总成本反而降了。原因就是云的GPU可以按模型实际吞吐动态调整副本数,而不是按“物理卡数”硬撑。这个决策背后的逻辑,其实就是调度思维:用弹性伸缩代替静态分配。
5. 推理框架层面的调度配合:让GPU忙起来的关键
模型部署时用哪个推理框架,直接决定了GPU能不能被充分调度。这也是很多人忽略的环节——换一个框架,同样的模型、同样的GPU,吞吐能差好几倍。
5.1 动态Batch(Continuous Batching):推理框架的“软调度”
传统深度学习框架在处理推理请求时,一般是一个batch满了一定数量才一起推理,或者一个batch算了就不动。这种静态batching导致GPU算力断断续续,尤其LLM解码阶段,GPU频繁“空转”。
**Continuous Batching(连续批处理)**的做法是:不固定batch,而是把到达的请求动态插入当前正在执行的batch里,一个请求的解码步骤结束,立刻把另一个待处理请求补进来,让GPU始终有活干。
这个优化在vLLM、NVIDIA Triton等新一代推理框架里已经内置。部署LLM时,开头倾向于“用原生PyTorch写个推理脚本”的做法,在并发稍高时会发现GPU利用率只有10%不到;换成vLLM后,同样的卡跑到95%利用率,吞吐直接翻了几倍。这就是框架调度能力的差别。
vLLM里两个关键参数直接控制GPU资源利用:
code复制--max-num-seqs:最大并发序列数,调大可以让GPU更忙,但受显存限制
--max-num-batched-tokens:单个batch允许的最大token数,影响算力饱和度和延迟
建议根据压测逐步调整这两个参数,找到“GPU利用率85%~90%、P99延迟可接受”的平衡点。
5.2 KV Cache预分配与显存的弹性调度
LLM推理的显存占用大头除了权重,还有KV Cache。每多处理一个并发请求,就要多占一块显存来缓存历史token的Key/Value向量。vLLM这类框架会提前申请一整块KV Cache池,比如gpu_memory_utilization=0.85表示让框架最多使用GPU显存的85%,剩下的留给CUDA上下文、运行时和突发。
KV Cache池是不能太小,否则并发上不去内存直接OOM;也不能太大,否则剩下的显存余量不够模型权重和算子临时缓冲。实践经验是:7B模型在24GB卡上,gpu_memory_utilization设0.85~0.9,max-model-len根据业务上下文长度精打细算,KV Cache和权重刚好卡住。
5.3 通过推理引擎做多模型“同卡调度”
前面说的都是单模型调度。如果一张卡要共存多个模型,可以用NVIDIA Triton Inference Server自带的Per-Model GPU资源限制。Triton里可以给每个模型指定显存上限和实例数:
code复制model_1: {
backend: "tensorrt",
max_batch_size: 32,
instance_group: [{ kind: KIND_GPU, gpus: [0], count: 1 }]
}
这个配置意思是:模型只在GPU 0上跑,且只有1个实例。配合NVIDIA的GPU MPS或者MIG,一套服务可以同时跑多个模型,每个模型“独占”虚拟GPU实例,互不影响。Triton还支持按模型配置动态batch、并发度上限,这比自己在业务代码里写并发控制强得多。
从经验来看,单卡上并存模型的最好方案组合是:MIG/容器做资源隔离 + Triton/vLLM做推理优化 + K8s做副本调度,三层各管一件事,清晰不混乱。
6. 生产环境里的GPU调度事故:三个典型问题的完整排查链路
调度这种事,理论说一百遍不如碰上一次故障。我把自己遇到过的三次影响最大的GPU事故整理一下,每条都是完整排查链路,你之后遇到类似问题可以照着这个思路走。
6.1 “gpu crash dump triggered”到底在说什么
这个是热词里很多人搜的问题,也是最吓人的一种:进程还在跑,但GPU突然“掉线”,dmesg里刷出一行gpu crash dump triggered。
我遇到过的情况是这样:某天凌晨,集群里一个跑着CUDA进程的节点突然在监控大盘上消失,所有推理请求超时。SSH上去,nvidia-smi返回错误,dmesg里大量NVML_ERROR_DRIVER_NOT_LOADED和gpu crash dump triggered。
排查顺序:
- 先判断是单卡事件还是整机事件:
nvidia-smi -L看系统是否还能枚举到卡。如果卡消失了,大概率是驱动或硬件层面的问题。 - 看掉卡前的痕迹:
dmesg -T | tail -100,重点找ECC错误、温度警告、总线链路错误。
那次现场找到的关键日志是:掉卡前几次Xid 79(显存ECC错误),随后触发了GPU的自我保护机制,把整个GPU设备从驱动层摘掉了。这个过程中,正在用这张卡的进程全部遭殃。
- 排查ECC错误来源:
nvidia-smi -q -d ECC查看历史ECC错误计数。
ECC错大多是显存老化或者温度过高。那次就是因为机房空调故障导致GPU长期在85°C以上运行,最终产生了不可纠正的ECC错误。当ECC错误超过阈值,驱动主动把卡“下线”了。
这个问题的防范思路是:在调度系统里增加GPU健康检查。我们当时写了一个巡检脚本,每小时查一次nvidia-smi -q -d ECC的Aggregate计数变化,只要发现ECC持续增长,就先把该卡标记为“不可调度”,再人工介入。这比等GPU crash dump触发后再恢复,影响面小得多。
6.2 显存泄漏:卡看着满,服务却一个接一个OOM
这是个比较隐蔽的坑。现象是:推理服务运行几天后,nvidia-smi显存慢慢涨,直到接近100%,然后新请求直接OOM,服务崩溃重启,一切归零重来。
排查链路:
- 用
nvidia-smi -l 1实时观察显存变化,确认是“缓慢上涨”而不是“瞬时占满”。缓慢上涨大概率是泄漏,不是突发流量。 - 在代码里配合打点,看进程内缓存和张量数。重点怀疑对象是:
torch.no_grad()没写导致中间变量被保存、数据加载时没有cleanup、或者在循环里不断创建torch.cuda.FloatTensor且没有及时释放。 - 用
torch.cuda.memory_allocated()和torch.cuda.memory_reserved()对比,判断是框架缓存池还是真实张量泄漏。
那次最终定位到的问题很丢人:数据预处理时某个第三方库在GPU上创建了特征向量,返回结果后没有del,循环里每次迭代都留下不可达张量。PyTorch的引用计数机制能自动释放,但这个库自己开了CUDA内存池,绕过了自动释放机制,导致每次都涨。修复方式就是在循环结束后强制torch.cuda.empty_cache(),同时用上下文管理器隔离GPU操作范围。
这个阶段的调度经验是:显存监控不能只看nvidia-smi总量,必须对齐到进程级别,并记录历史变化曲线。一台机器上多个服务互相抢显存时,没有历史曲线,你根本分不清到底是谁在泄漏。
6.3 Windows下Ollama不调用GPU:一个环境变量引发的调度问题
Ollama是目前在Windows上跑本地模型最主流的工具,但它在Windows下有“GPU带不动”的问题。热词里“windows ollama 未使用gpu”被大量搜索,说明中招的人不少。
现象是:ollama run能跑,但看任务管理器,GPU计算那一栏使用率为0%,CPU和内存反而被拉满了。说明模型压根没有再GPU上计算,而是在CPU上跑,速度慢得离谱。
排查链路:
- 确认Ollama是否有GPU支持:在系统环境变量里加
OLLAMA_DEBUG=1,然后在命令行跑Ollama,看日志里是否包含gpu相关字样。 - 确认显卡是否满足要求:Ollama在Windows上需要NVIDIA显卡且驱动版本450+/CUDA 11.0+支持。AMD和Intel核显虽然可以做部分加速,但支持不稳定。
- 查看是否被环境变量禁用了GPU:Ollama支持
OLLAMA_GPU_OVERHEAD、CUDA_VISIBLE_DEVICES这些变量,如果不小心设置成了空或者设备编号不对,就会降级回CPU。
那次实际原因很出人意料:显卡是GT 730,属于“能运行CUDA但性能太弱”的卡,Ollama检测时因为设备算力太低自动放弃GPU加速。这个不算配置问题,只是硬件不满足要求。换一块稍微主流的卡之后问题立刻消失。
这个案例给调度带来的启发是:“调度”不只是服务器上的K8s和老手操作,任何AI推理服务都有自己的“硬件选型门槛”。部署前一定先确认硬件、驱动和框架三者的兼容矩阵,别让调度策略在错误的硬件基础上打转。
7. 不同阶段团队的GPU调度选型与避坑建议
调度方案没有“绝对最优”,只有“阶段合适”。根据不同团队规模,我给出一个直接的选型参考:
| 团队规模 | 推荐方案 | 核心逻辑 |
|---|---|---|
| 个人开发者/轻量试用 | 单机 + CUDA_VISIBLE_DEVICES + 环境变量限显存 | 最大化利用本地/单台租用服务器的资源 |
| 中小团队/单项目上线 | Docker容器 + Triton/vLLM + 简单GPU监控 | 通过框架本身的调度能力提升稳定性,避免手写烦杂调度 |
| 规模化多模型平台 | K8s + GPU Operator + MIG/MPS + Prometheus监控告警 | 资源全纳管,通过K8s声明式方式管理GPU分配和自动扩缩容 |
个人开发者阶段,别为了“专业”就上K8s,太重了。租一台云上GPU,装好驱动、部署vLLM或Triton,再用环境变量把显存控制好,已经很稳了。
中小团队阶段,重点把精力放在推理框架选型上。用vLLM之后,你可以在不增加GPU数量的情况下获得更高的吞吐,调度压力自然减小。这个阶段也不要强行上MIG,除非你的卡是A100/H100且模型确实需要硬隔离。
到了规模化阶段,GPU调度开始变成“显存管理、算力隔离、弹性伸缩”三件事。这时候需要一套统一平台来管理,K8s+GPU Operator是目前最成熟的路线。上平台前先建好监控大盘:每张卡的利用率、显存占用、温度、ECC错误、每个模型的QPS和P99延迟,这些数据是后续调度策略迭代的基础。没有监控直接谈调度策略,就像闭着眼开车。
关于具体的避坑建议,我再补充几条过来人的经验:
- 不要在业务代码里直接指定
cuda:0写死设备号。换成从环境变量读取设备编号,方便后续接入容器和K8s时不用改代码。 - 给每个推理服务加上“并发上限”配置,无论从框架层还是业务层,防止流量洪峰把整卡显存打穿。
- 显存配额宁可少给,不要不给。不限制显存的容器,在某次模型加载异常时可能一次性吃掉整卡显存,全盘崩溃。
- 定期做GPU健康和ECC检查,尤其自建机房,GPU掉卡率比你想的高得多。
根据我个人经验,AI模型推理的GPU资源调度,本质上是把“跑通模型”提升到“稳定高效地运行模型”的那道分水岭。训练阶段你面对的是一堆不挑剔资源的算法工程师,推理阶段你会遇到割裂的负载、酷爱爆显存的模型、说不清道不明的驱动问题。把这些理顺了,整个AI服务的可靠性和成本会同时提升一个台阶。上面这些方法不一定每个都适合你,但至少能给你提供一个重新审视自己GPU使用方式的视角。
