Every saved model is a self-describing directory. See artifacts
for the format.
Save
policy is an rfx.nn.Policy instance (e.g., MLP, ActorCritic) after
training. robot_config and normalizer are optional — pass them to make
the artifact self-describing:
from rfx.nn import MLP
from rfx.utils import ObservationNormalizer
policy = MLP(obs_dim=48, action_dim=12) # or any trained Policy
config = rfx.GO2_CONFIG # RobotConfig (built-in or custom)
normalizer = ObservationNormalizer() # optional, bundles obs stats
# ...train policy + fit normalizer...
policy.save(
"runs/go2-walk-v1",
robot_config=config,
normalizer=normalizer,
training_info={"total_steps": 50000},
)
See Policies for how to define and train a policy.
Creates:
runs/go2-walk-v1/
├── rfx_config.json # architecture + robot + training metadata
├── model.safetensors # weights
└── normalizer.json # observation normalizer state
Load
loaded = rfx.load_policy("runs/go2-walk-v1")
loaded = rfx.load_policy("hf://rfx-community/go2-walk-v1")
loaded.policy # reconstructed policy
loaded.robot_config # RobotConfig or None
loaded.normalizer # normalizer or None
loaded.policy_type # "MLP", "ActorCritic", ...
LoadedPolicy is callable and handles torch / tinygrad conversion
automatically.
Inspect
config = rfx.inspect_policy("runs/go2-walk-v1")
print(config["policy_type"]) # "MLP"
print(config["policy_config"]) # {"obs_dim": 48, ...}
HuggingFace Hub
Push:
rfx.push_policy("runs/go2-walk-v1", "rfx-community/go2-walk-v1")
Pull (at load time):
loaded = rfx.load_policy("hf://rfx-community/go2-walk-v1")
rfx deploy calls load_policy internally. If the artifact’s
rfx_config.json records a robot, --robot is inferred automatically.