ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

GPU利用率低怎么办?从单机到集群的算力组织与调度实践

GPU利用率低怎么办?从单机到集群的算力组织与调度实践 一面是团队申请 GPU 排队等到怀疑人生一面是机房后台显示一堆卡的利用率不到 20%甚至一到夜间就长期空转。这个画面不是个例而是当前 AI 算力领域最常见的“供需错配”有人一卡难求有人把算力闲置到不好意思看监控。别急着得出结论说“显卡真的不够”。稍微拆开看会发现很多问题不是芯片数量不够而是算力没有被重新“组织”起来有卡的人不一定有任务有任务的人不一定能碰到卡一张大显存卡被某个小任务独占后其余几十个任务只能在外面排队为了稳定跑一批突发任务堆了很多机器任务结束后资源又没人回收。本文不写某个模型的一键安装教程而是把 GPU 当成一块需要调度和治理的公共资源来聊。重点会落在四个工程动作上查清 GPU 利用率、单机多卡分配、容器化集群调度、推理与批量任务的服务化组织。全文面向算法工程师、后端开发、运维和技术决策者目标是提供一套能直接落地试用的排查与组织方案。先建立一个判断框架一张卡利用率不高问题大概率在单机层多张卡负载不均问题可能在调度层多团队各自独占导致空闲问题已经上升到资源治理层。你先定位算力丢在哪一层再谈要不要买新卡这个顺序比盲目“抢卡”重要得多。1. 现象拆解算力是怎么被“用丢”的1.1 一卡难求与算力闲置并存的常见原因AI 项目的算力消耗有三个特点峰值高、波动大、任务类型极度不均匀。训练阶段需要大显存、高带宽推理阶段则对延迟和并发更敏感白天线上请求密集晚上和周末又明显回落。如果所有环节都按最高峰采购 GPU资源闲置几乎是必然结果。另一个问题是资源权限粗放。传统做法里一个团队申请到一台 GPU 服务器后通常把整台设备“圈”在自己手里。哪怕跑的任务只需要 8GB 显存只要这台机器上有一张 80GB 的大卡别人也无法使用剩余资源。结果就是组织层面看起来卡很多实际每一张卡都被低效地占着。还需要注意任务本身的结构差异。大模型训练往往需要多卡并行模型切分、通信同步都需要设计而小模型推理、数据处理、传统 CV 任务可能只需要小块算力。把不同大小的任务一股脑塞进同一种资源池很容易出现“大任务进不去、小任务占着大卡”的尴尬。1.2 三种常见的算力损失从工程侧归纳算力损失通常出现在三个环节等待损耗提交任务后排队的时长超过任务本身运行时长。碎片损耗显存或 GPU 忙碌时间无法被其他任务填补。空闲损耗资源已分配但因为业务波峰波谷或缺乏调度而长期低载。这三种损耗不会直接出现在财务报表里却会实打实拉高单位算力的真实成本。很多团队说算力不够其实应该先问一句现有 GPU 的平均利用率到底是多少2. 算力组织方案速览在动手之前先用一张表看清楚算力组织不是一个单一动作而是从单机到平台逐层递进的过程。组织层级核心对象典型手段解决什么问题落地门槛单机层GPU、显存、进程CUDA_VISIBLE_DEVICES、任务分组、进程隔离一张卡和多任务之间的分配低集群层多节点 GPU 资源容器化、Kubernetes、GPU 调度器多团队共享、排队与弹性扩容中高服务层推理任务、在线请求常驻推理服务、接口化、动态批量稀疏请求导致的显卡空转中平台层算力供给与需求资源池、配额、配额队列、算力运营跨团队甚至跨组织供需匹配高这四层不是互相替代而是层层递进的关系。单机层解决不了多团队排队的问题集群层解决不了接口稀疏调用的问题平台层则需要前几层的数据支撑。下面按实际落地的顺序展开。3. 第一步查清 GPU 到底有没有被用起来3.1 nvidia-smi 基础自查命令很多团队所谓的“GPU 不够用”其实是从头到尾没统计过现网 GPU 的真实利用率。最基础的自查工具是 NVIDIA 驱动自带的nvidia-smi。# 查看当前所有 GPU 的状态 nvidia-smi# 每 1 秒刷新一次适合观察训练或推理过程中的波动 watch -n 1 nvidia-smi# 只提取关键字段方便记录到日志或监控系统 nvidia-smi --query-gpuindex,name,memory.used,memory.total,utilization.gpu --formatcsv执行后重点看两个指标Memory-Usage表示显存占用GPU-Util表示计算单元利用率。注意这两个指标不是同一个东西。显存被占满不代表 GPU 在 Full Speed 计算GPU-Util 很高也不代表显存一定吃紧。如果你想定位当前到底哪些任务正在使用 GPU可以执行nvidia-smi --query-compute-appspid,used_memory,process_name --formatcsv这条命令会返回进程 PID、显存占用和进程名。如果在机器上看到多个进程在跑但 GPU-Util 长期偏低就需要继续往下查。3.2 除了看监控还要看任务延迟与吞吐GPU 利用率只是一个中间指标最终要落到业务的吞吐和延迟上。例如一个推理服务单张卡一次只能处理一个请求GPU 可能在大部分时间处于空闲等待状态此时即使显存被模型占满计算利用率也上不去。建议每次资源自查都同步记录五个维度指标观察点判断参考显存占用模型权重 激活值 临时缓存接近上限时容易 OOMSM 利用率nvidia-smi 的 GPU-Util长期低于 30% 需要排查瓶颈任务耗时单次推理/单步训练耗时波动大说明外部依赖不稳定温度与功耗是否处于高负载区间功耗低但任务在跑可能卡在数据等待IO 等待磁盘读写、网络传输数据加载慢会拖累 GPU 吞吐这一套数据建议至少记录 7 天。只有拿到“波峰、波谷、平均利用率”之后才能知道该不该买新卡或者能不能用调度手段解决。4. 单机层组织把任务分配到“对的 GPU 和显存”4.1 用 CUDA_VISIBLE_DEVICES 控制设备可见性单机多卡场景下最简单也最实用的设备控制方式是环境变量CUDA_VISIBLE_DEVICES。它决定了当前进程能“看到”哪几张 GPU常用于同时跑多个互不干扰的任务。# 只使用第 0 张卡 CUDA_VISIBLE_DEVICES0 python train.py # 只使用第 1 张卡 CUDA_VISIBLE_DEVICES1 python infer.py # 同时使用第 0、1 张卡通常用于多卡训练 CUDA_VISIBLE_DEVICES0,1 torchrun --nproc_per_node2 train.py这里的train.py、infer.py和启动参数都是示例实际需要按项目脚本调整。CUDA_VISIBLE_DEVICES适合人工分配设备但如果任务数量多、团队规模大只靠这个方式容易失控。4.2 一个小规模多卡并行示例在一台 2 卡机器上可以用脚本把不同输入分配到不同 GPU避免“所有任务都挤在 0 号卡”。#!/usr/bin/env bash # 通用示例把两个任务分别放到 0 号和 1 号 GPU CUDA_VISIBLE_DEVICES0 python run_task.py --input input/a CUDA_VISIBLE_DEVICES1 python run_task.py --input input/b wait echo all tasks finished这个示例适合小规模测试不适合直接用于生产。真实批量任务还需要考虑显存余量、进程失败和并发数控制不能简单地把任务全部丢到后台。4.3 不同显存任务建议分桶单机层的“组织”不是只会用环境变量还要会按任务大小分桶。大显存任务和高显存需求任务放到大卡上小显存任务放到小卡上或复用空闲时间片。以下是一个常见问题多张卡显存不一样但调度器只按“有没有 GPU”分配导致大任务被调度到小显存的机器上运行几分钟就 OOM。分桶的落地方法是给每张 GPU 卡加一个“角色标签”。例如用脚本读取nvidia-smi --query-gpuindex,memory.total然后维护一份卡与任务的映射关系。更规范的做法是进入集群调度阶段让调度器根据标签分配资源。5. 集群层组织容器化与 Kubernetes 调度5.1 为什么要先走容器化单机脚本分配 GPU 的优点是简单缺点是“不可复制、不可观测、容易冲突”。当团队里多个人同时提交任务时手工分配一定会出错。把任务打包成容器后至少带来三个好处环境一致CUDA、Python、依赖库固定不再出现“我本机能跑、服务器不能跑”。资源声明清晰一个任务需要几张 GPU、多少显存、多少内存在配置里写清楚。调度标准化容器只是启动单元调度权交给 Kubernetes 等编排平台。5.2 在 Kubernetes 中声明 GPU 资源Kubernetes 本身不直接管理 NVIDIA GPU需要先部署对应的 GPU device plugin这一步通常由集群管理员完成。部署完成后Pod 或 Job 可以通过nvidia.com/gpu声明需要几张 GPU。# 通用示例声明 1 张 GPU 的推理 Job # 镜像名需要替换为团队实际使用的推理镜像 apiVersion: batch/v1 kind: Job metadata: name: gpu-infer-demo spec: backoffLimit: 3 template: spec: restartPolicy: Never containers: - name: infer image: your-registry.example.com/ml-image:v1 command: [python, run_predict.py] resources: limits: nvidia.com/gpu: 1这段 YAML 说明调度器会把 Pod 放到带 GPU 的节点上并在容器内只暴露 1 张 GPU 给任务使用。实际集群里可能还需要配合nodeSelector选择特定机型或者配合队列插件实现排队和优先级。直接复制到生产环境前请确认三件事集群是否已经部署 NVIDIA 的 GPU device plugin。nvidia.com/gpu资源名称是否与部署版本一致。镜像内是否自带目标卡可用的 CUDA 驱动运行环境。5.3 调度成功不是结束还要看进程真在卡上跑提交 Job 后先用下面的命令确认调度状态# 查看 Job 对应的 Pod 运行状态和所在节点 kubectl get pods -o wide | grep gpu-infer-demo# 查看容器日志 kubectl logs job/gpu-infer-demo如果日志里报 CUDA error或者容器内执行nvidia-smi看不到 GPU优先检查 device plugin 状态和容器资源声明。集群调度成功只代表 Pod 被安排到了 GPU 节点不代表进程真的能访问到卡。6. 服务化组织让算力变成可调用的接口6.1 训练任务和推理任务不要无脑混跑训练任务通常追求高吞吐、高占用可以容忍偶发的任务暂停推理任务则要求低延迟、高稳定。把两者直接混在同一个 GPU 上容易出现训练任务批量跑满导致推理响应变慢。一个务实的做法是错峰白天重点保障在线推理夜间把训练或离线批量任务提上来占满资源。如果资源确实有限需要混跑就必须给两类任务设置强弱隔离限制训练任务的最大显存与并发并给推理服务预留足够的计算额度。没有隔离机制时不建议把关键在线推理和资源饥渴型训练任务放在同一张卡。6.2 推理服务化能解决“稀疏调用浪费”很多 GPU 闲置来自“行为密集型但调用稀疏”的任务模型已经加载到显存但每个请求之间间隔很久GPU 大部分时间在空转。这时最有效的组织方式是服务化把模型常驻在进程中对外提供 HTTP/gRPC 接口让多个调用方共享同一个模型副本。服务化之后还能做请求合并。框架层面可以支持动态批处理把短时间内到达的多条请求收集成一批模型一次前向推理处理多条数据。这样一来即使单个请求很稀疏服务也能在单位时间内处理更多请求GPU 吞吐明显提高。6.3 给“空闲算力”生成一个最小 API下面用一个最小示例展示服务化的结构。实际使用时需要把infer函数内部替换成你自己的模型加载与推理逻辑。# 通用示例把本地推理能力包装成 HTTP 接口 # 生产环境请使用正式 WSGI/ASGI 服务并增加鉴权与限流 from flask import Flask, request, jsonify app Flask(__name__) def infer(text: str): # 这里替换为真实的模型推理逻辑 # 例如result model.generate([text]) return {received: text[:50], length: len(text)} app.post(/api/infer) def api_infer(): data request.get_json(forceTrue) text data.get(text, ) if not text: return jsonify({error: text is required}), 400 return jsonify(infer(text)) if __name__ __main__: # 仅用于本地测试生产环境不要直接暴露 0.0.0.0 的 Flask 开发服务 app.run(host0.0.0.0, port8000)测试接口curl -X POST http://127.0.0.1:8000/api/infer \ -H Content-Type: application/json \ -d {text: hello gpu}启动后可以看到正常情况下会返回一段 JSON。这个例子最重要的是“模型常驻、接口对外”而不是每次请求都要重新加载模型权重。实际操作时还要考虑并发上限、排队策略、超时、日志和鉴权。6.4 调用方怎么判断服务是否正常判断服务是否正常不能只看“接口有返回”。还需要看单次请求平均耗时和 P95/P99 延迟。服务所在 GPU 的显存和 SM 利用率。服务副本数量与请求量是否匹配。请求失败率和超时率。当接口被多团队共用时最好保留一份“服务清单”模型版本、请求参数格式、输出格式、负责人、资源限制。这份清单能让算力开放给更多人时不至于失控。7. 批量任务组织控制并发、队列和重试7.1 不要一上来就把整池资源跑满很多离线任务是批量的比如批量图片处理、批量文本生成、批量 OCR 识别。这类任务最容易踩的坑是为了追求速度一次性拉起几十个并发进程结果显存瞬间被打满任务批量失败。批量任务的第一原则是“先小规模验证再逐步扩容”。用一个小样本集验证流程能跑通后再慢慢增加并发数并在每个并发档位观察显存占用和运行时长。7.2 限制并发数的批量调度示例下面是一个 Python 示例用于把多个任务按固定并发数提交并把不同任务分配到不同 GPU。这里用了ThreadPoolExecutor控制并发实际生产任务建议改成有队列、有日志、有重试机制的调度系统。# 通用示例批量任务按 GPU 编号分发并用并发池限制同时运行的任务数 import os import subprocess from concurrent.futures import ThreadPoolExecutor # (输入文件路径, 目标GPU编号) TASKS [ (input/a.jpg, 0), (input/b.jpg, 1), (input/c.jpg, 0), ] def run_one(task): input_path, gpu_id task env os.environ.copy() env[CUDA_VISIBLE_DEVICES] str(gpu_id) result subprocess.run( [python, process.py, --input, input_path], envenv, checkFalse, ) return input_path, result.returncode if __name__ __main__: with ThreadPoolExecutor(max_workers2) as pool: results list(pool.map(run_one, TASKS)) for path, code in results: print(path, exit, code)这里把并发数限制为 2实际需要根据单张卡的显存和单任务显存占用调整。如果并发进程把显存撑爆请优先减小并发数而不是增加更多任务。7.3 失败重试与日志批量任务最容易出问题的不是流程而是“某个样本跑到一半崩溃后面的任务全被带停”。所以批量任务必须有错误隔离和失败重试。# 通用示例同一个任务最多重试 3 次 for i in 1 2 3; do python run_task.py --input input_file --gpu 0 break echo task failed, retry $i sleep 5 done生产环境建议把日志单独落盘每条任务记录 id、输入、开始时间、结束时间、退出码。出现失败时可以快速定位是哪一条任务导致而不是在几百条日志里翻找。8. 中间层与算力平台谁在真正重新“组织”算力8.1 现在的“组织者”大致有四类把算力组织起来不能只靠单机脚本和 Kubernetes还需要中间层平台。从市场现状看有四类角色在承担“组织者”职责一是云厂商的 GPU 实例与容器服务。优点是交付快、弹性强缺点是长期使用成本高数据要离开本地环境。二是企业内部统一调度平台。通常基于 Kubernetes 或专业调度器构建统一纳管训练、推理、批处理任务提供配额、排队、优先级和计费能力。三是算力资源池化与虚拟化服务。通过显存/GPU 切片技术把一张物理卡切成多份分配给不同任务或租户解决“大卡跑小任务”的浪费。四是算力租赁与共享平台。把分散的供应方和需求方连接起来提高社会层面的使用率但这类平台对安全、信任、网络带宽和合规要求极高。8.2 多租户、配额与队列是资源治理的关键如果团队超过 10 个人或者有多个项目组共用算力单纯“分卡”已经不够。需要引入资源治理机制每个团队设置 GPU 配额上限。任务可以排队而不是抢到就算。离线任务优先级低于在线推理任务。管理员能查看每个团队的资源使用率。有账单或成本核算避免某些团队长期占用大卡跑小任务。配额和队列看似增加管理成本实际上是提高 GPU 利用率最有效的手段。它把“谁抢到算力”变成“谁的任务更合理”让资源真正流向高优先级任务。8.3 安全与合规边界在讨论算力共享和闲置利用时必须把安全和合规放在前面。第一不要私自把未经授权的服务器或 GPU 接入外部共享平台。算力的所有权属、运维责任和管理边界必须提前确认个人擅自使用单位资源或第三方资源可能带来法律和商业风险。第二不要把企业敏感数据传送到未经安全评估的外部算力环境。训练数据、业务日志、用户隐私数据一旦进入不可控的第三方资源池泄密风险会显著上升。第三涉及人脸、声音、版权素材、个人信息等内容的训练或推理任务必须确认是否有合法授权。技术上的“可以做”不等于“可以公开发布”。第四不要使用刻意宣称“无限制”“无审核”的算力和 AI 工具。这类服务极有可能绕过了应有的内容安全、平台规则和知识产权保护使用风险比节省的那点算力成本更高。合规红线绝不是一句口号而是所有算力组织方案的前置条件。9. 常见问题与排查方法问题现象可能原因排查方式解决方案GPU 利用率很低但显存被占完推理请求稀疏或训练 batch 太小用 nvidia-smi 查看进程和显存占用推理请求合并、提高 batch、减少不必要常驻进程多卡任务跑起来只有一张卡有负载代码写死了 cuda:0未做分布式初始化查看训练日志和进程环境去掉设备硬编码按框架初始化分布式组容器里看不到 GPUdevice plugin 未部署或资源声明缺失进入容器执行 nvidia-smi安装 GPU device plugin在资源限制中声明 GPU任务启动后报 CUDA out of memory多任务共享同一张卡显存超额查看当前卡已用显存和进程 PID减少并发、换更大显存卡、使用显存隔离/切片推理 API 偶发超时单副本吞吐不足请求排队过长压测观察延迟 P95/P99增加副本、实现动态批处理、加负载均衡GPU 高负载但任务推进很慢CPU 预处理或数据加载成为瓶颈看 CPU 占用率、磁盘 IO、数据队列开启多进程数据加载、预取数据、降低 CPU 重复计算批量任务中途卡死单个异常样本导致进程挂起无超时控制看日志定位最后一条任务单任务加超时失败自动跳过并记录日志显存高占用但 GPU 平均利用率低服务常驻模型但没有足够请求对比请求量和 GPU 利用率曲线将多个模型合并到一个服务或启用动态批次排查这类问题时一定要先看数据再看结论。只凭一次nvidia-smi的截图判断“GPU 够不够”很容易误判最好至少观察一周的利用率趋势。10. 从抢卡到组织算力可以先做的三件事这件事可以不用先从大平台、大集群开始普通团队可以先做三个低成本的尝试。第一给现网 GPU 做一次资源普查。连续一周记录每张卡的显存占用、SM 利用率、任务进程数和空闲时段。这份数据能直接告诉你算力到底丢在了哪个时段、哪个环节。第二把单一任务的启动方式容器化。很多团队最缺的不是编排平台而是“环境复制能力”。把模型任务打包成容器后后续无论是上 Kubernetes 还是交给其他调度系统都会顺畅很多。第三挑一个可以共享的推理模型做接口化试点。把“某个人独占某张卡跑脚本”改成“一个常驻服务 几个团队共用 API”看请求量上来后 GPU 利用率能不能提升。算力问题的下半场重点不是继续比谁的卡多、谁的型号新而是单位 GPU 能产出多少有效结果。谁能把显存碎片利用起来、把任务队列排得更合理、把平台与接口层做得更安全可靠谁就能用更少的卡做更多的事。如果你也在做算力调度或资源治理选型可以把你的场景和踩过的坑写在评论区这类问题的经验往往比硬件选型更容易被低估。
返回列表