A tinygrad-inspired robotics framework for Unitree Go2
Project description
uv pip install rfx-sdk
import rfx
# Every robot, same interface
robot = rfx.RealRobot("so101.yaml")
obs = robot.observe()
robot.act(action)
# Every model, self-describing
loaded = rfx.load_policy("hf://rfx-community/go2-walk-v1")
rfx.run(robot, loaded, rate_hz=50)
Why rfx
ROS was built for message passing between components. We're in a different era -- the workflow is collect demos, train a policy, deploy, iterate. rfx is built from scratch for that loop, with all the infrastructure you'd expect from a robotics framework underneath.
- Rust core for real-time control, Python SDK for fast research
- Three-method robot interface --
observe(),act(),reset()-- same API for sim and real - Self-describing models -- save once, load anywhere, deploy with zero config
- HuggingFace Hub native -- push and pull policies like you push datasets
- Zenoh transport -- pub/sub topics, nodes, launch graphs -- all the ROS primitives, none of the pain
- ROS 2 interop -- coexist with existing ROS 2 stacks via zenoh-plugin-ros2dds bridge
- Batteries included -- simulation (Genesis, MJX), teleoperation, LeRobot export, hardware drivers
Install
uv pip install rfx-sdk
With simulation and robot extras:
uv pip install rfx-sdk rfx-sdk-sim rfx-sdk-go2 rfx-sdk-lerobot
From source:
git clone https://github.com/quantbagel/rfx.git && cd rfx
bash scripts/setup-from-source.sh
The interface
Every robot in rfx -- simulated or real -- implements the same three methods:
robot = rfx.SimRobot.from_config("go2.yaml", backend="genesis")
# robot = rfx.RealRobot("so101.yaml", port="/dev/ttyACM0")
obs = robot.observe() # {"state": Tensor(1, 64), "images": ...}
robot.act(action) # Tensor(1, 64)
robot.reset()
Run a policy against any robot with one call:
rfx.run(robot, policy, rate_hz=200, duration=30.0)
Rate-controlled loop with jitter tracking, error handling, and clean shutdown built in.
Train
from rfx.nn import MLP
from rfx.utils.transforms import ObservationNormalizer
policy = MLP(obs_dim=48, act_dim=12, hidden=[256, 256])
normalizer = ObservationNormalizer(state_dim=48)
# ... your training loop ...
tinygrad-native policies. MLP, ActorCritic, or subclass Policy for your own architecture.
Save
Every saved model is a self-describing directory. Weights, architecture, robot config, normalizer -- everything needed to reconstruct and deploy.
policy.save("runs/go2-walk-v1",
robot_config=config,
normalizer=normalizer,
training_info={"total_steps": 50000, "best_reward": 245.3})
runs/go2-walk-v1/
rfx_config.json # architecture + robot + training metadata
model.safetensors # weights
normalizer.json # observation normalizer state
Push to HuggingFace Hub:
rfx.push_policy("runs/go2-walk-v1", "rfx-community/go2-walk-v1")
Load and deploy
Load from disk or Hub. No need to know the architecture, hyperparameters, or training setup.
loaded = rfx.load_policy("runs/go2-walk-v1")
loaded = rfx.load_policy("hf://rfx-community/go2-walk-v1")
loaded.policy_type # "MLP"
loaded.robot_config # RobotConfig(name="Go2", ...)
loaded.training_info # {"total_steps": 50000, "best_reward": 245.3}
# Deploy -- torch/tinygrad conversion handled automatically
robot = rfx.RealRobot(loaded.robot_config)
rfx.run(robot, loaded, rate_hz=loaded.robot_config.control_freq_hz)
Inspect metadata without loading weights:
rfx.inspect_policy("runs/go2-walk-v1")
Supported hardware
| Robot | Type | Interface | Status |
|---|---|---|---|
| SO-101 | 6-DOF arm | USB serial (Rust driver) | Ready |
| Unitree Go2 | Quadruped | Ethernet (Zenoh transport) | Ready |
Custom robots: implement observe() / act() / reset() or write a YAML config with URDF.
Simulation
# Genesis (GPU-accelerated)
robot = rfx.SimRobot.from_config("so101.yaml", backend="genesis", viewer=True)
# MJX (JAX-accelerated MuJoCo)
robot = rfx.SimRobot.from_config("go2.yaml", backend="mjx", num_envs=4096)
# Mock (zero dependencies, for testing)
robot = rfx.MockRobot(state_dim=12, action_dim=6)
Teleoperation
Bimanual SO-101 recording:
from rfx.teleop import run, so101
arm = so101()
run(arm, logging=True, rate_hz=200, duration_s=30.0)
Communication and runtime
Under the hood, rfx has a full robotics communication stack built on Zenoh -- topics, nodes, launch files, graph introspection. It's there when you need it, invisible when you don't.
from rfx.teleop import create_transport
# Pub/sub messaging -- Zenoh transport (Rust-backed, compiled in by default)
transport = create_transport()
transport.publish("robot/state", state_bytes)
sub = transport.subscribe("robot/cmd")
envelope = sub.recv(timeout_s=1.0)
Nodes follow a simple lifecycle contract:
from rfx.runtime.node import Node
class ControlNode(Node):
publish_topics = ("robot/cmd",)
subscribe_topics = ("robot/state",)
def setup(self): ... # init
def tick(self): ... # one loop iteration
def shutdown(self): ... # cleanup
Launch multiple nodes from a YAML graph:
name: go2-deploy
nodes:
- package: go2_ctrl
node: policy_node
rate_hz: 200
- package: go2_ctrl
node: logger_node
rate_hz: 10
rfx launch go2_deploy.yaml
rfx graph # inspect the active node graph
rfx topic-list # see all live topics
ROS 2 coexistence: rfx topics are visible to ROS 2 tools via the zenoh-plugin-ros2dds bridge. Migrate incrementally -- run rfx nodes alongside existing ROS 2 nodes, no code changes required on either side.
Enterprise scaling: The same code that runs on one robot runs on a fleet. No architecture changes, no extra infra. Get in touch -- we'll handle scaling so you don't have to.
Production Zenoh setup
Zenoh is compiled into the native extension by default -- no extra packages to install. Single-machine setups work out of the box with automatic peer discovery.
For multi-machine deployments, pass endpoints explicitly:
from rfx.node import auto_transport
transport = auto_transport(
connect=["tcp/192.168.1.100:7447"], # remote Zenoh router
shared_memory=True, # zero-copy on same machine
)
Or configure via environment:
export RFX_ZENOH_CONNECT="tcp/192.168.1.100:7447" # comma-separated endpoints
export RFX_ZENOH_LISTEN="tcp/0.0.0.0:7447" # listen for incoming peers
export RFX_ZENOH_SHARED_MEMORY=0 # disable shared memory if needed
Shared-memory transport is enabled by default for same-machine zero-copy between processes.
rfxJIT
Built-in kernel compiler that lowers and executes across cpu, cuda, and metal. IR-based autodiff, optimization passes (constant folding, dead-op elimination, fusion), and a tinyJIT-style cache+replay runtime.
from rfx.jit import value_and_grad, available_backends
# JAX-style functional transforms
loss_and_grads = value_and_grad(loss_fn)
# Check what's available on this machine
available_backends() # {"cpu": True, "cuda": True, "metal": False}
Enable globally via environment:
export RFX_JIT=1 # enable rfxJIT execution paths
export RFX_JIT_BACKEND=auto # auto | cpu | cuda | metal
When enabled, policy inference automatically routes through rfxJIT when possible, with transparent fallback to tinygrad's TinyJit.
Custom policies
Register your own architectures for automatic save/load:
from rfx.nn import Policy, register_policy
@register_policy
class MyPolicy(Policy):
def __init__(self, obs_dim, act_dim):
self.obs_dim, self.act_dim = obs_dim, act_dim
# ... build layers ...
def config_dict(self):
return {"obs_dim": self.obs_dim, "act_dim": self.act_dim}
def forward(self, obs):
# ... your forward pass ...
Docs
Community
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rfx_sdk-0.2.0.tar.gz.
File metadata
- Download URL: rfx_sdk-0.2.0.tar.gz
- Upload date:
- Size: 253.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f75b01a79611015cd2b6f81065ea4b02d614966f5a8a75a3c120cde94d011d76
|
|
| MD5 |
24dcfb97d807e40a9c8788812eba1f13
|
|
| BLAKE2b-256 |
b9fac8b39385489319378b23789bcc2c862f08c5f1f594cc05c89f1f768f5ee8
|
File details
Details for the file rfx_sdk-0.2.0-cp313-cp313-win_amd64.whl.
File metadata
- Download URL: rfx_sdk-0.2.0-cp313-cp313-win_amd64.whl
- Upload date:
- Size: 3.7 MB
- Tags: CPython 3.13, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f9085c22699a79a9b72247de9e7ebce7ea1ab00666065b947a1875d81fbc7b7b
|
|
| MD5 |
25538e60836068ebf9a3ec4036307728
|
|
| BLAKE2b-256 |
8cf2fed3cc158d6043ca1f85763215a64690bc83ba6e12cc94288211967143a6
|
File details
Details for the file rfx_sdk-0.2.0-cp313-cp313-macosx_11_0_arm64.whl.
File metadata
- Download URL: rfx_sdk-0.2.0-cp313-cp313-macosx_11_0_arm64.whl
- Upload date:
- Size: 3.6 MB
- Tags: CPython 3.13, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.4 {"installer":{"name":"uv","version":"0.10.4","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bbd79f532d20ecf79c6d007bc2e9839bea179aea67903c299c9428bdbad4e589
|
|
| MD5 |
fb4a322edea320f8b94089d262fc2d35
|
|
| BLAKE2b-256 |
df882739d8c77dca9d1f0b639a4814f979c775c307026df78bb8aab1eaeade7d
|