轻量的 Agent-style 性能分析系统:把真实 ARCHER2 上的 PETSc benchmark 数据导入 SQLite,用一组确定性分析工具 + 中文规则路由做可评测的 baseline,再接入 LLM function calling,与规则基线做可量化的对比。
HPC CSV
-> SQLite benchmark memory
-> analysis tools (best_config / metrics / compare_modes / scaling_report)
-> router: Chinese rule-based baseline (CLI default) + LLM function calling (P2, eval-scored)
-> Markdown report / structured trace / HTTP API / lexical retrieval (P3)
| 30 问中文评测集 | 规则 router 83.3% vs LLM router 100% —— 同一评测集、同一打分 harness |
| system prompt 消融 | 同一模型去掉 system prompt:100% → 63.3% |
| 测试 | 229 passed,全程离线,不需要 API key(LLM 部分走 FakeLLM 注入)——CI 每次 push 在无 key 环境下重跑 |
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
python -m pytest # 229 passed以下输出为真实终端输出:
python -m hpc_agent import-csv examples/sample.csv
# Imported 36 rows into data/benchmarks.sqlite
python -m hpc_agent ask "100万规模下最快配置是什么?"
# unknowns=1000000 的最快配置:ranks=128, threads=1, total_cores=128,耗时 3.866 秒(迭代 10000 次)。
python -m hpc_agent ask "对比一下100万规模下mpi和hybrid模式"
# unknowns=1000000 的模式对比:mpi_like 最快 ranks=128, threads=1,耗时 3.866 秒;
# hybrid_like 最快 ranks=64, threads=2,耗时 4.369 秒。mpi_like 更快,hybrid_like 的耗时是它的 1.13 倍。
python -m hpc_agent report --unknowns 1000000 --output reports/scaling_1000000.md
# Wrote report to reports/scaling_1000000.md
python -m hpc_agent ask "100万规模下最快配置是什么?" --trace trace.jsonl
# 同样的答案,另外把 5 条事件写进 trace.jsonl:
# ask_start -> route_result -> tool_call -> tool_result -> answer(同一 trace_id)生成的报告见 reports/scaling_1000000.md。
同一套能力也可以走 HTTP(P3):
uvicorn hpc_agent.api:app --port 8000curl -s -X POST localhost:8000/ask -H 'Content-Type: application/json' \
-d '{"question": "100万规模下最快配置是什么?"}'
# {
# "answer": "unknowns=1000000 的最快配置:ranks=128, threads=1, total_cores=128,耗时 3.866 秒(迭代 10000 次)。",
# "trace_id": "e15242fe277d4ec7b593fba0a0e3982c",
# "events": null
# }加 "trace": true 会把同一条 trace 的 5 个事件放进 events 字段返回(服务端不落盘);交互式文档在 http://localhost:8000/docs。
--rag <目录> 会先对该目录下的 .md 笔记做检索,命中的片段作为额外上下文交给 LLM(下面的检索本身完全离线,不需要 key):
python -c "
from hpc_agent.rag import load_notes, retrieve
chunks = load_notes('examples/notes')
for c, s in retrieve('hybrid 模式为什么比纯 MPI 慢', chunks):
print(f'{s:5.2f} {c.source} {c.text.splitlines()[0][:40]}')
"
# 12.02 hybrid-vs-pure-mpi.md # 混合模式为什么常常跑不过纯 MPI
# 3.51 hybrid-vs-pure-mpi.md 第三类是线程绑定。没有正确设置绑定策略时,线程可能被调度到跨 socket 的核
# 2.81 hybrid-vs-pure-mpi.md 第二类是内存局部性。纯 MPI 每个进程有独立的地址空间,数据天然按进程切分;混python -m hpc_agent ask "hybrid 模式为什么会变慢" --router llm --rag examples/notes问"量子计算"这类语料里没有的话题,检索返回空列表、不注入任何上下文——没找到就是没找到。
这一项只声称做了什么,不声称它提升了准确率。 现有 30 问评测的期望值是 tool call(intent + unknowns),而检索片段影响的是解释性内容,两者不在同一个评价维度上;要证明 RAG 有用,需要另建一套针对解释质量的评测,目前没有。
P1 只依赖标准库 + pytest;P2 引入 openai SDK(兼容各 OpenAI-compatible 服务商)。API key 走环境变量、永不入库;pytest 全程不需要 key,只有评测脚本的 --router llm 会调用真实 API。
| 你想知道 | 看这里 |
|---|---|
| 项目到哪一步、怎么配环境、怎么复现 demo | 本文件 |
| 做什么、边界在哪、完成标准是什么 | SPEC.md |
| 代码的流转路径、修改某个文件的波及范围、某个函数的作用 | WORKFLOW.md |
| 两个 router 的逐题错因、消融实验、结论与局限 | evals/eval_report_p2.md |
| 能力 | 说明 | 设计要点 |
|---|---|---|
| CSV 导入 | import-csv 把 benchmark 运行记录导入 SQLite,带 schema 与三个索引 |
malformed 行显式报错,不静默跳过 |
| 分析工具 | best_config(某规模最快配置)、compare_modes(MPI 式 vs hybrid 式,含 time ratio) |
纯函数,返回带 found 标志的 dict;查无数据时显式失败,不做推测 |
| 扩展性指标 | speedup / parallel_efficiency / scaling_summary,以单核为 baseline |
缺少单核 baseline 时整体放弃,不以近似值替代 |
| 中文规则路由 | ask 接受中文问题:关键词分类 + 规模解析("100万"/"一百万" → 1000000) |
确定性、离线、零成本;128核 这类核数不会被误读成规模 |
| LLM function calling | llm_route 走任意 OpenAI-compatible API 做工具选择 |
输出与规则 router 完全同形状 {"intent", "unknowns"}——这是两者能共用一套评测和一个 --router 开关的前提 |
| 防御式解析 | 模型不调工具 / 幻觉工具名 / 坏 JSON 参数 | 一律收敛到显式 unknown,不伪装成路由决策 |
| 评测 harness | evals/questions.jsonl 30 问 5 类题型(正常/追问/拒答/数字陷阱/对抗题) | 期望值是 tool call(intent + unknowns)而非答案文本;两者均正确才计为正确 |
| Markdown 报告 | report 生成单一规模的扩展性报告(最快配置/扩展性表/模式对比/规则化解读) |
字节级确定——同一数据库逐字节一致,git diff 只反映真实变化 |
| HTTP 接口 | uvicorn hpc_agent.api:app 暴露 POST /ask,请求体 question / router / trace;/docs 自动生成交互式文档 |
只做协议转换,业务逻辑零重复;校验写进类型(非法 router → 422);缺 key 映射为 503(服务端未就绪)而非 500 |
| 结构化 tracing | ask --trace t.jsonl 产出可回放的 JSON-lines 事件流,同一 trace_id 贯穿 |
脱敏收在单一收口点:绝对路径降级为文件名、疑似密钥字段一律 [redacted],由扫描测试强制保证 |
| 词法检索(最小 RAG) | ask --rag <目录> 对本地 .md 笔记做 IDF 加权词重叠检索,命中的片段作为独立一条 system message 进入 LLM 上下文 |
刻意不用向量检索:零新依赖、离线、可逐条解释命中理由(能指着 idf 分数说话)。检索不到就返回空、不注入,不退而求其次给"最像的一条"。不传 --rag 时 messages 与评测时逐字节相同,由测试锁死 |
hpc_agent/
├── db.py # 连接、schema、底层查询(全项目唯一数据出入口)
├── importer.py # CSV -> SQLite
├── metrics.py # speedup / parallel efficiency / scaling_summary
├── tools.py # best_config / compare_modes
├── router.py # 中文规则路由 + ask 编排 + router 选择
├── llm_schema.py # function calling 的 tool schemas + tool call -> intent 映射
├── llm_router.py # LLM router:调 API、防御式解析,输出与规则 router 同形状
├── llm_config.py # 环境变量配置(OPENAI_API_KEY / BASE_URL / MODEL)
├── tracing.py # trace id + JSONL 事件流 + 脱敏(横切,不依赖任何业务模块)
├── rag.py # 本地笔记的词法检索(IDF 加权词重叠,纯标准库,无向量库)
├── report.py # Markdown 扩展性报告(组合上述工具)
├── cli.py # import-csv / ask / report 子命令
└── api.py # FastAPI 薄封装:POST /ask(与 cli.py 平级,互不依赖)
tests/ # pytest 验收测试(工具合同 + 端到端,LLM 部分走 FakeLLM 离线)
evals/ # 30 问中文评测集 + 打分 harness + 评测报告与逐题结果
examples/ # 脱敏 CSV fixture 与检索用的 HPC 笔记语料
reports/ # 生成的 Markdown 报告
依赖只向下,无循环。虚线是懒加载——跑规则 router 时 llm_router.py 根本不会被导入,openai SDK 也不会,这是 pytest 无 key 全绿的根因。
graph TD
CLI["cli.py 进程边界"] --> ROUTER["router.py 规则路由 + ask 编排"]
API["api.py HTTP 边界"] --> ROUTER
API --> TR
CLI --> REP["report.py"]
CLI --> IMP["importer.py"]
CLI --> TR["tracing.py 横切"]
CLI --> RAG["rag.py 词法检索"]
ROUTER --> TOOLS["tools.py 确定性工具"]
ROUTER --> TR
ROUTER -.懒加载.-> LLMR["llm_router.py"]
REP --> TOOLS
REP --> MET["metrics.py"]
LLMR --> LS["llm_schema.py"]
LLMR --> LC["llm_config.py"]
TOOLS --> DB["db.py 唯一数据出入口"]
MET --> DB
IMP --> DB
一次提问的主线:cli.py 解析参数并建 Tracer → router.ask() 埋点、select_router() 返回一个 callable(规则 router 是 route 本身,LLM router 是绑好 client 的闭包)→ 两者输出同形状的 {"intent", "unknowns"} → 经 traced() 包装调用 tools.py → 回到 ask() 渲染中文。换 router 只改路由决策,不改答案格式,这是回归测试锁死的。
完整时序图、每个文件的上下游与改动影响面、全部公开函数的一行速查,见 WORKFLOW.md。
同一份 30 问评测集、同一个打分 harness,对两种 router 各出一个准确率:
| Router | 准确率 | 说明 |
|---|---|---|
| 规则 router | 83.3% (25/30) | 离线、零成本、确定性;丢分集中在对抗题(别名、歧义句、中文复合数词) |
| LLM router(含 system prompt) | 100% (30/30) | qwen3.7-plus · temperature 0 · 2026-07-23 · 单次运行 |
评测集故意包含规则 router 已知会错的题(rule_wrong 类),用来量化 LLM 的增量价值;错题逐条归因、system prompt 消融(63.3% → 100%)和结论见评测报告,逐题结果在 evals/results/。
python -m evals.run_eval --router rule # 离线,无需 key
export OPENAI_API_KEY=... # 任意 OpenAI-compatible 服务商;见 hpc_agent/llm_config.py
python -m evals.run_eval --router llm # 真实 API,会产生费用如何解读:规则 router 的结果字节级可复现;LLM 的结果是带元数据(model / date / temperature)的测量记录,temperature=0 也不保证逐次一致——"可评测"承诺的是打分流程可重跑,而非每个分数可复现。样本量 30、单模型、单次运行,是这组数字的已知局限。
| 阶段 | 内容 | 状态 |
|---|---|---|
| P1 | 核心分析工具 + 中文规则 router + CLI ask / report + 评测集种子 |
已完成 |
| P2 | LLM function calling(OpenAI-compatible API)+ 30 问评测集,与规则 baseline 对比准确率 | 已完成 |
| P3 | FastAPI 薄封装 + 最小 RAG + TraceID 结构化日志 | 已完成(三项均已落地) |
| P4 | 完整 demo(GIF、示例报告)与文档打磨 | 未开始 |
后续方向(不在当前范围):Redis 缓存、Docker、MCP;以及复用本项目 benchmarking 方法论的 LLM 推理 benchmark(llama.cpp / vLLM)。约束与完成标准详见 SPEC.md。
ARCHER2 是英国国家超算服务,部署于爱丁堡大学 EPCC,HPE Cray EX 架构,每个计算节点 128 核(双路 AMD EPYC 7742)。它是作者课业中使用的计算环境——本项目的数据来自在其上运行 PETSc 求解器 scaling 实验的真实导出,不是构造的样例。
读这个仓库不需要了解 ARCHER2,也不需要任何超算访问权限。 数据一旦落成 CSV 并导入 SQLite,本项目的全部功能(分析工具、路由、报告、评测)都在本地离线运行。ARCHER2 只解释这些数字从哪来,不构成运行门槛。
几个会反复出现的术语:
| 术语 | 含义 |
|---|---|
unknowns |
问题规模,即待求解方程组的未知数个数(本项目中 = 网格 m × n) |
ranks / threads |
MPI 进程数 / 每进程线程数;total_cores = ranks × threads |
mpi_like / hybrid_like |
按 threads == 1 / threads > 1 推断的保守标签,只描述进程与线程的配比形态,不能证明运行时真的启用了 OpenMP |
| speedup / efficiency | 相对单核 baseline 的加速比与并行效率 |
数据来自 ARCHER2 上 PETSc scaling_grid 基准的真实运行导出(覆盖 5 个 2D 问题规模,1000000 ~ 40960000 unknowns)。完整数据集不入库;仓库内提供脱敏 fixture examples/sample.csv:单节点、1000000 unknowns(1000 × 1000)、total_cores 从 1 到 128 的 36 条运行记录。所有公开样例和报告均已去除集群绝对路径。
python -m pytest # 全量
python -m pytest tests/test_router.py -x # 单文件快速反馈测试即验收合同:工具层用精确断言钉结构(dict 逐键比对),渲染层用要素断言留文案自由度。LLM 相关单元测试全部通过注入 FakeLLM 客户端离线运行——pytest 在无网络、无 key 的环境下也全绿。