feat(janus-pro): add i2t and t2i AR training support - #141
Conversation
|
Could you provide the training curve (reward / loss)? |
…janus-pro-i2t-t2i
GRPO was the only PPO-style algorithm without the old_logp_source knob that
FlowGRPO / DRPO / CPPO / DPPO already expose, so stages whose rollout decode
sits numerically far from teacher-forced replay had no supported way to
re-anchor the ratio and were open-coding it in autoregress.
Declaring anchor_fields=("log_probs",) with recomputes_anchor() true in replay
mode lets TrainStack drive prepare_segment per micro-slice, so the anchor is
frozen at exactly the geometry training replays at instead of whatever shape
rollout happened to use.
Both Janus-Pro recipes opt in: cached bf16 decode vs full-sequence attention,
amplified by CFG on image tokens, otherwise pushes nominally on-policy samples
past clip_range=1e-2.
ar.py replay ran the fused LlamaForCausalLM forward and kept logits for the whole prompt+response window to read T rows out of it. At Janus-Pro's 102400 vocab and max_prompt_length=2048 that is ~430 MB of bf16 logits per sequence, retained by autograd, to use ~3% of it. Take last_hidden_state from the decoder body and run lm_head only at each row's predict positions, mirroring the hidden-then-gen_head split image_ar.py already used. Feeding the response trimmed to [0, T-2] also drops the one input position that is a label only. Both autoregress paths no longer overwrite segment.log_probs with a replay forward; that anchoring is now algorithm.old_logp_source, which the train stack applies at true micro geometry. Doing it in the stage was invisible to the rollout/replay K3 metric and only matched training geometry while forward_batch_size and micro_batch_size both stayed 1. _language_body moves to ar.py so both stages share one resolver.
trust_remote_code=true lets a checkpoint ship its own modeling file that wins over the vendored registration, which would silently drop the transformers-5.x patches recorded in VENDOR_COMMIT.txt — including the grad-safety clone in prepare_inputs_embeds that replay backprop depends on. Assert the resolved class is the vendored MultiModalityCausalLM instead of training a different implementation than the one that was reviewed.
Both stages carried their own copy of the left-repack loop that unirl.models.types.ar.left_pad_prompt already provides (qwen3 and qwen_vl use it). Collapse them onto one helper in ar.py; the image-aware variant now only adds the images_seq_mask permutation, which has to follow input_ids or prepare_inputs_embeds scatters image embeddings into the wrong slots. ar.autoregress accumulated tokens by calling .item() per row per step, costing 2*B host syncs per decode step on top of the one the all-ranks-done reduction already pays. Accumulate into preallocated device tensors with a length counter and sync once after the loop, matching image_ar. cfg_weight now comes only from conditions: replay runs from GRPO with no sampling_params, so sampling under a different value would bias every ratio. A disagreement is now an error rather than a silent skew. Drops the stages' own .eval() calls — TrainsideRolloutEngine already saves and restores training mode around generate, and model.model.eval() additionally flipped the frozen towers with nothing restoring them.
The package did not import: Tencent-Hunyuan#214 replaced RolloutReq / RolloutResp with Sample / Part, and the main merge into this branch did not conflict because pipeline.py exists only on the feature side, so it kept importing unirl.types.rollout_req. Every validation number in the PR description was produced before that merge. generate is now Sample -> Sample. Task selection reads parts[0].control["task"] and otherwise infers from Sample.has_image_input(). i2t pulls its turns from vision_conditioning() and t2i from turns(); both fail loud on a multi-turn trajectory, which the single-user-turn Janus chat template cannot encode. Sampling params come off the frontier gen shell rather than a request-level dict, and t2i requires JanusProImageARSamplingParams because the image grid, CFG weight, and token count all ride on it. Results go back through Sample.with_filled_frontier, so the input chain and reward_compute_s survive. The stages, conditions, bundle, config, chat_template, and image_prompt were already API-agnostic and are untouched.
|
Reviewed this and pushed five commits to the branch ( Blocking issue found: the package did not import#214 replaced
Training curveJanus-Pro-1B,
The near-zero start is 1B ignoring "answer with the letter only"; GRPO teaches the format first, which is also why step time drops 5.5x and grad_norm falls two orders of magnitude once it is learned. W&B: https://wandb.ai/hfmoe-hfmoe/unirl/runs/i5jx76la Not covered: 7B (1B was the fastest route to a green end-to-end curve; the code path is identical but 7B throughput/memory are unmeasured), t2i, and anything past the format-learning phase. Review fixes
Worth flagging for the description:
Still openA 7B curve, and t2i. Happy to run either — |
|
7B curve, closing the gap I left open above. Same recipe, same overrides, same 8x H20 node — only
Both 40/40, 7B does not beat 1B here, and that is worth being explicit about. Last-5 means are within noise of each other (0.3313 vs 0.3289) and both sit only a little above the 0.25 four-way random baseline. What 40 rollouts of LoRA GRPO buys on this task is output-format compliance — the model learns to emit a bare letter, which is also why step time drops ~3x and grad_norm falls two orders of magnitude — not geometry reasoning. So these curves demonstrate that the training path runs end to end and stays numerically well-behaved; they are not evidence that Janus-Pro learns Geometry3K. A longer run, a harder reward, or full fine-tuning instead of LoRA r16 would be needed for that. What the 7B run does confirm that 1B could not:
One operational note for anyone reproducing: startup was 31 minutes before the first rollout, almost entirely eight Ray workers each cold-reading the 14.85 GB checkpoint from CephFS. Still not covered: t2i. |
|
Follow-up on the T2I half, which my earlier comments left uncovered. Posting for the record since the PR is closed — nothing here asks for it to be reopened. A real bug: every T2I reward was silently zeroed
Janus-Pro T2I emits one image token per grid cell, so its length is Localised by instrumenting the backend: PickScore returned Fix: Commit Two more T2I findings
PickScore with T2I trains, but does not learn on PickScoreWith the fix in, 100 rollouts at the recipe defaults produce a flat curve: least-squares drift Four-arm ablation, 8x H20 each:
No arm clears a drift-to-noise of 1, so none of them is a demonstrated effect. But the split is clean: the two Two things this rules out: raising the learning rate does nothing under A 500-rollout To be explicit about what is and is not shown: the T2I path runs end to end and stays numerically well-behaved ( |
Summary
Adds Janus-Pro support for both Text+Image -> Text and Text -> Image AR training in UniRL.
This includes Janus-Pro bundle/config/pipeline wiring, multimodal I2T conditions, AR image-token prompt/rollout/replay/decode support for T2I, vendored Janus runtime code, LoRA training recipes for Geo3K-MC I2T and PickScore T2I, and the direct Janus runtime dependencies.
The branch is rebased onto the current UniRL API. Both I2T and T2I now store old-policy log-probabilities using the same full-sequence teacher-forced geometry used by training. T2I replay is a single batched forward rather than one cached forward per image token, and the recipes enable gradient checkpointing.
Related Issue
Related to #25
Test Plan
tests/tests passed.1daa72faexcept for declared integration compatibility changes; the upstream MIT license is preserved.(2, 626)token batch with 1,152 image slots.(1, 3, 64, 64)image, replayed, backpropagated, and optimized.0.6168to0.0by replay-geometry anchoring.unirl.train_arsmoke with Ray, DP2 FSDP, LoRA, local MC reward, and one complete rollout:reward=0.5000grad_norm=0.3854ratio=1.0000±0.0000clip_fraction=0.00Compatibility / Risk
This is a new model package and two new recipes, so the changes are isolated from existing model paths. The old-policy anchoring adds one graph-free full-sequence forward per rollout, but removes the previous 576-forward T2I replay pattern and prevents nominally on-policy samples from being clipped solely because cached BF16 decode and full-sequence replay use different numerical kernels.
The remaining validation gap is a sustained multi-step training curve, especially with the 7B checkpoint and the full T2I trainer path. The PR remains draft until that acceptance criterion is agreed or supplied.
Reviewer Notes
AI assistance was used. No AI co-author trailer is present. Local validation did not add tests to the PR. Please review the old-policy anchoring in both AR stages, Janus-Pro pipeline routing, and the two recipe resource settings first.
Checklist