ARTICLE DETAIL

资讯详情

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

高性能计算中的三层负载均衡:从LVS到MoE专家均衡实战

高性能计算中的三层负载均衡:从LVS到MoE专家均衡实战 聊到自己折腾高性能计算环境里的负载均衡我最大的感受是这个领域里“负载均衡”四个字的含义比大多数人想象的要宽得多。很多人一听到负载均衡脑子里就是加个VIP、配个轮询、挂几个后端把流量打散就完事。但在高性能计算场景里负载均衡至少横跨三个层面——网络流量、主机链路、算力任务分配而且每个层面的“均衡”逻辑完全不同踩过的坑也完全不同。这篇文章我把这三年在HPC集群和AI训练集群里摸出来的经验整理出来重点覆盖三块传统但依然硬核的LVS网络负载均衡、常被忽略但也常出问题的Windows多网卡负载均衡、以及当前最热的MoE大模型训练里的等开销负载均衡。最后一部分会给出可直接参考的负载均衡代码和调参思路这部分是现在做分布式训练绕不开的硬骨头。不管你是刚入门的运维还是已经在碰大规模训练框架的算法工程师这篇文章里应该都有你能直接拿去用的东西。1. 高性能计算环境里的负载均衡到底在“均”什么我觉得很多人对负载均衡的理解太单薄了。在实际的高性能计算集群里“均衡”这个词是分层的每一层的目标和手段都不同如果混淆了后面排查问题会非常痛苦。1.1 三个层面的负载均衡形态完全不同先说网络流量层。这一层最经典就是LVS、Nginx、HAProxy这类东西在做的事。HPC集群里计算节点要访问调度服务、存储网关、license服务这些入口往往是集中式的流量大了之后单台机器扛不住。这个层面追求的是“请求/连接的数量均分”核心指标是并发连接数、吞吐量以及后端节点的CPU利用率不能太悬殊。然后是主机链路层。这一层很多人不重视但它实实在在影响HPC节点的数据搬运能力。常见做法是Windows下的NIC Teaming或者Linux下的bonding。把两块物理网卡聚合成一块逻辑网卡既做冗余又扩带宽。这个层面追求的是“链路负载均衡”但和流量层的区别在于它作用于数据链路层和网络层之间均衡的粒度是“流”不是“请求”。到了算力层也就是现在大模型训练里最头疼的部分。MoE模型里有几十上百个专家网络每个token会动态选择几个专家去计算。如果某个专家吸收了大部分token而另外一些专家闲着那就是典型的“算力不均衡”。这个层面追求的是“每个专家处理等开销的任务量”也就是等开销负载均衡。它的难度比前两层高得多因为前两层好歹是确定性的转发规则而这一层要在一个不可导的路由选择过程里用各种巧妙的方式引导模型学会“雨露均沾”。1.2 为什么算力层MoE专家分配难度最高网络层负载均衡后端节点之间是互相独立的A节点忙不忙和B节点接收多少请求没关系。但MoE训练的专家分布不是这样的专家之间通过all-to-all通信耦合在一起一个专家被塞满意味着它所在的GPU卡计算和通信都变成热点其他卡的GPU利用率再高也没用——一个step的完成时间是所有卡里最慢的那张决定的。更麻烦的是MoE里的路由选择router本身是参数的一部分你没法直接告诉它“你分配得不好请平均分配”。argmax这个操作不可导费了很大劲设计的辅助损失函数本质上是在用一个“软的代理信号”去引导一个“硬的离散选择”。同时还不能把这个均衡优化得太狠太狠会损害路由本身的合理性——有些token就是适合特定专家处理强行均分会牺牲模型效果。所以这一层不是单纯的技术问题而是个需要权衡的艺术。等开销负载均衡之所以成为热点就是因为它要同时解决可导性、均衡性和模型质量这三个目标。这也是我写这篇文章想重点讲清楚的部分。2. 网络层怎么把好第一道关LVS在HPC集群里的选型与配置LVSLinux Virtual Server到现在仍然是高性能场景下四层负载均衡的首选因为它直接工作在Linux内核里不走用户态协议栈性能损耗极小而且不像Nginx那样需要维护一堆连接状态。2.1 三种工作模式NAT、DR、TUN场景决定选择LVS有三种模式很多人文档看了一百遍还是不知道选哪个其实就是看流量怎么走。NAT模式最简单客户端连上负载均衡器负载均衡器把包的目的地址改成后端服务器地址后端返回时再改回来。整个请求响应流量都要过DirectorDirector就成了绝对瓶颈。在HPC场景里NAT模式只适合小规模管理面比如几百个节点的心跳通信、IPMI代理这类低流量场景别拿它做数据面。DR模式是HPC集群里最常用的。客户端请求到达Director后Director只修改MAC帧头把包发给后端后端处理完直接回包给客户端不走Director。这样响应的大流量完全绕开了负载均衡器Director只承担请求链路的处理带宽瓶颈直接消解。代价是需要后端服务器在lo回环接口上绑定VIP并且要抑制ARP响应不能让后端机器对外声称自己拥有VIP。我第一次配DR模式时就在这里翻车lo上绑了VIP没设arp_announce和arp_ignore整个交换机ARP表被打乱全网断了几分钟。TUN模式可以跨网段做IP隧道封装后端服务器可以和Director不在同一个二层网络。这听起来很美但隧道封装有额外开销HPC环境里很少遇到必须跨二层做四层代理的场景所以我基本不推荐。一句话总结同网段、追求高吞吐就选DR跨网段、低流量才考虑NAT或TUN。2.2 调度算法选择轮询够用就别上最花哨的LVS内置的调度算法有十几种但我实际用下来HPC场景里大部分时候轮询就是最好的。原因是HPC的请求普遍短小、频繁、无状态比如计算节点定时去调度服务拉任务去存储网关刷元数据。这些请求本身对后端服务器来说开销不大瓶颈经常在连接数上而不是CPU计算量上轮询能保证连接数均匀散开。如果后端机器性能差异明显用加权轮询wrr就好权重按核数或实测性能设置比如一台96核机器给权重3一台48核机器给权重1简单粗暴有效。最小连接数lc在长连接场景下才真正有必要。HPC里如果有SSH会话转发、远程可视化传输这类长时间占用的连接要换成lc或者wlc。这里有个大坑别用源地址哈希sh做HPC的前端负载。计算集群里的登录节点往往是NAT出去的几个出口IP所有计算节点请求经NAT后源地址全一样哈希一算全都丢给同一个后端负载瞬间就歪了。我见过不止一次有人拿sh算法配调度服务然后百思不得其解为什么有一台后端打到冒烟、其他后端空转。2.3 keepalived打底的高可用配置示例VIP本身不能挂在单机上需要keepalived做高可用。下面是一个我在实际集群里用过的DR模式配置骨架稍微脱敏后可以直接参考。# 安装 ipvsadm 和 keepalived apt install ipvsadm keepalivedkeepalived主配置vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass C0mputeClu5ter } virtual_ipaddress { 10.0.0.5/24 } } virtual_server 10.0.0.5 8080 { delay_loop 6 lb_algo wrr lb_kind DR protocol TCP real_server 10.0.0.11 8080 { weight 3 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 2 } } real_server 10.0.0.12 8080 { weight 1 TCP_CHECK { connect_timeout 3 nb_get_retry 3 delay_before_retry 2 } } }后端的回环接口绑定VIP需要写进启动脚本否则机器一重启VIP就丢了ifconfig lo:0 10.0.0.5 netmask 255.255.255.255 up echo 1 /proc/sys/net/ipv4/conf/all/arp_ignore echo 1 /proc/sys/net/ipv4/conf/lo/arp_ignore echo 2 /proc/sys/net/ipv4/conf/all/arp_announce echo 2 /proc/sys/net/ipv4/conf/lo/arp_announcearp_ignore1的意思是只应答目标IP是本机接口IP的ARP请求arp_announce2的意思是ARP请求源IP只能用接口上的主IP这样后端机器就不会因为lo上挂了VIP而抢答ARP。这一块如果不配置DR模式整个架构就是塌的。3. 主机层的Windows多网卡负载均衡NIC Teaming实操记录说句实话Windows多网卡负载均衡在HPC里常常是被忽略的环节但它出问题的频率一点不比网络上层低。很多Windows计算节点同时跑文件共享、作业通信、数据回传单块万兆网卡带宽不够于是就想把两块网卡凑成一块用。Windows Server 2012之后自带NIC Teaming功能不需要厂商专用工具就可以把多块物理网卡聚合成一个逻辑网卡这对HPC存储节点非常实用。3.1 创建网卡组前的准备做NIC Teaming之前先过一遍硬件要求能省掉后面一大堆麻烦。建议用同一品牌同一型号的网卡固件和驱动版本保持一致。不同速率混用、不同厂商混用都能配出来但负载均衡效果会大打折扣——比如一块万兆一块千兆组合后系统会以慢卡的能力去处理某些哈希流结果链路利用率奇差无比。另外要注意如果服务器上有HBA卡、RAID卡占用了PCIe通道确认两块网卡各自能跑满带宽别出现共享同一个PCIe插槽带宽的情况。我见过一个案例两块网卡插在同一个PCIe x8插槽的拆分线上标称万兆实际只有一半吞吐Teaming做完之后总带宽看起来正常但单块卡跑压力的时候怎么都跑不满排查半天才发现是硬件问题。3.2 创建与模式选择在Windows Server里从“服务器管理器”进入“本地服务器”找到NIC Teaming的入口选中需要聚合的多块网卡右键“添加到新团队”。如果Win11家庭版用户可以直接在“更改适配器选项”里选择多块网卡后右键也能看到“网桥”或“分组”选项但功能不如Server完整建议用Server系统做HPC节点。创建时有两个关键选择Teaming模式Switch Independent和Switch Dependent。前者不需要交换机配合两块网卡分别插在两个交换机上都行靠Windows自己分担流量后者需要交换机侧做链路聚合LACP对HPC这种固定机架拓扑来说更规范但配置复杂度多了一个环节。如果交换机是华为/思科/H3C的框式设备端口是固定的建议用LACP稳定性最好有一种“链路算是真正绑在一起了”的踏实感。负载均衡模式微软提供了Address Hash、Hyper-V Port、Dynamic三种。默认推荐Dynamic它会根据TCP端口和IP做一个多层散列对出站入站都能做负载均衡。如果这台机器要用Hyper-V承载一堆虚拟机选Hyper-V Port模式最合适它按虚拟机MAC和端口做分发不会被VM内部那层流量哈希搞乱。3.3 场景限制与常见观念误区NIC Teaming最大的一个认知误区是以为两块网卡叠一起任何一条连接都能跑双倍带宽。实际上按默认的哈希均衡机制一条TCP连接的四元组源IP、源端口、目标IP、目标端口只会落在其中一张物理网卡上单流永远跑不满聚合带宽。要真正吃满两张卡的吞吐要么并发多条连接要么用SMB Multichannel这类支持多路径的协议做文件共享。所以HPC里给存储节点做Teaming的时候我通常建议同时打开SMB Multichannel。Windows的SMB协议天生支持多网卡并行传输一个文件复制任务会自动拆成多条并发的TCP连接这时候Teaming的端口哈希反而会限制它——需要确认Teaming的负载均衡模式和SMB Multichannel不冲突。具体表现就是复制大文件只有单卡速度小文件批量传输却能跑满双卡因为小文件天然是并发多连接。另外要留意厂商管理工具的混用问题。有些网卡驱动自带teaming工具你在工具的界面上配过一次team又跑去Windows原生NIC Teaming界面操作两边配置会互相打架导致网卡频繁重置。我踩过这个坑之后定了一条规矩一台机器只允许用一种teaming管理方案原生就用原生厂商就用厂商绝不混用。配完记得用这几条命令验证一下Get-NetLbfoTeam Get-NetLbfoTeamMember Get-NetAdapter每个成员网卡的状态都应该是Up主团队的状态应该是OK。如果用了备用适配器模式要确认主成员和备成员的区分是否符合预期HPC里我基本不用备用适配器因为计算节点丢一块网卡本来就该触发告警而不是让系统悄悄降级继续跑。4. 算力层的等开销负载均衡MoE训练里的专家负载均衡终于到了现在最热门也最麻烦的部分。MoEMixture of Experts模型比如Switch Transformer、Mixtral、DeepSeek-MoE这类架构在FFN层放了多个独立的专家网络每个token通过一个router选择专家来处理。理论上模型参数量可以做得很大但每次推理只激活部分参数算是用稀疏激活换模型容量。问题是router学习出来的分配策略经常会走向极端有些专家成为“热门专家”吸收了绝大多数token有些专家成了“闲置专家”训练半天没接到几个活。这些闲置专家白白占了显存和通信带宽贡献却趋近于零。这时候就需要等开销负载均衡让每个专家在一个batch里收到尽量等量的token让所有GPU卡都忙起来。4.1 为什么MoE必须做等开销负载均衡表面上看专家闲置只是“浪费参数容量”但实际危害比这个大得多。在分布式训练中MoE层通常采用专家并行Expert Parallelism也就是把不同的专家分布到不同的GPU上。一个step里每个token需要把token embedding通过all-to-all通信发到目标专家所在的卡计算完再all-to-all传回来。如果热门专家集中在某一张卡上那张卡的GPU计算量最大通信量也最大其他卡早早算完开始等它。整个训练step的耗时被最热的那张卡拖住GPU利用率出现明显的锯齿波动。更烦人的是all-to-all通信是无界的热点卡的收发流量远超其他卡以太网里这种高潮流量还会加剧网络延迟抖动连其他正常计算的node都跟着遭殃。显存层面也有问题。热门专家所在卡的激活显存和梯度显存都会偏高如果某张卡因为显存溢出OOM整个训练都得停下来重启。所以在MoE训练里负载均衡不再是个优化项而是能不能稳定训练的前提。4.2 辅助损失aux loss的原理与手推公式最经典的让router学会均衡分配的方法是Switch Transformer提出的辅助负载均衡损失。它设计得非常巧妙因为router的argmax决策不可导所以需要用一个可导的软信号来驱动router参数更新。先约定符号假设一个MoE层有N个专家一个batch里有T个token每个token选择top-1专家先以Switch Transformer的top-1为例。定义两个量f_i 实际被分配到专家i的token数占总token数的比例数学表达是 f_i (1/T) * Σ_x 1[argmax router(x) i]。注意这里的argmax不可导所以f_i只能作为“观测值”参与损失计算不提供梯度。P_i router给专家i的平均softmax概率P_i (1/T) * Σ_x softmax(router(x))_i。这个是可导的会正常回传梯度。辅助损失的形式是aux_loss α * N * Σ_i f_i * P_i这个公式我第一次看的时候愣了半天为什么不直接用均方差后来想通了这个乘积形式同时做到了三件事第一如果f_i和P_i都大说明专家i不仅实际接收的token多router心里的偏好也大两者相乘会放大惩罚第二f_i是硬的观测P_i是软的概率乘积里P_i负责传输梯度f_i负责刻画偏差方向解决了不可导的限制第三乘以专家数量N是为了让loss的量级不随专家数增长而失控方便跨不同规模的模型调整超参。我个人的理解是这本质上是在算一个“期望过载度”。一个专家如果被选中的概率高、实际也真的被塞了很多token它的过载贡献就大loss就高优化器会往降低router对该专家偏好概率的方向调整。4.3 无辅助损失的高效均衡方案用了aux loss之后训练是稳定了但辅助损失也带来一个问题它会干扰router本身的判断。有些token明显更适合去专家A但因为均衡损失的惩罚router被迫把一部分token分给专家B模型表达能力受到了约束。辅助损失系数α调大了模型困惑度perplexity会上升调小了负载又不均衡。DeepSeek给出的思路是抛弃辅助损失改用可学习的偏置项。具体做法是在每个专家的router logits上加一个可训练的标量偏置b_i也就是每个专家一个可学习参数初始化为0。训练中如果专家i过载b_i会被优化器朝负方向调整使该专家的选择概率自然下降如果专家i闲置b_i朝正方向调整提升被选概率。这个bias引导的均衡过程是不需要额外损失项的router仍然完全按目标函数优化只是多了一个“自动调节水龙头”的阀门。训练完成后推理阶段会把bias项去掉或者设成固定值避免它影响模型最终的路由偏好。我实测下来这种方案在训练稳定性上和aux loss相当但模型效果普遍好一些代价是实现和调试时要额外维护这套bias逻辑框架代码不如aux loss那样开箱即用。4.4 MoE 负载均衡代码实现PyTorch版下面给一份我实际用在训练脚本里的MoE负载均衡损失实现。这个版本支持top-k路由覆盖了f_i和P_i的计算细节。import torch import torch.nn.functional as F def moe_load_balance_loss(router_logits, dispatch_indices, top_k2): 等开销负载均衡损失Switch Transformer aux loss 的 top-k 版本 router_logits: [num_tokens, num_experts]router 的输出 logits dispatch_indices: [num_tokens, top_k]每个 token 实际被分到的专家 id num_tokens router_logits.size(0) num_experts router_logits.size(1) probs F.softmax(router_logits, dim-1) # [num_tokens, num_experts] # 1) 计算 f_i真实分发比例 tokens_per_expert torch.zeros(num_experts, devicerouter_logits.device) for k in range(top_k): tokens_per_expert.scatter_add_( 0, dispatch_indices[:, k], torch.ones(num_tokens, devicerouter_logits.device), ) f_i tokens_per_expert / (num_tokens * top_k) # 2) 计算 P_irouter 平均分配概率只统计被选中的 top-k 位置 mask torch.zeros_like(probs) for k in range(top_k): mask.scatter_(1, dispatch_indices[:, k:k1], 1.0) p_i (probs * mask).sum(dim0) / (num_tokens * top_k) # 3) 等开销负载均衡损失乘以专家数做归一化 aux_loss num_experts * (f_i * p_i).sum() return aux_loss这段代码有两个细节值得说。一是f_i的归一化top-k路由下每个token会被分配top_k个专家所以总的分配次数是num_tokens * top_k不能用num_tokens直接做分母否则f_i加起来会超过1loss数值虚高。二是P_i的统计router算出来的probs分布在所有专家上但实际只有被选中那top_k个位置才对本次分发起作用所以要用mask把没选中的位置清零再除以num_tokens * top_k这是top-k版本和Switch Transformer原文top-1版本的主要区别。在分布式训练里关键要确认tokens_per_expert是从all-to-all通信之后的真实结果统计出来的不是本地router的预估值。因为token被发送到专家所在设备后才真正“占用”了专家的计算资源负载均衡损失必须以真实占用为准。实践上在Megatron或DeepSpeed框架里all-to-all之后每个EP rank都持有自己负责的那批专家的token数跨rank汇总后再算这个loss才能在全局意义上做到均衡。4.5 容量因子与token丢弃以及训练超参经验即使有均衡损失router在一个batch内也做不到完全平均富余的token超过专家处理上限时怎么办这就引入了容量因子capacity factor的概念capacity ceil(tokens_per_batch / num_experts) * capacity_factor比如一个batch有1024个token专业化分到128个tokencapacity_factor设成1.25那每个专家的token容量就是160个。如果实际分配到某个专家的token超过160超出的部分会被丢弃drop或转给其他专家overflow routing。在训练初期或finetune阶段模型路由还很不稳定capacity_factor建议设大一点1.2到1.5宁可多算一点也别让token丢太多预训练后期模型路由逐渐稳定可以往1.0到1.05收紧追求极致算力效率。关于aux loss系数α的经验Switch Transformer原文里不少实验用的是0.01但我自己的实践是这个值要和损失量级、batch大小一起看。loss公式里已经乘了专家数N做了归一化所以专家数量变化不敏感但batch越大的时候f_i和P_i的统计噪声越小同样的α可以保持效果会更稳。微调阶段我强烈建议把α调小或者直接置0。原因很简单预训练要的是一个通用且均衡的router微调时已经用高质量指令数据修复了模型的具体能力再强制执行“雨露均沾”router会把本该分给专业专家的token强行分走表现为微调后模型在特定任务上的表现停滞甚至倒退。我自己在做领域微调时会把α从预训练时的0.01降到0.001然后在几千步内观察tokens_per_expert的分布是否仍然均匀如果不均匀再微调α。有一种更精细的做法是给α加warmup前2000步从0线性升到目标值避免训练起步阶段均衡压力太大让router先学一些基本的token-expert对应关系。5. 常见问题与排查技巧实录这一节把我真实踩过、也帮别人排查过的高频问题整理成一个速查表。每一类问题背后都有对应的排查路径和修复思路。5.1 LVS的哈希失效导致的单机热点现象配置了正确的轮询或最小连接算法但通过ipvsadm -L -n看连接分布流量依然清一色打在同一个后端上。排查方向先看算法是否真的生效了。有一种情况是调度请求来自同一个NAT网关的同一组源端口段LVS对TCP状态做了持久性绑定persistence默认persistence_timeout为0才不粘滞但如果你之前设置过需要确认清掉了。还有一种情况是后端服务主动对客户端做了长连接保持比如HPC里的任务分发服务用HTTP keep-alive本来10秒内的请求都被服务端吸附在一条TCP连接上跟负载均衡器没关系。修复确认LB算法执行ipvsadm -L -n --persistent-conn查看持久连接数量。如果是服务层长连接导致的那就不是LVS的问题别在四层硬扛该调服务端keep-alive超时就调。HPC的API网关一般不用长连接粘滞把后端的idle超时调小比较省事。5.2 NIC Teaming单TCP流不叠加现象Windows节点做完Teaming之后单块万兆卡的吞吐测试能跑满但同一个大文件传输任务只吃到一块卡的带宽另一块卡几乎没有流量。原因正如前文说的哈希均衡粒度是流而不是字节。同一对IP和端口的连接只能映射到一张物理网卡上。Teaming没法让单流跑多卡。修复HPC文件共享场景用SMB Multichannel通用场景用多个并发TCP流来测吞吐。如果你确实有单流超高带宽需求那就别指望网卡绑定上RoCE或者InfiniBand更现实。这个认知建议在方案设计阶段就确认不然后面验证吞吐的时候会一直怀疑自己的配置有问题。5.3 MoE训练GPU利用率锯齿波动现象训练时在监控面板上看到GPU利用率先往上冲然后突然掉到很低的水平再往上冲形成锯齿波形。同时所有卡的利用率走势不一致有几张卡明显偏忙。排查方向先看是不是数据加载或通信没有overlap再看all-to-all这部分的耗时分布。如果单张卡在多个step里都是最晚结束的很大概率是它承载了过热的专家。这个时候把训练日志里的tokens_per_expert打出来看一下比如Total tokens: 8192, Expert 3 received 4320 tokens, Expert 7 received 312, ...如果某几个专家拿了超过平均值一倍以上的token那就是负载均衡没做好。修复先加大capacity_factor到1.25尝试缓解token丢弃再调高aux_loss的α观察2到3个step内tokens_per_expert的max/mean比值。如果α提到0.02以上还是不均衡就要检查是不是token的序列位置分布有强规律导致同一个位置上的token经常触发同一批专家。这种情况可以考虑调整MoE层的专家数量或者把router层的attention维度做大一点让它有更多特征可学。还有一种不常见但我碰到过的问题aux loss梯度爆炸。检查loss曲线如果aux loss比主loss高了好几个数量级大概率是f_i或P_i的计算没有正确归一化比如top-k的k没除导致数值倍增。这种情况下模型会快速退化loss直接飘走最好在代码里加个断言确认f_i.sum()和p_i.sum()都在1.0附近。5.4 一套快速自检清单网络层用ipvsadm -L -n --stats看每台后端的活跃连接数和累计流量差距超过15%就要警惕。主机层用iperf3 -P 8并发测试带宽确认Teaming总吞吐是否达到预期。算力层监控日志里打印的max_tokens_per_expert / mean_tokens_per_expert这个比值超过2基本就是负载失衡。训练状态留意GPU利用率曲线是否平滑整体利用率长时间低于60%时先怀疑负载均衡而不是网络故障。最后我把这些经验写下来最想表达的一个体会是高性能计算里的负载均衡越往上走越难看到立竿见影的收益但出了问题影响也越大。网络层的LVS是显式的配置错了能立刻看出来流量分布就摆在那里Windows多网卡Teaming是半显式的哈希算法隐匿了细节需要测吞吐才能发现问题而MoE里的等开销负载均衡是隐式的它藏在loss曲线和GPU利用率锯齿背后需要盯tokens_per_expert的分布才能抓住线索。每次在训练集群里处理负载失衡我都提醒自己先看数据分布再看配置项别凭感觉调参。希望这篇文章能帮你在自己的HPC环境里少走几个我走过的弯路。
返回列表