A policy that hits 95% success in simulation and 20% on the real robot is one of the most common — and most frustrating — outcomes in robot learning. The gap between simulated and real-world dynamics, sensing, and visuals is called the sim-to-real gap, and closing it is its own engineering discipline. This tutorial covers the practical techniques that actually move the needle.
Sim-to-real failures usually trace back to one of three sources:
Before trying to bridge the gap with randomization or fancy techniques, measure your real robot and match the simulation to it as closely as possible.
def identify_actuator_response(real_robot, sim_env, test_commands):
"""Compare real vs simulated actuator response to the same commands."""
real_trajectories = []
sim_trajectories = []
for cmd in test_commands:
real_robot.reset()
real_traj = real_robot.apply_and_record(cmd)
real_trajectories.append(real_traj)
sim_env.reset()
sim_traj = sim_env.apply_and_record(cmd)
sim_trajectories.append(sim_traj)
return real_trajectories, sim_trajectories
Use this data to tune simulator parameters — actuator gains, joint damping, friction coefficients — via optimization (grid search, Bayesian optimization, or even gradient-based system identification if your simulator supports differentiable physics).
from scipy.optimize import minimize
def sim_real_error(params, sim_env, real_trajectories, test_commands):
sim_env.set_dynamics_params(params)
total_error = 0.0
for cmd, real_traj in zip(test_commands, real_trajectories):
sim_env.reset()
sim_traj = sim_env.apply_and_record(cmd)
total_error += np.mean((np.array(sim_traj) - np.array(real_traj)) ** 2)
return total_error
result = minimize(
sim_real_error,
x0=initial_params,
args=(sim_env, real_trajectories, test_commands),
method="Nelder-Mead",
)
If your policy is vision-based, the observation pipeline matters as much as the physics. Concretely:
def match_camera_intrinsics(sim_camera, real_camera_calibration):
sim_camera.set_fov(real_camera_calibration["fov"])
sim_camera.set_resolution(*real_camera_calibration["resolution"])
sim_camera.set_principal_point(real_camera_calibration["cx"], real_camera_calibration["cy"])
Don't go straight from "trains in sim" to "deploy on hardware." Use intermediate checkpoints:
def staged_validation(policy, sim_env, replay_dataset, real_robot):
sim_success = evaluate_policy(sim_env, policy, n_episodes=50)
print(f"Stage 1 (sim): {sim_success:.1%}")
action_error = offline_policy_comparison(replay_dataset, policy, stats=None)
print(f"Stage 2 (offline real data): mean action error = {action_error:.4f}")
if sim_success > 0.8 and action_error < 0.1:
print("Proceeding to supervised real rollout...")
else:
print("Not ready for hardware — investigate gaps first.")
The most reliable long-term fix for sim-to-real gaps is mixing in real demonstration data, even in small amounts, alongside simulated or synthetic data (see the synthetic data pipeline tutorial). Fine-tuning a sim-trained policy on a modest set of real demonstrations often closes a surprising amount of the gap.
def finetune_on_real_data(model, sim_pretrained_weights, real_data, epochs=20):
model.load_state_dict(sim_pretrained_weights)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-5) # lower LR for fine-tuning
for epoch in range(epochs):
for batch in real_data:
pred = model(batch["image"], batch["state"])
loss = nn.functional.mse_loss(pred, batch["action_chunk"])
optimizer.zero_grad()
loss.backward()
optimizer.step()
Track these metrics side by side, not just final task success:
| Metric | Sim | Real |
|---|---|---|
| Task success rate | ||
| Average episode length | ||
| Action magnitude distribution | ||
| Failure mode categories |
A useful sanity check: if failure modes in sim and real are qualitatively different (e.g., sim fails from imprecise grasping, real fails from the gripper never closing at all), that's a strong signal you have an actuation or sensing gap, not a policy capability gap.
Domain randomization is one of the most effective tools for making a policy robust enough to survive the sim-to-real gap without needing perfect system identification — that's the focus of the next tutorial.
Website: www.v-modal.com
SDK Flutter: v-modal/vmodal_sdk_flutter
SDK Android: v-modal/vmodal_sdk_android
Discord: https://discord.gg/K72z28KUx