ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Agentic Workflow改造传统HPC代码:以双电子积分模块为例

2026/8/31 2:34:59 拓冰建站 浏览量
Agentic Workflow改造传统HPC代码:以双电子积分模块为例 之前在维护一套传统 HPC 数值计算模块时我最大的感受是代码完全能跑结果也正确但没有人愿意动它。牵一发动全身循环里藏着几十年前的编程习惯全局 COMMON block 把数据耦合得密密麻麻。直到引入 Agentic Workflow把“理解老代码 - 设计替换方案 - 生成等价实现 - 数值回归验证”这条链路自动化之后才真正敢对一个核心模块动刀。本文就以量子化学软件 GAMESS 中的双电子积分Two-Electron Integral模块为背景完整拆解如何用 Agentic Workflow 对传统 HPC 代码做现代化改造。适合有一定 HPC 或科学计算背景、想用大模型辅助重构遗留系统的开发者阅读。读完你会理解双电子积分为什么难改、Agentic Workflow 如何分角色协作以及怎么搭建一套可验证、可回退的改造流程。1. 背景与核心概念1.1 GAMESS 与双电子积分是什么GAMESSGeneral Atomic and Molecular Electronic Structure System是一款开源量子化学计算软件用于计算分子的能量、几何结构优化、反应路径、光谱性质等。它的底层核心是求解薛定谔方程的近似解常用方法是 Hartree-FockHF和密度泛函理论DFT。无论是 HF 还是 DFT整个计算过程中最耗时、最核心的数值模块之一就是积分计算。特别是双电子积分Two-Electron Integral它描述的是两个电子之间的库仑排斥作用数学形式为[ (ij|kl) \iint \phi_i(\mathbf{r}_1)\phi_j(\mathbf{r}1) \frac{1}{r{12}} \phi_k(\mathbf{r}_2)\phi_l(\mathbf{r}_2) d\mathbf{r}_1 d\mathbf{r}_2 ]其中 ( \phi_i ) 是基函数( r_{12} ) 是两个电子之间的距离。这个积分在 SCF 迭代中会被反复调用。理论上双电子积分的数量随体系大小呈 ( O(N^4) ) 增长实际程序中会利用对称性、积分 cutoff 和 Schwarz 不等式做大量剪枝降到 ( O(N^2) ) 到 ( O(N^3) ) 的量级。尽管如此它仍然是整个 SCF 计算中最大的热点。1.2 为什么双电子积分模块是典型的 Legacy HPC 代码传统意义上的 HPC 代码并不只是“跑得慢”的代码而是“在上一代硬件和编译器环境下写出来的、难以被现代工具链理解和优化”的代码。双电子积分模块是典型代表。早期 Fortran 代码通常具备以下特征大量使用 COMMON block 保存全局状态模块之间的隐式耦合非常强。循环内出现 GOTO 跳转控制流复杂。使用固定维度数组数组大小在编译期写死。对现代 CPU 的向量化、寄存器分配、缓存局部性并不友好。依赖特定 Fortran 编译器的优化开关换一个编译器可能性能差异巨大。这些代码的正确性经过几十年工程验证但可读性、可维护性和性能都已经严重落后。替换它等于在飞行中换引擎。1.3 Agentic Workflow 如何切入Agentic Workflow代理式工作流不是简单的“用大模型重写代码”而是把一次大型重构任务拆成多个可验证的小步骤由不同角色的 AI Agent 分别执行并通过自动化的数值回归测试作为质量门禁。在双电子积分模块的改造中Agentic Workflow 可以拆成四个关键角色代码理解 Agent解析 Fortran 源文件输出函数依赖、变量作用域、全局状态访问情况。转换 Agent基于理解结果把遗留 Fortran 函数等价翻译成现代语言如 C、CUDA 或 Python 原型。验证 Agent构造随机输入、已知解析值、回归数据集对比新旧实现的数值误差。性能 Agent对替换后的代码做 profile确认性能不降级并给出优化建议。这四类 Agent 之间不是简单的串行关系而是一个闭环验证失败时要把错误信息反馈给转换 Agent让它重新生成实现。2. 环境准备与版本说明本文案例需要以下环境。版本不需要和你本机完全一致核心是思路组件说明操作系统LinuxUbuntu 22.04、Rocky Linux 9 或 HPC 集群均可Fortran 编译器gfortran 11 或 Intel ifx用于编译遗留代码Python3.10用于编写验证脚本和 Agent 编排Python 依赖numpy、pytestLLM 接口OpenAI 兼容 API或本地部署 vLLM 等推理服务静态分析工具ctags、tree-sitter-fortran用于代码结构解析性能分析工具perf、gprof用于热点确认版本管理Git用于管理源码和 Agent 中间产物需要说明的是GAMESS 是一个非常庞大的真实科学计算软件完整改造它需要大量工程配合。本文给出的双电子积分示例是教学化最小版本保留真实积分的数学形式和数值特征但把问题缩小到可演示、可验证的规模。理解了这个小闭环再把它放大到真实模块只是工作量问题。3. 核心原理拆解3.1 双电子积分的数学结构与计算特征考虑最简情况四个基函数都是 s 型高斯函数[ \phi_a(\mathbf{r}) \exp(-\alpha |\mathbf{r} - \mathbf{A}|^2) ]其中 ( \alpha ) 是指数( \mathbf{A} ) 是原子中心坐标。此时双电子积分 ( (ss|ss) ) 存在解析表达式。首先两个高斯函数的乘积仍然是一个高斯函数。对于中心在 ( \mathbf{A} )、指数为 ( \alpha ) 的高斯和中心在 ( \mathbf{B} )、指数为 ( \beta ) 的高斯乘积的中心和指数为[ p \alpha \beta,\quad \mathbf{P} \frac{\alpha \mathbf{A} \beta \mathbf{B}}{p} ]最终 ( (ss|ss) ) 的解析形式为[ (ss|ss) \frac{2\pi^{5/2}}{p \cdot q \cdot \sqrt{pq}} \cdot \exp\left(-\frac{\alpha\beta}{\alpha\beta}|\mathbf{A}-\mathbf{B}|^2\right) \cdot \exp\left(-\frac{\gamma\delta}{\gamma\delta}|\mathbf{C}-\mathbf{D}|^2\right) \cdot \exp\left(-\frac{pq}{pq}|\mathbf{P}-\mathbf{Q}|^2\right) ]从代码实现角度看这个公式包含多次指数运算和幂运算浮点开销高。多组三维向量运算遍历内存次数多。中间变量多编译器需要合理调度寄存器。真实 GAMESS 中的积分模块远比这个复杂因为它要处理 p、d、f 等高角动量基函数还要利用对称性。但核心瓶颈特征一致本文用 ( (ss|ss) ) 完全够了。3.2 传统 Fortran 实现为什么会成为改造难点下面是一段教学化的遗留 Fortran 实现把它当成从老代码里抽出来的最小核心片段! legacy_ss_integral.f90 subroutine ss_integral(alpha, beta, gamma, delta, A, B, C, D, result) implicit none double precision, intent(in) :: alpha, beta, gamma, delta double precision, intent(in) :: A(3), B(3), C(3), D(3) double precision, intent(out) :: result double precision :: p, q, pref double precision :: P(3), Q(3) double precision :: AB2, CD2, PQ2 p alpha beta q gamma delta P(1) (alpha * A(1) beta * B(1)) / p P(2) (alpha * A(2) beta * B(2)) / p P(3) (alpha * A(3) beta * B(3)) / p Q(1) (gamma * C(1) delta * D(1)) / q Q(2) (gamma * C(2) delta * D(2)) / q Q(3) (gamma * C(3) delta * D(3)) / q AB2 (A(1) - B(1)) ** 2 (A(2) - B(2)) ** 2 (A(3) - B(3)) ** 2 CD2 (C(1) - D(1)) ** 2 (C(2) - D(2)) ** 2 (C(3) - D(3)) ** 2 PQ2 (P(1) - Q(1)) ** 2 (P(2) - Q(2)) ** 2 (P(3) - Q(3)) ** 2 pref 2.0d0 * 3.141592653589793d0 ** 2.5d0 / (p * q * sqrt(p q)) result pref * exp(-alpha * beta / p * AB2) * exp(-gamma * delta / q * CD2) * exp(-p * q / (p q) * PQ2) end subroutine ss_integral这段代码看起来还比较规范但真实遗留代码往往存在这些问题P 和 Q 是局部数组但真实代码中会被放进 COMMON block供其他子程序共享。大量计算用 GOTO 和递推公式完成循环层数深循环体内部还有条件分支。数组访问顺序与缓存优化不匹配现代 CPU 的 SIMD 向量化很难生效。数学函数调用密集编译器难以判断是否可以安全重排。所以改造的核心目标不只是“换成 Python”而是把难以向量化、难以维护、难以测试的 Fortran 代码替换成结构清晰、可单元测试、可性能调优的现代实现。3.3 Agentic Workflow 的任务分解模式我们不需要让一个 Agent 从头到尾读完整个 GAMESS那是错误思路。正确做法是把大规模任务拆成小粒度、可验证的子任务。下面用文字流程表示 Agent 协作关系注意在真实系统中这通常是一个带循环的 pipeline代码理解阶段代码理解 Agent 接收源文件输出函数清单、调用关系、全局变量访问表。这一步可以用 tree-sitter-fortran 做静态解析减少 LLM 的幻觉风险。热点识别阶段性能 Agent 通过 gprof 或 perf 确认哪些函数是真正值得替换的热点。不要盲目重构所有代码。等价转换阶段转换 Agent 只针对一个函数生成替代实现同时必须输出“转换说明”包括数学公式来源、边界条件处理、浮点累加顺序的变化。验证回归阶段验证 Agent 运行数值测试把误差结果反馈给转换 Agent。如果超过阈值转换 Agent 需要重新生成实现。集成部署阶段替换后的代码通过验证后才合入主工程并保留旧实现作为回退开关。这种设计的关键在于每一步都有明确产出并且每一步都是可验证的。Agent 的“自由发挥”被限制在一个非常窄的范围内。4. 完整实战案例从 Fortran 到 Python 等价转换下面我们用一个最小但完整的案例演示整个 Agentic Workflow。案例会包含四个文件legacy_hpc_modernization/ ├── legacy_ss_integral.f90 # 遗留 Fortran 实现 ├── agentic_workflow.py # Agent 编排脚本 ├── modern_ss_integral.py # 转换 Agent 生成的等价实现 └── validate_conversion.py # 数值验证脚本4.1 编写遗留 Fortran 实现先在legacy_ss_integral.f90中放入前面那段ss_integral子程序并补充一个测试主程序方便后续编译运行! legacy_ss_integral.f90 subroutine ss_integral(alpha, beta, gamma, delta, A, B, C, D, result) implicit none double precision, intent(in) :: alpha, beta, gamma, delta double precision, intent(in) :: A(3), B(3), C(3), D(3) double precision, intent(out) :: result double precision :: p, q, pref double precision :: P(3), Q(3) double precision :: AB2, CD2, PQ2 p alpha beta q gamma delta P(1) (alpha * A(1) beta * B(1)) / p P(2) (alpha * A(2) beta * B(2)) / p P(3) (alpha * A(3) beta * B(3)) / p Q(1) (gamma * C(1) delta * D(1)) / q Q(2) (gamma * C(2) delta * D(2)) / q Q(3) (gamma * C(3) delta * D(3)) / q AB2 (A(1) - B(1)) ** 2 (A(2) - B(2)) ** 2 (A(3) - B(3)) ** 2 CD2 (C(1) - D(1)) ** 2 (C(2) - D(2)) ** 2 (C(3) - D(3)) ** 2 PQ2 (P(1) - Q(1)) ** 2 (P(2) - Q(2)) ** 2 (P(3) - Q(3)) ** 2 pref 2.0d0 * 3.141592653589793d0 ** 2.5d0 / (p * q * sqrt(p q)) result pref * exp(-alpha * beta / p * AB2) * exp(-gamma * delta / q * CD2) * exp(-p * q / (p q) * PQ2) end subroutine ss_integral program test_legacy implicit none double precision :: alpha, beta, gamma, delta double precision :: A(3), B(3), C(3), D(3) double precision :: val alpha 1.0d0 beta 0.5d0 gamma 0.2d0 delta 0.1d0 A (/0.0d0, 0.0d0, 0.0d0/) B (/0.0d0, 0.0d0, 1.0d0/) C (/1.0d0, 0.0d0, 0.0d0/) D (/0.5d0, 0.5d0, 0.0d0/) call ss_integral(alpha, beta, gamma, delta, A, B, C, D, val) print *, val end program test_legacy编译运行gfortran -O2 -o test_legacy legacy_ss_integral.f90 ./test_legacy你会发现输出的是一个双精度浮点数这个值就是后续所有验证的基准之一。4.2 用 Agent 编排脚本规划改造任务下面用 Python 写一个最小化的 Agent 编排脚本。实际项目中你可能用 LangChain、LlamaIndex 或自研 pipeline但核心结构相同# agentic_workflow.py from dataclasses import dataclass, field from typing import Callable dataclass class AgentTask: name: str goal: str input_files: list output_files: list constraints: str class Agent: def __init__(self, role: str, llm: Callable[[str], str]): self.role role self.llm llm def run(self, task: AgentTask) - str: prompt f 你是{self.role} Agent。 任务名称{task.name} 目标{task.goal} 输入文件{, .join(task.input_files)} 约束{task.constraints} 请给出可执行的分析结论或代码并说明理由。 return self.llm(prompt) def build_pipeline(llm: Callable[[str], str]): # 阶段 1代码理解 analyzer Agent(代码理解, llm) analyze_task AgentTask( name解析遗留积分函数, goal列出 ss_integral 子程序的输入、输出、局部变量和潜在数值风险, input_files[legacy_ss_integral.f90], output_files[analysis_report.md], constraints只做静态分析不要修改代码, ) report analyzer.run(analyze_task) print([代码理解阶段], report) # 阶段 2等价转换 translator Agent(等价转换, llm) translate_task AgentTask( name将 Fortran 转换为 Python, goal生成 modern_ss_integral.py保持数学公式完全一致, input_files[legacy_ss_integral.f90, analysis_report.md], output_files[modern_ss_integral.py], constraints必须保留原始公式禁止修改数值算法输出 float64, ) code translator.run(translate_task) print([等价转换阶段], code) # 阶段 3数值验证实际项目里由 pytest 实现 verifier Agent(数值验证, llm) verify_task AgentTask( name验证新旧实现一致性, goal对比 Fortran 与 Python 输出误差小于 1e-12, input_files[modern_ss_integral.py], output_files[verification_report.md], constraints使用随机高斯指数和随机坐标至少测试 1000 组, ) report verifier.run(verify_task) print([数值验证阶段], report) if __name__ __main__: # 用本地函数模拟 LLM实际替换为 OpenAI 兼容 API 或 vLLM def mock_llm(prompt: str) - str: return 已收到任务下一步输出由验证脚本完成。 build_pipeline(mock_llm)这个脚本展示了 Agent 之间如何传递上下文。关键点是每个 Agent 只看到它需要的那部分信息不会一次把整个程序喂给大模型减少了生成无关代码和出错的可能性。4.3 转换 Agent 生成的现代实现在真实流程中转换 Agent 的输出可能是 C 或 CUDA。这里为了演示数值验证先生成 Python 等价实现# modern_ss_integral.py import numpy as np def ss_integral(alpha, beta, gamma, delta, A, B, C, D): 等价实现 (ss|ss) 双电子积分。 对应遗留 Fortran 子程序 legacy_ss_integral.f90 中的 ss_integral。 全部计算使用 float64确保数值精度。 p alpha beta q gamma delta P (alpha * A beta * B) / p Q (gamma * C delta * D) / q AB2 np.dot(A - B, A - B) CD2 np.dot(C - D, C - D) PQ2 np.dot(P - Q, P - Q) pref 2.0 * np.pi ** 2.5 / (p * q * np.sqrt(p q)) return (pref * np.exp(-alpha * beta / p * AB2) * np.exp(-gamma * delta / q * CD2) * np.exp(-p * q / (p q) * PQ2))这里要注意 numpy 向量运算和 Fortran 数组运算的语义一致。A - B是逐元素相减np.dot对应 Fortran 中手写的平方和累加。4.4 数值验证脚本数值验证是整个 Agentic Workflow 的安全网。我们写一个脚本把 Fortran 测试程序输出的基准值与 Python 实现计算结果做对比# validate_conversion.py import subprocess import numpy as np from modern_ss_integral import ss_integral # 编译并运行 Fortran 测试程序 subprocess.run([gfortran, -O2, -o, test_legacy, legacy_ss_integral.f90], checkTrue) output subprocess.check_output([./test_legacy], textTrue) legacy_value float(output.strip().split()[-1]) # 使用与 Fortran 测试程序完全相同的输入 alpha, beta, gamma, delta 1.0, 0.5, 0.2, 0.1 A np.array([0.0, 0.0, 0.0]) B np.array([0.0, 0.0, 1.0]) C np.array([1.0, 0.0, 0.0]) D np.array([0.5, 0.5, 0.0]) modern_value ss_integral(alpha, beta, gamma, delta, A, B, C, D) diff abs(legacy_value - modern_value) print(fFortran 结果: {legacy_value:.16e}) print(fPython 结果: {modern_value:.16e}) print(f绝对误差: {diff:.4e}) if diff 1e-12: print([PASS] 数值一致性验证通过) else: print([FAIL] 数值不一致请检查转换过程)运行python validate_conversion.py预期输出类似Fortran 结果: 1.2345678901234567e00 Python 结果: 1.2345678901234567e00 绝对误差: 0.0000e00 [PASS] 数值一致性验证通过实际数值会受到编译选项和浮点运算顺序的影响但只要阈值设成1e-12对这个简单的 ( (ss|ss) ) 积分来说是很稳妥的。4.5 扩展为随机化回归测试固定输入只能说明“这个点通过了”不够。建议验证脚本改成随机生成 1000 组指数和坐标做批量对比。这一步就是 Agent 验证 Agent 真正执行的任务# stochastic_validation.py import numpy as np from modern_ss_integral import ss_integral rng np.random.default_rng(42) max_diff 0.0 for _ in range(1000): alpha, beta, gamma, delta rng.uniform(0.1, 2.0, size4) A rng.uniform(-1.0, 1.0, size3) B rng.uniform(-1.0, 1.0, size3) C rng.uniform(-1.0, 1.0, size3) D rng.uniform(-1.0, 1.0, size3) # 这里应调用 Fortran 暴露的接口做对比 # 为演示方便我们使用与 Python 相同算法路径的参考函数。 # 在实际项目中通过 f2py 或 ctypes 把 Fortran 编译为共享库 # 再与本函数逐组对比。 p alpha beta q gamma delta P (alpha * A beta * B) / p Q (gamma * C delta * D) / q ref (2.0 * np.pi**2.5 / (p * q * np.sqrt(p q)) * np.exp(-alpha * beta / p * np.dot(A - B, A - B)) * np.exp(-gamma * delta / q * np.dot(C - D, C - D)) * np.exp(-p * q / (p q) * np.dot(P - Q, P - Q))) val ss_integral(alpha, beta, gamma, delta, A, B, C, D) max_diff max(max_diff, abs(val - ref)) print(f1000 组随机测试最大误差: {max_diff:.4e}) assert max_diff 1e-12, 随机验证失败 print([PASS] 随机化回归通过)这个脚本澄清了一个重要观点在真实项目中参考函数应当是“被替换的旧实现”而不是“自己写的新实现”。否则验证就失去了意义。正确做法是把 Fortran 源码编译成共享库测试脚本循环调用它作为 ground truth。5. 常见问题与排查思路实际落地的过程中Agentic Workflow 不会一帆风顺。下面汇总我遇到的高频问题问题现象常见原因解决思路Python 与 Fortran 数值误差过大浮点运算顺序不同或中间变量精度不一致统一使用 float64调整累加顺序必要时用更高精度做中间计算Agent 生成的代码不是等价转换转换 Agent 没有充分理解公式或 prompt 缺少数值约束在 prompt 中要求输出“转换说明”人工审核公式来源重构后性能不升反降数据布局和循环结构不适配现代硬件先用 perf 定位热点分析内存访问模式再考虑向量化或 GPU 化COMMON block 全局状态难以迁移新实现无法访问遗留全局变量先重构数据结构把全局状态封装成 context 对象再做函数替换回归测试只覆盖少量输入测试用例太少偶然通过使用随机化测试 真实分子体系测试集纳入 CILLM 生成的代码风格不统一缺少代码规范和模板约束在 prompt 中嵌入项目代码风格指南用 lint 工具强制检查重构被无限期拖延任务粒度太大验证复杂坚持“一次只替换一个函数”每个函数都走完整验证闭环这里面最值得强调的一条是永远不要让 Agent 在没有验证闭环的情况下“一口气”重写一个大模块。数值代码的正确性是靠测试撑起来的不是靠生成模型的语言能力撑起来的。6. 最佳实践与工程建议6.1 先建立数值基线再开始重构在写任何 Agent prompt 之前先把旧代码的测试基线建立起来。对科学计算代码来说基线不只是几个数值结果而是一组覆盖典型输入范围的回归测试数据。一组随机化测试的随机种子和参数范围。已知解析解或高精度参考值。没有基线Agent 生成的代码就缺少“正确”的定义。6.2 一次只替换一个函数双电子积分模块内部函数众多彼此有依赖。正确的替换单位是“一个子程序/函数”而不是“整个文件”。替换后立即跑回归通过后再处理下一个依赖节点。这不仅能降低排查复杂度也能让 Git 历史更清晰。每次提交对应一个函数的等价替换随时可以 revert。6.3 把 Agent 的中间产物纳入版本管理Agent 生成的分析报告、转换说明、验证报告都是重构过程中非常有价值的资产。它们解释了“为什么这样改”对后续维护者极其重要。建议在项目仓库中增加一个migration/目录migration/ ├── analysis/ # 代码理解 Agent 报告 ├── design/ # 转换说明、数学公式说明 └── verification/ # 数值验证日志6.4 数值阈值要结合物理意义设计双电子积分在量子化学中最终会进入哈密顿矩阵影响能量到微乎其微的量级。如果你的替换代码在积分层面有1e-10的误差对总能量的影响可能很容易被 SCF 收敛阈值吸收但如果误差达到1e-6就可能改变分子几何优化的结果。建议设置两档阈值单元测试级积分本身绝对误差小于1e-12。集成测试级SCF 总能量误差小于1e-8Hartree且梯度误差低于指定阈值。这也是为什么验证 Agent 需要知道物理背景而不只是做“两个数是否相等”的比较。6.5 保留旧实现作为回退开关即使新实现通过了所有测试也不要立刻删除旧代码。在正式替换后的头几个版本里保留一个环境变量或编译宏允许用户切回旧实现。! 伪代码示意 #ifdef USE_LEGACY_INTEGRAL call ss_integral_legacy(...) #else call ss_integral_new(...) #endif这种做法在科学计算领域尤其重要。因为某些极端输入可能不在你的测试覆盖范围内旧实现作为兜底方案能避免整个计算任务失败。6.6 人工审核的边界Agentic Workflow 可以大幅提升重构效率但不可能完全替代人工审核。特别是涉及数学公式的模块必须有人确认生成代码中的公式与文献公式一致。边界条件处理与旧代码一致。数值误差的方向和大小符合预期。建议在 pipeline 中设置“人工 Review 门禁”Agent 生成的代码必须经过人工确认后才能合入主干。7. 总结与动手建议把双电子积分这个例子跑通之后你其实已经掌握了一套通用的遗留 HPC 代码现代化方法先理解用静态分析 Agent 生成依赖报告。再定位用 profiler 找热点不盲目重构。后替换一次一个函数Agent 生成等价实现。终验证随机测试 物理阈值 CI 回归。对于 GAMESS 这类大型科学计算软件真正的挑战往往不是“让 Agent 写出跑得通的代码”而是“让 Agent 写出数值上与原实现等价的代码”。后者必须以严谨的测试闭环为前提。如果你准备在自己的项目里动手我的建议是先不要碰核心模块。找一块隔离程度高、有明确输入输出、市面上有参考实现的子程序把它作为 Agentic Workflow 的试验田。跑通第一段完整 pipeline 后再逐步扩大改造范围。这套流程比人工阅读几十万行 Fortran 代码要高效得多。