UniAD · Planner Post-Training

Post-Training the UniAD Planner with LoRA and GRPO

A planner-only migration, seven-phase curriculum, and a close look at the gap between finding one good sampled future and deploying the policy mean.

FROZEN CONTEXT BEV · queries · command B A LOW-RANK UPDATE W + (α/r)BA GROUPED FUTURES G samples · deploy μ

nuScenes v1.0-mini (323 train frames) · Checkpoint: ckpts/uniad_base_e2e.pth (frozen except planner LoRA + log_std) · NVIDIA RTX 4060 Ti 16GB, WSL2, FP32 · 5666 total iterations · Generated 2026-08-09T05:53:53.321920Z

Sample-best minADE (phase3)0.00 m
Mean-path ADE (12 eval)3.60 m
Best minFDE (phase3)0.052 m

1. One question drives the whole experiment

UniAD unifies perception, prediction, occupancy, and planning in one BEV-centric model. After stage-2 end-to-end training, the planning head outputs six future ego waypoints — but the objective is primarily supervised L2 / ADE. That leaves little room to optimize comfort, collision avoidance, or multi-modal uncertainty without expensive full-model fine-tuning.

Following the spirit of Thinking Machines' LoRA work, we freeze the entire UniAD backbone and post-train only low-rank adapters plus a small Gaussian policy head on the planner. GRPO (group-relative policy optimization) lets us sample multiple waypoint trajectories per frame and reinforce the best ones relative to the group — without re-running the frozen perception stack.

The throughline is simple: preserve the scene representation, introduce the smallest trainable policy correction, branch it into candidate futures, learn from their relative quality, and finally judge the one deterministic path that deployment will use.

The experiment revealed a distinction that is easy to lose in a training dashboard. GRPO can move probability mass toward a good future without moving the center of the distribution by the same amount. A purple rollout can nearly touch the expert path while the blue trajectory actually shipped at inference remains several meters away. The curriculum after phase 3 is an attempt to close precisely that gap.

Sample-best

$\mathbb{E}_b[\min_g\mathrm{ADE}_{b,g}]$ from training JSONL. It measures whether one of $G$ stochastic rollouts found a good future.

Mean-path

$\mathrm{ADE}(\mathrm{cumsum}(\mu),\tau^{\star})$ on 12 fixed frames. It measures the deterministic blue path used at deployment.

Scope. All numbers below are open-loop metrics on nuScenes v1.0-mini (sample-best minADE/minFDE over G stochastic trajectories during training logs, and mean-path ADE in waypoint plots). This is not the official nuScenes planning benchmark on trainval.

Thread continuesTo change only the planner, the codebase needs an explicit boundary between frozen scene understanding and trainable future selection.

2. Preserve the invariant: fork only the planner path

We kept the default UniAD training path intact. GRPO post-training is an opt-in fork activated by config (PlanningHeadGRPO, planner_posttrain_only=True). No changes to PlanningHeadSingleMode or stage-2 configs unless you explicitly switch. The integration follows the repository’s MMDetection3D registry and config conventions [mmdet3d].

ONE ENTRY POINT · TWO EXPLICIT PATHS The migration is a guarded fork, not a rewrite SHARED ENTRY UniAD.forward_train() DEFAULT OPT-IN GUARD NATIVE STAGE-2 track · map · motion · occupancy PlanningHeadSingleMode L2 planning loss · unchanged configs planner_posttrain_only = True frozen state · no_grad PlanningHeadGRPO + LoRA G futures → reward → loss → deterministic μ default path remains byte-for-byte available post-training activates only by configuration
The only architectural branch is the configuration guard. Turning GRPO off leaves PlanningHeadSingleMode and the native stage-2 path intact.

File-by-file inventory

StatusPathRole
NEW projects/mmdet3d_plugin/models/utils/lora.py LoRALinear wrapper; apply_lora_to_planning_head(); set_planning_head_lora_enabled().
NEW projects/mmdet3d_plugin/uniad/dense_heads/planning_head_grpo.py PlanningHeadGRPO: Gaussian waypoint policy, G-sample GRPO train, deterministic mean inference.
NEW projects/mmdet3d_plugin/losses/grpo_loss.py PlannerReward, group advantages, clipped surrogate, reference KL helpers.
NEW projects/mmdet3d_plugin/uniad/hooks/grpo_experiment.py Per-iteration JSONL logging (PlannerGRPOExperimentHook).
NEW projects/mmdet3d_plugin/uniad/hooks/grpo_evidence.py Smoke invariants: frozen base params, LoRA-only grads.
NEW projects/configs/planner_posttrain/*.py Phased experiment configs (_base_grpo, phase0–phase3).
NEW tests/test_planner_grpo_lora.py Unit tests: LoRA disable, shapes, frozen base, group advantages.
NEW tools/run_planner_grpo_experiments.py Multi-phase campaign orchestrator.
NEW tools/analyze_planner_grpo_campaign.py Aggregate JSONL → report_data.json.
NEW tools/export_planner_waypoints.py GT / SFT / GRPO waypoint BEV SVG export.
NEW tools/render_planner_grpo_report.py This blog generator.
MOD projects/mmdet3d_plugin/uniad/detectors/uniad_e2e.py planner_posttrain_only flag; no_grad backbone; planning-only losses.
MOD projects/mmdet3d_plugin/datasets/nuscenes_e2e_dataset.py max_train_samples for phased subsampling.
MOD projects/mmdet3d_plugin/uniad/dense_heads/__init__.py Register PlanningHeadGRPO.
MOD projects/mmdet3d_plugin/losses/__init__.py Register PlannerReward.
MOD projects/mmdet3d_plugin/uniad/hooks/__init__.py Register new hooks.
UNCHANGED projects/mmdet3d_plugin/uniad/dense_heads/planning_head.py PlanningHeadSingleMode — default when GRPO config not used.
UNCHANGED projects/configs/stage2_e2e/base_e2e.py Original stage-2 training path untouched.

Integration checklist

  1. Step 1 — Add LoRALinear. Wrap selected nn.Linear modules in the planning head. Freeze base weights; train only A and B matrices.
  2. Step 2 — Subclass planning head. PlanningHeadGRPO extends PlanningHeadSingleMode. Reuse mlp_fuser → TransformerDecoder → reg_branch path.
  3. Step 3 — Gaussian waypoint policy. reg_branch output (pre-cumsum) = mean μ of 6×2 increments. Learn log_std[T,2]. Trajectory = cumsum(Δ).
  4. Step 4 — GRPO training forward. Sample G trajectories without re-running frozen backbone. Compute group-relative advantages and clipped surrogate.
  5. Step 5 — Reference policy KL. Disable LoRA adapters; compute ref log-prob on same samples. Anchor to stage-2 SFT planner.
  6. Step 6 — planner_posttrain_only on UniAD. Freeze all parameters; re-enable LoRA + log_std. Run track/motion under torch.no_grad(); optimize planning only.
  7. Step 7 — Configs + hooks. Register head/loss/hooks in mmcv. Add phased configs and JSONL experiment logging.
  8. Step 8 — Validation. pytest unit tests + mini campaign + waypoint export for qualitative BEV plots.

Design constraints we preserved

Thread continuesThe boundary is safe. Now we need to understand the exact shape of the small correction allowed to cross it.

3. Adapt the map, not the world: LoRA on the planner

Post-training a full UniAD stack is expensive and risks catastrophic forgetting of perception. Following the PEFT intuition in [lora] and the “low-regret LoRA” regime emphasized by [lora-noregret], we freeze stage-2 weights and train only low-rank adapters on the planning head — plus a tiny log_std for the Gaussian policy.

THE GEOMETRY OF A LOW-RANK UPDATE A wide frozen map with one narrow trainable corridor W dout × din · frozen + B dout × r × A r × din BOTTLENECK r ≪ d COMPOSED OUTPUT W′x = Wx + α/r · BAx disable BA → exact SFT reference knowledge retained in W planner correction learned in A,B
LoRA replaces each selected Linear with W′ = W + (α/r)BA. Only A and B train; disabling the adapter recovers the SFT reference used for KL.

The useful mental shape is a narrow corridor. $A$ first compresses a planner feature from $d_{\mathrm{in}}$ coordinates into only $r$ coordinates; $B$ expands that compact correction back to $d_{\mathrm{out}}$. The base transformation $W$ remains the wide, frozen road. The adapter can steer the output, but only through an $r$-dimensional subspace.

For a frozen linear layer $W$:

$$W' = W + \frac{\alpha}{r} B A,\quad B\in\mathbb{R}^{d_{\mathrm{out}}\times r},\; A\in\mathbb{R}^{r\times d_{\mathrm{in}}}$$

projects/mmdet3d_plugin/models/utils/lora.py PYTHON · REPOSITORY SOURCE
1 def forward(self, x):2 out = self.base(x)frozen route W(x)3 if self.lora_enabled:4 lora_out = self.dropout(x) @ self.lora_A.t() @ self.lora_B.t()low-rank residual BAx5 out = out + self.scale * lora_outmerge without replacing W6 return out
Forward pass of LoRALinear. base stays requires_grad=False; lora_B is init ~ N(0,1e-3) (zero-init killed GRPO/KL at step 0).

Where we attach LoRA

bev_adapter stays in the forward path but is frozen and LoRA-free, so the reference policy (LoRA off) matches deployed SFT [uniad].

Thread continuesLoRA gives the planner a narrow steering surface. GRPO turns that surface into a distribution over complete futures and asks which branch is better.

4. Explore futures, then turn preference into a gradient

Group Relative Policy Optimization [grpo][trl] lets us improve a planner with reinforcement learning without a value network. We treat each nuScenes frame [nuscenes] as a contextual bandit: one state, $G$ sampled trajectories, group-normalized advantages, PPO-style clipping [ppo], and a LoRA-off SFT reference for KL.

STATE s frozen BEV + queries ACTION a Δ₁…Δ₆ ~ N(μ,σ²) τ = cumsum(Δ) REWARD R(s,a) −w·ADE −w·FDE −w·col −w·comfort
MDP → bandit mapping for open-loop waypoint planning. The whole 6-step trajectory is one action.

4.1 Why GRPO (not L2-only, not full PPO)?

4.2 State, action, policy

ObjectSymbolCode / tensor
State$s$ bev_embed, bev_pos, sdc_*_query, command (backbone no_grad)
Action$a={\Delta_t}_{t=1}^{6}$ increments $[B,G,6,2]$; $\tau=\mathrm{cumsum}(\Delta)$
Policy$\pi_\theta(a\mid s)$ diag-Gaussian; $\mu=$ reg_branch, $\sigma=$ log_std.exp()
Reference$\pi_{\mathrm{ref}}$ same head, set_planning_head_lora_enabled(False)
Deploy$\mu(s)$ forward() — no sampling

$$\Delta^{(g)}=\mu+\sigma\odot\varepsilon^{(g)},\quad \varepsilon^{(g)}\sim\mathcal{N}(0,I),\quad \tau^{(g)}=\mathrm{cumsum}(\Delta^{(g)})$$

THE ACTION HAS A SHAPE Six local motions become one global future t=1Δx, Δyt=2Δx, Δyt=3Δx, Δyt=4Δx, Δyt=5Δx, Δyt=6Δx, Δy POLICY OUTPUT μ, σ ∈ ℝ⁶ˣ² CUMSUM IS THE GEOMETRY BRIDGE ego τ = cumsum(Δ) one state, G complete paths μ deploys samples train
The action tensor is not six absolute map points. It is six local 2D increments. Cumulative summation gives each sample its visible path; only the blue mean path ships.
projects/mmdet3d_plugin/uniad/dense_heads/planning_head_grpo.py PYTHON · REPOSITORY SOURCE
1 def _sample_increments(self, mean, num_samples):2 batch_size = mean.shape[0]3 log_std = self.log_std.clamp(4 min=self.grpo_cfg['min_log_std'],5 max=self.grpo_cfg['max_log_std'],6 )7 std = log_std.exp()learned exploration scale8 noise = torch.randn(G independent noises9 batch_size,10 num_samples,11 self.planning_steps,12 2,13 device=mean.device,14 dtype=mean.dtype,15 )16 mean_expanded = mean.unsqueeze(1)17 std_expanded = std.view(1, 1, self.planning_steps, 2)18 increments = mean_expanded + std_expanded * noisereparameterized action19 return increments, mean_expanded, log_std
Gaussian rollout over increments — matches UniAD’s native cumsum parameterization.

4.3 Rollout pipeline (the interesting part)

Yes: run UniAD once → define $s$ → encode plan_query → sample $G$ noisy trajectories → reward → group advantages → losses. The backbone is not re-executed per sample.

① once / batch Frozen UniAD ② state s BEV + queries ③ LoRA encode plan_query → μ,σ ④ sample G Δ = μ + σ⊙ε Open-loop rollouts (same s — backbone not re-run) ego μ (deploy) G samples ⑤ Reward ADE·FDE col·comfort ⑥ Group  (R−μ)/σ ⑦ L = clipped GRPO(r, Â) + β KL_ref + λ ADE(μ)
Open-loop GRPO rollout. Purple dashed = G samples; blue = deterministic mean μ used at deploy.

Live examples from our checkpoint

projects/mmdet3d_plugin/uniad/dense_heads/planning_head_grpo.py · forward_train PYTHON · REPOSITORY SOURCE
1 def forward_train(2 self,3 bev_embed,4 outs_motion={},5 sdc_planning=None,6 sdc_planning_mask=None,7 command=None,8 gt_future_boxes=None,9 ):10 sdc_traj_query = outs_motion['sdc_traj_query']11 sdc_track_query = outs_motion['sdc_track_query']12 bev_pos = outs_motion['bev_pos']13 14 plan_query = self._encode_plan_query(encode state once15 bev_embed, bev_pos, sdc_traj_query, sdc_track_query, command)16 mean_increments = self._predict_mean_increments(plan_query)17 num_samples = self.grpo_cfg['num_samples']18 19 increments, mean_expanded, log_std = self._sample_increments(branch into G futures20 mean_increments, num_samples)21 traj_samples = self._increments_to_positions(increments)22 23 old_log_prob = self._policy_log_prob(24 increments, mean_expanded, log_std).detach()25 new_log_prob = self._policy_log_prob(26 increments, mean_expanded, log_std)27 28 self._set_lora_enabled(False)29 with torch.no_grad():30 ref_mean = self._predict_mean_increments(plan_query)31 ref_mean_expanded = ref_mean.unsqueeze(1)32 ref_log_prob = self._policy_log_prob(33 increments, ref_mean_expanded, log_std)34 self._set_lora_enabled(True)35 36 reward, reward_metrics = self.planner_reward(measure each future37 traj_samples, sdc_planning, sdc_planning_mask, gt_future_boxes)38 advantages = compute_group_advantages(reward)compare within group39 40 loss_grpo, grpo_metrics = grpo_surrogate_loss(41 new_log_prob, old_log_prob, advantages, self.grpo_cfg['clip_eps'])42 loss_kl, kl_metrics = reference_kl_loss(43 ref_log_prob, new_log_prob, self.grpo_cfg['kl_coef'])44 45 mean_traj = self._increments_to_positions(mean_increments).unsqueeze(0)46 loss_aux = self.loss_planning(47 mean_traj,48 sdc_planning[0, :, :self.planning_steps, :2],49 torch.any(sdc_planning_mask[0, :, :self.planning_steps], dim=-1),50 )51 52 entropy = gaussian_entropy(53 log_std,54 planning_steps=self.planning_steps,55 min_log_std=self.grpo_cfg['min_log_std'],56 max_log_std=self.grpo_cfg['max_log_std'],57 )58 59 losses = dict(join learning signals60 loss_grpo=loss_grpo,61 loss_kl=loss_kl,62 loss_ade=loss_aux * self.grpo_cfg['aux_loss_weight'],63 reward_mean=reward_metrics['reward_mean'],64 reward_std=reward_metrics['reward_std'],65 ade=reward_metrics['ade'],66 fde=reward_metrics['fde'],67 min_ade=reward_metrics['min_ade'],68 min_fde=reward_metrics['min_fde'],69 collision=reward_metrics['collision'],70 comfort=reward_metrics['comfort'],71 entropy=entropy,72 ppo_ratio=grpo_metrics['ppo_ratio'],73 clip_fraction=grpo_metrics['clip_fraction'],74 kl=kl_metrics['kl'],75 )76 77 outs_planning = dict(78 sdc_traj=mean_traj,79 sdc_traj_all=mean_traj,80 traj_samples=traj_samples,81 log_prob=new_log_prob,82 )83 return dict(losses=losses, outs_motion=outs_planning)
End-to-end training step. Steps ④–⑦ are cheap relative to frozen perception.

4.4 Reward function

For sample $g$, GT path $\tau^{\star}$, mask $m_t$:

$$ \mathrm{ADE}_g=\frac{1}{\sum_t m_t}\sum_t m_t \left\|\tau^{(g)}_t-\tau^{\star}_t\right\|_2 $$

$$ \mathrm{FDE}_g= \left\|\tau^{(g)}_{t_{\mathrm{last}}} -\tau^{\star}_{t_{\mathrm{last}}}\right\|_2 $$

$$ \mathrm{Comfort}_g= \mathrm{mean}_{t\ge 3} \left\|\Delta_t-2\Delta_{t-1}+\Delta_{t-2}\right\|_2^{2} $$

$$ R_g=-w_{\mathrm{ade}}\mathrm{ADE}_g -w_{\mathrm{fde}}\mathrm{FDE}_g -w_{\mathrm{col}}\mathrm{Collision}_g -w_{\mathrm{com}}\mathrm{Comfort}_g $$

REWARD IS NOT AN ABSTRACT SCORE It is four rulers laid over the same future FDE ADE = mean of six gaps OCC collision ego expert τ★ sample τ(g) COMFORT RULER second difference of Δ zig-zag → larger jerk R = − Σ w · penalty less error, collision and jerk means a larger reward
ADE, FDE, occupancy collision, and comfort are four measurements on one sampled path—not four unrelated objectives. Their weighted negative sum is the scalar used for ranking.
projects/mmdet3d_plugin/losses/grpo_loss.py · PlannerReward PYTHON · REPOSITORY SOURCE
1 def forward(self, traj_samples, sdc_planning, sdc_planning_mask, future_gt_bbox=None):2 gt = _planning_xy_target(sdc_planning, self.planning_steps)3 mask = compute_timestep_mask(sdc_planning_mask, self.planning_steps)4 5 ade = masked_ade(traj_samples, gt, mask)average path gap6 fde = masked_fde(traj_samples, gt, mask)last-point gap7 collision = self.collision_helper(8 traj_samples, sdc_planning[0, :, :self.planning_steps, :3], future_gt_bbox)9 comfort = comfort_penalty(traj_samples)second-difference penalty10 11 reward = (four rulers → one rank12 -self.reward_ade_w * ade13 - self.reward_fde_w * fde14 - self.reward_collision_w * collision15 - self.reward_comfort_w * comfort16 )17 metrics = dict(18 ade=ade.mean(),19 fde=fde.mean(),20 min_ade=ade.min(dim=1).values.mean(),21 min_fde=fde.min(dim=1).values.mean(),22 collision=collision.mean(),23 comfort=comfort.mean(),24 reward_mean=reward.mean(),25 reward_std=reward.std(dim=1).mean(),26 )27 return reward, metrics
Higher reward is better. Weights are phase-dependent (see §6b).

minADE in logs = $\mathbb{E}_b[\min_g\mathrm{ADE}_{b,g}]$ (best sample). Deployed ADE uses $\mu$ — a different statistic.

4.5 Group-relative advantages

Per batch row, over the $G$ samples [grpo]:

$$ \hat A_{b,g}= \frac{R_{b,g}-\mathrm{mean}_{g'}R_{b,g'}} {\mathrm{std}_{g'}R_{b,g'}+\varepsilon} $$

Same state s — group of G rewards → zero-mean unit-scale advantages Rewards R_g g1 g2 g3 g4 mean Advantages Â_g + ++ −− Â = (R − mean) / (std + ε) · no critic V(s)
Group mean is a free baseline: better-than-average trajectories get Â>0, worse get Â<0.
projects/mmdet3d_plugin/losses/grpo_loss.py PYTHON · REPOSITORY SOURCE
1def compute_group_advantages(reward):2 """Group-relative normalization over samples dim=1."""3 adv = reward - reward.mean(dim=1, keepdim=True)subtract group baseline4 adv = adv / (reward.std(dim=1, keepdim=True) + 1e-8)normalize local scale5 return advrelative preference
Normalize over dim=1 (group). No learned V(s).

4.6 Clipped surrogate (PPO-style)

Importance ratio $r=\exp(\log\pi_\theta-\log\pi_{\mathrm{old}})$ with $\log\pi_{\mathrm{old}}$ detached [ppo]:

$$ \mathcal{L}_{\mathrm{GRPO}}=-\mathbb{E}\big[ \min\big(r\hat A,\; \mathrm{clip}(r,1-\varepsilon,1+\varepsilon)\hat A\big)\big] $$

Clipped surrogate: trust region on importance ratio r r obj r·Â (Â>0) 1−ε 1+ε clip binds → no huge updates ε = clip_eps (0.2 → 0.05 across phases)
When Â>0, the objective stops rising past r=1+ε — a trust region on LoRA updates.
projects/mmdet3d_plugin/losses/grpo_loss.py PYTHON · REPOSITORY SOURCE
1def grpo_surrogate_loss(new_log_prob, old_log_prob, advantages, clip_eps=0.2):2 ratio = torch.exp(new_log_prob - old_log_prob)current ÷ behavior3 surr1 = ratio * advantages4 surr2 = torch.clamp(ratio, 1.0 - clip_eps, 1.0 + clip_eps) * advantagestrust-region boundary5 loss = -torch.min(surr1, surr2).mean()pessimistic objective6 clipped = (ratio != torch.clamp(ratio, 1.0 - clip_eps, 1.0 + clip_eps)).float()7 metrics = dict(8 ppo_ratio=ratio.mean(),9 clip_fraction=clipped.mean(),10 )11 return loss, metrics
clip_eps shrinks from 0.2 (smoke) → 0.05 (GT-anchor) as we move to near-imitation.

4.7 Reference KL to SFT

Disable LoRA → reference mean equals frozen stage-2 planner:

$$ \mathcal{L}_{\mathrm{KL}}=\beta\, \mathbb{E}[\log\pi_{\mathrm{ref}}-\log\pi_\theta] $$

THREE POLICIES, THREE JOBS The reference anchors; the behavior samples; the current policy learns πref · LoRA off πold · makes actions πθ · receives gradients KL resists drift from SFT STRICT PPO NEEDS sample with πold update πθ later same pass ⇒ ratio = 1
Reference, behavior, and current policy are conceptually different distributions. The campaign used one forward pass for old and new log-probabilities, collapsing their ratio to one.
projects/mmdet3d_plugin/losses/grpo_loss.py PYTHON · REPOSITORY SOURCE
1def reference_kl_loss(ref_log_prob, new_log_prob, kl_coef=0.01):2 kl = (ref_log_prob - new_log_prob).mean()3 return kl_coef * kl, dict(kl=kl)
β=kl_coef. Large in phase3 (stay near SFT); tiny in phase6 (chase GT mean path).

Caveat: this MC estimate can go negative when $\pi_\theta$ is denser than $\pi_{\mathrm{ref}}$ on samples — consider a KL floor for trainval.

4.8 Total loss

$$ \mathcal{L}= \mathcal{L}_{\mathrm{GRPO}} +\mathcal{L}_{\mathrm{KL}} +\lambda\,\mathrm{PlanningLoss}(\mathrm{cumsum}(\mu),\tau^{\star}) $$

THE COMPLETE LEARNING CIRCUIT Three signals meet, but gradients have only two destinations RELATIVE PREFERENCELGRPO REFERENCE ANCHORβ LKL MEAN-PATH PULLλ ADE(μ) TOTALL TRAINABLELoRA A, B TRAINABLElog σ frozen UniAD · no gradient
The full learning circuit. All three loss terms meet at L, but autograd can update only the LoRA matrices and log σ; the frozen UniAD representation is a hard stop.

Gradients: LoRA $\{A,B\}$ + log_std only. Curriculum dials $(\lambda,\beta,G,\varepsilon)$ are documented in §6b.

Thread continuesThe equations now form one circuit. The next section follows that circuit in repository order before the experiment changes any of its dials.

5. Follow one batch through the actual implementation

UniAD (frozen) ──no_grad──► track / motion ──► bev_embed, sdc_traj_query, sdc_track_query, command
                                                      │
PlanningHeadGRPO ◄────────────────────────────────────┘
  mlp_fuser [LoRA] → TransformerDecoder [LoRA] → reg_branch [LoRA] → μ, log_std
  train: sample G increments → PlannerReward → GRPO + KL + aux ADE
  ref:   same forward, LoRA off → KL anchor
  test:  sdc_traj = cumsum(μ)   # deterministic waypoints

When planner_posttrain_only=True, every parameter is frozen except LoRA matrices and log_std. All non-planning loss weights are zeroed in config.

Thread continuesOne batch now has a complete learning circuit. The curriculum decides which part of that circuit dominates at each stage.

6. A curriculum organized around one metric gap

Multi-phase curriculum on nuScenes mini, then GT-fit iterations (phase4–6) targeting mean-path alignment.

PhaseTrain samplesEpochsGLoRA rankStatusGoal
phase0_smoke 1 1 4 8 complete Verify frozen backbone, LoRA-only grads, and GRPO pipeline.
phase1_warmup 50 2 4 8 complete High aux ADE, low KL; stabilize LoRA on 50 mini frames.
phase2_grpo 100 3 6 16 complete Balanced reward weights, rank-16 LoRA, G=6 samples.
phase3_sota 323 3 8 16 complete Full nuScenes mini, G=8, resume from phase2 checkpoint.
phase4_fit_gt 323 5 6 16 complete Strong aux ADE, tighter policy; resume phase3.
phase5_tight_fit 323 5 6 16 complete Higher aux, lower KL; resume phase4.
phase6_gt_anchor 323 8 4 16 incomplete Near-imitation aux=8, minimal exploration; resume phase4 best.
PhaseStatusItersBest minADEBest minFDE Last10% minADELast10% minFDEKL<0 ratio
Phase 0 — Smoke complete 1 9.797 16.991 9.797 16.991 0.0%
Phase 1 — Warmup complete 100 0.000 0.264 4.768 8.204 55.0%
Phase 2 — GRPO complete 300 0.000 0.304 1.893 3.194 85.0%
Phase 3 — Full-mini GRPO complete 969 0.000 0.052 1.253 1.406 92.0%
Phase 4 — GT fit complete 1615 0.000 0.017 0.870 1.332 98.7%
Phase 5 — Tight fit complete 1615 0.000 0.008 0.768 1.323 100.0%
Phase 6 — GT anchor incomplete 1066 0.000 0.004 0.923 1.690 100.0%

Best mean-path checkpoint: projects/work_dirs/planner_posttrain/campaign/phase4_fit_gt/latest.pth

6b. Why the question changes phase by phase

The campaign is a deliberate curriculum, not a single long GRPO run. Early phases buy correctness and a usable LoRA basin; middle phases unlock group-relative RL at scale; late phases close the gap between sample-best training metrics and the deterministic mean path used at inference.

THE CURRICULUM CHANGES ITS QUESTION, NOT ITS THEME From “does it run?” to “does the deployed mean follow GT?” P0provewiring P1stabilizeμ P2compareG=6 P3sample-bestfull mini P4mean pathbest μ P5ablateregresses P6anchorpending resume the best checkpoint, not the latest purple: find one good sampled future blue: improve the deployed mean
Every phase resolves one uncertainty and hands a narrower question to the next. Phase 5 is a branch that failed; phase 6 deliberately returns to the phase-4 basin.

1. Phase 0 — Smoke (pipeline verification)

Role: Sanity gate before any real optimization

Why this phase exists. Before spending GPU hours on curriculum training, we need a one-iteration proof that the LoRA+GRPO stack is wired correctly: frozen backbone, LoRA-only gradients, reward tensors, JSONL logging, and evidence hooks. A single fixed frame (smoke_index=8) makes failures cheap and deterministic.

Design choices.

  • 1 sample × 1 epoch × G=4 — minimal compute; fails in minutes, not hours.
  • strict PlannerGRPOEvidenceHook — asserts base weights frozen, LoRA grads non-zero.
  • Default-ish GRPO knobs (clip=0.2, kl=0.01, aux=1.0, rank=8) — exercise the full loss, not a stripped stub.
  • Cold start from uniad_base_e2e.pth — also catches LoRA key remapping / checkpoint load bugs.

Key knobs: G=4 · rank=8 · aux=1.0 · kl_coef=0.01 · clip=0.2 · lr=1e-4 (base)

Success criterion / observed: Train completes with returncode 0; evidence JSON shows LoRA grads live; JSONL has one row with finite loss / reward / minADE. Metric quality is irrelevant here.

Lesson: Zero-init LoRA-B made GRPO/KL identically zero at step 0 — discovered and fixed before later phases.

Next questionWith the gradient path proven, the next question is whether LoRA can recover a useful mean.

2. Phase 1 — Warmup (stabilize LoRA via imitation)

Role: Move the adapter into a useful basin without aggressive RL

Why this phase exists. Fresh LoRA adapters start near the SFT planner. Jumping straight into high-G GRPO with strong reward weights produces noisy advantages and can push μ far from GT. We first warm LoRA with a high auxiliary ADE loss (supervised mean-path imitation) on a small 50-frame subset, with weak RL signal and tiny KL.

Design choices.

  • 50 samples × 2 epochs — enough diversity to move LoRA, small enough to iterate quickly.
  • aux_loss_weight=2.0 — mean trajectory μ is pulled toward GT via PlanningLoss.
  • reward weights halved (ADE/FDE 0.5) — GRPO is present but not dominant.
  • kl_coef=0.001 — allow the policy to leave the SFT reference gently.
  • lr=5e-5, rank=8 — conservative capacity; avoid overfitting the 50-frame pocket.
  • G=4 — cheap exploration while the mean path learns.

Key knobs: G=4 · rank=8 · aux=2.0 · kl_coef=0.001 · clip=0.2 · lr=5e-5 · 50 samples × 2 ep

Success criterion / observed: Sample-best minADE drops below ~1 m on the warmup set; loss_ade becomes the primary driver; KL stays small and non-pathological.

Lesson: Warmup is the hinge of the curriculum: without it, phase2/3 need many more iters to recover a usable mean path.

Next questionWith a stable mean, the next question is whether grouped futures add useful preference information.

3. Phase 2 — GRPO (balanced policy optimization)

Role: Turn on real group-relative RL with more capacity

Why this phase exists. Once LoRA can imitate, we introduce a balanced GRPO recipe: more samples G, higher LoRA rank, and reward weights that care about FDE and collision — not only ADE. Data grows to 100 frames so advantages are estimated on a richer set of scenes, still short of the full mini.

Design choices.

  • Resume from phase1 checkpoint — continue adapters, do not reset exploration.
  • rank 8→16, α 16→32 — extra capacity for multi-scene waypoint corrections.
  • G=6 — better group statistics for relative advantages without full-campaign cost.
  • aux=0.5 — imitation still anchors μ; GRPO can improve sample-best without ignoring the mean.
  • reward ADE:FDE:collision:comfort = 1.0 : 1.5 : 2.5 : 0.1 — endpoint & safety weighted up.
  • clip_eps=0.15 — slightly tighter PPO-style clipping as policy updates grow.
  • 100 samples × 3 epochs — mid-scale curriculum step before full mini.

Key knobs: G=6 · rank=16 · aux=0.5 · kl_coef=0.01 · clip=0.15 · lr=1e-4 · 100 samples × 3 ep

Success criterion / observed: Training log sample-best minADE/minFDE keep improving vs phase1; reward_mean rises; no NaN / dead LoRA grads.

Lesson: Collision reward stayed ~0 on mini — sparse positives; keep the term for API compatibility but do not claim safety gains yet.

Next questionWith balanced sampling working, the next question is whether the recipe survives full-mini scale.

4. Phase 3 — Full mini (maximum exploration budget)

Role: Scale the balanced recipe to all 323 mini frames

Why this phase exists. This is the campaign’s strongest full-mini checkpoint under open-loop sample-best metrics. We use the full nuScenes mini train split, G=8 for richer groups, and a slightly higher KL to keep the policy from drifting too far from SFT while grouped sampling tests whether any rollout reaches a better future.

Design choices.

  • Resume from phase2 — curriculum continuity across data scale jumps.
  • 323 samples × 3 epochs (~969 iters) — full mini coverage.
  • G=8 — largest group size in the campaign (VRAM ~7.3 GiB on 4060 Ti).
  • aux=0.3 — lean more on GRPO sample-best; mean path still supervised lightly.
  • kl_coef=0.015 — stronger SFT anchor at scale.
  • reward FDE=2.0, collision=3.0 — emphasize endpoint accuracy for planning eval style.
  • lr=8e-5 — slight decay vs phase2 for stability on more data.

Key knobs: G=8 · rank=16 · aux=0.3 · kl_coef=0.015 · clip=0.12 · lr=8e-5 · 323 × 3 ep

Success criterion / observed: Sample-best minFDE reaches ~0.05 m in logs; best minADE hits 0 on easy frames. This is the headline phase-3 mini result — not yet mean-path GT fit.

Lesson: Sample-best ≠ deployable mean. Waypoint export showed mean-path ADE still ~4 m with outliers 240/280. The later ratio audit also showed clip_fraction=0 throughout, so no causal gain should be assigned to clipping.

Next questionWith a strong sampled future found, the next question becomes the article’s central one: where is μ?

5. Phase 4 — GT fit (optimize the inference mean path)

Role: Close the train/deploy gap: make μ hug GT

Why this phase exists. Inference uses the deterministic mean μ (cumsum), not the best of G samples. Phase3 was selected by sample-best logs, so purple rollouts can look great while the blue mean path lags. Phase4 flips the objective mix toward aux ADE and tighter σ so the deployed trajectory is the thing we improve.

Design choices.

  • Resume phase3 epoch_3 — keep the best sample-search adapters as a warm start.
  • aux_loss_weight=2.5 — strong PlanningLoss on μ (imitation dominates again).
  • reward ADE/FDE = 2.0 / 3.0 — even the GRPO branch prefers endpoint-accurate samples.
  • max_log_std=-1.0 (was up to +2) — shrink exploration; force samples near μ.
  • kl_coef=0.005 — freer adaptation of LoRA away from SFT when GT pull is strong.
  • G=6, 5 epochs, lr=5e-5 — longer fine-tune at moderate exploration cost.
  • Collision/comfort downweighted — GT ADE is the primary product metric this phase.

Key knobs: G=6 · aux=2.5 · kl=0.005 · clip=0.10 · lr=5e-5 · log_std∈[-6,-1] · 323 × 5 ep

Success criterion / observed: Best mean-path eval so far: mean ADE 3.60 m, median 0.98 m on 12 frames. Most scenes look good; hard outliers remain.

Lesson: Median ≈ 1 m is encouraging; max ADE ≈ 19 m (idx 240/280) proves we need either hard-example mining or even stronger mean supervision.

Next questionWith μ improved, the next question is whether simply increasing imitation closes the outlier tail.

6. Phase 5 — Tight fit (more imitation, less entropy)

Role: Stress-test whether “more aux + less KL” beats phase4

Why this phase exists. Hypothesis: if phase4’s bottleneck is residual exploration / weak imitation, raising aux to 4.0 and clamping σ further should improve mean-path ADE. This phase is an ablation-style continuation, not a guaranteed upgrade.

Design choices.

  • Resume phase4 latest — continue the GT-fit direction.
  • aux=4.0, ADE/FDE rewards 3/4 — near-imitation with GRPO as a secondary nudge.
  • kl_coef=0.002, clip=0.08 — very conservative policy updates.
  • max_log_std=-2.0 — almost deterministic sampling.
  • lr=3e-5 — smaller steps to avoid overshooting the phase4 basin.
  • Same 323×5 schedule for a fair comparison to phase4.

Key knobs: G=6 · aux=4.0 · kl=0.002 · clip=0.08 · lr=3e-5 · log_std∈[-7,-2] · 323 × 5 ep

Success criterion / observed: Did not beat phase4 on mean-path ADE (3.65 m vs 3.60 m). Confirms that simply cranking aux is not enough when a few hard frames dominate the mean.

Lesson: Keep phase4 as the best mean-path checkpoint. Next steps should target outliers (oversampling / hard mining) rather than globally higher aux alone.

Next questionWhen that ablation regresses, the evidence says to resume the best branch instead of the latest one.

7. Phase 6 — GT anchor (near-imitation marathon)

Role: Long, low-LR push of μ toward GT from the best phase4 weights

Why this phase exists. Phase5 overshot the phase4 basin. Phase6 therefore resumes phase4 epoch_5 (not phase5), sets aux=8.0 (almost pure imitation), nearly zeros KL, and runs 8 epochs at lr=2e-5 so μ can slowly absorb remaining easy/medium errors while GRPO remains a light regularizer via G=4.

Design choices.

  • Explicit load_from=phase4_fit_gt/epoch_5.pth — discard phase5 regression.
  • aux=8.0 — PlanningLoss on μ is the primary objective.
  • kl_coef=0.0005, clip=0.05, G=4 — minimal RL / exploration overhead.
  • max_log_std=-3.0 — samples stay tightly around the mean path.
  • reward ADE/FDE = 5/6 — if GRPO fires, it still prefers GT-aligned samples.
  • 8 epochs — longer horizon for slow LoRA updates on full mini.
  • Stop criterion via run_grpo_until_fit.py: mean ADE≤0.5, max ADE≤1.5, mean FDE≤1.0 on 12 eval frames.

Key knobs: G=4 · aux=8.0 · kl=0.0005 · clip=0.05 · lr=2e-5 · log_std∈[-8,-3] · 323 × 8 ep

Success criterion / observed: In progress / pending eval. Target is mean-path ADE that visually overlays GT on the eval set, including former outliers if possible.

Lesson: Curriculum principle: when a later phase regresses, branch from the best prior checkpoint — never blindly resume the latest failed experiment.

Next questionThe remaining question moves outside this campaign: strict behavior-policy replay and hard-frame mining.

6c. Read the curriculum as signal allocation

Thinking Machines–style exploration of the knobs we actually swept across phases. Hover a point to read exact values. These are not a full factorial rank×G grid (that is future work on trainval), but they show how the curriculum moves $G$, rank, $\lambda$, $\beta$, and $\varepsilon$ as we go from smoke → full-mini sample search → mean-path GT fit.

Group size $G$ & LoRA rank

Loss dials: aux $\lambda$, KL $\beta$, clip $\varepsilon$

Mean-path ADE after GT-fit phases (12 eval frames)

Per-sample GRPO mean ADE (live waypoint export)

How to read this. Capacity ($G$, rank) peaks at phase3 for sample-best hunting; GT-fit phases raise $\lambda$ and shrink $\varepsilon$/exploration. The right-hand ADE strip shows the remaining hard outliers (often idx 240 / 280) that dominate mean ADE even when the median is already near 1 m.

7. What the implementation learned from the evidence

Chronological changes and what we observed on mini:

v0 — Initial GRPO + LoRA stack

  • PlanningHeadGRPO with Gaussian policy on 6×2 increments
  • LoRA on mlp_fuser, decoder FFN/out_proj, reg_branch
  • Group-relative reward: ADE, FDE, collision, comfort
  • Reference policy = SFT planner with LoRA disabled

Issue: LoRA-B initialized to zero → policy ≡ reference at step 0; loss_grpo = loss_kl = 0.

v1 — LoRA-B small random init

  • lora_B ~ N(0, 1e-3) instead of zeros

Observed: KL becomes non-zero immediately (≈3e-5 on smoke). A later campaign audit found that the single-pass PPO ratio still remained exactly 1.0.

v2 — Per-iteration JSONL logging

  • PlannerGRPOExperimentHook → experiment_log.jsonl

v3 — Phased curriculum

  • phase1: aux_loss_weight=2, kl_coef=0.001, 50 samples
  • phase2: rank=16, G=6, balanced rewards, 100 samples
  • phase3: full mini 323×3, G=8, resume checkpoint chain

v4 — Campaign observations (nuScenes mini)

  • See campaign JSONL logs

Issue: Raw KL can go strongly negative when LoRA policy assigns higher density than reference on sampled actions.

Observed: Tune kl_coef / add KL clamp before trainval; open-loop minADE=0 on easy frames is not a deployment guarantee.

v5 — GT-fit iterations (phase4–phase6)

  • phase4: aux_loss_weight=2.5, max_log_std=-1.0, 5 epochs on full mini
  • phase5: aux=4.0, tighter KL — did not beat phase4 on mean-path ADE
  • phase6: aux=8.0 near-imitation, 8-epoch target schedule
  • tools/run_grpo_until_fit.py: stop when mean-path ADE ≤ target on 12 eval frames

Issue: Hard scenes (sample idx 240, 280) dominate mean-path ADE despite good median (~1 m).

Observed: Sample-best minADE in logs ≈ 0, but deterministic mean μ still diverges on ~2/12 eval frames.

8. The complete training trace (5666 iterations)

The rail marks curriculum boundaries; the curves are uniformly downsampled for display while epoch tables and report_data.json retain the full series.

P0P1P2P3P4P5P6

9. Open the trace, phase by phase

Phase 0 — Smoke 1 / 1 iters · complete · best minADE 9.797 m

Verify frozen backbone, LoRA-only grads, and GRPO pipeline. · 1 samples × 1 epochs

Best minADE9.797 m
Best minFDE16.991 m
Last-10% mean minADE9.797 m
KL < 0 fraction0.0%
Hyperparameters
aux_loss_weight1.0
clip_eps0.2
kl_coef0.01
lora_alpha16.0
lora_dropout0.0
lora_rank8
max_log_std2.0
min_log_std-5.0
num_samples4
reward_ade_w1.0
reward_collision_w2.5
reward_comfort_w0.1
reward_fde_w1.0
Per-epoch aggregates
EpochItersmean minADEmean minFDEmean lossmean reward
01 9.79716.991 10.244-30.565
Phase 1 — Warmup 100 / 100 iters · complete · best minADE 0.000 m

High aux ADE, low KL; stabilize LoRA on 50 mini frames. · 50 samples × 2 epochs

Best minADE0.000 m
Best minFDE0.264 m
Last-10% mean minADE4.768 m
KL < 0 fraction55.0%
Hyperparameters
aux_loss_weight2.0
clip_eps0.2
kl_coef0.001
lora_alpha16.0
lora_dropout0.0
lora_rank8
max_log_std2.0
min_log_std-5.0
num_samples4
reward_ade_w0.5
reward_collision_w1.0
reward_comfort_w0.05
reward_fde_w0.5
Per-epoch aggregates
EpochItersmean minADEmean minFDEmean lossmean reward
050 6.52611.218 15.301-11.062
150 6.15810.451 14.595-10.501
Phase 2 — GRPO 300 / 300 iters · complete · best minADE 0.000 m

Balanced reward weights, rank-16 LoRA, G=6 samples. · 100 samples × 3 epochs

Best minADE0.000 m
Best minFDE0.304 m
Last-10% mean minADE1.893 m
KL < 0 fraction85.0%
Hyperparameters
aux_loss_weight0.5
clip_eps0.15
kl_coef0.01
lora_alpha32.0
lora_dropout0.0
lora_rank16
max_log_std2.0
min_log_std-5.0
num_samples6
reward_ade_w1.0
reward_collision_w2.5
reward_comfort_w0.1
reward_fde_w1.5
Per-epoch aggregates
EpochItersmean minADEmean minFDEmean lossmean reward
0100 5.7219.850 3.471-26.763
1100 3.0945.659 2.100-17.774
2100 2.2073.853 1.323-13.675
Phase 3 — Full-mini GRPO 969 / 969 iters · complete · best minADE 0.000 m

Full nuScenes mini, G=8, resume from phase2 checkpoint. · 323 samples × 3 epochs

Best minADE0.000 m
Best minFDE0.052 m
Last-10% mean minADE1.253 m
KL < 0 fraction92.0%
Hyperparameters
aux_loss_weight0.3
clip_eps0.12
kl_coef0.015
lora_alpha32.0
lora_dropout0.0
lora_rank16
max_log_std2.0
min_log_std-5.0
num_samples8
reward_ade_w1.0
reward_collision_w3.0
reward_comfort_w0.15
reward_fde_w2.0
Per-epoch aggregates
EpochItersmean minADEmean minFDEmean lossmean reward
0323 2.4004.124 0.574-18.190
1323 1.5272.064 -0.040-12.320
2323 1.3521.661 -0.161-11.315
Phase 4 — GT fit 1615 / 1615 iters · complete · best minADE 0.000 m

Strong aux ADE, tighter policy; resume phase3. · 323 samples × 5 epochs

Best minADE0.000 m
Best minFDE0.017 m
Last-10% mean minADE0.870 m
KL < 0 fraction98.7%
Hyperparameters
aux_loss_weight2.5
clip_eps0.1
kl_coef0.005
lora_alpha32.0
lora_dropout0.0
lora_rank16
max_log_std-1.0
min_log_std-6.0
num_samples6
reward_ade_w2.0
reward_collision_w1.0
reward_comfort_w0.05
reward_fde_w3.0
Per-epoch aggregates
EpochItersmean minADEmean minFDEmean lossmean reward
0323 1.0231.734 1.708-10.804
1323 0.9671.582 1.459-10.273
2323 0.9391.477 1.339-9.972
3323 0.9171.505 1.230-9.777
4323 0.8441.336 1.067-9.240
Phase 5 — Tight fit 1615 / 1615 iters · complete · best minADE 0.000 m

Higher aux, lower KL; resume phase4. · 323 samples × 5 epochs

Best minADE0.000 m
Best minFDE0.008 m
Last-10% mean minADE0.768 m
KL < 0 fraction100.0%
Hyperparameters
aux_loss_weight4.0
clip_eps0.08
kl_coef0.002
lora_alpha32.0
lora_dropout0.0
lora_rank16
max_log_std-2.0
min_log_std-7.0
num_samples6
reward_ade_w3.0
reward_collision_w0.5
reward_comfort_w0.02
reward_fde_w4.0
Per-epoch aggregates
EpochItersmean minADEmean minFDEmean lossmean reward
0323 0.8111.456 -0.163-10.205
1323 0.7731.361 -0.278-9.742
2323 0.7781.374 -0.336-9.805
3323 0.7891.432 -0.271-10.005
4323 0.7601.348 -0.428-9.630
Phase 6 — GT anchor 1066 / 2584 iters · incomplete · best minADE 0.000 m

Near-imitation aux=8, minimal exploration; resume phase4 best. · 323 samples × 8 epochs

Best minADE0.000 m
Best minFDE0.004 m
Last-10% mean minADE0.923 m
KL < 0 fraction100.0%
Hyperparameters
aux_loss_weight8.0
clip_eps0.05
kl_coef0.0005
lora_alpha32.0
lora_dropout0.0
lora_rank16
max_log_std-3.0
min_log_std-8.0
num_samples4
reward_ade_w5.0
reward_collision_w0.2
reward_comfort_w0.01
reward_fde_w6.0
Per-epoch aggregates
EpochItersmean minADEmean minFDEmean lossmean reward
0323 0.8611.575 0.182-14.758
1323 0.8311.495 -0.011-14.110
2323 0.8271.507 -0.095-14.172
397 0.9391.705 0.146-15.914

10. The result depends on which path you measure

MetricFrozen SFT mean path (12 eval)Phase3 sample-best (training JSONL)Phase4 GT-fit mean path (12 eval)
ADE 6.71 m mean · 4.70 m median 0.00 m 3.60 m mean · 0.98 m median
FDE 10.55 m mean 0.052 m 6.83 m mean
Best checkpoint uniad_base_e2e.pth phase3_sota/epoch_3.pth projects/work_dirs/planner_posttrain/campaign/phase4_fit_gt/latest.pth

Columns are intentionally labeled by statistic: sample-best training minima are not directly comparable to deterministic mean-path averages. The table keeps both because the gap is the central campaign finding.

10b. Chase the metric that deployment actually uses

After phase3, we run additional GRPO phases optimizing the deterministic mean trajectory (inference path), not just sample-best minADE over G rollouts. Evaluation uses 12 held-out frame indices on nuScenes mini.

PhaseMean ADEMedian ADEMax ADE Mean FDETarget met?Checkpoint
phase4_fit_gt 3.599 m 0.976 m 19.497 m 6.833 m latest.pth
phase5_tight_fit 3.653 m 1.047 m 20.052 m 6.994 m latest.pth
phase6_gt_anchor mean-path evaluation pending; training status: incomplete

Stop target: mean ADE ≤ 0.5 m, max ADE ≤ 1.5 m, mean FDE ≤ 1.0 m.
Best so far: phase4_fit_gt — projects/work_dirs/planner_posttrain/campaign/phase4_fit_gt/latest.pth
Key finding: median ADE ≈ 1 m on most frames, but hard scenes (idx 240, 280) remain outliers.

11. Put the blue deployment path under a microscope

Each panel shows 6 future waypoints in the ego LiDAR frame (x forward, y left). The black dot is the ego origin. We compare three planners on identical frozen backbone features:

Reading the plots: The campaign headline sample-best metric comes from training JSONL. The purple minimum shown here is a separate, seeded export-time diagnostic over G rollouts. At inference we deploy the blue mean path only.

sample idx=8 ego GT SFT GRPO mean GRPO samples
Sample index 8 · ADE SFT 10.243 m → GRPO mean 1.340 m · exported G-rollout minADE 1.147 m
sample idx=24 ego GT SFT GRPO mean GRPO samples
Sample index 24 · ADE SFT 5.012 m → GRPO mean 0.707 m · exported G-rollout minADE 2.442 m
sample idx=48 ego GT SFT GRPO mean GRPO samples
Sample index 48 · ADE SFT 13.772 m → GRPO mean 1.551 m · exported G-rollout minADE 1.234 m
sample idx=96 ego GT SFT GRPO mean GRPO samples
Sample index 96 · ADE SFT 0.343 m → GRPO mean 0.092 m · exported G-rollout minADE 0.578 m
sample idx=160 ego GT SFT GRPO mean GRPO samples
Sample index 160 · ADE SFT 0.493 m → GRPO mean 0.306 m · exported G-rollout minADE 1.749 m
sample idx=240 ego GT SFT GRPO mean GRPO samples
Sample index 240 · ADE SFT 4.667 m → GRPO mean 19.497 m · exported G-rollout minADE 18.418 m
sample idx=32 ego GT SFT GRPO mean GRPO samples
Sample index 32 · ADE SFT 3.328 m → GRPO mean 1.136 m · exported G-rollout minADE 1.854 m
sample idx=64 ego GT SFT GRPO mean GRPO samples
Sample index 64 · ADE SFT 13.077 m → GRPO mean 0.976 m · exported G-rollout minADE 1.738 m
sample idx=128 ego GT SFT GRPO mean GRPO samples
Sample index 128 · ADE SFT 4.728 m → GRPO mean 0.399 m · exported G-rollout minADE 1.376 m
sample idx=192 ego GT SFT GRPO mean GRPO samples
Sample index 192 · ADE SFT 20.269 m → GRPO mean 0.546 m · exported G-rollout minADE 1.469 m
sample idx=280 ego GT SFT GRPO mean GRPO samples
Sample index 280 · ADE SFT 3.879 m → GRPO mean 16.463 m · exported G-rollout minADE 16.003 m
sample idx=300 ego GT SFT GRPO mean GRPO samples
Sample index 300 · ADE SFT 0.704 m → GRPO mean 0.176 m · exported G-rollout minADE 1.333 m

Quantitative comparison (open-loop, 6 steps)

SampleADE SFTADE GRPO mean FDE SFTFDE GRPO meanexport G minADEexport G mean ADE
8 10.243 1.340 17.417 2.007 1.147 1.951
24 5.012 0.707 7.749 1.630 2.442 4.744
48 13.772 1.551 23.559 2.573 1.234 2.305
96 0.343 0.092 0.678 0.232 0.578 1.583
160 0.493 0.306 0.735 0.460 1.749 2.377
240 4.667 19.497 1.995 37.646 18.418 20.299
32 3.328 1.136 5.968 1.856 1.854 2.416
64 13.077 0.976 22.907 1.985 1.738 3.223
128 4.728 0.399 7.745 0.535 1.376 2.542
192 20.269 0.546 34.977 0.672 1.469 2.244
280 3.879 16.463 1.788 31.949 16.003 17.256
300 0.704 0.176 1.123 0.453 1.333 2.288

Per-step waypoint tables

Per-step coordinates — sample 8
StepGT (x, y)SFTGRPO mean|GRPO−GT|
1 (0.02, 3.38) (0.13, 0.03) (-0.16, 2.74) 0.667
2 (-0.01, 6.27) (0.11, 0.51) (-0.41, 5.71) 0.687
3 (-0.09, 9.42) (0.37, 0.69) (-0.54, 8.19) 1.312
4 (-0.29, 12.40) (0.72, 0.70) (-0.64, 10.80) 1.639
5 (-0.62, 15.25) (0.74, 0.87) (-0.71, 13.53) 1.728
6 (-1.14, 18.24) (1.18, 0.98) (-0.62, 16.30) 2.007
Per-step coordinates — sample 24
StepGT (x, y)SFTGRPO mean|GRPO−GT|
1 (-0.12, 1.84) (0.10, 0.18) (-0.13, 1.56) 0.283
2 (-0.37, 3.78) (-0.00, 0.57) (-0.29, 3.25) 0.529
3 (-0.63, 5.24) (0.05, 0.58) (-0.68, 4.92) 0.327
4 (-0.92, 6.57) (0.25, 0.73) (-0.51, 6.68) 0.422
5 (-1.16, 7.57) (0.30, 0.98) (-0.71, 8.52) 1.052
6 (-1.44, 8.77) (0.40, 1.25) (-0.67, 10.21) 1.630
Per-step coordinates — sample 48
StepGT (x, y)SFTGRPO mean|GRPO−GT|
1 (0.01, 4.25) (0.23, 0.27) (0.14, 4.88) 0.646
2 (0.01, 8.59) (0.46, 0.91) (0.18, 9.66) 1.089
3 (0.04, 12.83) (0.87, 1.09) (0.41, 14.03) 1.257
4 (0.12, 17.09) (1.13, 1.27) (0.38, 18.79) 1.721
5 (0.23, 21.24) (1.12, 1.48) (0.56, 23.23) 2.022
6 (0.36, 25.22) (1.35, 1.68) (0.45, 27.79) 2.573
Per-step coordinates — sample 96
StepGT (x, y)SFTGRPO mean|GRPO−GT|
1 (0.00, 0.00) (0.23, 0.01) (-0.01, -0.03) 0.026
2 (-0.00, 0.00) (0.03, 0.29) (-0.04, 0.04) 0.057
3 (0.00, 0.00) (0.05, 0.13) (-0.02, 0.04) 0.042
4 (-0.00, 0.00) (0.39, -0.09) (0.01, -0.02) 0.017
5 (-0.00, 0.00) (0.31, 0.07) (-0.18, 0.00) 0.180
6 (-0.00, 0.00) (0.68, 0.06) (-0.19, 0.13) 0.232
Per-step coordinates — sample 160
StepGT (x, y)SFTGRPO mean|GRPO−GT|
1 (-0.00, -0.00) (0.28, -0.06) (0.03, 0.08) 0.083
2 (0.00, 0.00) (0.28, 0.31) (0.16, 0.23) 0.282
3 (0.00, 0.00) (0.24, 0.32) (0.11, 0.27) 0.288
4 (0.00, 0.00) (0.55, 0.03) (0.15, 0.33) 0.360
5 (0.00, 0.00) (0.58, -0.03) (-0.02, 0.36) 0.364
6 (0.00, 0.00) (0.73, -0.04) (0.04, 0.46) 0.460
Per-step coordinates — sample 240
StepGT (x, y)SFTGRPO mean|GRPO−GT|
1 (0.13, 7.54) (0.26, 0.15) (0.00, 6.24) 1.299
2 (0.38, 15.12) (0.47, 0.81) (0.17, 12.68) 2.449
3 (0.00, 0.00) (0.70, 0.96) (0.12, 18.82) 18.825
4 (0.00, 0.00) (0.94, 1.12) (0.26, 25.15) 25.156
5 (0.00, 0.00) (1.04, 1.29) (0.31, 31.61) 31.608
6 (0.00, 0.00) (1.37, 1.45) (0.05, 37.65) 37.646
Per-step coordinates — sample 32
StepGT (x, y)SFTGRPO mean|GRPO−GT|
1 (0.00, 1.17) (0.15, 0.16) (0.13, 0.74) 0.452
2 (-0.00, 2.29) (0.03, 0.78) (0.15, 1.69) 0.625
3 (-0.05, 3.42) (0.28, 0.81) (0.05, 2.52) 0.906
4 (-0.16, 4.64) (0.39, 0.89) (0.02, 3.47) 1.187
5 (-0.40, 6.04) (0.44, 1.08) (-0.13, 4.27) 1.789
6 (-0.64, 7.04) (0.49, 1.18) (-0.03, 5.28) 1.856
Per-step coordinates — sample 64
StepGT (x, y)SFTGRPO mean|GRPO−GT|
1 (0.04, 3.80) (0.22, 0.32) (0.18, 4.03) 0.268
2 (0.13, 7.88) (0.28, 0.96) (0.42, 7.94) 0.299
3 (0.29, 12.11) (0.57, 1.14) (0.47, 11.39) 0.745
4 (0.44, 16.41) (0.75, 1.31) (0.33, 15.39) 1.029
5 (0.62, 20.66) (0.88, 1.60) (0.71, 19.13) 1.532
6 (0.78, 24.82) (1.13, 1.91) (0.52, 22.85) 1.985
Per-step coordinates — sample 128
StepGT (x, y)SFTGRPO mean|GRPO−GT|
1 (0.06, 1.78) (0.18, 0.08) (-0.06, 1.52) 0.284
2 (0.12, 3.49) (0.28, 0.75) (-0.16, 3.22) 0.390
3 (0.16, 4.99) (0.47, 0.94) (-0.13, 4.61) 0.483
4 (0.21, 6.35) (0.74, 0.89) (-0.03, 6.29) 0.252
5 (0.27, 7.56) (0.83, 0.94) (-0.18, 7.53) 0.450
6 (0.31, 8.63) (1.11, 0.93) (-0.16, 8.87) 0.535
Per-step coordinates — sample 192
StepGT (x, y)SFTGRPO mean|GRPO−GT|
1 (0.03, 6.01) (0.38, 0.31) (0.08, 6.42) 0.405
2 (0.10, 12.15) (0.38, 0.83) (0.01, 12.61) 0.467
3 (0.16, 18.19) (0.57, 0.87) (-0.05, 18.66) 0.512
4 (0.20, 24.28) (0.68, 1.00) (-0.00, 24.81) 0.568
5 (0.24, 30.31) (0.79, 1.33) (0.09, 30.95) 0.652
6 (0.30, 36.35) (1.25, 1.39) (0.06, 36.98) 0.672
Per-step coordinates — sample 280
StepGT (x, y)SFTGRPO mean|GRPO−GT|
1 (0.04, 6.27) (0.07, 0.20) (0.03, 5.32) 0.949
2 (0.08, 12.62) (0.17, 0.85) (0.25, 11.07) 1.557
3 (0.00, 0.00) (0.52, 0.90) (0.36, 16.24) 16.239
4 (0.00, 0.00) (0.78, 0.93) (0.21, 21.38) 21.383
5 (0.00, 0.00) (0.86, 1.10) (0.24, 26.70) 26.700
6 (0.00, 0.00) (1.20, 1.33) (-0.12, 31.95) 31.949
Per-step coordinates — sample 300
StepGT (x, y)SFTGRPO mean|GRPO−GT|
1 (0.01, 0.12) (0.27, -0.08) (0.06, 0.02) 0.107
2 (0.01, 0.16) (0.23, 0.48) (0.04, 0.02) 0.138
3 (0.01, 0.15) (0.27, 0.48) (-0.07, 0.07) 0.115
4 (0.02, 0.15) (0.98, 0.44) (0.03, 0.20) 0.047
5 (0.07, 0.37) (1.03, 0.48) (0.05, 0.18) 0.195
6 (0.14, 0.69) (1.24, 0.46) (0.10, 0.24) 0.453

12. What the blue path lets us conclude

Claims we can defend on mini:
  • The frozen-backbone planner-only recipe runs on a single 16 GB GPU with G=8.
  • Observed sample-best minFDE fell from 16.99 m to 0.052 m.
  • The staged curriculum changed both sampled and mean-path metrics, but the logs do not isolate a causal contribution from PPO clipping.
  • Default UniAD stage-2 path remains unchanged when GRPO configs are not used.
Not yet claimed: trainval planning SOTA, closed-loop collision rate, alignment with VAD/STP3 official metrics.

Roadmap

  1. Make the policy update strict: cache a behavior-policy snapshot, detach actions / advantages, and replay multiple updates so $r$ can move and clipping can activate.
  2. Add a KL floor or adaptive $\beta$ when raw KL goes negative (policy denser than reference).
  3. Run nuScenes trainval and report L2 @ 3 s and collision rate with official eval scripts.
  4. Hard-example mining for idx 240 / 280, then export LoRA-only deployment weights.

13. Reproduce

cd UniAD
# Full campaign + GT-fit iterations
/root/miniconda3/envs/uniad2.0/bin/python tools/run_planner_grpo_experiments.py \
  --work-root projects/work_dirs/planner_posttrain/campaign --gpu 0
/root/miniconda3/envs/uniad2.0/bin/python tools/run_grpo_until_fit.py --gpu 0

# Aggregate metrics + render blog
/root/miniconda3/envs/uniad2.0/bin/python tools/analyze_planner_grpo_campaign.py
/root/miniconda3/envs/uniad2.0/bin/python tools/export_planner_waypoints.py \
  --grpo-checkpoint projects/work_dirs/planner_posttrain/campaign/phase4_fit_gt/epoch_5.pth
/root/miniconda3/envs/uniad2.0/bin/python tools/render_planner_grpo_report.py

# Serve (WSL → Windows browser)
cd docs/planner_grpo_lora && python -m http.server 8765 --bind 0.0.0.0
# Open http://localhost:8765/index.html

References

  1. [lora] Hu et al., LoRA: Low-Rank Adaptation of Large Language Models, ICLR 2022
  2. [lora-noregret] Thinking Machines Lab, LoRA Without Regret, 2025
  3. [grpo] Shao et al., DeepSeekMath: Pushing the Limits of Mathematical Reasoning via Reinforcement Learning of Process Supervision / GRPO, 2024
  4. [ppo] Schulman et al., Proximal Policy Optimization Algorithms, 2017
  5. [uniad] Hu et al., Planning-oriented Autonomous Driving (UniAD), CVPR 2023
  6. [mmdet3d] OpenMMLab, MMDetection3D
  7. [nuscenes] Caesar et al., nuScenes: A multimodal dataset for autonomous driving, CVPR 2020
  8. [trl] Hugging Face TRL — Group Relative Policy Optimization docs