Table of Contents
如何在新工作站上复现科研代码
公开的科研仓库不一定能直接运行。论文也许把算法写得很清楚,但真正可用的环境往往只存在于某位实验室成员的终端历史里:特定 commit、没有记录的参数、厂商 SDK、驱动版本,或代码默认存在的数据结构。
与其一开始重建整个项目,不如先选择一个能回答实际问题的阶段,固定输入,并为它建立一个小型复现包。你需要知道的不是“看起来差不多能跑”,而是究竟运行了什么、在哪里失败,以及下一步是否值得投入。
只定义一个阶段和一个可观察结果
“复现整个仓库”太宽泛。更好的目标是:
在
REVISION版本上,用一个已获授权的样本运行scripts/make_plot.py,得到非空 PNG,并返回退出码 0。
结果也可以是具有已知结构的表格、通过的测试,或与明确输出对应的日志。先写下成功信号,再修改环境;同时写清楚这次测试不覆盖什么。可视化脚本不能证明采集硬件有效,预处理阶段也不能复现论文的最终指标。
调试之前先固定源码
mkdir -p repro/environment repro/output
git rev-parse --verify HEAD > repro/environment/git-commit.txt
git status --short > repro/environment/git-status.txt
git submodule status --recursive > repro/environment/git-submodules.txt
`git rev-parse --verify` 会把版本解析为明确的对象 ID。工作树状态同样重要:干净的 commit 与附带三个本地补丁的 commit 不是同一个输入。
如果仓库没有 release 或环境锁,不要悄悄创建一个再说它来自上游。把兼容性补丁分开保存,并记录每一处修改。
记录机器,但不要复制秘密
python -VV > repro/environment/python.txt 2>&1
python -m platform > repro/environment/platform.txt
python -m pip --version > repro/environment/pip.txt
python -m pip freeze --all > repro/environment/packages.txt
if command -v nvidia-smi >/dev/null 2>&1; then
nvidia-smi --query-gpu=name,driver_version --format=csv,noheader > repro/environment/gpu.txt
fi
Python 的 `platform` 模块可以记录系统与解释器概要。`pip freeze` 保存的是已安装软件包快照,不是完整 lockfile,也不是依赖求解结果。
对于 GPU 代码,还要记录 GPU、驱动、编译扩展时使用的 toolkit,以及框架看到的 CUDA runtime。NVIDIA 的 CUDA 兼容性文档 说明了为什么只写“CUDA 12”仍然不够。
分享复现包之前,检查并移除用户名、主机名、私有路径、令牌、网络地址、许可证数据与设备序列号。
移动数据之前先写输入契约
很多看似依赖问题的错误,其实来自输入形状。至少记录:
- 文件类型、数组键或表格字段;
- 维度、dtype、单位、顺序与坐标约定;
- 数值范围与缺失值规则;
- 测试样本的 SHA-256;
- 样本是合成、公开、已授权,还是私下提供。
不要把客户或实验室数据放进公开复现包。小型合成样本通常足以证明文件与接口路径可用,但不能证明它与真实实验在科学上等价。
优先使用项目自己的环境路径
先采用仓库已经记录的安装方式。如果存在 lockfile、环境 YAML、容器定义或精确安装脚本,就保留其版本。
如果使用容器,除了标签还要记录 image digest。Docker 的 `image pull` 文档说明了 digest 如何固定一个不可变的镜像版本,而标签以后可能移动。
如果没有锁定环境,可以创建干净环境并保留命令历史。不要一遇到错误就升级全局 Python、替换系统 CUDA,或加入无关的软件源;先找出真正阻塞这个阶段的依赖。
保留第一次真实失败
set +e
python -u scripts/make_plot.py --input sample/input.npz --output repro/output/result.png > repro/output/run.log 2>&1
run_status=$?
set -e
echo "$run_status" > repro/output/exit-code.txt
不要用最终成功日志覆盖第一次失败。第一次失败往往最有价值:它会暴露缺少的动态库、意外字段、形状不匹配或不支持的设备能力。
每次修复只记录四件事:观察到的错误、最小修改、修改理由、下一次运行结果。这样,“装了几个东西终于能跑”就变成了别人可以检查的路径。
对复现包做哈希,然后作决定
最终复现包应把环境、输入契约、日志、输出、失败台账与报告放在一起。使用 SHA-256 对选定的样本、脚本、日志与输出进行哈希。Python 的 `hashlib` 在受支持的版本中提供 SHA-256;Linux 上也可以直接使用 sha256sum。
哈希不能证明科学结论正确,它只能证明报告所指的是哪些字节。
最后给出明确结论:
- GO: 指定阶段在记录的环境中完成,并产生约定结果。
- GO WITH LIMITS: 阶段在记录修改后完成,但可移植性或数据假设仍有限制。
- NO-GO: 阶段被明确的依赖、输入、许可证、硬件要求或验收失败阻塞。
一个范围清楚的 NO-GO 也很有价值,它能阻止团队在错误的层级继续花费数天。
一个具体的 OpenHI 例子
我用这套方法测试了事件式高光谱成像项目 OpenHI 的一个公开阶段。在 080ad074… 版本上,weighted-cumulative 可视化脚本使用包含 4,096 个事件的确定性合成样本完成了无界面运行。复现包保存了 Python 3.10.13、NumPy 2.2.6、Matplotlib 3.10.9、精确命令与日志、120 个时间 bin、生成的 PNG,以及输入输出哈希。
结论被刻意限制为:这个文件、接口与可视化路径为 GO;它不能证明采集、学习补偿、校准、重建精度、硬件兼容性或论文科学结果。
你可以先查看完整的 OpenHI 软件阶段样本。
如果你的问题正好是现有工作站上的一个 OpenHI 阶段,可以先提交只包含元数据的适配检查。可选的固定 500 美元 sprint 会对一个约定阶段和一份已获授权的数据执行相同的环境记录、命令、输出、失败台账与 go/no-go 流程;硬件与新的科研开发另行处理。
顺序保持简单:定义阶段,固定源码,描述输入,记录环境,保留第一次失败,再依据证据作决定。
