Skip to content

feat: plantf decoder head - #42

Draft
yamsam wants to merge 29 commits into
devfrom
feat/plantf-decoder-head
Draft

yamsam wants to merge 29 commits into
devfrom
feat/plantf-decoder-head

Conversation

@yamsam

@yamsam yamsam commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

What changed

  • Added the PlantF decoder and PlantF-specific training path.
  • Kept the DP encoder, input features, turn-indicator output, normalization contract, and DP-shaped prediction output.
  • Added one-shot ego trajectory regression with optional multimodal outputs.
  • Added direct neighbor trajectory regression.
  • Added relative-XY prediction support for ego and neighbors.
  • Added PlantF validation, ONNX wrappers, checkpoint visualization compatibility, and unit tests.
  • Removed mode-one-only constant validation metrics and the zero-valued mode classification loss.

PlantF versus the DP head

DP diffusion head PlantF head
Iterative denoising / sampling One decoder forward pass
Diffusion reconstruction objective Winner-takes-all Smooth L1 trajectory regression
Joint diffusion output for ego and neighbors Direct ego and per-neighbor regression heads
Guidance and delay-prefix support Not used by the one-shot decoder
Sampling result Highest-probability trajectory mode

Current production baseline

The current baseline uses a two-GPU, mode-one MLP head:

decoder_type=plantf
plantf_head_type=mlp
num_modes=1
plantf_relative_xy=True
plantf_use_ego_state_in_head=True
use_velocity_representation=False
plantf_tail_weight=1.0
coeff_smoothness_loss=1.0
plantf_smoothness_tail_weight=1.0
freeze_encoder_epochs=2
global_batch_size=96
head_lr=5e-4
encoder_lr=5e-5
use_ema=True

The complete production command, normalization requirements, checkpoint behavior, and validation checklist are documented in:

  • docs/plantf_usage.md
  • docs/plantf_head_integration_plan.md

Validation

  • diffusion_planner/tests/test_plantf_decoder.py: 38 tests passed.
  • One-epoch local small-subset training and validation smoke tests passed.
  • Mode-one logs retain ADE, FDE, miss rate, progress, smoothness, stratified metrics, and replan consistency while omitting constant mode
    diagnostics.

Deployment notes

  • Use the full PlantF ONNX graph with a single-step runtime.
  • The DP split multi-step runtime remains diffusion-specific and is not compatible with the one-shot PlantF decoder.
  • Closed-loop evaluation and EMA-versus-regular-weight comparison are still required before deployment promotion.

yamsam and others added 25 commits July 16, 2026 19:33
Adds decoder_type=plantf which replaces the diffusion decoder (DiT +
DPM-Solver) with a one-shot multi-modal regression head ported from
planTF (Cheng et al., ICRA 2024), sharing the encoder and the
input/output contract. Training uses winner-takes-all regression over
num_modes ego candidates plus mode classification, reusing DP's
lat/lon/heading losses, penalty losses and turn-indicator heads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
The export pipeline assumed the diffusion decoder (decoder.dit), so
checkpoint exports failed with decoder_type=plantf. The planTF head is
one-shot, so it exports full.onnx, encoder.onnx and a single-call
decoder.onnx (encoding -> prediction, probability, turn_indicator_logit)
with no separate turn-indicator graph. Mode selection now uses gather so
the batch axis stays dynamic under tracing, and torch2onnx.py validates
the planTF split graphs against torch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
The planTF head ignores sampled_trajectories / ego_current_state / delay
and the legacy exporter prunes unused graph inputs, so the exported
full.onnx had 14 inputs while the Autoware node feeds all 17 by name
(both ONNX Runtime and TensorRT reject names absent from the graph).
PlanTFFullONNXWrapper now anchors the three inputs with a zero-valued
scalar-slice residual, keeping the node unchanged and the outputs
bit-identical. Documented the node ONNX compatibility investigation in
the planTF integration plan.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
Under winner-takes-all training, rarely-winning modes receive almost no
regression gradient; with Xavier output weights they keep emitting
white-noise trajectories, which the argmax(pi) mode selection can pick
at inference (randomly jagged outputs). Zero-init (same convention as
Decoder.dit.final_layer) makes every mode start at the
normalized-space mean, so an undertrained mode degrades into a smooth
prior trajectory instead of noise. Analysis and further mitigations in
docs/plantf_dead_mode_improvement.md. Requires retraining to take
effect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
Retraining with modes=1 showed the planTF head collapsing to a
near-stationary oscillation around the origin while the averaged
validation loss stayed unremarkable. Three changes to detect and
counter that:

- compute_trajectory_progress_metrics reports endpoint progress, path
  length ratio, FDE, mean second difference and speed MAE for the
  selected ego prediction, each additionally binned by GT endpoint
  distance (stop / slow / move) so stop-heavy data cannot mask a
  moving-scene failure. Logged as valid_traj/* for every decoder type.
- An endpoint FDE loss in metres (coeff_endpoint_fde_loss, planTF only)
  adds a direct forward-progress signal that the 80-step pointwise
  decomposition dilutes.
- The longitudinal velocity down-weighting is now opt-in for the planTF
  head (plantf_use_lon_velocity_weight, default off): dividing the lon
  error by up to |v| suppresses exactly the progress signal the
  regression head needs.

Analysis and the recommended ego_history_dropout_rate=0 ablation are
documented in docs/plantf_dead_mode_improvement.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
The endpoint FDE loss was computed in metres (endpoint_diff * std). With
ego xy std = 20 that made it ~60x the normalized-space per-timestep
position loss, so at coeff=1.0 it dominated the total and the model
collapsed each path to a straight terminal-homing line (the endpoint
term constrains only the endpoint, not the shape), losing the gentle
curves GT and the diffusion head produce. Compute it as a squared error
in the normalized state space instead, matching ego_planning_loss, so
the per-timestep losses still shape the trajectory. See
docs/plantf_dead_mode_improvement.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
The endpoint loss is not needed for forward progress: once the lon
velocity down-weighting is off, the per-timestep position loss teaches
progress at every step including the endpoint, so the endpoint term
only double-weights a point the position loss already covers. Because
it constrains the endpoint but not the path shape, it also adds a
straight-line bias (the metre-scale version made the model ignore
curves entirely). Default coeff to 0; the term is still computed for
logging and can be opted back in. See docs/plantf_dead_mode_improvement.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
A/B evaluation at modes=6 showed the planTF-specific loss shaping made
results worse (ADE 6.80 vs 5.20 without it, and a straight-line
prediction that cannot follow gentle curves). The dango collapse it was
meant to fix was actually caused by modes=1 (single-mode L2 = mean
regression) and was already resolved by modes>=2 with WTA, so the lon
velocity down-weighting was not at fault. Restore
plantf_use_lon_velocity_weight=True (endpoint loss already defaulted
off), making the planTF loss identical to the diffusion head. The
progress metrics are kept for diagnosis. See
docs/plantf_dead_mode_improvement.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
The planTF head regressed 80 absolute waypoints independently per
timestep, which A/B pinned as the cause of the residual straight-line +
comb-jitter failure (second_diff_m_move ~0.5 vs GT ~0.007, fde_m_move
~20m) that survived reverting every loss change. It now regresses
per-step displacement when use_velocity_representation is set, and
_decode integrates it with a cumulative sum into absolute waypoints
before mapping to the normalized state space. The cumsum gives the
temporal continuity a per-timestep absolute regression lacks (adjacent
waypoints differ by exactly the predicted displacement, killing the
comb jitter) and keeps forward progress from collapsing to a stall.

Because the integration is confined to _decode, WTA selection,
loss_func, mode metrics, prediction and ONNX export are all unchanged
(1-epoch smoke exports all three ONNX graphs, no nan loss). The
NotImplementedError guard is removed. Still applies the absolute
lat/lon/heading loss; switching to the HDP hybrid loss is the next step
if the cumsum gradient proves unstable. See
docs/plantf_dead_mode_improvement.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
min_fde_k=3m vs top1 fde=20m confirmed the residual planTF error is a
mode-selection failure, not a regression failure: a mode that reaches
the curve endpoint exists but argmax(pi) never picks it. plantf_mode_usage
only shows the selected-mode distribution; whether collapsing to modes=1
is safe depends on the oracle (best-ADE) mode distribution, which was not
logged. Add plantf_oracle_usage_{k}: concentrated => data is effectively
uni/bi-modal and modes=1 suffices, spread => genuinely multi-modal and one
mode would average the trajectories away, requiring multi-mode output.
Computable by re-running validation on the existing model (no retrain).
See docs/plantf_dead_mode_improvement.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
Iterating on settings (modes, velocity representation, endpoint weight,
alpha) only changed the shape of the collapse — dango, straight-line,
reverse-direction, comb jitter — because the loss itself had diverged
from the original planTF: compute_plantf_training_loss grafted the DP
diffusion-decoder loss (lat/lon/heading L2 decomposition + longitudinal
velocity down-weighting + timestep weighting + endpoint term) onto the
one-shot regression head, which destabilized it.

Original planTF (jchengai/planTF) uses a plain smooth L1 on the WTA best
mode over all channels, uniform across timesteps, plus a cross-entropy on
the detached best mode — no velocity normalization, no timestep weighting,
no lat/lon split. Restore exactly that for ego and neighbor regression.
The turn-indicator and road-border/collision penalties are kept for the
DP/Autoware interface. Recommended config is num_modes>=2,
use_velocity_representation=False (original is absolute-coordinate), and
the default learning_rate 1e-4 (1e-5 underfits in 20 epochs).

Verified: unit test asserts the ego loss equals an independently computed
smooth_l1 of the WTA best mode; 1-epoch smoke completes and exports all
three ONNX graphs. See docs/plantf_dead_mode_improvement.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
The planTF head kept diverging (speed_mae_stop 80, then 20 with a lower
lr) and producing start-point-scattered trajectories at settings where
the diffusion head is stable. The difference is structural: the
diffusion decoder pins the current state into its prediction sequence,
so its output is anchored to the current motion; the planTF head
regresses 80 absolute waypoints from the ego token alone and never sees
the current position/velocity, so the regression is unanchored and
scatters/diverges.

Feed ego_current_state[:, 4:10] (vx, vy, ax, ay, steering, yaw_rate,
already normalized) into PlanTFTrajectoryHead: project to hidden and add
to the ego token before the mode branch. This anchors the prediction to
the current motion the way the diffusion head's pinned state does, so
the head can output a stop when the current speed is 0 and the correct
forward extent/curve otherwise. Controlled by plantf_use_ego_state_in_head
(default True). The full ONNX graph gets this via model inputs
automatically; the split decoder graph still needs ego_current_state
added as a second input (follow-up). Verified by a unit test (ego state
changes the prediction when enabled, no-op when disabled) and a 1-epoch
smoke that exports all three ONNX graphs. See
docs/plantf_dead_mode_improvement.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
…a options (A2/C1)

Three planTF-alignment changes from the roadmap toward closing the gap with
original planTF. All except B1 default OFF (no behavior change unless enabled).

- B1: AdamW weight decay (config weight_decay, default 1e-4) applied only to
  matmul weights; biases and LayerNorm/BatchNorm/GroupNorm/Embedding are
  excluded via param groups. Previously torch AdamW's default 0.01 hit all
  params including norms/biases. Affects both decoder types.
- A2 (plantf_ego_state_token, default False): replace the ego encoder token
  with an embedding of the current motion state (vx,vy,ax,ay,steer,yaw_rate)
  plus state_dropout (default 0.75), matching original planTF's
  use_ego_history=false path. Anchors the prediction to the current motion the
  way the diffusion decoder's pinned state does.
- C1 (plantf_input_delta, default False): feed agent (ego+neighbor) history as
  consecutive-frame xy deltas.

Unit tests (17) cover A2/C1 forward contract and A2+C1 backward; the param
groups are verified to cover all parameters. See
docs/plantf_original_comparison_and_roadmap.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
Two fixes from the ablation analysis (docs/plantf_original_comparison_and_roadmap.md).

- lr schedule: the misnamed CosineAnnealingWarmUpRestarts held the lr CONSTANT
  after warmup (MultiplicativeLR lambda=1.0). Every model, diffusion included,
  peaked during warmup and then collapsed, and the checkpoint_trajectory viz
  showed comb/straight-line predictions past a few epochs. Replace the
  post-warmup phase with a real CosineAnnealingLR decay to eta_min=1e-6, matching
  original planTF. Also removed the redundant final-10-epochs manual lr step-down
  in train.py (the cosine now owns the decay).
- planTF smoothness_loss: the head regresses 80 absolute waypoints independently
  per timestep, producing comb jitter (roughness ~2 vs GT ~0.007) even when
  ADE/FDE look fine. The diffusion head follows curves smoothly at the same
  settings, so this is structural. Penalize the xy second difference of the best
  mode (coeff_smoothness_loss, default 0).

Both affect training only. Tests pass (17); cosine schedule verified to warm up
then decay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
Add --enable_checkpoint_viz / --enable_onnx_export toggles (both default
True to preserve behavior). Disabling viz makes matplotlib/visualize_input
a lazy import inside render_checkpoint_trajectory_figure, so training runs
on environments without matplotlib. Disabling ONNX skips export at each
checkpoint save. Lets other teams run large-scale experiments without the
optional dependencies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
…ker, NLL/tail losses

For the upcoming comprehensive experiments. All default to the current behavior
and are independently combinable (docs/plantf_head_development_notes.md §9):

- plantf_head_type=cross_attn: K learnable mode queries cross-attend to ALL
  encoder tokens (map/agents/route) instead of reshaping the single pooled ego
  token, giving the 80-point head the scene context the bottleneck lacks.
- plantf_route_rerank (+topk): at inference, pick the ego mode by route
  adherence among the top-k pi modes instead of argmax(pi). Recovers oracle-ish
  modes for the smooth velocity-rep head at zero training cost. Validation
  forward path only; the ONNX deploy graph (no route_lanes) keeps argmax.
- plantf_use_laplace_nll: head predicts a per-point log-scale; ego regression
  uses Laplace NLL instead of smooth-L1 (planTF's probabilistic regression;
  calibrates tail uncertainty and mode confidence).
- plantf_tail_weight / plantf_smoothness_tail_weight: weight the ego regression
  / curvature penalty toward the late horizon where divergence is worst.

9 new tests cover head shapes (incl. predict_scale), cross-attn forward+loss,
NLL/tail losses changing the loss, and route re-rank fallback + identity-norm run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
…e input

The mini dataset's goal_pose is in global/map frame while the Autoware C++ node
feeds it correctly ego-transformed, so goal_pose is a train/deploy-mismatched
input (a likely contributor to the Autoware "way-off/dango" behavior). With
plantf_mask_goal_pose=True the encoder zeroes goal_pose, so the model plans from
route_lanes (which already encodes the goal) instead. Default False (unchanged).
Bakes into the ONNX graph, so Autoware needs no change to deploy it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
The flat mlp head regresses 80 waypoints as independent linear read-outs
(Linear(hidden, T*C)) — no output-side temporal inductive bias; coherence is
only bolted on via velocity-cumsum + smoothness penalty. Add two combinable
head types that put temporal structure in the architecture, both keeping the
mlp-style single-ego-token mode formation (deploy robustness) and the exact
[B, K, T, 4] output contract (velocity integration, WTA, zero-init unchanged):

- `--plantf_head_type basis` (+ `--plantf_basis_control_points`, default 8):
  regress Bezier control points expanded to T via a fixed Bernstein basis
  buffer. Structurally C-infinity smooth (partition of unity + convex hull),
  a candidate replacement for the smoothness penalty. ONNX = one extra matmul.
- `--plantf_head_type gru`: a GRU unrolls the waypoints (non-autoregressive:
  fed a learned per-step temporal embedding + mode context, so it exports as a
  single ONNX GRU op with no feedback loop). Experimental — RNN heads are more
  deploy-fragile (batch!=1 GRU export caveat; Autoware runs batch=1).

Both verified: full 17-input ONNX export with exact PyTorch parity (max|d|=0).
34 tests pass (added basis/gru shape, zero-init, contract, smoothness,
velocity+backward, and bezier partition-of-unity/endpoint tests).

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
Different converter versions store neighbor_agents_future as 3 channels
(x, y, heading) or 4 (x, y, cos, sin). train_epoch applies heading_to_cos_sin
AFTER the DataLoader collate, so a batch mixing a 3-channel dataset (mini) with
a 4-channel dataset (odaiba) fails to collate ("Trying to resize storage that is
not resizable"). Convert to 4 channels in __getitem__, before collation, so
differently-generated datasets can be combined for training.

Padded (all-zero) rows stay all-zero, so downstream masking is unchanged, and
already-4-channel data is returned untouched — the result matches what
train_epoch would have produced for 3-channel data, so single-dataset behavior
is preserved. Verified: a mixed odaiba(4ch)+mini(3ch) batch now collates to
[B, 320, 80, 4]. 34 plantf tests still pass.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Isamu Yamashita <isamu.yamashita@tier4.jp>
@yamsam yamsam changed the title Feat/plantf decoder head feat: plantf decoder head Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant