Source code for protomotions.agents.peft.sft_model

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

"""SFT model for PEFT adapters on a frozen discrete-token GPC prior."""

import torch
from tensordict import TensorDict

from protomotions.agents.common.latent import (
    LATENT_KEY,
    LATENT_LOGITS_KEY,
    TARGET_LATENT_KEY,
)
from protomotions.agents.peft.model import DiscretePriorPEFTModel


[docs] class DiscretePriorPEFTSFTModel(DiscretePriorPEFTModel): """Discrete-prior PEFT model used by the supervised SFT agent. Rollout uses the frozen target encoder as the expert: encode the target motion into prior tokens, decode those tokens to an action, and store the tokens as supervision labels. Optimization replays the batch with teacher forcing and writes ``latent_logits`` for the generic supervision loss. """
[docs] def collect_expert_rollout(self, tensordict: TensorDict) -> TensorDict: target_prior_tokens = self._actor.predict_target_prior_tokens(tensordict) fsq_indices = self._actor.prior_tokens_to_fsq_indices(target_prior_tokens) fsq_codes = self._actor.fsq_indices_to_codes(fsq_indices) action = self._actor._decode(tensordict, fsq_codes) tensordict["action"] = action tensordict["mean_action"] = action tensordict["prior_tokens"] = target_prior_tokens tensordict[LATENT_KEY] = target_prior_tokens tensordict[TARGET_LATENT_KEY] = target_prior_tokens # The expert encoder is deterministic and SFT trains with # cross-entropy, so neglogp is an unused rollout-contract placeholder. if "neglogp" in self.out_keys: tensordict["neglogp"] = torch.zeros( action.shape[0], self._actor.num_prior_tokens, device=action.device, dtype=action.dtype, ) return tensordict
[docs] def materialize(self, tensordict: TensorDict) -> TensorDict: expert_td = self.collect_expert_rollout(tensordict.clone()) return self.forward(expert_td)
[docs] def forward(self, tensordict: TensorDict) -> TensorDict: if not isinstance(tensordict, TensorDict): raise TypeError( "DiscretePriorPEFTSFTModel.forward expects a TensorDict input." ) if TARGET_LATENT_KEY not in tensordict: tensordict = self.collect_expert_rollout(tensordict) target_prior_tokens = tensordict[TARGET_LATENT_KEY].detach() teacher_tokens = self._actor.perturb_tokens( target_prior_tokens, rate=self.config.token_perturb_rate, mode=self.config.token_perturb_mode, ) prior_dict = self._actor.build_prior_input(tensordict, tokens=teacher_tokens) tensordict[LATENT_LOGITS_KEY] = self._actor(prior_dict) return tensordict