1.1 快速上手
# 性质预测(单卡)
python property_prediction/train.py -c property_prediction/configs/spherenet/spherenet_qm9_U0.yaml
# 原子间势(多卡)
python -m paddle.distributed.launch --gpus="0,1,2,3" \
interatomic_potentials/train.py -c interatomic_potentials/configs/chgnet/chgnet_mptrj.yaml
# 结构生成(单卡)
python structure_generation/train.py -c structure_generation/configs/diffcsp/diffcsp_mp20.yaml
# 临时覆盖配置项(命令行追加 Key=Value)
python <task>/train.py -c <config.yaml> Trainer.max_epochs=50
6 个任务入口:
property_prediction/(DimeNet++/MEGNet/SphereNet/iComformer)、interatomic_potentials/(CHGNet/M3GNet/SphereNet)、electronic_structure/(InfGCN/MatENO)、spectrum_enhancement/(SFIN)、spectrum_elucidation/(DiffNMR 系)、structure_generation/(DiffCSP/MatterGen)。完整模型清单见 全任务模型总览。预测式 vs 生成式:前 4 个任务是预测式(train+eval 走 BaseTrainer,推理走
predict.py);后 2 个是生成式(train 走 BaseTrainer,生成走 sample.py + Sampler)。两者模型契约不同,见 §2.3。1.2 我想做哪件事
flowchart TD
Q(["我想做什么?"])
Q --> W1["从头训练"] --> C1["do_train=True
python train.py -c config"] Q --> W2["断点续训"] --> C2["Trainer.resume_from_checkpoint=ckpt"] Q --> W3["加载预训练微调"] --> C3["Trainer.pretrained_model_path=权重"] Q --> W4["只评估/测试"] --> C4["do_train=False do_eval=True
pretrained_model_path=权重"] Q --> W5["多卡加速"] --> C5["paddle.distributed.launch --gpus=..."]
python train.py -c config"] Q --> W2["断点续训"] --> C2["Trainer.resume_from_checkpoint=ckpt"] Q --> W3["加载预训练微调"] --> C3["Trainer.pretrained_model_path=权重"] Q --> W4["只评估/测试"] --> C4["do_train=False do_eval=True
pretrained_model_path=权重"] Q --> W5["多卡加速"] --> C5["paddle.distributed.launch --gpus=..."]
1.3 配置文件
Global:
do_train / do_eval / do_test : True
Trainer:
max_epochs / max_iter / seed / output_dir
eval_freq | eval_freq_steps # 二选一(epoch 级 vs 步级 eval,互斥)
save_freq | save_freq_steps # 检查点节奏
pretrained_model_path / resume_from_checkpoint
use_amp / eval_with_no_grad / gradient_accumulation_steps
best_metric_indicator / name_for_best_metric / greater_is_better
use_tensorboard / use_visualdl / use_wandb
Model / Dataset / Optimizer / Vocabulary / Metric # 按 __class_name__ 注册
1.4 Trainer 配置项
| 配置项 | 作用 | 例子 |
|---|---|---|
max_epochs | 最大训练轮数 | 1000 |
max_iter | 全局步数预算,到步早停 | 10000 |
eval_freq | 每 N epoch 验证(epoch 级 eval) | 1 |
eval_freq_steps | 每 N 步验证(步级 eval);设了即关闭 epoch 级 | 200 |
save_freq / save_freq_steps | latest / step_N 检查点节奏 | 2000 |
resume_from_checkpoint | 断点续训(含优化器+进度) | latest.pdparams 路径 |
pretrained_model_path | 加载预训练权重(仅权重) | 权重路径 |
use_amp / amp_level | 混合精度 | True / 'O1' |
gradient_accumulation_steps | 梯度累积 | 2 |
eval_with_no_grad | 评估 no_grad(含高阶导则关) | True |
best_metric_indicator + name_for_best_metric + greater_is_better | best 检查点评选口径 | eval_loss / mae / False |
1.5 常见任务
从头训练
python <task>/train.py -c <config.yaml>
断点续训
python <task>/train.py -c <config.yaml> Trainer.resume_from_checkpoint=./output/.../latest.pdparams
恢复优化器状态 + 训练进度;勿与
pretrained_model_path 同用。加载预训练微调
python <task>/train.py -c <config.yaml> Trainer.pretrained_model_path=./pretrained/model.pdparams
只评估 / 测试
python <task>/train.py -c <config.yaml> \
Global.do_train=False Global.do_eval=True \
Trainer.pretrained_model_path=./output/.../best.pdparams
步级 eval(长 epoch 用)
Trainer.eval_freq_steps=200 Trainer.save_freq_steps=2000
设了
eval_freq_steps,epoch 级 eval / epoch 级 best / by-epoch lr 全部关闭,改由步级 eval 接管。1.6 运行后产物
best.pdparams
验证最优检查点,推理优先用。
latest.pdparams
最近检查点,续训用它。
step_N.pdparams
每 save_freq_steps 步存一个。
run.log
训练日志。
tensorboard/
曲线:tensorboard --logdir <dir>
config.yaml 快照
本次运行配置(含覆盖),便于复现。
output_dir 运行时自动加 _t_<时间戳>_s_<seed> 后缀。
1.7 训练时间线
flowchart LR
S(["train.py -c config"]) --> A["装配:模型/数据/优化器"]
A --> B["训练循环(逐 epoch)"]
B --> C["每 log_freq 步打日志
按节奏存检查点"] C --> D{"按 eval 节奏评估"} D --> E["更新 best + 学习率"] E --> F{"还有 epoch 且未到 max_iter?"} F -- 是 --> B F -- 否 --> G(["产出:best/latest + 日志"])
按节奏存检查点"] C --> D{"按 eval 节奏评估"} D --> E["更新 best + 学习率"] E --> F{"还有 epoch 且未到 max_iter?"} F -- 是 --> B F -- 否 --> G(["产出:best/latest + 日志"])
2.1 装配全景
flowchart LR
C(["YAML"]) --> V[build_vocab]
V --> M["build_model
你的模型"] V --> D["build_dataloader
你的 Dataset+collate"] M --> O[build_optimizer] D --> O --> MM["build_metric
你的指标"] --> T[BaseTrainer] --> RUN(["train/eval"]) M -.注册.-> R["__class_name__/__init_params__
MODEL_REGISTRY(权重)"]
你的模型"] V --> D["build_dataloader
你的 Dataset+collate"] M --> O[build_optimizer] D --> O --> MM["build_metric
你的指标"] --> T[BaseTrainer] --> RUN(["train/eval"]) M -.注册.-> R["__class_name__/__init_params__
MODEL_REGISTRY(权重)"]
2.2 注册约定
Model:
__class_name__: MyModel # 类名,需 import 进对应 __init__.py
__init_params__: { hidden_dim: 128 }
| build 函数 | 位置 | 说明 |
|---|---|---|
build_model(cfg, vocab=None) | models/__init__.py | vocab 注入(若构造函数接受) |
build_dataloader(cfg, vocab=None) | datasets/__init__.py | 读 Dataset.{train,val,test} |
build_optimizer(cfg, model, max_epochs, n_steps) | optimizer/ | 含 lr scheduler |
build_metric(cfg) / build_graph_converter / build_vocab | metrics/ · models/ · vocab/ | 同注册模式 |
MODEL_REGISTRY(models/__init__.py)是预训练权重下载注册表(name→BCE url),与类的 import 注册是两回事。2.3 模型契约
flowchart TD
B(["batch=collate(samples)"]) --> F["model.forward(batch)"]
F --> LD["loss_dict:{'loss':Tensor,...}"] --> BL["loss_dict['loss']→backward"]
F --> PD["pred_dict:{...}"] --> MET["指标/预测"]
class MyModel(paddle.nn.Layer):
def __init__(self, hidden_dim=128, vocab=None, **kwargs):
super().__init__(); self.vocab=vocab; self.target_name="energy"
def forward(self, batch):
pred = self._compute(batch)
loss = some_loss(pred, batch["label"])
return {"loss_dict": {"loss": loss, "mae": paddle.mean(paddle.abs(pred-batch["label"]))},
"pred_dict": {self.target_name: pred}}
def before_train(self, trainer): ... # 可选 hook
loss_dict["loss"] 是反向唯一入口;其它键可作 best 指标(name_for_best_metric)。
生成式模型(扩散:DiffNMR / DiffCSP / MatterGen)
生成式模型的 训练 也返回 loss_dict(BaseTrainer 优化),但 生成/采样 走独立的 sample() 方法 + Sampler,不经 Trainer:
# 训练(BaseTrainer 优化)
def forward(self, batch):
return {"loss_dict": {"loss": total_loss, "loss_coord": ..., "loss_atom": ...}}
# 生成(sample.py 入口 + MolecularSampler/StructureSampler,不经 Trainer)
def sample(self, batch, num_inference_steps=1000, **kwargs):
return generated_structures
预测式任务(性质/势/电子结构/图像增强)推理走
predict.py + BasePredictor 子类;生成式任务(NMR/结构)生成走 sample.py + ppmat/sampler/。2.4 数据集契约
class MyDataset(paddle.io.Dataset):
def __getitem__(self, idx):
return {"graph": pgl_graph, "x": np.ndarray, "y": np.ndarray, "id": idx, "info": {...}}
def __len__(self): ...
| collate_fn | 处理 |
|---|---|
DefaultCollator | ndarray→stack、Mapping→递归、pgl.Graph→batch、Data→Batch.from_data_list |
RadiusGraphCollator | 批量半径图 + triplet 偏移 |
DensityCollator | 体密度场:变长 padding + mask |
节点特征键名:
cart_coords / atom_types / x(RadiusGraphConverter 产出),接入已有模型需保持一致。2.5 指标
(a) 函数式:def metric(pred,label)->scalar,注册到 Metric 段。
(b) 流式:实现 update_step/compute_epoch/reset,框架在 epoch 边界聚合。
best 口径:best_metric_indicator + name_for_best_metric + greater_is_better。
2.6 训练入口
config = OmegaConf.to_container(OmegaConf.merge(OmegaConf.load(c), OmegaConf.from_dotlist(args)), resolve=True)
vocab = build_vocab(config.get("Vocabulary"))
model = build_model(config["Model"], vocab=vocab)
loaders = read_dataloader_config(config, vocab=vocab)
opt, lr = build_optimizer(config["Optimizer"], model, config["Trainer"]["max_epochs"], len(loaders[0]))
trainer = BaseTrainer(config["Trainer"], model, *loaders[:2], optimizer=opt, lr_scheduler=lr,
compute_metric_func_dict=build_metric(config.get("Metric")))
if do_train: trainer.train()
if do_eval: trainer.eval(loaders[1])
if do_test: trainer.eval(loaders[2])
2.7 BaseTrainer 能力
| 能力 | 配置 |
|---|---|
| 训练循环+反向+优化 | 梯度累积、裁剪、AMP |
| 检查点 | best/latest/step_N(分布式仅 rank0 写) |
| 续训/预训练 | resume_from_checkpoint / pretrained_model_path |
| 评估节奏 | eval_freq(epoch)或 eval_freq_steps(步) |
| lr 调度 | ReduceOnPlateau(按指标)或其它;by-epoch/by-step |
| 日志/分布式/早停 | tb/vdl/wandb、fleet、max_iter |
2.8 最小骨架
# my_model.py
class MyModel(paddle.nn.Layer):
def __init__(self, hidden_dim=64, vocab=None, **kw):
super().__init__(); self.linear=paddle.nn.Linear(hidden_dim,1)
def forward(self, batch):
pred=self.linear(batch["x"]).squeeze(-1)
loss=paddle.nn.functional.mse_loss(pred,batch["y"])
return {"loss_dict":{"loss":loss,"mae":paddle.mean(paddle.abs(pred-batch["y"]))},
"pred_dict":{"y":pred}}
# ppmat/models/__init__.py 加: from .my_model import MyModel
# config.yaml: Model/Dataset/Optimizer/Trainer/Global 同标准结构
2.9 常见陷阱
- 类必须 import 进 __init__.py,否则
eval(__class_name__)报 NameError。 loss_dict必须有"loss"。- collate 要与样本类型匹配(pgl.Graph / Data 各有路径)。
- 分布式自定义存盘要尊重 rank(只 rank0)。
vocab仅当构造函数有该参数才注入。${}插值在 resolve=True 展开。
3.1 代码地图
| 模块 | 职责 |
|---|---|
ppmat/trainer/base_trainer.py | 训练/评估循环、检查点、lr、日志、分布式 |
ppmat/datasets/__init__.py · collate_fn.py | build_dataloader、DefaultCollator/Radius/Density |
ppmat/datasets/build_field.py · build_* .py | 场/结构/分子/图像构建器 |
ppmat/datasets/density_dataset.py · grid_sampler.py | 密度数据集族、网格采样 |
ppmat/datasets/graph_utils/ | radius/radius_graph 等图工具 |
ppmat/models/__init__.py · common/graph_converter.py | build_model/graph_converter、MODEL_REGISTRY、RadiusGraphConverter |
ppmat/optimizer/ · metrics/ · vocab/ | 优化器+lr、指标(函数式+流式)、词表 |
ppmat/predictor/ · sampler/ · schedulers/ | 推理包、采样器、扩散调度 |
ppmat/utils/ | download、io(write_cube/calc_md5)、save_load、logger、misc、crystal、pgl_compat |
ppmat/visualization/ | molecule/crystal/structure/volume/animation |
<task>/{train,predict,sample}.py | 任务入口脚本 |
test/ · setup.py · requirements.txt · .pre-commit-config.yaml | 测试、打包、依赖、提交钩子 |
3.2 架构分层
flowchart TD
L1["入口层 <task>/train.py"] --> L2["Trainer 层 BaseTrainer"]
L2 --> L3["组件层 build_model/dataloader/optimizer/metric"]
L3 --> L4["基础层 models/datasets/optimizer/metrics/vocab/utils"]
L2 -.使用.-> U["utils: save_load / logger / download / io"]
L4 -.注册.-> R["__class_name__ 反射 + MODEL_REGISTRY"]
分层原则:入口只编排;Trainer 不感知具体模型(只认 forward 契约);组件层走注册反射;基础层可独立单测。
3.3 新增一个任务(checklist)
- 建目录
<task>/,含train.py/predict.py(或 sample.py) /configs/。 - 实现模型(§2.3 契约),
import进ppmat/models/__init__.py。 - 实现数据集(§2.4),选/写 collate_fn,
import进ppmat/datasets/__init__.py。 - (按需)指标、graph converter、词表(注册到 vocab)。
- config 模板:Global/Trainer/Model/Dataset/Optimizer/Vocabulary/Metric。
- README:命令 + 结果表 + 数据准备说明。
- (可选)预训练权重:上传 BCE →
MODEL_REGISTRY登记 → 按 §3.8 打包。 - (可选)推理:实现/复用
ppmat/predictor/子类。 - 测试:
test/test_<task>.py(契约、collate、小样本前向)。 - 冒烟:
max_epochs=1跑通 → 提交。
3.4 Trainer 内部机制
方法职责边界
| 方法 | 职责 |
|---|---|
train() | resume → epoch 循环(max_iter 早停)→ 每 epoch 调 train_epoch → epoch 级 streaming/log/save → eval 节奏门 → best/by-epoch lr |
train_epoch(train,val) | 逐 batch forward/loss/backward(梯度累积+AMP+裁剪)→ opt.step + by-step lr → log(log_freq) → 步级 eval(eval_freq_steps) → step 检查点(save_freq_steps) |
eval_epoch(loader, reset_streaming_metrics=) | gather → compute_metric_func → 流式聚合(reset 受控)→ 返回 meters |
_compute_streaming_metrics(stage, reset=) | 聚合 metric_modules:compute_epoch + 条件 reset |
两套 eval 节奏(互斥)
flowchart TD
G{"eval_freq_steps
是否设置?"} G -- "None(未设)" --> EP["epoch 级:每 eval_freq epoch → eval_epoch
→ best + by-epoch lr(在 train() 里)"] G -- "非空(已设)" --> ST["步级:每 eval_freq_steps 步 → eval_epoch(在 train_epoch 里)
→ best + latest + by-epoch lr;并关闭 epoch 级"]
是否设置?"} G -- "None(未设)" --> EP["epoch 级:每 eval_freq epoch → eval_epoch
→ best + by-epoch lr(在 train() 里)"] G -- "非空(已设)" --> ST["步级:每 eval_freq_steps 步 → eval_epoch(在 train_epoch 里)
→ best + latest + by-epoch lr;并关闭 epoch 级"]
检查点与分布式
- 前缀:
best(_determine_best_metric)、latest(save_freq / 步级 eval)、step_N(save_freq_steps)。 save_load.save_checkpoint仅 rank0 写;多卡 cache 构建后dist.barrier()同步。- 步级
step_N检查点在 batch 循环顶层独立判断(不依赖 eval 命中)。
lr / AMP / 累积
ReduceOnPlateau按indicator/indicator_name取值 step;其他调度器直接 step。- AMP:
paddle.amp.GradScaler+amp.decorate;梯度累积按gradient_accumulation_steps。
3.5 缓存子系统(密度数据集)
- 首次构建把 field/graph 序列化到
<cache>/fields|graphs/{idx:010d}.pkl;后续直读。 - 校验:比
build_field_cfg.pkl/build_graph_cfg.pkl/graph_vocab.pkl(is_equal)。 - 多进程:
ProcessPoolExecutor(spawn)+ worker initializer,避免继承 CUDA 状态。 - MD17 子类:FFT→CUBE 物化(首次),复用 base cache(见 §Ⅰ/Ⅱ 的数据契约)。
维护注意:缓存键当前不含 split 内容指纹;改 split 顺序需
overwrite=True 或清缓存。mmap 在某些网络 FS 不可用(原版即如此)。3.6 配置系统
OmegaConf.load → merge(dotlist) → to_container(resolve=True)。${a.b}插值在 resolve 阶段展开;CLI 用 dotlistA.b.c=v。- rank0 把最终 config 存
<output_dir>/<name>.yaml快照。 - 未知键:构造函数用
**kwargs吞掉或strict_unused报错(build_model)。
3.7 下载与注册表
download.get_datasets_path_from_url(url, md5)/get_weights_path_from_url:下到 DATASETS_HOME/WEIGHTS_HOME,md5 校验,自动解压。MODEL_REGISTRY(models/__init__.py):name → BCE 权重 url;VOCAB_REGISTRY(vocab):name → 词表 url + md5。- 数据集子类用类属性
url/md5声明下载源;split_url/split_md5为预留字段。
3.8 预训练模型包契约
注册的预训练模型须用固定 archive 布局(见 docs/model_package.md):
<model_name>.zip
└── <model_name>/
├── <model_name>.yaml # 配置
└── checkpoints/
└── best.pdparams # 权重
包名、顶层目录、配置文件名须与 MODEL_REGISTRY 的 key 一致。FieldPredictor 等按此布局解析。
3.9 测试
test/:pytest,覆盖 spherenet/scatter/md17_dataset/download 等基础路径。- 建议补:collate 行为、cache 命中/未命中、模型 forward 契约(
loss_dict/pred_dict)。 - 运行:
python -m pytest test/ -q(注意某些环境对 mmap/网络挂载敏感)。 - Cython 扩展(如 mattersim)需先
setup.py build_ext --inplace再跑相关测试。
3.10 依赖与发布
requirements.txt:运行时依赖(setup.py读取并去重)。pyproject.toml:构建/开发依赖。setup.py:Cython 扩展编译 +setuptools-scm版本。.pre-commit-config.yaml:black / ruff / eof / yaml / 大小写等钩子;提交前pre-commit run。- 新增运行时 import 的三方库(如 plotly/matplotlib/kaleido)须同时进 requirements。
3.11 代码风格约定
- 节点特征键名:
cart_coords/atom_types/x(勿再用旧pos/atomic_number)。 - 模型统一
forward→{loss_dict,pred_dict}契约。 - 组件走
__class_name__/__init_params__注册;类 import 进对应__init__.py。 - 日志用
ppmat.utils.logger;分布式写盘只 rank0。 - 用户输入校验抛
ValueError/TypeError(勿用assert)。 - 遵循 black/ruff;行宽 88。
3.12 维护注意事项(演进时)
- 改 forward 契约 / 节点特征键名 / 注册模式属破坏性变更,需同步所有模型与测试。
- 改缓存布局或校验键会失效旧缓存——升
schema_version或文档说明清缓存。 - 改
build_*签名注意向后兼容(vocab 等可选注入)。 - eval 节奏 / 检查点时序的改动要有测试覆盖(易出静默不存盘/指标污染)。
- 预训练权重 / 词表 URL 改动要同步
MODEL_REGISTRY/VOCAB_REGISTRY的 md5。 - 新增依赖同步 requirements + 文档;重模型/数据 import 避免放到包
__init__顶层(影响 import 时延)。