尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用Agentic Workflow改造GAMESS二电子积分:遗留HPC代码现代化实践

用Agentic Workflow改造GAMESS二电子积分:遗留HPC代码现代化实践 改造目标An Agentic Workflow for Legacy HPC Modernization: Converting the Two-Electron-Integral Core of GAMESS如果把 GAMESS 这样已经跑了三十多年的量子化学程序比作一座老建筑二电子积分Two-Electron Integral就是这座建筑里负荷最重的承重墙。它每天都在被 SCF、DFT、MP2 这些主流电子结构方法反复调用但代码本身却保留着 Fortran 时代的全局变量、GOTO 跳转和隐式类型约定改起来非常吃力。最近我在尝试用智能体工作流Agentic Workflow把这堵“承重墙”改造成可测试、可移植、可以逐步 GPU 化的现代模块过程中踩了不少坑也沉淀了一套可以复用的方法。本文就围绕这个完整的现代化改造过程展开既讲思路也讲可落地的代码、配置和验证方案。如果你正在做传统 HPC 代码维护、AI 辅助编程落地或者对量子化学计算内核的工程化改造感兴趣这篇文章应该能帮你省下不少时间。1. 背景与核心概念1.1 GAMESS 与二电子积分在量子化学中的作用GAMESS 的全称是 General Atomic and Molecular Electronic Structure System是一套开源量子化学计算软件广泛应用于分子结构优化、反应机理研究、光谱性质预测等领域。在 Hartree-Fock 和密度泛函理论这些主流方法中计算量最大的部分往往不是求解 Fock 矩阵本身而是反复计算电子之间的库仑排斥积分也就是二电子积分。它的一般形式可以写成$$ (\mu\nu|\lambda\sigma) \int\int \phi_\mu^(\mathbf{r}1)\phi\nu(\mathbf{r}1)\frac{1}{r{12}}\phi_\lambda^(\mathbf{r}2)\phi\sigma(\mathbf{r}_2)d\mathbf{r}_1d\mathbf{r}_2 $$其中 $\phi$ 是基函数$r_{12}$ 是两个电子之间的距离。如果分子有 $N$ 个基函数理论上二电子积分的数量是 $N^4$ 的量级。虽然利用排列对称性可以压缩到约 $N^4/8$再结合积分筛选可以跳过很多数值上小于阈值的贡献但这仍然是电子结构计算中最消耗资源的“计算热点”。这也是为什么几乎所有高性能量子化学软件都要花大量精力优化二电子积分代码它不仅决定单次积分计算的效率还直接决定整个 SCF 迭代需要多少时间。GAMESS 中的二电子积分核心几十年来经历了大量数学家、化学家和工程师的优化算法上已经非常成熟但代码结构和工程形态大多停留在上世纪八九十年代。1.2 遗留 HPC 代码为何难以现代化“遗留代码”这个词听起来像是贬义但在 HPC 领域它往往意味着“经过了数十年验证、数值精度极高、没人敢乱动的宝贵资产”。GAMESS 二电子积分部分就是这种典型底层多使用 Fortran 77 或 Fortran 90 编写。大量使用 COMMON 块共享全局状态。函数之间通过隐式类型约定传递参数稍不留神就会读错变量。控制流中常见 GOTO导致静态分析和重构工具很难建立清晰的基本块。文档往往跟不上代码演进很多“为什么这么做”只存在于老开发者的记忆里。这些特征导致几个非常现实的问题。第一新人接手成本极高光是读懂一个积分函数的调用关系就可能要花几周。第二并行化和 GPU 化改造非常困难因为 COMMON 块让多个线程之间很难隔离状态。第三编译器优化也受到限制现代编译器在对 Fortran 77 做 aggressive optimization 时常常因为代码复杂的别名分析和全局状态而放弃很多优化机会。所以现代化改造并不是“看不懂才重写”而是为了突破性能瓶颈、降低维护成本同时保留几十年验证出来的数值精度。1.3 Agentic Workflow 是什么解决什么问题Agentic Workflow 是最近 AI 编程领域非常热门的方向。简单说它不再像 Copilot 那样只帮你补全一段代码而是让一个智能体Agent在给定任务目标之后自己拆解任务、调用工具、读取代码、生成补丁、运行测试、根据报错修改方案直到任务完成。把它用在 Legacy HPC 现代化上最大的好处是能把“理解旧代码、设计新接口、写新实现、回归验证”这条长链路自动化。过去我们需要人肉去翻源码、画调用关系、手写测试样例现在可以让 Agent 按照一个编排好的工作流逐步执行。比如Agent 用 grep、ctags 或代码索引工具扫描旧代码。Agent 根据扫描结果生成模块接口设计。Agent 逐函数生成现代语言实现。Agent 调用编译器完成构建。Agent 读取参考输出用数值回归脚本做比对。Agent 把失败信息反馈给生成环节继续迭代修复。市面上常见的框架有 LangChain、AutoGen、CrewAI 等但核心思想是一致的把 LLM 作为“规划大脑”外部脚本、编译器、测试框架作为“手脚”通过工具调用闭环完成工程任务。这里需要提醒的是Agentic Workflow 并不是万能的。它特别适合“目标清晰、验证标准明确、反馈闭环短”的任务而二电子积分改造恰好符合这样的特征我们清楚输入输出是什么也知道怎么判断结果是否正确。2. 环境准备与版本说明2.1 目标代码现状分析开始改造之前第一步不是写代码而是先把 GAMESS 源码摸清楚。这里我建议先把原始源码目录设为只读在所有后续操作中都不修改它只把新代码写在独立目录里。这样既能保证旧版本随时可以回滚也避免 Agent 在探索过程中误改原始文件。在你的工作机上先进入 GAMESS 源码目录做一个初步扫描# 进入源码目录确认当前目录结构 find . -maxdepth 2 -type d | head -n 50然后搜索二电子积分相关的源文件。不同版本的 GAMESS 目录结构会有差异文件名也不完全一样但一般可以通过关键字找到# 搜索二电子积分相关文件 grep -ri two.electron\|tei\|eri\|integral src --include*.F --include*.f \ --include*.F90 --include*.f90 -l | head -n 30这一步的目标是建立“代码地图”。你不需要马上读懂所有文件但要能回答几个问题核心积分函数在哪个目录它依赖哪些 COMMON 块谁在调用它调用方式是什么有没有现成的测试或基准输入梳理完现状后可以把结果导出成一份 JSON 文件作为后续 Agent 的输入上下文。这样 Agent 一开始就具备全局视角而不是从零开始瞎猜。2.2 工具链与运行环境工具链的版本需要根据你的项目实际情况调整这里以常见环境为例重点演示配置思路。推荐的基础环境如下操作系统Linux x86_64 发行版例如 CentOS 7、Ubuntu 20.04 或更新版本。编译器GCC 自带 gfortran或 Intel oneAPI 中的 ifort/ifx。Python 版本3.10 或以上。Agent 框架LangChain、AutoGen、CrewAI 任选其一也可以自己用 OpenAI/本地模型的 Function Calling 接口编写。科学计算库NumPy、SciPy用于写数值回归脚本。代码索引工具ctags、cscope或者使用基于 tree-sitter 的解析器。在正式改造前建议先确认一下 GAMESS 当前版本能否在你的编译器下正常构建并跑通一个小分子算例比如水分子的 HF/STO-3G。这一步很有必要因为后续所有回归测试的参考值都来源于原始代码的输出。如果本地没有安装完整的 GAMESS也可以用简化版测试基组和少量积分数据做验证但最后仍然需要回到完整 GAMESS 做端到端对比。2.3 实验目录结构为了让整个改造过程可复现、可审计推荐使用下面的目录结构legacy-hpc-modernization/ ├── gamess-src/ # 原始 GAMESS 源码只读 ├── agent-log/ # Agent 运行时日志 ├── converted/ # 转换后的现代模块 ├── tests/ # 单元测试与数值回归测试 ├── artifacts/ # 基准结果、性能报告、参考积分值 └── workflow/ # Agent 编排脚本与 Prompt 模板这里的关键是把“原始代码”和“改造产物”严格分开。Agent 可以读 gamess-src但只允许在 converted 和 artifacts 目录下写入文件。权限隔离能有效防止意外损坏原始代码。3. 二电子积分的计算结构与现代化改造目标3.1 二电子积分的数学与计算模式前面提到二电子积分在数学上是一个六维积分。直接做数值积分是不现实的实际程序会采用更聪明的方法。当前主流量子化学程序里二电子积分通常使用三类方法Pople-Hehre 方法适合 s/p 壳层的小基组利用高斯函数乘积定理简化积分。McMurchie-Davidson 递推通过 Hermite 展开把积分拆成递推关系适合中等角动量的基函数。Rys 求积使用 Rys 多项式数值积分在高角动量基函数下效率很高。GAMESS 的不同版本和不同积分模块可能会使用不同算法这是正常的。现代化改造时最重要的一件事是不要随意改动底层算法。你需要把旧代码中某个积分路径的“公式来源、变量约定、缩放因子”完整保留下来否则很可能出现“代码写对了但数值差一点”的情况。从计算模式上看二电子积分部分通常是这样工作的接收两个基函数对的壳层角动量信息。读取基函数的指数和收缩系数。计算缩并积分。把积分值填入二维积分表或直接进入 Fock 矩阵构建循环。整条链路上的数据流非常清晰非常适合用现代编程语言里的结构体或类来建模。3.2 遗留实现的典型结构为了帮助不熟悉 Fortran 的读者理解后续 Agent 改造过程我在这里给出一段示意性伪代码说明遗留二电子积分实现的典型结构。请注意这不是 GAMESS 的真实源码只是用于演示。C 示意遗留 ERI 实现的典型结构 SUBROUTINE ERI_SH(LA, LB, LC, LD, AB, CD, GOUT) COMMON /BASIS/ NGAUSS, ZETA(3), COEFF(3) DOUBLE PRECISION GOUT(*) C 对每个高斯原函数做遍历 DO I 1, NGAUSS DO J 1, NGAUSS DO K 1, NGAUSS DO L 1, NGAUSS CALL FORM_QUARTET(..., GOUT) END DO END DO END DO END DO END这段代码有几个典型问题COMMON /BASIS/ 让模块内外共享全局状态任何一处线程修改都会影响到所有调用者。四重循环嵌套让代码很难被向量化。所有变量名称都很短可读性差。没有显式接口文件调用者在参数传错时编译器不报错。这些点正是现代化的重点改造对象。3.3 现代化改造的拆分思路现代化改造不等于“用 Python 重写整个 GAMESS”。更合理的思路是分三步走第一步冻结行为。先用原始代码在小分子体系上跑出参考积分值保存到 artifacts 目录。这些参考值就是后续所有重构是否正确的唯一标准。第二步抽出接口。把二电子积分核心中“输入结构体、输出缓冲区、数据库访问”这三件事拆分清楚。原来的 COMMON 块变量全部收敛到一个显式数据结构中所有积分函数都只通过参数传递数据。第三步小步替换。逐个函数重构每重构完一个函数就编译一次、回归一次而不是等所有代码都改完再测试。改造后的理想形态可以是用现代 Fortran 或 C/Kokkos 编写的模块。以 C 为例如果以后要上 GPUKokkos 可以比较方便地把循环改写为并行执行如果暂时不上 GPU用标准 C 也能明显提升代码可读性和可测试性。4. Agentic Workflow 落地实战4.1 任务建模与阶段设计Agentic Workflow 落地的第一件事不是写 Prompt而是把大任务拆成小阶段。二电子积分改造可以拆成下面几个阶段第一阶段代码探索与接口生成。Agent 读取原代码生成接口清单包括输入参数、输出参数、依赖的全局变量、调用的子程序等。第二阶段参考实现生成。Agent 根据接口清单生成一个最小可运行的新实现。这里可以使用一种中间表示比如先用 Python/NumPy 写一个清晰但未必高性能的版本用来验证算法和数值逻辑。第三阶段构建与编译。Agent 在隔离容器中运行编译命令收集报错信息如果编译失败自动分析日志并修复。第四阶段数值回归。Agent 把新实现的计算结果和参考值进行比对输出差异大于阈值的情况。第五阶段性能分析与优化。如果数值正确再对热点循环做性能分析和改写。这个阶段不一定需要 Agent 全自动完成也可以人工参与。Agent 之间需要一种稳定的数据交换格式。我们使用 JSON 作为“语言”每个阶段结束后把结构化结果写入一个 JSON 文件下一个阶段从 JSON 文件中读取依赖信息。{ module: tei_core, files: [eri_sh.f, form_quartet.f], dependencies: [basis_block, symmetry_block], interfaces: [ { name: compute_eri, inputs: [la, lb, lc, ld, basis], output: eri_matrix } ] }这张接口清单是整个改造流程中的“共同语言”可以让不同 Agent 之间不依赖自然语言就能正确协作。4.2 代码理解 Agent 的 Prompt 设计代码理解 Agent 的目标是“从旧代码中提取结构化信息”。这种 Agent 的 Prompt 要尽量具体、可验证不要让它自由发挥。这里给出一个可以直接参考的 Prompt 模板你是资深 HPC 代码迁移专家。项目目标将 GAMESS 二电子积分核心从遗留 Fortran 迁移为现代可测试模块。 请阅读以下文件 - gamess-src/integrals/eri_sh.f - gamess-src/integrals/form_quartet.f 任务 1. 提取每个子程序的输入参数、输出参数和返回值。 2. 列出所有 COMMON 块及其中的变量名。 3. 画出 main 调用链识别被调用子程序列表。 4. 找出所有 GOTO 跳转的位置并说明其语义。 输出格式 - 只输出 JSON不要包含任何自然语言总结。 - 如果某个信息无法确认请标记为 unknown。这个 Prompt 的关键在于第一明确输入文件第二明确要提取什么信息第三要求输出为结构化 JSON方便后续工具链处理。如果你使用的 Agent 框架支持工具调用还可以让 Agent 自动执行 grep、ctags 等命令。4.3 代码生成与重构 Agent 示例代码生成 Agent 的任务是根据接口清单生成新实现。下面给出一个用 Python/NumPy 编写的“示意性”积分实现。请注意这只是一个教学演示真实生产必须使用经过验证的积分算法比如 Rys 求积或 McMurchie-Davidson 递推不能在真实代码中直接使用朴素的四维数值积分。# 文件路径converted/tei_core.py 二电子积分核心的现代实现示意。 注意本文件用于演示 Agent 生成的代码结构和数值回归流程 实际生产项目应使用正式的积分递推算法而不是朴素的网格求和。 import numpy as np def primitive_gaussian(x, center, exponent): 一维高斯原函数用于演示。 return np.exp(-exponent * (x - center) ** 2) def naive_two_electron_integral(basis_a, basis_b, grid16): 用朴素网格求积近似计算两个一维高斯函数的积分。 这里只用于验证 Agent 工作流的数值回归闭环。 xs np.linspace(-5.0, 5.0, grid) h xs[1] - xs[0] phi_a primitive_gaussian(xs, basis_a[center], basis_a[exponent]) phi_b primitive_gaussian(xs, basis_b[center], basis_b[exponent]) # 近似计算 phi_a|phi_b实际项目应使用解析积分 overlap np.sum(phi_a * phi_b) * h return overlap实际项目中Agent 生成的代码可能是 C 或现代 Fortran 版本。无论是哪种语言都要遵循同样的验证闭环编译成功、数值正确、性能达标这三个条件全部满足后才算完成。4.4 数值回归验证二电子积分改造最怕的就是“看起来能跑跑出来却不对”。为了避免这个问题我们在一开始就把参考值导出成标准格式然后用一个独立的回归脚本做自动比对。下面是一个简单的数值回归脚本# 文件路径tests/regression_check.py 数值回归验证脚本。 用法python regression_check.py --reference artifacts/reference.json \ --candidate converted/output.json --rtol 1e-8 --atol 1e-10 import argparse import json def regression_check(reference, candidate, rtol1e-8, atol1e-10): 比较两组积分值返回是否通过。 failed [] for key in reference: ref_val reference[key] cand_val candidate.get(key) if cand_val is None: failed.append((key, missing, ref_val, None)) continue # 对绝对值很小的积分使用绝对误差 if abs(ref_val) atol: ok abs(cand_val - ref_val) atol else: ok abs(cand_val - ref_val) rtol * abs(ref_val) if not ok: failed.append((key, mismatch, ref_val, cand_val)) total len(reference) passed total - len(failed) print(fPASS: {passed}/{total}) for item in failed[:10]: print(FAIL:, item) return len(failed) 0 if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--reference, requiredTrue) parser.add_argument(--candidate, requiredTrue) parser.add_argument(--rtol, typefloat, default1e-8) parser.add_argument(--atol, typefloat, default1e-10) args parser.parse_args() with open(args.reference) as f: reference_data json.load(f) with open(args.candidate) as f: candidate_data json.load(f) ok regression_check(reference_data, candidate_data, args.rtol, args.atol) exit(0 if ok else 1)在筛选阈值上要注意二电子积分里有大量绝对值接近零的远端积分如果只用相对误差一个接近零的参考值会放大误差。所以通常同时使用 rtol 和 atol 两个阈值取两者中的较大者作为判断标准。4.5 运行结果说明假设你跑完回归脚本可能的输出如下PASS: 3400/3400这说明新实现和参考值全部一致Agent 改造达到数值正确的要求。如果输出出现PASS: 3398/3400 FAIL: (eri_3_7_3_7, mismatch, 1.234e-05, 1.256e-05)说明有两个积分值不一致。这时候 Agent 不应该继续盲目生成代码而应该把差异反哺到代码生成阶段优先检查这两个积分的角动量、基函数指数和缩放因子是否在转换过程中出了问题。5. 常见问题与排查思路在实际改造过程中最容易遇到的问题有下面几类。问题现象常见原因解决思路新代码编译通过但数值不一致算法细节差异、坐标系约定不同、缩放因子丢失先对比参考值的输入输出检查变量单位和公式来源编译报错找不到变量迁移时遗漏 COMMON 块变量使用 ctags/静态分析工具重新提取接口清单Agent 生成代码上下文溢出任务拆得太粗单个函数过长拆成更小粒度先让 Agent 只处理一个子函数并行执行性能反而下降遗留全局变量导致数据竞争或 false sharing先把全局变量收敛到显式结构体再进行并行化数值误差被 SCF 迭代放大少数积分误差虽然小但会在自洽场迭代中累积提高阈值标准增加对称性一致性检查Agent 修改了不该改的文件没有做权限隔离用容器或文件系统权限限制 Agent 只能写指定目录下面挑几个重点展开。数值不一致是改造中最常见的问题。遇到这种情况不要立刻怀疑浮点精度优先检查“符号约定”。量子化学程序里积分缩放因子、基函数归一化系数、角动量排序这些细节很容易在迁移中被忽略。我的建议是在 Prompt 中明确要求 Agent 保留原始公式来源注释并且在代码理解阶段输出一份“变量对照表”。编译失败的问题通常和 Fortran 隐式类型有关。原始代码里可能有一个变量在不同子程序中分别被当作整数和浮点数使用。现代编译器通常不会报这种错但迁移到类型严格的语言后就会立刻暴露。解决方法是先用工具扫描所有 COMMON 块和隐式变量再交给 Agent 生成显式类型声明。上下文溢出是 Agentic Workflow 里非常影响体验的问题。单个 Fortran 函数如果超过几百行塞给模型时很容易丢失重要细节。此时可以把一个函数拆成两个子任务第一次只让 Agent 生成“接口骨架”第二次再让 Agent 填充“函数体实现”第三次做整体整合。6. 最佳实践与工程建议6.1 项目管理与版本控制整个改造过程必须纳入版本控制。我的建议是原始 GAMESS 源码单独放在一个仓库使用只读 tag。改造代码放在另一个分支每次 Agent 提交前都要通过数值回归。在 CI 中增加一条流水线每次合并前自动执行回归脚本防止“今天通过、明天又跑挂”的情况。如果改造周期比较长还要定期把 Agent 的执行日志归档到 artifacts 目录。这些日志能帮助你理解“为什么当时做了这个决定”对后续人工 review 很有价值。6.2 数值正确性验证体系数值验证是所有量子化学代码改造的生命线。建议建立三层验证体系第一层单元积分级。直接对比单个二电子积分值这是定位问题最快的粒度。第二层SCF 能量级。用同一个分子和基组分别用原始版本和改造版本跑完整个 SCF 迭代比较总能量和每个轨道的差值。第三层性质级。对比梯度、Hessian 或者光谱性质确保宏观物理量没有受到微小数值变化的影响。如果某一层失败不要直接跳到下一层。SCF 能量对积分误差非常敏感很多微小的积分差异会在迭代中不断放大。6.3 安全与权限边界涉及 Agent 自动执行命令时一定要设置权限边界。最稳妥的方式是在隔离容器里运行 Agent只挂载原始源码目录为只读给 Agent 提供独立的构建目录和临时目录。这样即便 Agent 生成了错误的补丁也不会影响核心代码。另外Agent 调用 shell 命令时需要遵循最小权限原则。不要让 Agent 以 root 身份运行只给它提供构建、测试、文件读写这几个特定命令的权限而不是开放的终端。6.4 从 CPU 到 GPU 的后续扩展二电子积分改造最终目标往往不只是“可读性更好”而是“能上 GPU”。在接口设计阶段就要为这一步做好准备。比如数据结构尽量设计成适合 AoSArray of Structures或 SoAStructure of Arrays切换的形式。核心循环避免内部依赖和分支方便后续用 OpenMP 或 Kokkos 做并行化。输出缓冲区提前分配避免在热循环里做动态内存分配。先保证 CPU 上的数值正确和性能稳定再迁移到 GPU。GPU 上并行二电子积分设计还包括积分筛选、任务负载均衡、积分直接使用还是存盘等策略这些都属于下一步扩展不建议在第一步就全部引入。7. 总结与学习路线这套 Agentic Workflow 跑通之后最明显的改变是我们终于可以在不牺牲数值精度的前提下把二电子积分这一块的代码交给新人维护了。回顾整个流程核心经验是不要把 Agent 当成“自动写码工具”而要把它当成一个“可以随时反馈和迭代的初级工程师”通过接口清单、数值回归脚本和权限边界来约束它的每一步动作。下一步如果你想继续深入可以从三个方向入手。一是学习现代 Fortran 和 C 在科学计算中的设计模式特别是 Kokkos 这类可移植并行框架二是研究二电子积分的核心算法比如 Rys 求积和 McMurchie-Davidson 递推理解为什么不同算法在不同壳层下效率差异巨大三是把 Agentic Workflow 扩展到 GAMESS 的其他模块比如梯度计算、几何优化和 MP2 相关章节逐步建立一套完整的 Legacy HPC 代码现代化流水线。实际项目中我建议你优先做三件事先把参考值基准建好这是所有改造的锚点再把 COMMON 块收敛成显式数据结构这是并行化的前提最后才是让 Agent 大规模生成代码。顺序不要反过来否则后面每一步都会在数值验证上反复返工。
返回列表