OpenRadioss万核并行优化:从8192到10000进程的国产超算实践
2026/9/24 20:43:44 网站建设 项目流程

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 fast=2 -no-prec-div -no-prec-sqrt

-fp-model fast=2允许编译器做更激进的浮点优化,对于显式动力学这种对精度要求相对宽松(相比隐式求解)的场景,可以换来 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.845
MPICH 4.06.57.238
平台自带 MPI4.18.522

平台自带 MPI 在 Allreduce 上的优势非常明显,这直接决定了全局同步操作的耗时。但自带 MPI 在某些集合通信模式上存在兼容性问题,需要配合特定的环境变量才能稳定运行。

关键的环境变量配置:

export MPI_BUFFER_SIZE=65536 export MPI_COLL_OPTIMIZE=1 export MPI_SMALL_MSG_THRESHOLD=8192 export MPI_LARGE_MSG_THRESHOLD=65536

MPI_SMALL_MSG_THRESHOLD这个参数很关键。它决定了 MPI 库在什么消息大小以下使用 eager 协议(直接发送,不等接收方确认),以上使用 rendezvous 协议(先握手再发送)。OpenRadioss 的邻居交换消息大小通常在几百字节到几 KB 之间,如果阈值设得太低,大量消息会走 rendezvous 协议,延迟翻倍。我们实测下来,8192 字节是一个比较合适的阈值。

3.3 进程绑定与内存亲和性设置

国产超算的节点通常有多个 NUMA 域,如果 MPI 进程在 NUMA 域之间漂移,内存访问延迟会显著增加。进程绑定策略必须和节点的物理拓扑匹配。

# 进程绑定示例 export OMP_NUM_THREADS=1 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部分的代码,主要是sphcomcomchk这两个子程序。核心逻辑是维护一个邻居列表数组,在每次通信前把数据按邻居 ID 排序并打包。

4.2 负载均衡策略的调整

域分解的质量直接决定负载均衡程度。OpenRadioss 默认用 METIS 做图划分,METIS 的目标是最小化边界节点数,但它不保证每个子域的单元数完全相等。在 8192 进程时,最大子域和最小子域的单元数差异可能在 20% 以内,问题不大。到了 10000 进程,这个差异可能放大到 50% 以上,因为 METIS 在处理超大规模图时,递归划分的误差会累积。

我们的解决方案是两级负载均衡:先用 METIS 做初始划分,然后统计每个子域的单元数和边界节点数,计算一个综合负载指标(单元数 × 计算权重 + 边界节点数 × 通信权重),对负载过重的子域做二次迁移。迁移的单位是“块”而不是单个单元,这样可以减少迁移开销。

负载指标的计算公式:

load_i = alpha * n_elem_i + beta * n_boundary_i

其中alphabeta是根据实测标定的权重系数。在我们的模型上,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 if

sync_interval设为 10,即最多每 10 步强制同步一次。实测下来,这个策略把 Allreduce 的调用次数减少了 70%,整体通信开销降低了约 12%。

5. I/O 并行化与内存管理

5.1 重启文件的并行写入

显式动力学计算通常需要定期写重启文件,防止计算中断后从头开始。OpenRadioss 默认的重启写入是主进程收集所有子域数据后串行写入,在 10000 进程时,这个串行写入可能耗时几分钟,所有计算进程都在空等。

我们改造成了并行写入:每个进程把自己的子域数据写到一个独立的临时文件,主进程只写元数据(进程数、子域偏移、时间步信息)。读取时,每个进程从对应的临时文件读取自己的数据,主进程读元数据后广播给所有进程。这样写入时间从分钟级降到了秒级。

临时文件的命名规则:

restart_<step>_<rank>.bin

元数据文件restart_<step>_meta.txt里记录每个 rank 对应的文件名和偏移量。这个改造需要修改 OpenRadioss 的restart模块,主要是wrrestrdrest两个子程序。

5.2 幽灵节点数组的内存优化

在 10000 进程时,每个进程的幽灵节点数量可能达到几千个,如果每个幽灵节点都分配完整的物理量数组(坐标、速度、加速度、力、质量等),内存开销会很大。我们统计了一下,幽灵节点数组占用的内存占总内存的 25% 左右。

优化方案是按需分配:只分配通信中实际需要的物理量数组,比如力交换只需要力的数组,不需要坐标和速度。这个改造需要仔细梳理 OpenRadioss 的通信逻辑,确定每个通信阶段实际用到的数组。

改造后,幽灵节点相关的内存占用降低了约 40%,对于内存受限的节点配置,这意味着可以跑更大的模型。

5.3 检查点策略与容错处理

万核规模下,硬件故障的概率不可忽略。10000 个进程跑几个小时,遇到某个节点宕机或者网络闪断的概率比千核级别高一个数量级。我们的做法是:

  • 每 30 分钟写一次重启文件,保留最近 3 个检查点
  • MPI_Barrier配合超时检测,如果某个进程在 Barrier 上等待超过 5 分钟,判定为异常,触发重启
  • 重启时从最近的检查点恢复,跳过已经计算完成的时间步

这个策略在实际运行中救过我们两次,一次是节点内存故障,一次是网络交换机端口异常。如果没有检查点,两次都要从头重算。

6. 常见问题与排查技巧实录

6.1 进程挂起与通信超时

症状:计算运行到某个时间步后,所有进程卡住,日志最后一行停在MPI_AllreduceMPI_Sendrecv

排查思路

  1. mpirun --timeout 300设置超时,让 MPI 在 5 分钟后自动终止,避免无限等待
  2. 检查是否有进程的内存占用异常增长,可能是幽灵节点数组越界导致内存踩踏
  3. MPI_Barrier在关键通信点前后加日志,定位是哪个通信操作卡住
  4. 检查国产 MPI 的版本和补丁级别,某些版本的集合通信在特定进程数下存在死锁 bug

解决方案:我们遇到的一次挂起是国产 MPI 在 10000 进程时MPI_Allreduce的死锁问题,升级 MPI 到最新补丁版本后解决。另一次是幽灵节点数组越界,修改数组分配逻辑后解决。

6.2 负载不均衡导致的性能下降

症状:整体并行效率低于预期,部分进程的 CPU 利用率明显低于其他进程。

排查思路

  1. 在计算过程中定期输出每个进程的单元数和边界节点数
  2. MPI_Allreduce统计最大和最小负载,计算不均衡度
  3. 检查域分解的 METIS 参数,-ptype=rb-ptype=kway在不同模型上的效果差异很大

解决方案:切换到-ptype=rb(递归二分)对于我们的碰撞模型效果更好,不均衡度从 45% 降到了 18%。另外,调整 METIS 的-ufactor参数,增大这个值会让 METIS 更倾向于生成大小均匀的子域。

6.3 重启文件写入失败

症状:计算正常,但写重启文件时报错,提示磁盘空间不足或文件句柄耗尽。

排查思路

  1. 检查临时文件目录的磁盘配额,10000 个进程同时写文件,如果每个文件几 MB,总大小可能达到几十 GB
  2. 检查系统的文件句柄限制,ulimit -n默认可能是 1024,10000 个进程同时打开文件会超限
  3. 检查并行文件系统的元数据性能,大量小文件写入可能触发元数据瓶颈

解决方案:把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_SIZEMPI_SMALL_MSG_THRESHOLD两个,其他全部用默认值,性能反而更稳定。

坑三:编译优化选项要做 A/B 测试-fp-model fast=2在我们的碰撞模型上提升了 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 调用在计算的同时做邻居交换;二是探索混合 MPI+OpenMP 模式,减少 MPI 进程数,用线程并行填充计算密集部分;三是针对国产平台的特定指令集做手工向量化,进一步压榨单核性能。这些方向我们还在试验中,有结果了再分享。

最后分享一个排查性能问题的小技巧:在 OpenRadioss 的engine主循环里加一个计时器,分别统计计算、通信、I/O 的耗时,每 100 步输出一次。这个简单的改动能让你快速定位瓶颈在哪,比盲目调参高效得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询