(十七)先完成目录,再发布版本
1. 区分完整版本与默认指向
# 续写同一文件:model_project/src/modeldelivery/releases.py
def activate(root, version):
# 验证完整版本后替换指向文件,已有 Predictor 不受这个动作影响。
root = Path(root).resolve()
verify(version_path(root, version))
temporary = root / "current.json.tmp"
write_json(temporary, {"version": version})
os.replace(temporary, root / "current.json")activate() 只改 current.json,先验证目标版本,再以临时文件替换指向。已经发布的 v1/ 不改写;训练新的结果用 v2/。这样检查旧版本、回退和复现预测时,都有明确的文件位置。
2. 在临时目录写完整套文件
# 续写同一文件:model_project/src/modeldelivery/releases.py
def publish(csv, root, version, epochs=35, seed=7):
root = Path(root).resolve()
root.mkdir(parents=True, exist_ok=True)
destination = version_path(root, version)
if destination.exists():
raise ValueError("这个版本已经存在,请选择新的版本名")
prepared = prepare(csv, seed)
weights, metrics, history = fit(prepared, epochs, seed)
with TemporaryDirectory(prefix=".build-", dir=root) as folder:
stage = Path(folder).resolve()
# 两个绝对路径都在指定模型根目录内,再发布完整目录。
if stage.parent != root or destination.parent != root:
raise ValueError("模型发布路径不在根目录内")
torch.save(weights, stage / "weights.pt")
np.savez(stage / "preprocess.npz", **prepared.scaler)训练结束后,在模型根目录内部创建 .build-... 临时目录。权重与缩放先写到这里;模型服务仍能继续读取先前完整版本。TemporaryDirectory 管理这次临时目录,写入失败后清理尚未发布的内容。
# 续写同一文件:model_project/src/modeldelivery/releases.py
# 将解释模型所需的元数据与同次训练记录一起保存。
metadata = {
"format": 1,
"version": version,
"features": FEATURES,
"architecture": "linear-2-1",
"source_hash": prepared.source_hash,
"seed": seed,
"epochs": epochs,
"torch_version": str(torch.__version__),
"numpy_version": str(np.__version__),
}
write_json(stage / "metadata.json", metadata)
write_json(stage / "metrics.json", metrics)
write_json(stage / "history.json", history)
write_json(
stage / "splits.json",
{name: ids.tolist() for name, ids in prepared.splits.items()},
)元数据保存版本名、数据摘要、随机种子、NumPy 与 PyTorch 版本。历史和分组下标与权重一起输出,之后复查选中轮数时能找到同次实验的记录。
3. 最后才改变加载入口
# 续写同一文件:model_project/src/modeldelivery/releases.py
# 清单写完并通过重算检查后,才发布正式目录。
write_json(
stage / "manifest.json",
{
"format": 1,
"files": {
name: hashlib.sha256((stage / name).read_bytes()).hexdigest()
for name in sorted(FILES)
},
},
)
verify(stage)
stage.rename(destination)
activate(root, version) # 最后才改变“下次启动加载哪一版”的指向。
return {"version": version, **metrics}写完六个文件,计算清单并重新核对;通过后将临时目录改名为正式版本。两者都在同一模型根目录下,这一步不跨磁盘搬运文件。只有完整版本已经存在,才调用 activate()。在保存数组时故意抛错的测试会检查:没有半份 v2,当前指向仍是 v1。
发布命令按顺序执行。先完成一次训练并检查报告,再决定激活与重开服务;同一个版本目录保存同一次发布的内容。