Skip to content

Add SGLang-Omni RL rollout integration - #2

Closed
Hayden727 wants to merge 24 commits into
mainfrom
hayden/omni-rl-clean-20260708
Closed

Add SGLang-Omni RL rollout integration#2
Hayden727 wants to merge 24 commits into
mainfrom
hayden/omni-rl-clean-20260708

Conversation

@Hayden727

Copy link
Copy Markdown
Collaborator

What this PR adds

This PR adds the Miles-side integration used to validate SGLang-Omni RL rollouts for:

  • Qwen3-Omni thinker text RL
  • Higgs TTS RL with codebook/audio rollouts

The SGLang-Omni server-side companion PR is:

Main changes

  • Adds an Omni rollout contract helper for /generate payload construction and strict response parsing.
  • Captures output_codebook_tokens and omni_rollout metadata for Higgs TTS rollouts.
  • Refactors the Higgs examples to use the shared rollout contract instead of local ad hoc response parsing.
  • Removes the stale local SGLang-Omni patch file; server-side support now lives in the SGLang-Omni PR.
  • Keeps the existing Qwen3-Omni thinker and Higgs TTS closed-loop examples for on-policy GRPO + NCCL weight sync.

Validation

Fast tests in the hongccc/sglang-omni:dev container on hyper01-omni:

PYTHONPATH=/data/rl-omni-pr/miles python3 -m pytest \
  tests/fast/test_omni_rollout_contract.py \
  tests/fast/test_omni_generate_fn.py -q

26 passed, 1 xfailed, 7 warnings

End-to-end smoke validation:

  • Qwen3-Omni thinker: 6-step on-policy GRPO, per-step NCCL sync to the served thinker stage, synced_params=160.
  • Higgs TTS: 6-step on-policy GRPO, /generate audio/codebook rollout, Whisper-CER reward, LoRA update, per-step NCCL sync to the served tts_engine stage, synced_params=72.
  • Higgs old-policy logprob parity after the SGLang-Omni raw codebook-0 fix:
WORST_MEAN_ABS_DIFF: 0.0646  (tol=0.1)   WORST_MAX_ABS_DIFF: 0.2033
PARITY_OK: True

RL E2E loss metrics

Hayden727 added 22 commits July 8, 2026 13:29
Foundational miles-side plumbing for Omni RL (Qwen3-Omni-Thinker AR + higgs TTS)
rollout against the sglang-omni /generate backend:

- miles/utils/processing_utils.py: encode_audio_for_rollout_engine (WAV base64
  data URI), the audio analog of encode_image_for_rollout_engine.
- miles_plugins/omni/rollout_contract.py: strict sampling-param whitelist/alias
  (sglang-omni RolloutSamplingParams forbids extra keys), /generate payload
  builder, response parser with loud length validation, and a sample-accumulation
  helper following the miles loss-mask convention (mask spans response tokens only).
- miles_plugins/omni/omni_generate_fn.py: OmniGenerateFn, a class-based
  --custom-generate-function-path entrypoint mirroring single_turn generate.
- tests/fast/test_omni_rollout_contract.py: positive + negative unit tests.

Omni-specific code stays under miles_plugins/omni with narrow public imports so it
can later be extracted; only the generic audio encoder lands in miles core.
…iring

Round-1 review fixes for the miles-side omni rollout integration:

- OmniGenerateFn now mirrors single_turn/compute_request_payload halt semantics:
  partial-rollout resume shrinks max_new_tokens and rollout_max_context_len is
  enforced (returns TRUNCATED with no budget, never over-generates).
- Decoded response audio moves to sample.metadata['generated_audio'] (reward-facing)
  instead of multimodal_train_inputs, which the training path moves to GPU and
  concatenates (would crash on a dict).
- Input audio is encoded from sample.multimodal_inputs and sent as audio_data; the
  generic compute_request_payload path gains the same audio_data support
  (encode_audios_for_rollout_engine), parallel to image_data.
- encode_audio_for_rollout_engine rejects out-of-range integer PCM; parse_generate_response
  is strict (entries must be exactly [log_prob, token_id]).
- tests: new test_omni_generate_fn.py loads the hook via load_generate_function, stubs
  post, and asserts the emitted request + sample (payload cleaning, budget truncation,
  audio encoding); test_omni_rollout_contract.py updated for metadata audio + strictness.
…fied audio keys

Round-2 review fixes completing the AC-3 miles-side audio scaffold:

- apply_response_to_sample now appends loss-mask entries whenever a mask already
  exists (partial-rollout off-policy masking pre-sets loss_mask=[0]*old_len), not
  only when update_loss_mask=True. Keeps len(loss_mask)==response_length so
  convert_samples_to_train_data does not crash on resumed rollouts.
- compute_prompt_ids_from_sample routes through call_processor (so audio_kwargs are
  actually injected) instead of calling state.processor directly, and normalizes
  processor input_ids to a JSON-safe list[int].
- one canonical extract_audio_inputs(multimodal_inputs) accepting 'audios'/'audio'
  is shared by compute_request_payload and OmniGenerateFn.
- tests: loadable-hook resume test (off-policy mask + new tokens stays aligned),
  apply append-to-existing-mask, generic compute_request_payload audio (both keys),
  and a strict xfail marker documenting deferred mm_data audio-INPUT token expansion.
Extracts the Qwen3-Omni thinker into a standalone HF checkpoint (strips the
thinker. prefix, writes thinker_config as config.json). The thinker config carries
vision_config, so miles FSDP get_model_cls() loads it via AutoModelForImageTextToText;
a forward pass on text input produces LM logits. RAM-bounded shard-by-shard copy so
the 30B model never needs to fit in memory. Enables GATE-A thinker-only RL training
without modifying miles core model loading.
async compute_math_reward(args, sample, **kwargs) -> 1.0 if the response contains the
gold answer (sample.label), else 0.0. Numeric-aware (12 == 12.0), deterministic and
dependency-free. Loaded via --custom-rm-path.
Self-contained single-GPU harness demonstrating all four closed-loop components on the
real Qwen3-Omni Thinker: rollout (sglang-omni /generate) -> reward (math) -> GRPO
advantage -> LoRA policy-gradient update, over multiple steps. Plus a tiny math_smoke
dataset. Behavior policy is the served base thinker (rollouts not yet weight-synced;
that is the documented next integration step).
…nker

Extends the GATE-A loop with the 4th component done properly: per-step LoRA-merged
weight broadcast into the served sglang-omni thinker stage via /init_weights_update_group
+ /update_weights_from_distributed (stages=[thinker]), so rollouts become on-policy. The
thinker load_weights accepts plain model.* names, so extracted-thinker names sync directly.
Add torch.cuda.set_device(0) before model load / weight-sync, matching sglang-omni's
E2E refit trainer, so NCCL broadcast uses the correct device. The NCCL rendezvous with
the served thinker was proven to succeed (WEIGHT_UPDATE_GROUP_READY); final broadcast
validation is pending a stable free GPU (shared host churn).
Allow a fresh NCCL group name per run so a re-run is not wedged by a stale
weight-update group left on the server by a prior interrupted attempt.
… work

The on-policy weight-sync broadcast failed with 'Cuda invalid argument' because the
trainer and server are separate single-GPU-masked processes; NCCL tried direct P2P
between their physical GPUs and could not resolve the masked peer device. Setting
NCCL_P2P_DISABLE=1 on both ends (SHM transport) fixes it. Verified end-to-end: 4 GRPO
steps on the real Qwen3-Omni-30B Thinker, synced_params=160/step, non-diverging loss,
stable reward — full on-policy GATE-A closed loop.
compute_tts_reward / TtsCompositeReward: transcribe generated audio with Whisper, score
content via CER vs target text, combined with hard audio-validity guards (decode success,
duration bounds, non-silence RMS floor). Failed decode -> deterministic low reward, never
crashes (DEC-2 composite design). Plus a tiny TTS smoke dataset. Pure logic verified
locally (CER, normalization, WAV decode, all guards).
gate_b_loop.py demonstrates the first 3 closed-loop components on the real Higgs-audio
model via sglang-omni: rollout (pretok /generate -> codec tokens + logprobs + decodable
audio, AC-2 now PASS) -> composite Whisper-ASR-CER reward + audio guards -> GRPO advantage.
The 4th (LoRA update + NCCL weight-sync) mirrors GATE-A. Bundles the verified sglang-omni
Higgs codec-token-logprob patch (examples/omni_gate_b/sglang_omni_patches/, 4 files:
model_runner/payload_types/request_builders/vocoder_scheduler).
Differentiable teacher-forced forward (transformers Qwen3 backbone + fused codec
embedding/head loaded from the Higgs ckpt) recomputing codebook-0 logprobs over a
sampled codec sequence, for GATE-B RL. Verified vs the served bf16 model:
mean|delta|~0.05, max~0.19 -- an fp32 trainer gives the SAME residual, so it is the
server's bf16/sglang-kernel numeric floor, not a reconstruction error (exp(0.2)~1.22
sits at the GRPO clip boundary, first-ratio-after-sync only). Unblocks GRPO +
tts_engine weight-sync.
…-sync)

Closes the TTS RL loop end to end, mirroring gate_a_full.py: rollout (full codec
tokens + codebook-0 logprobs + audio) -> composite ASR reward -> GRPO advantage ->
LoRA update (HiggsTtsActor recomputes new logprobs) -> NCCL broadcast of LoRA-merged
body.* weights into the served tts_engine stage (server fuses q/k/v on load).
Verified live: 3-step run, WEIGHT_UPDATE_GROUP_READY, 72 params synced/step,
non-diverging. (Reward saturates on the easy smoke set -> adv 0; proves the loop +
weight-sync, not policy improvement, same caveat as GATE-A.)

Also: higgs_actor uses get_input_embeddings() for LoRA-wrap safety.
TEMP/MAX_NEW envs to induce reward variance for a non-saturating run. Verified live
at TEMP=1.4: mean_reward 0.908 (variance -> non-zero GRPO advantage), avg_loss
-0.0004 (real gradient), 72 changed body.* params synced/step to tts_engine,
non-diverging. Demonstrates the GATE-B loop actually trains, not just plumbing.
…en copy)

Captures the d6fdbf4f sglang-omni fix (record true codebook-0 logprob instead of the
text-vocab zeros placeholder). Supersedes the stale higgs_codec_logprob.patch, which
encodes the broken placeholder approach (logprob == ln(1/151936)). Canonical source is
the sglang-omni repo (Hayden727/sglang-omni hayden/higgs-rl-rollout).
It encodes the placeholder bug (codec logprob == ln(1/151936)); superseded by
higgs_codec_logprob_fix.patch and the d6fdbf4f fix in the sglang-omni repo.
…ning runs

Adds math_harder.jsonl (10 moderate problems, base acc ~50%) and tts_harder.jsonl
(8 pronunciation-hard sentences, base CER ~0.11) so reward is non-saturated, plus a
MAX_NEW env on gate_a_full.py. Verified live: GATE-A reward 0.50->~0.70 (24-step run
stable, plateaus ~0.68); GATE-B CER 0.108->~0.062 -- genuine GRPO learning signal with
on-policy weight-sync, vs the saturated smoke sets.
… names

Per code-style (no plan-marker terms like GATE-/Phase- in code), rename by actual role:
  examples/omni_gate_a -> examples/thinker_text_rl
    gate_a_lora_smoke.py -> lora_grpo_smoke.py
    gate_a_full.py       -> onpolicy_grpo_weight_sync.py
    gate_a_trainer.sh    -> fsdp_trainer_launch.sh
  examples/omni_gate_b -> examples/higgs_tts_rl
    gate_b_loop.py        -> rollout_reward_advantage.py
    gate_b_full.py        -> onpolicy_grpo_weight_sync.py
    gate_b_parity_probe.py -> logprob_parity_probe.py
Also scrub GATE-A/GATE-B from docstrings/prints/GROUP_NAME and fix internal path refs
(incl. miles_plugins/omni docstrings). Datasets and miles_plugins module names unchanged
(already function-named).
Applies the docstring/print/GROUP_NAME/path-reference cleanup that the rename commit
missed (a bad git-add pathspec had left these unstaged), and tracks the renamed
fsdp_trainer_launch.sh + math_harder_msgs.jsonl.
--rollout-external / --rollout-external-engine-addrs were defined but unused: RolloutManager
always launched internal sglang engines (assert num_gpus>0). Add an external branch that
skips internal server launch and points sglang_router_ip/port at the supplied external addr,
so a custom generate function can drive an external engine (e.g. sglang-omni for an omni
pipeline). Verified: RolloutManager init now passes in external mode and the FSDP actor loads
the model; FSDP2 sharding of the 30B-A3B omni thinker is a separate experimental-backend issue.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3186a5a169

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread miles/ray/rollout/rollout_manager.py
Comment thread miles_plugins/omni/omni_generate_fn.py Outdated
Comment thread miles_plugins/omni/rollout_contract.py

@JingwenGu0829 JingwenGu0829 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a rough look and had the following findings. Will take a more detailed look this weekend.

Comment thread examples/higgs_tts_rl/onpolicy_grpo_weight_sync.py Outdated
Comment thread examples/higgs_tts_rl/onpolicy_grpo_weight_sync.py Outdated
Comment thread examples/higgs_tts_rl/onpolicy_grpo_weight_sync.py Outdated
Comment thread examples/thinker_text_rl/onpolicy_grpo_weight_sync.py Outdated
@yxs

yxs commented Jul 22, 2026

Copy link
Copy Markdown
Owner

close for now, the follow up pr will be #4 ...

@yxs yxs closed this Jul 22, 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.

3 participants