Source code for protomotions.agents.common.pretrained
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Utilities for loading frozen pretrained modules."""
from __future__ import annotations
from pathlib import Path
from typing import Any, Mapping
import torch
from torch import nn
from protomotions.agents.utils.normalization import (
materialize_lazy_running_stats_from_state_dict,
)
from protomotions.utils.config_utils import load_resolved_configs_from_checkpoint
from protomotions.utils.hydra_replacement import get_class
[docs]
def freeze_module(module: nn.Module) -> None:
"""Freeze a module in eval mode."""
module.eval()
for parameter in module.parameters():
parameter.requires_grad = False
[docs]
def get_path(root: Any, path: str) -> Any:
"""Resolve a dotted attribute/key path.
Module paths may use public names such as ``actor`` even when the model
stores the module as ``_actor``. An empty path returns ``root`` unchanged,
which lets callers request the whole loaded model.
"""
if not path:
return root
current = root
for part in path.split("."):
if isinstance(current, dict):
current = current[part]
elif isinstance(current, (list, tuple, nn.ModuleList, nn.Sequential)) and part.isdigit():
current = current[int(part)]
elif hasattr(current, part):
current = getattr(current, part)
elif hasattr(current, f"_{part}"):
current = getattr(current, f"_{part}")
else:
raise AttributeError(f"Could not resolve '{path}' at '{part}'")
return current
def _set_dotted_attr(root: Any, path: str, value: Any) -> None:
"""Assign ``value`` at a dotted config path below ``root``.
This is used for checkpoint-path overrides on loaded resolved configs.
Intermediate path segments can walk through dictionaries or object
attributes; the final segment is overwritten in-place. Empty paths are
rejected so callers cannot accidentally replace the whole config object.
"""
if not path:
raise ValueError("checkpoint_path override cannot use an empty path")
parts = path.split(".")
current = root
for part in parts[:-1]:
if isinstance(current, dict):
current = current[part]
else:
current = getattr(current, part)
if isinstance(current, dict):
current[parts[-1]] = value
else:
setattr(current, parts[-1], value)
[docs]
def load_pretrained_model_module(
config,
device: torch.device,
checkpoint_path_overrides: Mapping[str, str] | None = None,
prefer_inference_config: bool = False,
) -> nn.Module:
"""Instantiate a checkpoint-backed or embedded frozen module."""
checkpoint_path = Path(config.checkpoint_path) if config.checkpoint_path else None
if checkpoint_path is None or not checkpoint_path.exists():
module_config = getattr(config, "module_config", None)
if module_config is not None:
module_cls = get_class(module_config._target_)
module = module_cls(config=module_config).to(device)
module.eval()
if config.freeze:
freeze_module(module)
return module
raise FileNotFoundError(
f"Pretrained model checkpoint not found: {checkpoint_path}"
)
resolved_configs = load_resolved_configs_from_checkpoint(
checkpoint_path,
prefer_inference=prefer_inference_config,
)
model_config = get_path(resolved_configs, config.config_path)
for path, value in (checkpoint_path_overrides or {}).items():
_set_dotted_attr(model_config, path, value)
model_cls = get_class(model_config._target_)
model = model_cls(config=model_config).to(device)
checkpoint = torch.load(checkpoint_path, map_location=device, weights_only=False)
state_dict = checkpoint[config.state_dict_key]
model.materialize_from_state_dict(state_dict)
materialize_lazy_running_stats_from_state_dict(model, state_dict)
model.load_state_dict(state_dict, strict=config.strict)
model.eval()
module = get_path(model, config.module_path)
if config.freeze:
freeze_module(module)
return module