1. 实验室GPU的真实处境算力蛮荒与资源孤岛我们这个项目叫NebulaGrid起因其实是实验室里一件再普通不过的事组里陆陆续续攒了二十多张不同型号的卡分布在四五台机器上有人用PyTorch跑训练有人用vLLM起推理服务还有人拿ComfyUI做生成实验。表面上硬件不少实际用起来却特别别扭。最典型的场景是这样的A同学在跑一个微调任务把一张卡的显存占满了但GPU利用率只有30%左右B同学想跑实验发现手头那张卡被占了只能排队等。与此同时另一台机器上的两张卡可能已经连续几天没人碰过风扇都不转。你说把任务迁过去迁移环境要半天数据要拷CUDA版本还对不上最后大家宁可排队也不折腾。1.1 我们实验室踩到的三个现实问题第一个问题是发现难。每台机器上到底有没有空闲卡、显存还剩多少、算力是否紧张全靠群里的口头询问和一张手写的Excel表。等你去确认的时候状态早就变了。第二个问题是共享难。一张24GB显存的卡跑一个显存只要8GB的小模型剩下16GB就白瞎了。Grid上的任务调度器通常按整卡分配或者靠人工手动设置CUDA_VISIBLE_DEVICES来切分没有统一机制。时间一长资源的浪费是肉眼可见的。第三个问题是环境隔离难。同一个CUDA版本、同一个PyTorch版本要同时满足训练、推理、生成类任务几乎不可能。每个人都在自己的conda环境里折腾一旦涉及到不同容器或不同驱动版本场面就比较混乱。1.2 单机多卡与多机多卡的本质差异很多人觉得把几台机器用网线连起来装个分布式训练框架就算集群了。这是对计算集群非常普遍的误解。单机多卡的时候GPU之间的数据交换走的是NVLink或PCIe带宽从几十GB/s到几百GB/s不等。多机多卡一上网络交换带宽直接掉到万兆网卡的1.25GB/s甚至千兆的0.125GB/s数量级完全是另一个量级。这意味着做集群调度时不能只考虑哪张卡有空还得考虑通信拓扑。NebulaGrid最开始设计时我就想清楚了一件事我们不是要做一个功能完整的超算调度平台而是要解决实验室场景里把闲置算力用起来、把异构环境管起来、把分布式训练跑起来这三个实际问题。这也是整个项目所有技术决策的出发点。1.3 NebulaGrid要解决的核心矛盾实验室集群和大型数据中心有一个本质区别数据中心追求的是极致的资源利用率而实验室更看重灵活、好用、能折腾。所以NebulaGrid从第一天起就定了几条原则不强制用容器跑所有任务。你有conda环境直接提交python train.py也行。不追求把所有GPU虚拟化成细小切片。显存小于4GB的碎片宁可不用也不硬切因为日常训练任务根本用不上。调度延迟不能高。一个微调任务从提交到开始跑两分钟内算正常超过五分钟基本没人愿意用。用户上手成本要低。只要会写一行nebula-ctl submit就能用不需要理解K8s那一套复杂概念。项目名字叫NebulaGrid是星云网格的意思。GPU分布在多台机器上像星云一样离散但通过网格化的调度让它有统一的对外形态。核心思路可以概括为一句话用控制面把异构算力聚合成虚拟资源池按任务实际需求做动态分配让实验室里的每块GPU都真正流动起来。2. NebulaGrid的整体架构把设备变成资源池NebulaGrid的架构并不复杂核心分三层节点代理层、调度控制层、用户入口层。三层各司其职组成一个逻辑闭环。2.1 控制面与数据面的划分最早做技术选型时我考虑过两条路一是完全自研一套调度系统二是站在K8s生态上做扩展。最后选了后者但做了一层很关键的抽象。原因很简单实验室集群的节点变动频繁今天加一张卡明天挪一台机器如果用自研方案节点注册、状态同步、失败重试这一套全要自己写工作量巨大且容易出bug。K8s天然提供了节点管理、状态同步和声明式API的能力我只需要把精力集中在GPU资源模型上。NebulaGrid的节点代理跑在每台GPU服务器上负责三件事收集GPU的实时状态利用率、显存、温度、功耗、NVML事件、执行调度器下发的任务拉起指令、上报任务运行日志。控制面调度器是整个系统的核心维护了全局资源视图接收用户提交的任务进行资源匹配、调度决策、任务生命周期管理。用户侧则是命令行工具nebula-ctl负责提交任务、查询状态、查看日志。2.2 资源池的核心模型以切片为单位的GPU资源描述NebulaGrid里最核心的数据结构是GPU切片Slice描述。每张物理GPU进入集群后会先做一次画像生成一个JSON描述{ uuid: gpu-7ab3c91d, node: node03, model: NVIDIA GeForce RTX 4090, total_memory_mb: 24564, compute_capability: 8.9, pci_bus_id: 0000:3b:00.0, nvlink: false, status: idle, slices: [ { id: s-0001, memory_mb: 8192, state: free }, { id: s-0002, memory_mb: 8192, state: free }, { id: s-0003, memory_mb: 8192, state: free } ] }这里有一个关键设计切片不是物理划分而是逻辑上的划分。显存是GPU上最硬性的资源约束所以我选择了按显存切算力SM数量通常不硬切因为MPSMulti-Process Service或时间片切分的开销和复杂度都不低在实验室场景下性价比不高。用户提交任务时声明需要的显存量nebula-ctl submit --gpu-mem 8G --gpu-count 2 -- python train.py调度器会在全局资源视图里找满足条件的空闲切片组合然后分配。如果不满足就进入等待队列并在任务信息里注明缺少什么资源用户能直观看到。2.3 为什么选K8s生态而不是自研调度器为了给不同GPU虚拟化方案留出扩展接口NebulaGrid的资源模型基于K8s的Extended Resource机制实现。熟悉K8s的人知道默认不支持将GPU作为可调度资源因为GPU抽象太特殊。NVIDIA官方有device plugin方案但那种方案有一个对实验室场景极不友好的点默认只能整卡分配。NebulaGrid写了一个自定义的device plugin把每张卡注册成带显存属性的Extended Resource。同时在调度器里实现了一个预选和优选策略预选阶段根据显存需求过滤掉不满足条件的节点优选阶段根据卡片的当前负载、温度、PCIe位置打分。权重最高的是温度低且负载轻因为同型号卡在满载时核心频率会降用温度低的卡往往实际训练速度更快。提示节点上跑NebulaGrid的Agent都需要额外开启GPU拓扑感知模式否则两张卡明明在同一个PCIe Switch下可能被当成普通网络邻居处理导致多卡任务出现不必要的数据传输开销。3. 异构GPU纳管从NVIDIA到Intel再到昇腾的接入实践实验室的现实是不可能全部清一色用同品牌同型号GPU。我们集群里就混杂着NVIDIA RTX 4060 Laptop GPU、RTX 4090、Intel核显、甚至还有一块昇腾卡。NebulaGrid如果只支持NVIDIA那就解决不了实际问题。3.1 统一硬件抽象层NVML、zesapi与ACL的封装NVIDIA卡的状态获取走NVMLNVIDIA Management LibraryIntel GPU走Level Zero通过zesapi接口昇腾则通过ACLAscend Computing Language暴露能力。三种接口的侧重点差异很大能力项NVIDIANVMLIntelLevel Zero昇腾ACL显存状态支持支持支持利用率支持支持粒度较粗支持温度/功耗支持部分型号支持支持进程级追踪支持有限有限资源虚拟化vGPU/MPS无统一方案昇腾有自己的方案所以Agent内部实现了一个HardwareBackend接口每种卡写一个Backend。核心抽象是四个方法GetStatus()、GetProcessList()、Initialize()、Cleanup()。写Intel GPU Backend是常见的难点。当时我发现zesapi取利用率时会偶发返回一个极大值比如200%后来排查确认是驱动版本对某些频率档位的解析异常。这类问题只能靠在实际节点上反复压测来踩平文档里基本找不到答案。3.2 驱动兼容性排查4060 Laptop、5070 Laptop与新老CUDA的坑异构纳管最难的不是代码而是驱动兼容。我在部署过程中遇到的一个典型问题是RTX 4060 Laptop GPU在Windows节点上的错误代码43。这块卡在设备管理器里直接就报错不可用Agent探活时始终拿不到实际显存。排查下来原因很典型NVIDIA驱动在Windows上有时会因为TCC模式Tesla Compute Cluster和WDDM模式Windows Display Driver Model切换不彻底导致显卡核心与驱动通信异常。解决方法是进NVIDIA控制面板把该GPU的Compute Mode从默认切换为仅计算然后完全卸载驱动后重装Studio版驱动。这个坑在台式卡上不常见但在笔记本卡的实验室场景里出现频率极高因为笔记本GPU经常要被Display输出占用WDDM模式下显存分配和计算模式差异很大。另外还遇到过RTX 5070 Laptop GPU的CUDA兼容问题——卡的Compute Capability是sm_120但节点上的CUDA Toolkit还是11.x跑任何PyTorch版本都会报not compatible。这种问题跟NebulaGrid本身无关但Agent在调度时会通过nvidia-smi --query-gpucompute_cap拿到能力值把它作为注册信息的一部分。这样用户提交任务时如果指定了CUDA版本调度器能自动过滤掉跑不了的节点。3.3 节点代理Agent的探活与心跳设计Agent的探活逻辑是NebulaGrid稳定性的关键。GPU是很敏感的硬件一个不稳定的探活机制会造成两种恶果要么漏报卡已经挂了还继续分配任务要么误报卡活着但被标记为不可用白白浪费资源。我们最终采用了两级探活机制。第一级是常规心跳每5秒上报一次/healthz如果连续三次没有响应控制面标记该节点为Unknown暂时不分配新任务但不回收已运行的任务。第二级是NVML事件监听然后结合xid错误事件来判断GPU是否真的发生了硬件级故障。XID错误是NVIDIA驱动上报给操作系统的错误码常见的包括XID错误码常见含义处理策略79GPU has fallen off the bus节点隔离人工介入43驱动与设备通信异常尝试复位一次13显存ECC错误记录日志并触发GPU内存自检31程序非法访问显存记录任务日志任务失败但不隔离如果检测到xid 79Agent会主动把GPU标记为MaintenanceNeededNebulaGrid控制面会把已分配到该GPU的任务平滑迁移或重启后重新调度。有一个很坑的细节某些情况下xid 79并不代表硬件故障而是PCIe链路瞬时抖动。直接隔离会误伤我们加了短期内连续两次才隔离的策略实测能把误隔离率降低约七成。4. 调度器设计从排队到拓扑感知分配NebulaGrid的调度器是Python写的采用了事件驱动周期扫描混合模式。用户提交任务的时间点不可预测所以我用事件驱动保证低延迟但为了处理节点掉线、GPU释放等异步事件又保留了一个3秒一次的定时扫描循环做兜底。4.1 任务排队的公平性策略调度器刚上线时团队几个人都优先提交自己的任务队列里的任务权重全靠抢占很快就有人不满。后来引入了简单但实用的加权公平队列每个用户有一个初始权重1.0。每完成一个任务用户的实际权重加上0.1每长时间占用资源实际权重加上0.02/小时。排队时按实际权重降序排列权重低的人优先被调度。这个策略本质上偏向一直在线等待的人而不是一口气提交很多任务的人。训练场景下任务时常跑十几个小时如果某个人连续提交大批任务他的权重增长是线性的随着时间推移会自然让位给其他用户。4.2 显存切片与算力分配的取舍NebulaGrid的显存辅助切割逻辑在实验场景下需要额外谨慎。单一物理GPU上最多切成4个逻辑切片。为什么不是更多因为切片越多PCIe带宽竞争越激烈而且显存虽被切了SM和L2 Cache仍然是整个GPU共享的。一个训练任务如果占用了12GB显存但只用了10%的SM另一个任务用剩下2GB显存却要把SM拉满两者都会受影响。实际使用里我们对--gpu-mem参数做了归一化处理如果请求的显存超过单卡物理容量的一半调度器默认按整卡分配如果小于一半才会考虑切片共存。这也是为了避免显存看起来够用但性能互相干扰严重的情况。4.3 PCIe/NVLink拓扑感知的分配逻辑前面提到多卡通信的重要性。NebulaGrid启动时会扫描每台机器上GPU之间的nvidia-smi topo -m信息构建一张拓扑图。调度时优先选择满足以下条件的组合同一个PCIe Switch下的卡优先于跨Switch的卡。有NVLink连接的卡优先于只有PCIe连接的卡。同型号的卡优先于混合型号的卡。这一步原因很直接异构卡混跑分布式训练时通信速度会被最慢的那张卡的接口带宽拖住。比如一张4090接到PCIe 4.0 x16另一张4060 Laptop走的是PCIe 4.0 x8两者做AllReduce时实际吞吐会被限制在x8带宽附近。如果你用NebulaGrid跑torchrun调度器会自动生成一组NCCL_SOCKET_IFNAME和NCCL_IB_DISABLE1的环境变量注入到任务进程里避免NCCL选错网络接口。5. 任务提交实战从小模型微调到vLLM服务化部署架构和调度说了一堆最终用户感受到的其实只有命令行那几个子命令。下面写一下日常最常用的操作流程让大家感受下NebulaGrid用起来是什么样的。5.1 命令行工具与SDK的用法首次使用先注册集群访问凭证nebula-ctl login --endpoint https://nebula-grid.lab.local --token xxxxx然后查看集群的全局资源情况nebula-ctl status Cluster Overview: Nodes: 5 GPUs: 19 (12 idle, 5 busy, 2 maintenance) Total VRAM: 398 GB (free: 214 GB) Node Details: node01: RTX 4090 x2, RTX 4060 Laptop x1, load 42% node02: Intel Arc A770 x1, load 10% ...这个视图很重要它直接解决了发现难的问题。用户不再需要问别人哪张卡空闲看一眼输出心里就有数。提交训练任务时用--gpu-count指定卡数用--gpu-mem指定单卡显存需求nebula-ctl submit \ --name llama-ft-001 \ --image pytorch/pytorch:2.1.0-cuda12.1-cudnn8 \ --gpu-count 2 \ --gpu-mem 24G \ --workdir /workspace \ --command python train.py --config configs/llama-ft.yaml \ --data-mount /data/llama-dataset:/workspace/data这里要注意--gpu-mem和--gpu-count是协调工作的如果你指定了--gpu-count 4调度器会自动在各节点之间搜索4张显存均大于等于24GB的卡然后按拓扑优先级排序返回分配结果如果找不到满足条件组合任务会进入Waiting状态并提示缺什么资源。5.2 PyTorch训练任务的接入改造用NebulaGrid跑单卡任务几乎不需要改代码。任务进程启动后能看到一个自动注入的环境变量NEBULA_GPU_LIST里面是按调度结果排列的GPU UUID列表echo $NEBULA_GPU_LIST gpu-7ab3c91d,gpu-8f2ae813PyTorch侧只需要把可见设备设置成对应的CUDA index序列即可import os import torch gpu_ids os.environ.get(NEBULA_GPU_LIST, ).split(,) if not gpu_ids: raise RuntimeError(No GPU assigned by NebulaGrid) # 映射到CUDA visible devices os.environ[CUDA_DEVICE_ORDER] PCI_BUS_ID os.environ[CUDA_VISIBLE_DEVICES] ,.join( [f{uuid_to_index[g]} for g in gpu_ids] ) device torch.device(cuda if torch.cuda.is_available() else cpu) print(fUsing {torch.cuda.device_count()} GPUs: {torch.cuda.get_device_name(0)})多机任务时NebulaGrid还会自动生成torchrun需要的--master_addr、--master_port、--nnodes等参数。关键点在于MASTER_ADDR是调度器在任务之间建立的内网地址不需要用户自己手动去查IP。我测试下来感受比较深的是接入改造不应该让用户去适配调度器而是调度器去适配用户的习惯。大部分PyTorch训练代码对分布式部分是不关心的NebulaGrid要做的是提供一个合理的默认值。5.3 推理服务与训练任务混合调度的资源隔离推理服务和训练任务的资源模型完全不同。训练是短期高强度占用推理是长期稳定低延迟。NebulaGrid为推理服务设置了最小显存预留策略为每个vLLM服务保留至少2GB显存作为KV Cache的余量且不允许训练任务把推理服务的卡整卡抢走。实际压测过的场景一张A100 80GB上跑一个7B量级的vLLM服务设置max_num_seqs32KV Cache约占20GB。剩余60GB可以再起一个显存需求较小的微调任务两者并发时vLLM的TTFT首Token延迟波动实测在10%以内这在实验室场景完全可以接受。6. 可观测性与故障自愈GPU Crash不只靠人肉盯实验室里显卡出故障的频率比大多数人想的要高得多。尤其是并行跑多个任务时显存ECC错误、掉卡、驱动崩溃各种奇怪现象都冒出来。6.1 硬件层监控温度、频率、掉卡事件NebulaGrid的节点Agent默认每30秒采集一次温度、利用率、显存占用、功耗、风扇转速、核心/显存频率。这些数据直接写入时序数据库并在Web面板上画曲线。监控指标里有一个经常被忽略但非常关键的参数核心频率。如果你发现GPU利用率不高但温度很高比如75度以上同时核心频率掉到了基础频率以下大概率是散热或供电不稳。NebulaGrid的调度器会把这种情况视为一次潜在降频事件写入节点的健康评分。长期健康评分过低的节点在调度优选阶段会被扣分逐渐减少其承接新任务的概率。现场还遇到过一次很典型的故障排查某节点上的卡利用率固定在97%但loss曲线一直在震荡查监控发现显存频率始终在405MHz上不去而正常卡应该在5001MHz左右。后来定位到原因是显卡的电源管理策略被驱动锁成了低功耗模式用nvidia-smi -pm 1强制持久化模式后恢复。这类问题不监控频率曲线的话只看利用率很容易漏掉。6.2 日志与指标链路从XID事件到自动隔离节点NebulaGrid把故障处理设计成了流水线核心思路是让机器先处理人再复查。Agent通过NVML监听XID事件同时解析dmesg里的NVRM错误日志。一旦发现xid 79或xid 13这类明确故障码立刻给GPU打上HealthCheckFailed标签。控制面发送一个隔离请求给节点节点上的任务管理器把该GPU上正在运行的进程发SIGTERM信号然后重新调度。隔离之后Agent自动跑一轮显存自检和温度循环测试全部通过后恢复可用。这里有一个实际执行的细节直接给进程发SIGTERM可能会导致训练状态丢失用户会不满。所以NebulaGrid默认先发SIGUSR1给任务进程里的信号处理钩子一点时间保存checkpoint如果15秒内没有正常退出再升级为SIGKILL。很多调度系统做自动隔离时都太过粗暴NebulaGrid的妥协是宁可多等15秒也要尽量保住任务的现场。这对做科研的人来说非常重要因为这个机制能有效避免因硬件不稳定而浪费十几个小时的训练进度。6.3 用户视角的配额与账单实验室场景通常不涉及真实计费但如果你管理着一批不自律的用户配额机制还是很有用的。NebulaGrid的配额模型参考了核时的概念——定义成GPU卡时GPU-hour以整卡小时数为单位。每个用户默认配额是2000 GPU-hours/月超了就不能再提交新任务需要管理员手动审批扩容。这套机制上线之后集群资源的整体利用率从40%左右提到了70%以上有几个原因用户会主动清理不再使用的僵尸任务。调试任务大家更倾向于先用CPU跑通再提交GPU。用户对资源是稀缺的有了直观感受不再无限地驻留任务。7. 部署实录与避坑清单最后一部分讲讲从零开始搭NebulaGrid的实际步骤、踩过的坑和一些能让部署少走弯路的建议。7.1 从零搭建NebulaGrid的最小集群如果你也想在实验室里搭这样一个GPU集群最小规模是三台机器一台作为控制面不需要GPU2核4GB内存就够两台作为计算节点。我们在实测环境下用了一台4核8GB的旧服务器跑控制面完全够用控制面不是性能瓶颈。搭建步骤大致如下每台计算节点上安装NVIDIA驱动如果跨品牌还需要装对应的Intel/昇腾驱动确认nvidia-smi输出正常。在控制面上安装K8s可以用kubeadm最简配置然后安装NebulaGrid的controller和scheduler组件。每台计算节点上运行Agent安装脚本curl -fsSL https://nebula-grid.example.com/install.sh | sudo bash -s -- \ --controller-endpoint https://nebula-grid.example.com \ --token xxxxx执行nebula-ctl node add注册节点再执行nebula-ctl status确认所有GPU已被纳管。从一台GPU机器上运行一次冒烟测试nebula-ctl submit --gpu-mem 4G --command nvidia-smi等两分钟看任务日志里能否正常打印显卡信息能打印就说明链路是通的。部署过程中最实用的建议先把Agent部署到一台备用机器上不要一上来就接生产卡。因为Agent和驱动的兼容性问题尤其是不同驱动版本的NVML API差异是部署时最容易出错的地方先用闲置卡调试能避免影响正常使用。7.2 踩坑实录错误代码43、xid 79、SM_120不兼容这三个问题在热搜词里出现了我也确实都在实际部署中分别遇到过重点展开说一下排查思路。错误代码43主要在Windows节点上遇到表现为NVIDIA控制面板里GPU显示黄色感叹号设备状态代码43。核心排查步骤看Windows事件查看器里NVIDIA相关的错误日志确认是驱动版本问题还是硬件冲突。把GPU从同时负责显示输出改成仅计算模式可以绕过WDDM的部分限制。完全卸载驱动后用DDUDisplay Driver Uninstaller清干净再安装Studio版驱动。这一步的关键不是装哪个版本驱动而是理解43的本质是驱动已经加载但无法正常与该设备通信。只要驱动版本和GPU架构有代差这个问题就会出现。NebulaGrid的Agent在Windows节点上默认禁用TCC模式检测因为实验室里很多Windows机器还要同时做显示输出强制切TCC会导致显示器黑屏。xid 79这个我在前面已经提过。实际场景里它并不总是硬件故障偶尔是电源管理导致的PCIe链路瞬时中断。我们的排查思路是检查dmesg里是否伴随有NVRM: GPU at PCI:0000:3b:00.0 has fallen off the bus的记录。看同一时间节点的电源日志确认是否有瞬时过流保护触发。如果短时间内只出现一次且后续自检通过可以标记为瞬态事件不隔离。连续出现两次则必须隔离并检查物理插槽和电源线。SM_120不兼容这是比较新出现的问题。RTX 50系显卡的架构Blackwell计算能力是sm_120但很多现有的PyTorch wheel还停留在适配sm_90Hopper甚至sm_86Ampere的阶段。如果你的任务使用了编译期的cuda extension比如某些自定义算子老版本工具链编译出来的二进制是跑不起来的。在NebulaGrid里调度器通过注册信息知道哪张卡是sm_120哪张是sm_89然后在任务描述里加上--arch参数做过滤。对用户来说提交任务时显式指定--arch sm_120能有效避免被调度到不支持该架构的节点上。7.3 与K8s原生device plugin / HAMI虚拟化方案的对比很多人会问既然NebulaGrid基于K8s为什么不直接用NVIDIA官方device plugin或者上HAMI这样的GPU虚拟化方案我在这里做个简单的对比方案对实验室的友好度主要问题NVIDIA官方device plugin低默认整卡分配无法切分显存K8s HAMI虚拟化中功能强但调度策略偏数据中心学习成本高NebulaGrid高侧重于实验室场景的易用性和容错性NebulaGrid核心的问题是它把GPU的资源分成整卡、切片、多卡三种粒度并将对用户隐藏了大部分底层概念。HAMI这类专业虚拟化方案确实更强大但对一个只有几台机器、几十个用户的实验室来说运维成本偏高。NebulaGrid的定位就是在够用和好用之间取一个平衡。不过我也坦诚地讲NebulaGrid并非适合所有场景。如果你的实验室要做大规模推理服务每天几千个请求量那GPU虚拟化的精细程度直接决定成本你应该选择HAMI或专业的vGPU方案而不是用NebulaGrid。它解决的是如何让GPU利用率合理提升的问题不是如何把每一块GPU的价值榨干的问题。最后说两句实在话项目跑了大半年我最深的体会是实验室GPU集群的核心难点从来不是调度算法有多巧妙而是如何让每个环节都符合实验室人的使用习惯。说实话让实验室里的一堆异构GPU真正变成一个可以随时提交任务的集群NebulaGrid用了并不复杂的架构和算法。深度学习、CUDA、PyTorch这些重框架底层往往在很多细节上会超出你的预期——比如一张显卡在长期低负载下会进入P8状态唤醒到P0需要几百毫秒如果调度器在任务启动时不做预热整体启动时间会莫名其妙多出好几秒。后来我们在任务拉起过程中加了一步轻量级的显存预申请实测效果很好。我还想提醒一点给GPU集群做监控时温度是最应该关注的指标之一——它比利用率更早反映一个节点的健康状态。有两次节点上的卡片长期满载跑训练感觉利用率都正常但温度曲线持续爬升最终任务训练速度骤降。温度监控帮我们提前做了干预。如果你想在自己的实验室里复刻类似的东西我建议从最小闭环开始先把所有GPU的实时状态汇总到一个页面哪怕只是用一个脚本轮询nvidia-smi写入数据库再考虑调度。资源可见是资源调度的前提也是所有优化的起点。NebulaGrid还在持续演进。目前的方向是让调度器更好地感知GPU的实时功耗和温度在任务分配时自动做功耗均衡避免多卡任务全挤在同一台电源负载较高的节点上。这个功能做完之后再分享更详细的路由与调优策略。