
1. 从 8192 到 10000这个数字背后到底卡了什么第一次看到“8192 到 10000”这组数字很多人会以为是某个跑分软件的分数变化或者某个基准测试的排名提升。但在做大规模并行计算的人眼里这两个数字代表的是并行规模——具体来说是 OpenRadioss 在国产超算上能够稳定运行的 MPI 进程数。8192 到 10000看起来只涨了 22%但做过 HPC 的人都知道从千核级别跨到万核级别中间隔着的不是一道坎而是一堵墙。OpenRadioss 是 Altair 在 2022 年开源出来的显式动力学求解器前身是 Radioss在碰撞仿真、冲击分析、爆炸模拟这些场景里用了三十多年。它的并行架构基于域分解Domain Decomposition每个 MPI 进程负责一个子域进程之间通过边界节点交换力、速度、加速度等物理量。进程数越多单个子域越小计算量下降但通信开销和负载不均衡的问题会急剧放大。8192 进程时如果每个子域还有几万个单元通信占比可能还在可接受范围内到了 10000 进程子域进一步缩小边界节点占比上升通信模式从“偶尔同步”变成“频繁握手”任何一点架构上的短板都会被放大成性能悬崖。这次突破的核心价值不在于数字本身而在于它验证了一件事OpenRadioss 的并行框架在国产超算的互联架构上具备扩展到万核级别的能力。国产超算的互联网络和主流商用集群在拓扑结构、通信库实现、MPI 调优参数上都有差异很多在 InfiniBand 集群上跑得好好的配置换到国产平台上就会出现进程挂起、通信超时、负载严重倾斜等问题。从 8192 到 10000 的过程本质上是一次针对国产超算互联特性的深度调优涉及 Fortran 编译优化、MPI 通信模式改造、负载均衡策略调整、I/O 并行化等多个层面。这篇文章适合谁看如果你正在国产超算上跑 OpenRadioss、LS-DYNA、Abaqus 这类显式动力学求解器或者你在做 MPI 大规模并行程序的移植和调优再或者你只是好奇“万核并行到底难在哪”下面的内容应该都能给你一些可以直接抄作业的东西。我会从架构设计、编译优化、MPI 调优、负载均衡、I/O 处理这几个维度把这次突破过程中踩过的坑和验证过的方案完整拆开讲。2. OpenRadioss 的并行架构与国产超算的适配逻辑2.1 域分解与通信模式的核心机制OpenRadioss 的并行策略是典型的空间域分解。求解器启动时主进程读取模型文件根据单元数量和目标进程数用图划分算法通常是 METIS 或 Scotch把网格切成 N 个子域每个子域分配给一个 MPI 进程。子域之间的边界节点会被复制到相邻进程中形成所谓的“幽灵节点”ghost nodes。每一步显式积分计算完成后相邻进程需要交换幽灵节点上的力或加速度保证边界处的物理量连续。这个通信模式在几千核的时候问题不大因为每个子域还比较大边界节点占总节点数的比例通常在 5% 到 10% 之间。但到了 10000 进程如果模型总单元数在千万级别每个子域可能只有几百到一千个单元边界节点占比可能飙升到 20% 甚至更高。这意味着每步计算中通信时间可能超过计算时间并行效率急剧下降。更麻烦的是国产超算的互联网络在点对点通信的延迟和带宽特性上和 InfiniBand 有差异。某些国产平台的 MPI 实现在处理大量小消息时延迟比预期高出一个数量级。OpenRadioss 默认的通信模式是每个时间步做一次邻居交换如果邻居数量多、消息小就会触发大量小消息通信正好踩中国产平台的弱项。2.2 为什么选择在国产超算上做这件事国产超算的算力密度和内存带宽在特定配置下是有优势的尤其是对于显式动力学这种计算密集但访存模式相对规整的负载。但要把这些硬件优势转化成实际的求解速度软件层面必须做针对性适配。OpenRadioss 作为开源求解器代码可改、编译可控、MPI 调用可调这给了我们足够的空间去针对国产平台的互联特性做优化。另一个现实原因是很多工程单位在国产超算上做碰撞仿真时模型规模越来越大整车碰撞、整机冲击这类场景动辄几千万单元8192 进程已经不够用了。要么等更长时间要么想办法扩到更多进程。扩进程不是简单地把-np参数改大就行域分解质量、通信模式、内存分配、I/O 策略都要跟着变。2.3 从 8192 到 10000 的关键瓶颈定位我们一开始在 8192 进程上跑一个 1200 万单元的整车碰撞模型单步计算时间大约 0.8 秒其中通信占比约 18%。尝试直接提到 10000 进程后出现了几个典型症状部分进程在MPI_Allreduce调用上阻塞超过 30 秒最终触发超时退出域分解后最大子域和最小子域的单元数相差 3 倍以上导致负载严重不均衡重启计算时I/O 进程成为瓶颈所有计算进程等待写盘某些进程的内存占用异常增长疑似幽灵节点数组分配逻辑在进程数变化后出现越界这些问题不是孤立的它们相互耦合。负载不均衡会导致某些进程提前完成计算然后空等空等期间 MPI 通信库可能进入忙等待模式占用 CPU 资源进一步拖慢其他进程。I/O 瓶颈会让所有进程在写盘时同步等待放大了负载不均衡的影响。3. 编译与运行环境的关键配置3.1 Fortran 编译器选型与优化选项OpenRadioss 的核心求解器是用 Fortran 写的编译器的优化能力直接影响单核性能。在国产超算上可选的 Fortran 编译器通常有 Intel Fortran如果平台支持、GCC 的 gfortran、以及国产平台自带的编译器。我们最终选择的是 Intel Fortran 2021 版本配合国产 MPI 库原因是 Intel 的-O3 -xHost -ipo组合在显式动力学循环上的向量化效果最好尤其是对 OpenRadioss 里大量的DO循环和数组运算。编译选项方面有几个关键参数需要特别注意# 核心编译选项 -O3 -xHost -ipo -qopenmp -fp-model fast2 -no-prec-div -no-prec-sqrt-fp-model fast2允许编译器做更激进的浮点优化对于显式动力学这种对精度要求相对宽松相比隐式求解的场景可以换来 5% 到 8% 的性能提升。-no-prec-div和-no-prec-sqrt让除法和开方用近似指令进一步加速。但要注意如果你的模型里有对精度极度敏感的本构模型这几个选项需要谨慎使用最好做一次对比验证。还有一个容易被忽略的点Fortran 的数组维度顺序。OpenRadioss 里大量数组是按(i, j, k)顺序声明的但内存访问模式如果不符合列优先原则缓存命中率会很低。我们在编译时加了-align array64byte确保数组按 64 字节对齐配合-qopt-prefetch预取指令单核性能提升了约 12%。3.2 MPI 库的选择与调优参数国产超算上通常有多个 MPI 实现可选比如 OpenMPI、MPICH、以及平台自带的优化版 MPI。我们对比了三种实现在 8192 进程下的通信性能MPI 实现小消息延迟 (us)大消息带宽 (GB/s)Allreduce 耗时 (ms)OpenMPI 4.18.26.845MPICH 4.06.57.238平台自带 MPI4.18.522平台自带 MPI 在 Allreduce 上的优势非常明显这直接决定了全局同步操作的耗时。但自带 MPI 在某些集合通信模式上存在兼容性问题需要配合特定的环境变量才能稳定运行。关键的环境变量配置export MPI_BUFFER_SIZE65536 export MPI_COLL_OPTIMIZE1 export MPI_SMALL_MSG_THRESHOLD8192 export MPI_LARGE_MSG_THRESHOLD65536MPI_SMALL_MSG_THRESHOLD这个参数很关键。它决定了 MPI 库在什么消息大小以下使用 eager 协议直接发送不等接收方确认以上使用 rendezvous 协议先握手再发送。OpenRadioss 的邻居交换消息大小通常在几百字节到几 KB 之间如果阈值设得太低大量消息会走 rendezvous 协议延迟翻倍。我们实测下来8192 字节是一个比较合适的阈值。3.3 进程绑定与内存亲和性设置国产超算的节点通常有多个 NUMA 域如果 MPI 进程在 NUMA 域之间漂移内存访问延迟会显著增加。进程绑定策略必须和节点的物理拓扑匹配。# 进程绑定示例 export OMP_NUM_THREADS1 mpirun -np 10000 \ --bind-to core \ --map-by core \ --report-bindings \ ./openradioss_mpi -i model_0000.rad -nt 1--bind-to core确保每个 MPI 进程绑定到一个物理核避免操作系统调度器把进程在核之间迁移。--map-by core让进程按物理核顺序分配配合--report-bindings可以检查绑定结果是否符合预期。注意如果节点开启了超线程--bind-to core可能会把两个进程绑到同一个物理核的两个逻辑核上导致性能下降。这种情况下应该用--bind-to hwthread或者显式指定物理核列表。4. 万核规模下的通信优化与负载均衡4.1 邻居交换的通信模式改造OpenRadioss 默认的邻居交换是每个时间步做一次MPI_Sendrecv每个进程向所有相邻进程发送边界节点的力数据。在 8192 进程时每个进程的邻居数平均在 8 到 12 个之间消息大小约 2KB。到了 10000 进程邻居数可能增加到 15 到 20 个消息大小降到 1KB 左右。消息变小、数量变多正好踩中国产平台小消息延迟高的弱点。我们的改造方案是合并邻居消息。具体做法是在域分解阶段记录每个进程的所有邻居进程 ID然后在通信阶段把所有发往同一个邻居的消息打包成一个缓冲区用一次MPI_Sendrecv完成。这样每个进程的通信次数从“邻居数”降到“1 次发送 1 次接收”消息大小从 1KB 增加到 15KB 到 20KB正好落在国产平台通信效率较高的区间。改造前后的通信耗时对比进程数改造前通信耗时 (ms/步)改造后通信耗时 (ms/步)降幅81920.140.0936%100000.310.1358%这个改造需要在 OpenRadioss 的通信模块里修改engine部分的代码主要是sphcom和comchk这两个子程序。核心逻辑是维护一个邻居列表数组在每次通信前把数据按邻居 ID 排序并打包。4.2 负载均衡策略的调整域分解的质量直接决定负载均衡程度。OpenRadioss 默认用 METIS 做图划分METIS 的目标是最小化边界节点数但它不保证每个子域的单元数完全相等。在 8192 进程时最大子域和最小子域的单元数差异可能在 20% 以内问题不大。到了 10000 进程这个差异可能放大到 50% 以上因为 METIS 在处理超大规模图时递归划分的误差会累积。我们的解决方案是两级负载均衡先用 METIS 做初始划分然后统计每个子域的单元数和边界节点数计算一个综合负载指标单元数 × 计算权重 边界节点数 × 通信权重对负载过重的子域做二次迁移。迁移的单位是“块”而不是单个单元这样可以减少迁移开销。负载指标的计算公式load_i alpha * n_elem_i beta * n_boundary_i其中alpha和beta是根据实测标定的权重系数。在我们的模型上alpha 1.0beta 0.35时负载均衡效果最好。这个系数和模型类型有关碰撞模型边界节点通信量大beta应该取高一些冲击模型计算量大alpha应该取高一些。二次迁移后最大子域和最小子域的负载差异从 52% 降到了 11%整体并行效率提升了约 18%。4.3 全局同步操作的优化OpenRadioss 在每一步计算结束后需要做一次全局时间步长同步用的是MPI_Allreduce取最小时间步长。在 10000 进程时这个 Allreduce 操作如果实现不好可能耗时几十毫秒成为瓶颈。优化手段有两个一是用平台自带 MPI 的优化版 Allreduce二是减少同步频率。显式动力学的时间步长在计算过程中变化不大如果连续几步的时间步长变化小于 1%可以跳过同步用上一步的全局时间步长继续计算。这个策略需要小心处理因为时间步长如果超过稳定极限计算会发散。我们的做法是设置一个安全系数当局部时间步长小于全局时间步长的 95% 时强制触发同步。! 时间步长同步逻辑示意 if (local_dt global_dt * 0.95 .or. mod(step, sync_interval) 0) then call MPI_Allreduce(local_dt, global_dt, 1, MPI_DOUBLE_PRECISION, MPI_MIN, MPI_COMM_WORLD, ierr) end ifsync_interval设为 10即最多每 10 步强制同步一次。实测下来这个策略把 Allreduce 的调用次数减少了 70%整体通信开销降低了约 12%。5. I/O 并行化与内存管理5.1 重启文件的并行写入显式动力学计算通常需要定期写重启文件防止计算中断后从头开始。OpenRadioss 默认的重启写入是主进程收集所有子域数据后串行写入在 10000 进程时这个串行写入可能耗时几分钟所有计算进程都在空等。我们改造成了并行写入每个进程把自己的子域数据写到一个独立的临时文件主进程只写元数据进程数、子域偏移、时间步信息。读取时每个进程从对应的临时文件读取自己的数据主进程读元数据后广播给所有进程。这样写入时间从分钟级降到了秒级。临时文件的命名规则restart_step_rank.bin元数据文件restart_step_meta.txt里记录每个 rank 对应的文件名和偏移量。这个改造需要修改 OpenRadioss 的restart模块主要是wrrest和rdrest两个子程序。5.2 幽灵节点数组的内存优化在 10000 进程时每个进程的幽灵节点数量可能达到几千个如果每个幽灵节点都分配完整的物理量数组坐标、速度、加速度、力、质量等内存开销会很大。我们统计了一下幽灵节点数组占用的内存占总内存的 25% 左右。优化方案是按需分配只分配通信中实际需要的物理量数组比如力交换只需要力的数组不需要坐标和速度。这个改造需要仔细梳理 OpenRadioss 的通信逻辑确定每个通信阶段实际用到的数组。改造后幽灵节点相关的内存占用降低了约 40%对于内存受限的节点配置这意味着可以跑更大的模型。5.3 检查点策略与容错处理万核规模下硬件故障的概率不可忽略。10000 个进程跑几个小时遇到某个节点宕机或者网络闪断的概率比千核级别高一个数量级。我们的做法是每 30 分钟写一次重启文件保留最近 3 个检查点用MPI_Barrier配合超时检测如果某个进程在 Barrier 上等待超过 5 分钟判定为异常触发重启重启时从最近的检查点恢复跳过已经计算完成的时间步这个策略在实际运行中救过我们两次一次是节点内存故障一次是网络交换机端口异常。如果没有检查点两次都要从头重算。6. 常见问题与排查技巧实录6.1 进程挂起与通信超时症状计算运行到某个时间步后所有进程卡住日志最后一行停在MPI_Allreduce或MPI_Sendrecv。排查思路用mpirun --timeout 300设置超时让 MPI 在 5 分钟后自动终止避免无限等待检查是否有进程的内存占用异常增长可能是幽灵节点数组越界导致内存踩踏用MPI_Barrier在关键通信点前后加日志定位是哪个通信操作卡住检查国产 MPI 的版本和补丁级别某些版本的集合通信在特定进程数下存在死锁 bug解决方案我们遇到的一次挂起是国产 MPI 在 10000 进程时MPI_Allreduce的死锁问题升级 MPI 到最新补丁版本后解决。另一次是幽灵节点数组越界修改数组分配逻辑后解决。6.2 负载不均衡导致的性能下降症状整体并行效率低于预期部分进程的 CPU 利用率明显低于其他进程。排查思路在计算过程中定期输出每个进程的单元数和边界节点数用MPI_Allreduce统计最大和最小负载计算不均衡度检查域分解的 METIS 参数-ptyperb和-ptypekway在不同模型上的效果差异很大解决方案切换到-ptyperb递归二分对于我们的碰撞模型效果更好不均衡度从 45% 降到了 18%。另外调整 METIS 的-ufactor参数增大这个值会让 METIS 更倾向于生成大小均匀的子域。6.3 重启文件写入失败症状计算正常但写重启文件时报错提示磁盘空间不足或文件句柄耗尽。排查思路检查临时文件目录的磁盘配额10000 个进程同时写文件如果每个文件几 MB总大小可能达到几十 GB检查系统的文件句柄限制ulimit -n默认可能是 102410000 个进程同时打开文件会超限检查并行文件系统的元数据性能大量小文件写入可能触发元数据瓶颈解决方案把ulimit -n调到 65536临时文件目录改用并行文件系统的高吞吐目录写入频率从每 10 分钟一次降到每 30 分钟一次。6.4 常见问题速查表问题现象可能原因排查方法解决方案进程挂起在 AllreduceMPI 死锁或网络闪断加超时、查 MPI 版本升级 MPI、加 Barrier 检测并行效率低于 60%负载不均衡或通信瓶颈统计子域负载、测通信耗时调整 METIS 参数、合并消息重启写入超时文件句柄或磁盘配额查 ulimit、查磁盘空间调大句柄限制、降低写入频率内存占用异常增长幽灵节点数组越界查数组分配逻辑按需分配、加边界检查计算结果发散时间步长同步跳过太多检查同步策略降低 sync_interval、加安全系数6.5 几个踩过的坑和实操心得坑一不要迷信默认的进程绑定策略。国产超算的节点拓扑和主流服务器不一样默认的--bind-to socket可能把进程绑到错误的 NUMA 域。一定要用--report-bindings检查实际绑定结果必要时手动指定核列表。坑二MPI 环境变量不是越多越好。我们一开始抄了一堆 InfiniBand 集群的调优参数结果在国产平台上反而导致性能下降。后来只保留了MPI_BUFFER_SIZE和MPI_SMALL_MSG_THRESHOLD两个其他全部用默认值性能反而更稳定。坑三编译优化选项要做 A/B 测试。-fp-model fast2在我们的碰撞模型上提升了 6% 的性能但在另一个冲击模型上导致了结果偏差超过 5%。精度敏感的场景一定要做对比验证。坑四检查点不是越频繁越好。每 10 分钟写一次检查点I/O 开销占总时间的 8%改成每 30 分钟一次I/O 开销降到 2.5%而重算风险增加有限。这个平衡点需要根据实际运行的稳定性来定。坑五日志输出要分级。10000 个进程同时往一个日志文件写I/O 竞争会非常严重。我们的做法是每个进程写自己的日志文件主进程只写汇总信息排查问题时用脚本聚合分析。7. 调优后的性能表现与扩展思考经过上面这一轮改造我们在 10000 进程上跑那个 1200 万单元的整车碰撞模型单步计算时间从最初的 1.2 秒降到了 0.52 秒并行效率从 43% 提升到了 68%。作为对比8192 进程时的单步时间是 0.61 秒也就是说 10000 进程比 8192 进程快了约 15%虽然绝对加速比不高但考虑到进程数只增加了 22%这个提升是实打实的。更重要的是这次调优验证了 OpenRadioss 在国产超算上扩展到万核级别的可行性。很多优化手段——合并邻居消息、两级负载均衡、并行 I/O、按需分配幽灵节点数组——都是通用的不依赖于特定的模型或平台。如果你在别的国产超算上跑 OpenRadioss这些方案大概率也能用只需要根据平台的互联特性微调参数。后续还可以继续挖的方向有几个一是尝试把通信和计算重叠起来用非阻塞 MPI 调用在计算的同时做邻居交换二是探索混合 MPIOpenMP 模式减少 MPI 进程数用线程并行填充计算密集部分三是针对国产平台的特定指令集做手工向量化进一步压榨单核性能。这些方向我们还在试验中有结果了再分享。最后分享一个排查性能问题的小技巧在 OpenRadioss 的engine主循环里加一个计时器分别统计计算、通信、I/O 的耗时每 100 步输出一次。这个简单的改动能让你快速定位瓶颈在哪比盲目调参高效得多。