接了个内部系统的技术复盘把这一年多跟DSecDeepSeek Elastic Compute打交道的经历好好捋了一遍。简单说DSec是我们团队自研的一套弹性计算平台核心目标就一件事给AI训练、微调和在线推理任务提供一个统一、弹性、不浪费的算力底座。这个方向不算新但真把一个平台从设计到落地完整走一遭中间踩的坑和想明白的事情比读十篇架构文档都值钱。先说结论上线稳定运行后GPU平均利用率从原来的35%左右提到了70%以上作业排队时间从小时级压到分钟级推理服务在流量突增时能做到分钟级完成扩容训练任务的调度耗时控制在秒级。这个数据在我们内部算是一个比较大的改善。但比数据更重要的是这一套设计背后的取舍逻辑——什么东西用K8s原生方案什么东西必须自己造哪些环节要做得重一些哪些环节能省就省。这篇文章就是把这些决策过程、实现细节、调优记录和排查实录完整写下来。这篇内容适合正在搭AI基础设施的团队特别是对K8s有一定了解、但卡在GPU资源管理和弹性伸缩细节上的同学。也适合准备做平台型项目的个人开发者参考。如果你只是想知道DSec能干什么一句话版本就是它能把一堆零散的GPU资源池化按需切分、动态分配让训练任务和推理服务共享一套底座并且互不干扰。1. 项目定位与核心需求拆解1.1 旧有的算力管理方式到底卡在哪DSec这个项目不是凭空冒出来的它是对一套旧方案的彻底重构。之前团队的算力管理方式说起来很简单就是“静态分配加人工协调”。训练任务提申请管理员在集群里找空闲的GPU然后手动把节点绑给任务推理服务则是固定申请一批卡长期占着。这种方式在集群小、任务少的时候还能凑合但集群规模一上来问题就非常明显。第一个问题资源利用率低得吓人。固定分配的结果是很多卡在夜间和低峰期完全闲置但高峰期又有任务排不进去。我们统计过那时候整集群的GPU平均利用率只有35%上下一半以上的算力都是被“占着但没用”的状态。第二个问题训练和推理之间天然存在资源冲突。推理服务要求稳定的可用性必须预留容量训练任务则是突发性强、周期长两者混在一起就容易出现互相抢资源的情况。第三个问题没有弹性能力。大促或者活动流量上来的时候推理服务没办法快速扩容只能靠人肉盯监控然后手动调整。旧方案的这些痛点说到底就是三个关键词静态、隔离、无策略。资源是物理分割的任务是一个一个排队的扩缩容基本靠人工判断。这个背景下团队开始讨论要不要引入一套真正的弹性计算平台。1.2 方案选型为什么做在K8s之上而不是另起炉灶方案选型的过程其实挺有意思。当时大家提了好几个选项自己从零写一套资源调度系统基于K8s定制扩展控制器或者直接在物理机上做脚本化的容器管理。也认真考虑过是否完全基于K8s原生组件不写一行额外代码。我们最后的决策是保留K8s作为基础设施层在它之上实现自定义调度器和一套弹性控制面。理由有三条。第一K8s在容器编排、生命周期管理、服务发现和故障自愈这些方面已经足够成熟没有必要重新造轮子但原生的默认调度器在细粒度GPU管理上确实不够用比如它不感知显存大小、不会做拓扑亲和性调度也不支持训练任务常见的Gang调度语义这个层面必须自己做。第二训练任务和推理任务的是复杂的、长周期的workload原生HPAHorizontal Pod Autoscaler只针对无状态Web服务的简单扩缩容对AI推理场景里的模型预热、流量灰度、GPU切分这些需求完全覆盖不到。第三团队有资源也有精力去维护额外的一层控制面而且这一层控制面可以成为团队的长期技术积累。说实话这个决策也有风险的。自研控制面意味着要维护更多代码而且一旦K8s版本升级很多扩展接口都有兼容性成本。但从结果看收益是远大于成本的。提示如果你的业务场景只是少量GPU跑跑Jupyter或者固定几个推理服务K8s原生方案就够用了。真正需要DSec这类平台的是集群规模大、任务类型杂、资源利用率压力大的团队。2. 架构设计与核心模块拆解2.1 模块划分与职责边界DSec的架构在设计上分成了五个核心模块每个模块的职责边界很清晰配合关系比较简单资源抽象层负责把物理GPU池化抽象成带有显存、算力、拓扑标签的资源对象。这一层屏蔽了底层GPU型号差异让上层不需要关心节点插的是A卡、B卡还是C卡。调度引擎负责任务到资源的映射包含队列管理、优先级排序、Gang调度、拓扑亲和性和抢占逻辑。任务管理模块负责任务生命周期管理包括提交、取消、重启、日志收集和状态回传。弹性控制面负责推理服务的自动扩缩容以及训练任务的弹性容错和动态调整。数据与缓存层负责权重文件、数据集的缓存和加速分发避免任务启动时反复从远端拉数据。这五个模块之间通过事件和RPC通信。调度引擎是核心其他模块都围绕它来转。任务管理模块把用户提交的作业解析成内部数据结构交给调度引擎做决策调度引擎决策完成后通过K8s API创建Pod弹性控制面监听监控数据反馈给调度引擎做扩缩容决策。2.2 调度策略的设计取舍调度器是整个DSec中最有嚼头的模块。我们花了最多时间打磨三个策略排队策略、Gang调度和抢占策略。排队策略上我们没有用K8s原生的PriorityClass加DefaultScheduler的简单队列而是实现了自己的优先级队列。每个提交的任务自带优先级和资源需求调度器按照加权公平算法计算得分然后依次调度。这个队列的设计要考虑几个维度队列深度、等待时长、资源缺口大小、任务所属业务线的配额权重。一开始我们的实现只按优先级排序结果低优先级任务等了三四个小时都排不上后来加了一个等待时间衰减机制任务等待越久权重越高自然平衡了优先级的饥饿问题。Gang调度是训练任务特有的需求。一个分布式训练任务往往需要一次性申请多张卡比如一个8卡的训练任务如果调度器只调度到6张卡任务根本无法启动还白白占着6张卡。所以我们实现了All-or-Nothing语义任务必须拿到全部所需资源才会被调度器下发否则继续留在队列里等待。这个策略看起来简单但实现上非常需要细心尤其是资源预留和锁定的问题时序很容易出错。标准做法是先做资源可行性判断把所有候选节点算出来然后做资源预留再绑定到节点。如果过程中任何一步失败要做事务性回滚不能留下资源残留。抢占策略这块我们的原则是“最小代价抢占”。训练任务和推理服务混部的时候如果高优先级任务进来了但资源不够调度器会选择抢占低优先级的训练任务而不是动推理服务。因为训练任务有检查点机制被抢占后可以从最近的checkpoint恢复损失可控推理服务一旦被抢占直接影响线上流量。抢占时还需要考虑反亲和性避免同一个训练任务的多个worker被抢得七零八落。2.3 数据与缓存层的设计数据流是平台容易被低估的模块。AI任务启动的第一件事往往是加载数据集和模型权重。如果每个任务都从头拉数据再快的网络也扛不住。DSec的数据层设计了一套二级缓存机制第一级是本地节点磁盘热缓存第二级是集群内的分布式缓存服务。调度器在做节点选择的时候会把数据缓存命中率作为一个调度权重——优先把任务调度到已经缓存了所需数据集的节点上。这个设计直接解决了重复拉数据的开销尤其在大模型推理场景效果非常明显。缓存淘汰策略上我们用的是LRU变种加了尺寸权重的概念。因为模型文件动辄几GB甚至几十GB小文件把大文件挤掉是常见的问题。所以我们按“访问频率×文件大小”计算权重既保留高频的小文件数据也保留低频但体积巨大的模型权重。这个策略上线后推理服务的冷启动时间从平均120秒降到了8秒左右代价只是后端存储多占了不到两倍的冗余空间。3. 弹性伸缩与GPU资源管理实现机制3.1 弹性伸缩的指标设计不是越多越好做弹性控制面的时候我们花了大量时间讨论用什么指标来触发扩缩容。最容易想到的方案就是组合多种指标比如CPU使用率、GPU利用率、请求QPS、队列长度然后设置多个阈值。但实践证明指标越多出问题的概率越大。指标抖动、阈值冲突、不同指标给出相反信号的时候整个扩缩容逻辑会变得极其难调试。后面我们收敛设计方案把指标分成三类容量指标GPU利用率、显存占用率、负载指标推理QPS、训练Step吞吐和饱和度指标请求排队长度、等待时间。扩缩容触发以负载指标和饱和度指标为主容量指标只做辅助判断。具体的扩容公式用的是经过平滑的线性模型目标副本数 ceil(当前副本数 * (当前指标值 / 目标指标值))这个公式本身很朴素关键在平滑。我们用的是5分钟滑动窗口加指数加权平均每秒采样一次但最终进入决策的是一个平滑后的趋势值。这样避免了一瞬间的流量毛刺触发扩容然后流量恢复又触发缩容的震荡。冷却时间也是重点。扩容冷却时间设为1分钟缩容冷却时间设为5分钟。为什么扩容比缩容激进因为扩容是应对风险宁可多扩一点缩容是节约成本要确认流量是真的下去了再缩。这个不对称设计后面在实战中证明是必要的。另外还有一个细节缩容时每次最多减少当前副本数的20%防止一台一台连续删实例导致可用性波动。注意弹性伸缩最怕的不是决策慢而是决策反复。一次误扩容可能只是浪费钱但一次缩容误杀就是线上事故。所以冷却时间和平滑窗口宁可调大不要调小。3.2 GPU资源切分整卡、切片还是时间片GPU资源池化最核心的问题是一张卡能分成多少份。我们的实践是分成三个层级第一层是整卡分配适合大模型训练和重负载推理独占整张卡性能和隔离性最好。第二层是显存切片基于底层硬件和驱动能力一张卡切分成若干独立的小块每块有自己的显存和算力配额。这个模式适合中小模型的推理服务缺点是一张卡最多只能切固定的份数不够灵活。第三层是时间片轮转多个任务共享一张卡的计算单元通过时间片分时复用适合轻量级的批量推理和试验型任务。三个层级决定的策略是调度器先尝试整卡分配如果任务的显存需求小于单卡的某个阈值比如单卡显存的四分之一才会尝试切片切片也不满足才考虑时间片。这个优先级顺序是为了保证高优先级任务的计算性能稳定减少共享带来的干扰。这里有个容易踩的坑切片模式下的容错恢复比整卡复杂。整卡模式下如果GPU故障只需要把Pod重新调度到另一台节点但切片模式下故障卡上其他切片的任务也会受影响而且由于硬件资源视图是逻辑的排障时很难定位到具体物理位置。所以我们额外做了一层“物理卡健康检查”每个节点上的DaemonSet会周期上报每张物理卡的运行状态一旦发现硬件故障立即冻结该卡上的所有Pod调度而不是等到任务失败才处理。3.3 推理服务扩缩容的特殊处理训练任务的弹性是“排队等待”推理服务的弹性是“实时平滑”两者的处理逻辑完全不同。推理服务的扩缩容我们做了三个额外的机制。模型预热是最关键的一个。一个大模型推理实例从创建到真正能处理请求中间要经历加载权重、构建计算图、缓存预热。如果不做预热就放流量前几分钟的请求会大面积超时。实现方式是在Pod启动后通过健康检查接口挡住真实流量等预热完成后才摘掉屏蔽标记。预热完成的判断标准不是进程启动了而是模型已经加载、首次推理延迟已经降到正常水平的90%以下。最小实例保障是第二条线。不管流量多低每个推理服务至少保留2到3个实例。这个设计是为了应对流量突然回切的情况避免服务被缩容到只剩1个实例然后一个弹性的浪涌流量直接把唯一实例打挂。训练服务也类似每个训练任务至少保留一个主worker做检查点保存。流量渐进式灰度是第三个机制。扩容出来的新实例不会一瞬间承接全部流量而是按10%、30%、50%、100%的比例逐步接入。每一步接入后观察一小段时间的错误率和P95延迟如果指标恶化就停止接入并把新实例回收重扩。这个和发布系统的灰度逻辑很像但套用到弹性伸缩上能避免新实例还没完全就绪就被压垮的尴尬局面。4. 关键参数、配置与实际调优记录4.1 初始配置和压测场景设计DSec上线前我们做了一轮规模不算小的压测目的不是测极限性能而是找到一套适合内部业务的默认参数。压测场景分三块训练任务密集提交、推理服务突发流量、训练推理混部场景。训练任务提交压测模拟100个训练任务同时提交每个任务申请4到8卡资源总量为128卡验证排队公平性、调度延迟和Gang调度表现。推理服务压测模拟在线流量从低峰到高峰再到低峰的完整曲线峰值流量是平时的5倍验证扩缩容的响应速度和稳定性。混部压测训练任务和推理服务同时提交高优先级推理任务抢占低优先级训练任务验证抢占不伤推理服务。压测工具方面训练任务用一个内部脚本模拟器来提交实际跑的是假的权重加载流程推理流量用一套压测工具打真实请求并记录QPS、延迟分布、错误率等指标。观察窗口持续48小时因为弹性系统往往是长时间运行才能暴露出问题。4.2 参数调优过程从第一版到稳定版第一版的参数设置其实挺保守的。调度队列的轮询间隔是5秒扩缩容冷却时间是扩容3分钟缩容10分钟弹性触发的目标利用率是60%。跑下来问题很明显调度太慢一个紧急任务提交后要等小半分钟才被调度到扩容太保守流量突增后的几分钟内出现明显延迟上升。第一轮调整调度轮询间隔从5秒改成1秒调度延迟从平均10秒降到2秒以内。代价是调度器CPU占用率略微升高但完全可接受。第二轮调整扩缩容的触发目标从60%上调到75%让实例在真正忙起来之前就被扩容到位。同时把扩容冷却时间从3分钟压到1分钟缩容冷却时间保持10分钟。第三轮调整针对混部场景。测试中发现抢占过程中低优先级训练任务的检查点保存和资源释放顺序存在问题导致高优先级任务等了20多秒才拿到资源。我们在抢占流程里加了并行驱逐机制先预选目标节点并行发驱逐信号给低优先级任务同时提前分配新的加密等待队列把资源释放和分配重叠起来。优化后抢占耗时降到3秒以内。表格里是最终稳定运行的参数配置参数项初始值稳定值调整理由调度轮询间隔5秒1秒降低任务调度延迟扩容冷却时间3分钟1分钟提高突发流量响应速度缩容冷却时间10分钟5分钟平衡成本与稳定性弹性目标利用率60%75%减少过度预留浪费指标平滑窗口60秒300秒抑制毛刺导致的抖动缩容单次最大比例无限制20%防止缩容导致可用性波动数据缓存权重纯LRU加权LRU保护大文件不被频繁淘汰4.3 上线后的对比数据经过前后约两个月的压测、调优和灰度我们记录了上线前后的关键对比数据。调度延迟从分钟级降到秒级、作业排队时间从平均2.3小时降到20分钟以内、GPU平均利用率从35%提升到72%。冷启动时间这个指标对我们感触最深因为它是用户直接感知得到的体验指标从原来的分钟级压缩到秒级之后业务反馈“快得不像同一个平台”。指标优化前优化后提升幅度作业平均排队时间约2.3小时20分钟内提升85%GPU平均利用率35%72%提升105%任务调度延迟分钟级2秒以内降低90%以上推理服务冷启动时间约120秒约8秒降低93%抢占耗时约20秒约3秒降低85%推理服务突发扩容耗时10分钟级3分钟以内提升70%必须说的是这些数据不是极限调优的结果是在保证稳定性前提下的自然收益。后续如果继续优化调度算法和缓存策略这个数字还有进一步提升空间。5. 踩坑记录与问题排查实录5.1 扩缩容震荡问题副本数反复横跳DSec灰度期间遇到的最典型问题就是扩缩容震荡。某个推理服务在流量稍微波动的时候副本数在10到20之间反复横跳每5分钟一次看起来非常不稳定还造成了资源浪费。排查后发现根因是监控指标的抖动被扩缩容逻辑直接放大了。当时用的是单点的1分钟平均采样指标在阈值附近来回波动导致系统先扩后缩、反复横跳。解决方案分两层。第一层是引入5分钟滑动窗口加指数加权平均让指标曲线变得平滑。第二层是设置了一个死区区间当前指标在目标值的90%到110%之间时不触发任何扩缩容操作。这个死区本质上就是一个“非敏感区域”只有指标显著偏离目标才做决策。这两层加上不对称冷却时间扩缩容震荡问题基本消失了。5.2 冷启动时间太长权重加载路径的优化有个模型服务的冷启动时间一开始是150秒完全不可接受。排查后发现瓶颈不在镜像拉取而是模型权重的加载速度。DSec的调度器虽然在数据缓存层做了亲和性设计但权重文件从分布式缓存读到本地然后加载进GPU显存的链路是串行的每一步都有不小的开销。优化手段有三个。第一权重文件按照层次拆分成独立分片支持并行加载而不是整个文件一次性读进来。第二在节点本地增加持久化的模型缓存目录同一模型的多次启动直接在本地复用。第三优化了GPU显存分配的策略让加载过程对显存的请求尽量连续减少碎片化的分配和释放。做完这三项优化冷启动时间从150秒降到了8秒左右效果非常显著。5.3 慢任务占住资源不释放僵尸任务检测平台运行一段时间后发现一个隐蔽的问题有少量训练任务提交成功后训练进程由于代码bug卡住既没有正常退出也没有持续资源消耗。它们像一个“僵尸”一样占着GPU资源不动。如果没人发现这些任务会无限期占用资源直接影响集群的有效利用率。排查思路是结合任务状态和资源使用情况做综合分析。正常情况下训练任务即使训练缓慢也会有日志输出、指标上报或显存写入这样的“活性迹象”。如果一个任务超过30分钟没有日志输出、没有资源使用变化、没有网络通信量会被标记为疑似僵尸任务进入隔离观察区。观察10分钟后仍然没有变化自动终止并通知用户。第一版实现触发了一些误杀比如跑长任务间歇性保存模型时正好处于休眠期。后来加了“用户指定任务类型”的开关禁用了误杀标记。总体上这套僵尸检测机制为平台收回了大约6%的浪费资源是一个性价比很高的优化。写在最后一些真实的个人体会DSec这套平台折腾下来我最大的感受是做这类基础设施项目最难的往往不是技术本身而是怎么让业务方信任这个平台并愿意把真实任务迁移过来。迁移的意愿取决于两件事第一平台能不能保证稳定性训练跑到一半被杀死是不能接受的事情所以要有完善的检查点机制和优雅驱逐流程。第二平台能不能让用户看到收益当业务方看到排队时间从2小时降到20分钟冷启动从两分钟变成8秒时迁移阻力就会小很多。所以我们做了很详细的任务运行报告把资源利用率、调度等待、弹性表现等数据定期同步给业务方让他们直观感受到DSec的价值。另外一个比较重要的经验是弹性和利用率之间要主动做出取舍。一开始我们想把GPU利用率冲到90%以上实际发现这个目标会让调度变得过于激进调度器为了填满资源会把一些高优先级任务拆到碎片上反而影响了训练性能。最终我们维持在70%到75%的区间留出一定buffer来应对突发的调度需求。这个区间是经过反复测试才确定的如果一开始就咬着高利用率不放后面可能需要花更多时间处理稳定性问题。最后团队工程文化的积累也很重要。调度器、弹性控制面、数据缓存这些模块都不是一次性做完的而是经过灰度、压测、线上故障逐步沉淀下来的。每个模块都积累了完整的故障预案和调优记录。这也是我愿意把过程写下来的原因。如果你也在搭类似的平台希望这些经验能帮你避开一些我们已经踩过的坑。
阅读完成 · 觉得有帮助?