资讯详情

资讯详情

从GPU监控到模型管理:自建轻量AI训练平台实战指南

1. 从微信群喊卡到自建训练管理平台我们为什么做Panelai好几周前我们实验室的GPU资源管理还是这个画风某位同学要跑实验先在群里喊一声谁有空卡然后挨个登服务器看显存占用确认没人用再手动把任务拉起来。等模型跑完想复现参数还要翻聊天记录找当时的启动命令。到了月底想看每张卡的利用率给采购做依据只能凭记忆拍脑袋。后来我们做了一个内部训练管理平台代号就叫Panelai本意是AI训练的操控面板。这次更新的核心是三块GPU监控、子服务器集群、项目/模型管理。前两个解决卡去哪儿了任务跑在哪台机器上的问题第三个解决这个模型是怎么训出来的、训练产物搁哪了的问题。再加上年前开源版进入冲刺我打算把这段时间的工程思路、踩坑过程、以及最后几周要收紧的细节一次性讲透。这套东西不是要给大厂那种几千卡集群做管理的它的目标很朴素让一个二三十人的AI团队不需要专门运维也能把散落在几台服务器上的GPU用明白。如果你是那种手上管着几台带卡服务器、又不想靠嘴和Excel维护资源的人这篇文章应该能给你不少可以直接抄走的方案。2. GPU监控模块别被显存占用率骗了监控要看的是综合健康度2.1 指标体系的取舍不只是看显存和利用率刚开始做GPU监控的时候我以为就是调NVIDIA的NVML接口把显存占用、GPU利用率、温度、功耗拽出来画个曲线就完事了。真正接入之后才发现这里面的坑比预想的多得多。最大的误区是拿显存占用当作卡被用着的依据。显存占用高不代表GPU在做计算比如有人把数据预加载进显存、或者开了几个常驻进程把显存占着不释放。同样的道理GPU利用率也不能单看那一栏百分比CUDA Core的计算利用率和显存带宽利用率经常是两回事。有些算子瓶颈在显存带宽利用率90%GPU Core反而闲着有些小batch的推理任务GPU Core跑得很欢但整体利用率也就是20%。所以我们最后定下来的监控指标分了三层第一层是资源状态显存占用、显存总量、GPU利用率、功耗、温度、风扇转速、NVLink带宽多卡机器必看。第二层是任务状态当前这台机器上跑了哪些训练进程、进程占用的显存和算力、启动时间、所属项目。第三层是健康趋势过去1小时、24小时的负载曲线异常事件日志比如掉卡、温度告警、训练进程意外退出。第三层是最容易被漏掉的。很多监控工具都能展示实时曲线但这个卡昨天晚上发生了什么为什么凌晨三点训练断掉了这种追溯需求才是排障最常用的。我们为每一台服务器维护了独立的本地历史数据库按天滚动保留90天查询接口支持按时间范围拉取把这些数据做成对比视图方便同时看几张卡在同一时段的负载情况。2.2 数据采集的频率与推送机制频率太高会拖垮训练采集频率的设计也走过弯路。刚开始图省事每2秒采样一次结果发现NVML的调用虽然本身很轻但在多卡机器上高频调用加上每个进程都要汇报会明显挤占CPU时间片尤其是在数据加载和CPU预处理密集的任务里训练速度肉眼可见地掉了几个百分点。后来我们把采样周期调整成常规指标每15秒采一次高频变化的指标显存、利用率每30秒取一个均值上报温度、功耗这种变化慢的指标每60秒一次。实时展示的当前值其实是从最近几轮采样里做滑动窗口算出来的。数据上行用的是轻量的HTTP轮询加一次性推送结合。监控Agent在每台服务器上运行只负责采集和本地缓存主控端定期来拉取。遇到断网或主控端重启Agent会把最近的数据写进磁盘上的临时缓冲文件等网络恢复后再补传。这样不会因为监控链路抖动丢掉关键时段的数据。告警也做了分层而不是一刀切。显存95%持续超过10分钟才触发资源紧张警告温度超过85度直接触发硬件告警GPU利用率30秒内从90%掉到10%说明训练可能挂了会立刻发一条通知。通知渠道我们集成了通用的Webhook可以直接接到企业内部群不绑定具体厂商的推送服务。2.3 训练进程与GPU卡的映射让哪个进程吃了哪张卡可追溯只有指标曲线还远远不够。实际排障时问得最多的问题是这张卡到底是谁在用、跑的什么任务所以我们把进程级监控也做了进去。借助NVML的按进程查询能力可以列出每张卡上各进程占用的显存、SM利用率。我们把这些数据和Panelai里的任务表做关联能识别出这个进程是不是通过平台发起的训练。如果是平台任务直接显示项目名、任务ID和启动人如果不是平台发起的就标记为外部进程显示PID和可执行文件路径。曾经出现过有同学绕开平台直接跑实验把显存占满导致线上推理服务被挤下线的情况这个映射功能一下就抓到了真凶。进程级监控还有一个用途就是判断显存泄漏。正常训练过程中显存占用应该稳定或有规律的波动如果看到某个进程的常驻显存持续单调增长那基本可以断定代码里有泄漏。我们把每小时的显存增长斜率算出来超过阈值就在项目页面上标一个疑似内存泄漏的提示。3. 子服务器集群从单机脚本调度到多机任务分发3.1 架构选型为什么选择轻量Agent而不是一步到位上K8s子服务器集群这块团队内部其实讨论过要不要直接上Kubernetes。但评估到一半就放弃了。核心原因是我们的场景是GPU训练任务需要一个能够感知GPU拓扑、支持显存预占和排队调度的调度器而K8s原生的调度逻辑并不擅长处理这类资源。为了在K8s上跑GPU任务还要额外引入设备插件、自定义调度器结果就是给自己制造了一大堆新问题。所以最终采用了轻量Agent的架构每一台物理服务器上部署一个独立的Agent进程负责心跳上报、任务接收、日志回传。主控端只有一个Web服务加一个任务队列数据库。服务器的扩缩容只需要部署Agent然后注册接入不需要改任何主控端配置。这样的好处是任何一个环节挂了都能各自存活。Agent挂了主控端只是暂时接收不到那台机器的状态不会影响其他机器上的任务继续跑主控端挂了Agent上的训练任务照常进行只是没有办法再发起新任务和更新状态。这个各自独立、通过心跳同步的设计让我们省了很多事。3.2 机器注册与环境标记避免任务跑在不兼容的机器上服务器集群最大的麻烦不是通信而是环境差异。同样一张A100有的机器CUDA版本12.1有的还是11.8有的机器上装了新版驱动支持有的卡着旧驱动不敢动。如果把任务随机分发过去经常是代码都传上去了一启动就报找不到CUDA库或者驱动版本不支持。我们的解决办法是给每台服务器打上环境标签。注册机器时Agent会自动上报操作系统版本、GPU型号列表、驱动版本、CUDA运行时版本、Python版本、关键Python包PyTorch、TensorFlow的版本号。用户在提交训练任务时必须声明任务对环境的依赖只填一个标准化的环境配置文件。调度器做匹配时会把环境配置文件里的要求与机器标签做比对不满足直接跳过这台机器不会出现跑起来才发现装错环境的尴尬。另外仓库级的依赖冲突我们也做了隔离。每台机器上维护了一个环境目录树同一个框架版本可以共存多个实例训练任务通过环境变量指向特定目录。这样即便两个项目要求不同的PyTorch版本也能在同一台机器上并存互不干扰。3.3 任务调度与并发控制显存预占和排队机制调度逻辑我们做的比较保守因为训练任务一旦跑起来中途迁移的成本极高。策略是这样的任务提交时调度器先看所有机器的实时显存空闲量选出显存足够且环境标签匹配的候选机器。在多张卡都满足的情况下优先选当前空闲显存总量最大的机器降低碎片化的概率。确定目标机器后调度器会先预占显存也就是在本地状态里先把显存扣掉避免并发提交时出现两个任务都被分到同一台机器上。如果所有机器都不满足任务进入排队并给出预计等待时间。这个预计值是参考当前队列里高优先级任务的预估时长算出来的不一定准但能让大家心里有个底。预占机制用数据库的原子操作实现每一张卡都对应一条记录调度的第一步就是在事务里把目标卡的记录状态改成已预占同时写入任务ID。如果后面Agent上报的显存实际占用和预占值偏差太大比如任务代码里并行加载了很多CPU数据系统会发起一个校准流程把预占值修正成实际值。实际操作下来这个调度器已经能满足我们每天几十个训练任务的并发规模而且排队时间比原先靠肉眼找卡不知道快到哪里去了。3.4 心跳、断连与任务迁移的边界条件心跳机制听起来很简单但超时时间的设定很讲究。设太短Agent稍微卡顿一下主控端就误判为机器离线设太长机器真的挂了半天才发现任务恢复不及时。最后我们设定的方案是Agent每3秒上报一次心跳主控端连续丢失9个心跳27秒判定机器疑似离线会再发起一轮主动探测如果还是不通才标记为离线。标记为离线后该机器上正在运行的任务并不会立刻被调度到别处重跑。因为训练任务的断点续训需要配合检查点机制盲目的自动迁移很可能把已经跑了几小时的进度白白浪费掉。我们的策略是前台的任务列表里把受影响的任务标红提示可能中断如果任务本身开启了周期性检查点保存系统允许操作者选择在另一台机器上从最近检查点恢复如果没有检查点那就只能人工确认后重新发起。断电、拔卡这类硬件故障是躲不掉的能做的只有两点一是每台机器上的Agent把重要日志和最新检查点尽量留在本地磁盘的同时定期同步到文件服务器二是程序侧引入检查点抢占机制训练框架在每次保存检查点时确认一下远端备份的版本是最新的避免本地磁盘损坏后连备份都是旧的。4. 项目/模型管理让每个模型都说得清怎么来的4.1 项目作为一等公民把资源、数据、代码绑在一起GPU监控和集群调度解决的是资源怎么分的问题项目模型管理解决的是东西怎么找的问题。以前我们经常遇到这种情况过了一个月看到一个大模型文件文件夹名字起得还挺清楚但谁都不记得这个模型是用哪份数据、哪份代码、哪个参数训出来的。Panelai的项目管理模块就是为了终结这种混乱。每个项目在平台上是一个独立的命名空间里面包含了代码仓库地址支持私有仓库的只读权限运行环境要求即前面说的环境配置文件数据集路径可以是平台上的某个数据版本也可以是外部存储的路径训练入口脚本和默认超参该项目下的所有任务列表和模型产物新建项目时系统会根据代码仓库的默认分支自动拉一个快照记录commit哈希。训练任务启动时平台会把当前的commit哈希和关键文件指纹写入任务元数据。这样任何时候回看一个任务都能看到这个任务用的代码版本到底是什么再也不用考记忆力。4.2 模型版本与训练产物的归档模型管理这块我们没有用复杂的大模型注册中心方案而是采用文件系统元数据清单的组合。每个训练任务结束后平台会把产出目录包括权重文件、日志、配置副本、评估指标整个归档到统一的存储路径下命名规则是项目名-任务ID-时间戳。同时生成一个JSON格式的元数据文件记录模型名称和版本标签比如baseline_v1、finetune_v2来源任务的ID和commit哈希训练超参的完整JSON数据集版本的引用评估指标结果文件的SHA256校验和这样做的好处是模型文件在哪里一目了然元数据跟着模型走哪怕拷贝到别的地方也能通过清单文件还原出背景信息。版本对比也很简单把两个版本的元数据JSON拿来diff一下就知道超参和数据集的差异在哪。对模型版本我们还做了软链机制。不想每次写死绝对路径所以每个项目下有一个latest软链指向最新的归档目录另有v1、v2这种语义化版本标签可以手动指定便于在推理服务里引用固定版本。4.3 训练任务与模型的双向追溯一个比较实用的场景是线上推理效果突然变差了怀疑是某个版本的模型出了问题。传统做法是翻部署记录、翻代码提交记录折腾半天。在我们的Panelai里推理服务部署时会记录模型版本标签点开就能看到这个模型来自哪个任务、用的是什么数据、谁在什么时候把它设置为线上版本。反过来的追溯也支持从一次训练任务的详情页可以看到它产出的所有模型文件以及哪些环境里正在引用这些模型。另外我们在模型归档时会自动计算每个检查点的训练进度和损失曲线生成的曲线图会嵌入模型详情页。这样即使训练过程中途挂了后来从某个检查点继续训练也能看到从这个检查点接着训之后收敛情况如何判断是不是该从更早的检查点重来。5. 开源版年前的冲刺规划什么该保、什么该砍5.1 技术栈与部署形态的最终选择Panelai开源版的构建我们选了一个对中小团队最友好的组合后端用FastAPI PostgreSQL Redis前端用Vue3加一个开源的仪表盘框架监控数据采集的Agent用Python实现方便二次开发所有组件都打包成Docker镜像通过Docker Compose一键拉起。不少朋友问过为什么不用更高性能的方案。我们的权衡是这个平台的使用场景是几十台服务器的规模瓶颈根本不在后端性能而在易部署性和二次开发的门槛。FastAPI能在一两天里把接口都理清楚PostgreSQL足够存几百万条监控采样配合时间分区Redis用来做队列和WebSocket实时推送任何一个有Python基础的人都能看懂整个系统的代码结构这就够了。Docker Compose的编排分三块核心服务Web、数据库、缓存、Agent可选也可以直接在一台服务器上以裸进程方式跑、以及一个可选的存储服务用来统一归档模型产物。默认的快速安装模式是docker compose up -d一条命令起全部服务后面再用独立的命令把各台机器的Agent拉起来注册进来。5.2 功能优先级核心链路打磨优于功能铺量离发布只剩几周我们对功能的取舍非常明确。年前冲刺必须保证这三条链路跑通、跑稳资源入网新服务器一键注册、Agent自动上报环境信息、GPU状态可见。任务闭环提交任务、调度匹配、在多机间分发、实时看日志和指标、产出归档。模型可溯训练结束后自动生成元数据模型版本可标记查看任务能定位到模型产物。一些虽然好用但容易增加复杂度的功能我们先砍掉了多租户的权限细分保留基础的角色控制就够了、完整的计费统计、自动化的多机断点续训、推荐系统的因素分析等都留在下一个版本再做。安全方面开源默认配置会把所有需要鉴权的接口统一挂到一个网关后面只暴露最小的端口集合。平台本身不提供操作服务器终端的通道远程命令执行能力默认关闭需要的人可以自己加一层跳板。这样能把开源后被人拿去当跳板机的风险降得很低。5.3 文档与示例让大家真的装得上、跑得动只有代码没有文档的开源项目基本等于没开源。我们正在准备三类内容。第一份是面向新手的快速开始指南从准备一台带GPU的服务器到注册一台设备、发起一个训练任务全程截图和命令都验证过两遍第二份是管理员手册讲清楚环境标签怎么设、告警阈值怎么调、模型归档目录怎么规划第三份是API文档方便有人想把它集成到自己的工具链里。示例项目我们准备了一个小的图像分类训练脚本做了一个提交任务按钮点下去能看到一个标准任务的完整生命周期。这个例子不复杂但把环境和产物管理都串起来了起码让大家第一次跑通的时候有东西可以对照。6. 开发过程中踩过的几个真坑写出来帮大家避雷6.1 显存曲线毛刺和采样频率的拉锯战前面提到采样频率实际调试中出现过一个让人费解的Bug监控面板上的显存曲线明明任务稳定运行却每隔几分钟出现一次掉到零的大坑。查了半天发现是Agent在采集时偶尔会因为进程列表请求超时把一次无效读数当成0上报了。后来给采样模块加了一层数据有效性校验显存值变化超过50%时如果下一个采样周期没有延续就判定为异常读数直接丢弃再修正曲线。这个问题解决之后告警误报率直接降了一个量级。类似的坑在温度监控上也出现过。冬天机房温度低某台机器回风温度触发了告警阈值原因是风扇策略处于低转速状态。我们调整了告警阈值的策略把温度绝对阈值增加了一个温度变化率条件短时升温过快才告警单纯低温不打扰。6.2 心跳假死和CPU占用异常Agent的心跳线程早期用的是最简单的一次HTTP请求一个线程的模式连接数一多线程频繁创建销毁CPU占用接近40%这在一台要跑训练的机器上完全不可接受。后来改成线程池加连接池复用心跳请求合并成一个批量接口把监控上报的序列化和网络开销摊在一起CPU占用降到了5%以下。另外心跳本身也有一个反模式不要在一个死循环里发完请求就sleep这样万一请求时间抖动心跳的实际间隔会漂移。更好的做法是记录上一拍时间用下一次应该发送的时间减去当前时间得到等待时间保证平均间隔稳定不会因为某次网络慢导致整条心跳连发。6.3 模型文件损坏的隐患归档前的校验省了一整天的麻烦有一段时间训练任务结束后归档的模型偶尔无法加载损失函数和训练日志对不上查了很久发现是因为归档时文件还在被训练进程的异步写入线程使用复制过去的文件其实是半截的。排查到这一步其实已经晚了正确姿势是在训练脚本里约定先显式保存检查点并落盘再发归档通知。同时归档动作结束后平台会在目标存储上重新计算一次SHA256和训练节点上报的值做比对不一致就直接把这条归档标为损坏提醒重跑归档。这件事给我一个很深的体会平台能帮人规范流程但前提是规范本身得够硬。靠自觉记住保存完之后再归档完全不靠谱一定要靠接口层的约定和校验兜底。6.4 环境标签被忽略导致的任务错配环境匹配规则上线后仍然出现过一次任务被派到错误机器的案例。跟踪日志发现Agent上报的Python版本和用户任务配置文件里的版本号写法不一致一个写3.10.9另一个写3.10简单字符串匹配直接判为不兼容。类似的情况还有ubuntu20.04和20.04这种写法差异。解决方案是把环境标签的匹配逻辑改成语义化版本比较而不是字符串相等匹配。系统和用户都只维护一个语义版本号解析完毕后分别比较主版本、次版本、修订号主次版本相同才认为是兼容的。这个坑不算复杂但如果不统一规则后面会被各种写法的标签反复折磨。7. 写在年前冲刺的小结Panelai这次的三个模块——GPU监控、子服务器集群、项目/模型管理——开发到现在最直接的感受是它们其实解决的是同一件事就是让AI团队的资源使用从靠口头传话变成系统里有据可查。监控不是目的调度不是目的模型管理也不是目的最终目的是让负责训练的同学和负责资源的同学都能把精力放回模型本身。最后分享一个我自己体会最深的小技巧如果你也想搭类似的平台别急着从零开发重度功能先把一台服务器注册进系统、能看到GPU状态、能提交一个训练任务、能回看训练日志这条最小链路跑通再逐步加东西。这个最小闭环看起来简单但它能逼你把环境、调度、存储、监控的接口真正理顺后面的扩展就有了地基。我们也正是把这块地基夯实了才敢在年前集中精力冲刺开源版发布。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →