调试性能回退问题
摘要
Guix HPC 团队的一篇详细博客文章,描述他们如何在 Slingshot 互连上识别并调试 MPI 堆栈中的性能回退问题,展示了 Guix 的透明性和控制能力。
<p><a href="https://lobste.rs/s/ifefxo/debugging_performance_regressions">评论</a></p>
查看缓存全文
缓存时间: 2026/07/10 18:11
# 调试性能回退 来源:https://hpc.guix.info/blog/2026/07/debugging-performance-regressions/
MPI 软件栈是复杂的存在,在应用性能中扮演关键角色。Guix 为多种高速互连(InfiniBand、Omni-Path、Slingshot、ROCE 等)打包了 Open MPI(https://hpc.guix.info/blog/2019/12/optimized-and-portable-open-mpi-packaging/),目标是让每种互连都达到峰值性能。从打包角度看,关键挑战是确保当 Open MPI 或其依赖项发生变化时,任何网络类型都不会出现性能回退。以 Guix 为主要部署工具的集群管理员通常会有相应的检查机制。但如果回退真的发生了呢?这篇博文是一份来自一线的报告,展示了如何识别 Slingshot 互连上 MPI 栈的性能回退。这个调试故事本身就展示了 Guix 所提供的控制力和透明性。
## 剧情
Slingshot 支持在 Guix 打包的 Open MPI 中相对较新(https://hpc.guix.info/blog/2024/11/targeting-the-crayhpe-slingshot-interconnect/),并且迄今为止只涉及 Guix 在 HPC 中使用的较小部分。没有为此互连设置自动化的性能回退测试,主要是由于难以访问硬件来进行测试。于是,不可避免的事情发生了:性能回退降临了。
症状很简单:运行 MPI 带宽/延迟基准测试显示性能不佳(这里我们通过 `--with-input`(https://guix.gnu.org/manual/devel/en/html_node/Package-Transformation-Options.html)强制使用 Open MPI 版本 5):
```
$ guix shell intel-mpi-benchmarks --with-input=openmpi=openmpi@5 openmpi@5 -- \
mpirun IMB-MPI1 PingPong
...
# PingPong
#---------------------------------------------------
# Benchmarking PingPong
# #processes = 2
#---------------------------------------------------
#bytes #repetitions t[usec] Mbytes/sec
0 1000 37.94 0.00
1 1000 38.30 0.03
2 1000 38.41 0.05
...
1048576 40 575.85 1820.92
2097152 20 983.58 2132.15
4194304 10 1817.18 2308.15
```
哎呀——峰值带宽只有 2.3 GB/s,而预期是 25 GB/s 左右。发生这种情况的原因是 libcxi(底层 Slingshot 支持库)出错,然后 libfabric(位于 libcxi 上一层)将该错误传播给 Open MPI,后者又回退到普通的 TCP/IP over Ethernet。我们可以通过传递正确的标志和环境变量,从 Open MPI、libfabric 和 libcxi 获取调试信息:
```
$ FI_LOG_LEVEL=9 guix shell intel-mpi-benchmarks --with-input=openmpi=openmpi@5 openmpi@5 -- \
mpirun --mca btl_ofi_verbose 3 --mca pml_ucx_verbose 3 --mca btl ofi --mca mtl ofi --mca btl_base_verbose 100 IMB-MPI1 PingPong
[c1510:1386001] mca: base: components_register: registering framework btl components
[c1510:1386001] mca: base: components_register: found loaded component ofi
[c1510:1386001] mca: base: components_register: component ofi register function successful
...
[c1510:1386001] mtl_ofi_component.c:343: mtl:ofi:provider: cxi
[c1510:1386001] mtl_ofi_component.c:368: mtl:ofi:provider:domain: cxi0
libfabric:1386001:1781018439::cxi:domain:cxip_domain_enable():457 c1510: Security Issue: Using unrestricted service ID 1 for cxi0. Please provide a service ID via auth_key fields.
libfabric:1386001:1781018439::cxi:domain:cxip_cp_get():109 c1510: Failed to allocate CP, ret: -22 VNI: 1 TC: BEST_EFFORT TYPE: DEFAULT
libfabric:1386001:1781018439::cxi:domain:cxip_cp_get():112 c1510: Remapping original TC from BEST_EFFORT to BEST_EFFORT
libfabric:1386001:1781018439::cxi:domain:cxip_cp_get():137 c1510: Failed to allocate CP, ret: -22 VNI: 1 TC: BEST_EFFORT TYPE: DEFAULT
libfabric:1386001:1781018439::cxi:domain:cxip_cmdq_alloc():289 c1510: Failed to allocate CP: -22
libfabric:1386001:1781018439::cxi:ep_ctrl:cxip_ep_cmdq():95 c1510: Unable to allocate CMDQ, ret: -22
libfabric:1386001:1781018439::cxi:ep_ctrl:cxip_ep_ctrl_init():567 c1510: Failed to allocate control TXQ, ret: -28
libfabric:1386001:1781018439::cxi:ep_ctrl:cxip_ep_enable():691 c1510: cxip_ep_ctrl_init returned: -262
[c1510:1386001] mtl_ofi_component.c:1093:fi_enable failed: Invalid resource domain
[c1510:1386001] select: initializing btl component ofi
[c1510:1386001] btl_ofi_component.c:318: btl:ofi:provider_include = "(null)"
[c1510:1386001] btl_ofi_component.c:320: btl:ofi:provider_exclude = "shm,sockets,tcp,udp,rstream,usnic,net"
libfabric:1386001:1781018439::core:core:fi_getinfo_():1391 fi_getinfo: provider efa output empty list
```
这里的主要线索是“Failed to allocate CP”错误,这些错误源自 libfabric 的 Slingshot “provider”(https://github.com/ofiwg/libfabric/blob/38c339b225a30b9c98af08ca1eb552fe980e391a/prov/cxi/src/cxip_cmdq.c#L97-L113)(CP 在 libcxi 中代表“communication profile”)。
## 二分发行版
接下来该怎么办?如果不是 libcxi 专家,一个好的前进方向是找出导致该回退的变更。这里有两个好消息:首先,Guix 提供的 MPI 栈完全是**自包含**的——所以机器上非 Guix 提供的软件不太可能是罪魁祸首;其次,我们知道一个**已知良好的状态**。更准确地说,我们知道 Guix 的**确切提交**提供了 Slingshot 的“良好”MPI 栈;也知道一个提供了“不良”MPI 栈的提交。值得强调这一点:`guix describe` 显示的 Guix 提交**足以标识整个 MPI 栈**——包括包版本、配置标志、编译器等等。根据一个 Guix 提交,我们可以随时使用 `guix time-machine`(https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-time_002dmachine.html)重新部署该栈。
自然,我们对 Guix 本身进行了二分,以寻找有问题的提交。每一步,我们分配两个节点并运行:
```
guix time-machine -q --commit=THE-COMMIT -- \
shell intel-mpi-benchmarks --with-input=openmpi=openmpi@5 openmpi@5 -- \
mpirun IMB-MPI1 PingPong
```
几分钟之内,我们就可以将正在测试的提交归类为“良好”或“不良”。(通过解析基准测试输出来自动化分类固然很好,但在这个案例中成本/收益比似乎不理想。)有问题的提交最终是一个看似无害的 libcxi 升级(https://codeberg.org/guix/guix/commit/7541cb605c2133b52854e575b97a91d662fd399a)。
## 更细粒度的二分
为什么不精确到引入回退的 libcxi 提交,而非要停留在发行版层面呢?`time-machine` 让我们可以测试发行版中的特定变更,而 `--with-commit` 选项(https://guix.gnu.org/manual/devel/en/html_node/Package-Transformation-Options.html)则允许我们用其某个包的特定提交重新构建整个栈——同时保持栈的其余部分不变。利用这个特性,我们可以继续寻找 bug,这次是在 libcxi 的单个提交层面进行二分,从已知良好的提交(对应 libcxi 版本 13.0.0)到第一个已知不良的提交(libcxi 版本 13.1.0):
```
guix shell intel-mpi-benchmarks --with-input=openmpi=openmpi@5 openmpi@5 \
--with-commit=libcxi=THE-COMMIT -- \
mpirun IMB-MPI1 PingPong
```
这次二分比前一次稍贵一些,因为每一步都需要重新构建大部分 MPI 栈(特别是 libcxi 及其依赖:libfabric 和 Open MPI)。尽管如此,几个小时后还是确定了有问题的提交,使我们能够向 libcxi 开发者提供详细的错误报告(https://github.com/HewlettPackard/shs-libcxi/issues/18)(截至撰写本文时,该 bug 仍处于开启状态)。
## 总结
我写这篇轶事的原因,是因为我认为它展示了对软件栈的一种控制力,如果没有 Guix 或它的兄弟 Nix,这是不可想象的。借助 `guix time-machine`,我们能够二分**整个发行版**,直到找到引入回退的提交;使用 `--with-commit`,我们继续在单个包层面进行调查。目前还没有 `guix bisect` 命令,但正如这个故事所展示的,拥有这样一个命令肯定会很有用。至于跟踪 MPI 栈性能,我们期待建立一个公开可访问的面板,使用 Intel 或 OSU 微基准测试来跟踪 Open MPI 在受支持互连上的性能。敬请期待!
## 致谢
非常感谢 CINES 的同事在初步调查此性能回退时提供的指导。
除非另有说明,本博客文章版权归各自作者所有,并根据 CC-BY-SA 4.0(https://creativecommons.org/licenses/by-sa/4.0/)许可证和 GNU 自由文档许可证(https://www.gnu.org/licenses/fdl-1.3.html)(版本 1.3 或更高,无不变章节、无封面文字、无封底文字)的条款发布。
相似文章
调试挂起的Go程序的技巧
一份实用指南,涵盖了调试挂起的Go程序的三种方法:使用SIGQUIT打印堆栈跟踪、附加delve调试器以及保存核心转储供后续分析。
优化CPU密集型Go热路径的笔记
本文讨论了CPU密集型Go代码的性能优化技术,指出了泛型和接口抽象因无法内联而产生的局限性,并主张在热路径中使用代码复制。文章通过一个Brotli移植示例和深入基准测试进行了说明。
未充分利用的性能
一篇技术博客文章,演示了如何通过LLVM的配置文件引导优化(PGO)在标准-O3和LTO之外显著提升二进制性能,以SQLite作为基准测试。
我进行了一些模型优化技巧,将GH200系统上的GLM5.2从约2.5 tok/s提升至超过50 tok/s。
一篇详细博客文章,描述了如何通过停止模型跨模块通信,并将FP8 MTP头部嫁接至INT4基础模型上,将双Grace Hopper系统上的GLM-5.2推理速度从2.5 tok/s显著提升到超过50 tok/s。
Elixir 应用优化之旅
一位开发者分享了优化 Elixir 应用的经验与教训,重点介绍了针对 Postgres 连接池工具 Ultravisor 的性能改进。文章涵盖了使用火焰图、调用追踪等性能分析技术,以及 eFlambè 和 tprof 等工具。