Instructions to use RanveerSoni98/mlx-landing-rl with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use RanveerSoni98/mlx-landing-rl with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] hf download RanveerSoni98/mlx-landing-rl --local-dir mlx-landing-rl
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
mlx-landing-rl
A little rocket that learned to land itself. Custom PPO written from scratch in Apple MLX β no Stable-Baselines, no demos, just trial, error, and a few thousand crashes on the way to a soft touchdown.
GitHub (training code): https://github.com/RanveerSoni98/mlx-landing-rl
At a glance
| Env | LunarLander-v3 (Gymnasium + Box2D) |
| Algo | PPO clipped (Ξ΅=0.2), GAE (Ξ³=0.99, Ξ»=0.95), from scratch in MLX |
| Net | ActorCritic 8 β 128 β 128 β 4 + value (18,309 params) |
| Training | 500k steps (~244 rollouts Γ 2048), Adam 3e-4, ~10β15 min on Apple Silicon |
| Solved? | Yes β mean ~215 over 5 sampled episodes (official bar: avg β₯ 200) |
State is 8 numbers: x, y, vx, vy, angle, angular velocity, left-leg, right-leg.
Actions are 4 engines: 0=noop, 1=left, 2=main, 3=right.
Files
| File | What |
|---|---|
lunar_ppo.npz (best) |
Best checkpoint (by training reward) β use this one |
lunar_ppo_final.npz |
Last checkpoint, for comparison |
config.json |
Arch + hyperparams + env spec |
inference.py |
Standalone 15-line fly script (no repo clone needed) |
How to fly it
from huggingface_hub import hf_hub_download
import mlx.core as mx
import mlx.nn as nn
import numpy as np
class ActorCritic(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(8, 128)
self.fc2 = nn.Linear(128, 128)
self.actor = nn.Linear(128, 4)
self.critic = nn.Linear(128, 1)
def __call__(self, x):
h = nn.relu(self.fc1(x))
h = nn.relu(self.fc2(h))
return self.actor(h), self.critic(h).squeeze(-1)
path = hf_hub_download("RanveerSoni98/mlx-landing-rl", "lunar_ppo.npz")
model = ActorCritic()
model.load_weights(path)
mx.eval(model.parameters())
# then per step: sample (don't argmax β see below!)
x = mx.array(obs[None, :].astype("float32"))
logits, _ = model(x)
mx.eval(logits)
action = int(mx.random.categorical(logits, axis=-1).item())
Or just run inference.py from this repo β it does all of the above.
Important: sample, don't argmax. This pilot lands far better when you sample from the policy than when you take the greedy action. Greedy solves ~20%; sampling solves ~60β80%. Sampling keeps a little wiggle room to recover from wobbles.
Training
Full code + instructions on GitHub: https://github.com/RanveerSoni98/mlx-landing-rl
git clone https://github.com/RanveerSoni98/mlx-landing-rl.git
cd mlx-landing-rl
uv sync
uv run train-lander --timesteps 500000
uv run eval-lander --episodes 10
Results
Measured with the release checkpoint (lunar_ppo.npz), sampling mode:
| Run | Mean | Solved (β₯200) |
|---|---|---|
| 5 episodes | ~215 | ~80% |
| Typical | ~150β220 | ~60β80% (stochastic β every batch of flights varies) |
| Greedy (argmax), 10 eps | ~30 | ~20% (sampling is the way to fly this one) |
LunarLander counts as "solved" at an average β₯ 200. This pilot gets there β most flights stick the landing, a few still crater. It's a rocket.
Reward (env default)
+100β140 for landing, β100 for crashing, shaping for distance/velocity/angle, β0.3 per main-engine burn. Nothing custom β straight LunarLander-v3.
Limitations
- macOS + Apple Silicon only (MLX doesn't run elsewhere).
- MLX
.npzweights β no Safetensors/PyTorch export yet. - Sampling-only pilot: greedy mode is weak (see above).
- Single seed (0), single env β no domain randomization.
Citation
@software{soni2026mlxlandingrl,
author = {Soni, Ranveer},
title = {mlx-landing-rl: LunarLander PPO from scratch in Apple MLX},
year = {2026},
url = {https://github.com/RanveerSoni98/mlx-landing-rl}
}
License
MIT β see GitHub repo for the full text.
- Downloads last month
- 33