Source code for protomotions.agents.ppo.utils.per_motion_exploration
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Checkpoint preparation and inspection snapshots for per-motion exploration."""
import torch
[docs]
def prepare_checkpoint(checkpoint, motion_index, actor, load_training_state):
"""Validate row identity/bounds or expand shared std for a weights-only start.
Neither the input checkpoint nor the actor is modified. Validation happens
before the agent loads any weights or optimizer state.
"""
if load_training_state or checkpoint["model"]["_actor.logstd"].ndim == 2:
if checkpoint["motion_index"] != motion_index.state_dict():
raise ValueError("Motion index differs from checkpoint; remap explicitly")
else:
# Weights-only warm start: every motion starts with the shared vector.
# Optimizer/curriculum state starts fresh in the agent.
checkpoint = dict(checkpoint, model=dict(checkpoint["model"]))
source = checkpoint["model"]["_actor.logstd"]
checkpoint["model"]["_actor.logstd"] = source.expand_as(actor.logstd).clone()
logstd = checkpoint["model"]["_actor.logstd"]
if not torch.all(
(logstd >= actor.config.min_logstd) & (logstd <= actor.config.max_logstd)
):
raise ValueError("Checkpoint logstd lies outside configured bounds")
return checkpoint
[docs]
def save_snapshot(path, motion_index, logstd, epoch, step_count):
"""Atomically replace the inspection sidecar; resume uses the full checkpoint.
The agent owns rank-zero selection and distributed error propagation.
"""
temporary = path.with_suffix(".tmp")
torch.save(
{
"motion_index": motion_index.state_dict(),
"epoch": epoch,
"step_count": step_count,
"logstd": logstd.detach().cpu(),
},
temporary,
)
temporary.replace(path)