Source code for protomotions.agents.peft.model

# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Top-level discrete-prior PEFT model container.

The pretrained prior is passed in by the agent. This class only wires the
PEFT actor to the optional PPO critic. Rollout is the module forward path.
"""
from __future__ import annotations

import torch

from protomotions.agents.base_agent.model import BaseModel
from tensordict import TensorDict

from protomotions.agents.peft.actor import DiscretePriorPEFTActor
from protomotions.utils.hydra_replacement import get_class


[docs] class DiscretePriorPEFTModel(BaseModel): """Discrete-prior PEFT actor plus optional PPO critic. ``forward`` is reserved for rollout TensorDicts. SFT teacher forcing lives on ``DiscretePriorPEFTSFTModel`` so RLFT and SFT flows stay separate. """
[docs] def __init__( self, config, pretrained_prior_model, mimic_target_poses_dim: int = 0, ): super().__init__(config) ActorClass = get_class(config.actor._target_) self._actor: DiscretePriorPEFTActor = ActorClass( config=config.actor, pretrained_prior_model=pretrained_prior_model, mimic_target_poses_dim=mimic_target_poses_dim, ) self.in_keys = list(self._actor.in_keys) self.out_keys = list(self._actor.out_keys) self._critic = None critic_config = getattr(config, "critic", None) if critic_config is not None: CriticClass = get_class(critic_config._target_) self._critic = CriticClass(config=critic_config) # Add critic in_keys and out_keys self.in_keys = list(dict.fromkeys(self.in_keys + self._critic.in_keys)) self.out_keys = list(dict.fromkeys(self.out_keys + ["value"]))
[docs] def optional_checkpoint_modules(self) -> tuple[str, ...]: """SFT checkpoints can initialize RLFT with a fresh value critic.""" return super().optional_checkpoint_modules() + ("_critic",)
[docs] def materialize(self, tensordict: TensorDict) -> TensorDict: """Initialize shapes with teacher forcing, never sampling or pinning a reference.""" actor = self._actor tokens = torch.zeros( tensordict.batch_size[0], actor.num_prior_tokens, dtype=torch.long, device=tensordict.device or next(actor.parameters()).device, ) actor(actor.build_prior_input(tensordict, tokens=tokens)) fsq_codes = actor.fsq_indices_to_codes(actor.prior_tokens_to_fsq_indices(tokens)) actor._decode(tensordict, fsq_codes) return self._forward_critic(tensordict)
def _forward_critic(self, tensordict: TensorDict) -> TensorDict: if self._critic is None: return tensordict tensordict = self._critic(tensordict) out_key = self._critic.out_keys[0] if out_key != "value": tensordict["value"] = tensordict[out_key] return tensordict
[docs] def forward(self, tensordict: TensorDict) -> TensorDict: """Run rollout inference and critic evaluation.""" if not isinstance(tensordict, TensorDict): raise TypeError( "DiscretePriorPEFTModel.forward expects a TensorDict rollout input." ) return self.forward_rollout(tensordict)
[docs] def forward_rollout(self, tensordict: TensorDict) -> TensorDict: output_td = self._actor.get_action_and_logp(tensordict) return self._forward_critic(output_td)