[RL] Qwen3-Omni thinker: audio-input GRPO - #1
Open
yxs wants to merge 29 commits into
Open
Conversation
yxs
force-pushed
the
yxs/miles-omni-thinker-grpo
branch
4 times, most recently
from
June 17, 2026 10:38
4629316 to
6c60ccd
Compare
8 tasks
Add SGLang Omni multimodal input adapter
…>miles loop) First omni<->miles RL loop on the Qwen3-Omni-30B-A3B thinker (text MoE).
…eight naming - omni_thinker.py folded into generate_hub/sglang_omni.py: output_modalities/return_omni_rollout, pinned repetition_penalty=1.0, metadata passthrough, replay guard; temp-1 assert dropped (server sampler now returns post-temperature logprobs, matching trainer recompute) - megatron_to_hf/qwen3omni_moe.py: body.* -> thinker.* (body.* is the Higgs namespace; sglang-omni thinker load_weights strips thinker. and ignores tower/talker names) - extract tool: stream shards from flush() (peak RAM = one shard), fail loud on zero tensors - tests: converter/extract moved under tests/fast/backends/megatron_utils with explicit register_cpu_ci; sglang_omni variant registered in generation_fixtures, harness basic+resume
- miles_plugins/models/qwen3_omni_thinker.py: patch GPTModel.forward to fill input_ids==audio_token_id rows from the omni checkpoint's frozen audio tower (decoder_input path); audio-only prompts keep sequential position ids, so RoPE is untouched; image/video keys rejected loudly; tower loaded lazily from the omni safetensors (audio_tower.* only) and kept out of DDP/optimizer/checkpoints - model_provider: install hook behind --qwen3-omni-audio-encoder-path (classic + bridge) - training_utils/data.get_batch: right-pad variable-length audio tensors (input_features/feature_attention_mask) to the micro-batch max before dim-0 concat
- examples/omni_thinker: prepare_avqa.py converts Joysw909/AVQA (r1aqa jsonl) into the miles <audio>-placeholder format, letter label + choices metadata scored by --rm-type gpqa; run script reworked to the external-server topology (--rollout-external + addr, server doubles as router), audio-tower injection flag, no SP, and --sync-mode skip|distributed - sglang_engine: --rollout-external-admin-api sglang-omni no-ops /flush_cache and /begin|end_weight_update (routes the omni server does not expose; it quiesces+flushes internally) and delegates the post-update flush to the update request; --rollout-weight-update-stages scopes the three weight-group requests so a multi-stage server does not fan group init out to every stage and break NCCL world-size accounting
…/server_tp accounting - _init_external: sglang-omni exposes no /get_server_info; health check is the only preflight - example: decouple actor GPUs (TP=EP=actor_tp) from ray total (actor_tp + omni_server_tp external attach bundles); disagg placement, colocate stays off (external servers have no sleep/offload routes)
…68, inert for all-MoE)
…e.jinja The trainer loads AutoProcessor/AutoTokenizer from the extracted dir: preprocessor_config et al. make the omni processor resolvable there, and AutoTokenizer only auto-loads the .jinja template variant (chat_template.json is processor-level), so unpack it.
…equired upstream)
yxs
force-pushed
the
yxs/miles-omni-thinker-grpo
branch
from
July 23, 2026 19:24
35735df to
3d2e9e6
Compare
The default preflight polls /health_generate then /flush_cache; the omni server exposes neither route, so the 404s retried forever and the external attach hung before rollout.
RolloutSamplingParams is extra=forbid; miles' detok-only keys (skip_special_tokens, no_stop_trim, spaces_between_special_tokens) 422'd every request. Those flags never reach the token/logprob streams, so drop them; unknown keys still fail loud.
…tinue Both abort paths listed workers via the router /workers API and POSTed /abort_request; a bare omni server has neither route, so the cleanup after a fully successful rollout 404-crashed the job. pause_generation(mode=abort) + continue_generation is the omni-native abort_all.
yxs
force-pushed
the
yxs/miles-omni-thinker-grpo
branch
from
July 25, 2026 19:40
2d1f16c to
2a20bf7
Compare
…ter) mcore hard-requires SP for MoE + TP>1 in training, so the no-SP constraint made every 2-GPU layout either invalid (TP2 no-SP) or OOM (TP1: full expert grad buffers). The embedding output under SP is the rank's contiguous chunk; the scatter now slices the placeholder mask and the encoder rows to that window. Debug tier drops the zero-coef KL ref copy and trims the token budget.
- extract tool: drop the --variant vl path (belongs to the video/image PR #6); this branch keeps only the text-variant + processor artifacts - remove test_extract_thinker_vl.py (lives on #6) - sglang_omni.py: move the sampling-key frozensets below the import block - audio injection: note the TP-redundant tower forward tradeoff under SP
yxs
force-pushed
the
main
branch
2 times, most recently
from
August 1, 2026 16:13
bb21444 to
6c2fe27
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
End-to-end audio-input GRPO for Qwen3-Omni-30B-A3B (thinker): prompts carry audio (AVQA multiple-choice QA), rollouts run on an externally launched sglang-omni server, and the trainer holds only the extracted thinker text backbone, injecting frozen-audio-tower embeddings at audio placeholder positions.
upstream miles is not taking modality-specific RL for now (radixark#1700).
Design
Rollout. Generation runs on an externally launched sglang-omni server. The adapter sends processor-expanded
input_idsplus base64-serialized audio tensors and speaks the omni server's API.Training. The Megatron-side model is just the thinker text backbone (Qwen3-MoE). A frozen copy of the omni audio encoder turns each audio clip into embedding vectors, which replace the
<|AUDIO|>placeholder tokens in the input embeddings. Sequence-parallel aware: each TP rank scatters only its own sequence chunk. The encoder stays out of the optimizer, DDP, and checkpoints.Weight sync. After each optimizer step, Megatron weights are converted to HF
thinker.*names and broadcast to the server over NCCL.Example & data.
examples/omni_thinker/run_qwen3_omni_thinker_grpo.pyis the full driver (download → extract → convert → train).Validation
off-policy
--sync-mode skip: the server keeps its initial weights; TIS reweights samples to absorb the train/rollout gap. Debug tier (256-sample subset, 6 rounds): the pipeline runs end to end, rewards grade correctly, and the two implementations agree on logprobs,mis_rs_catastrophic = 0,tis_truncate ≤ 0.78%.on-policy
--sync-mode distributed: thinker weights are broadcast to the server after every optimizer step.Full AVQA epoch: 5120 samples, 160 GRPO steps, 161/161 weight refits succeeded (~5–11 s each for the 30B thinker), ~69 s/step, zero NCCL errors.
Reward 0.807 → 0.867 (20-step means):
reward curve
train_rollout_logprob_abs_diff0.002–0.02.weight_version = 161): the same 256 questions score 89.8% with audio vs 50.4% with audio stripped.note: On-policy requires the server co-located with the trainer (same container) and launched with
NCCL_CUMEM_ENABLE=0(miles pins the trainer to 0; the sglang-omni server entry does not set it, and the transport mismatch fails the first broadcast). Split-container deployments fail in the NCCL data plane. The audio tower is frozen, only the thinker text backbone trains.