Skip to content

[RL] Qwen3-Omni thinker: audio-input GRPO - #1

Open
yxs wants to merge 29 commits into
mainfrom
yxs/miles-omni-thinker-grpo
Open

[RL] Qwen3-Omni thinker: audio-input GRPO#1
yxs wants to merge 29 commits into
mainfrom
yxs/miles-omni-thinker-grpo

Conversation

@yxs

@yxs yxs commented Jun 14, 2026

Copy link
Copy Markdown
Owner

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_ids plus 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.py is 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

pr1-avqa-epoch-reward
  • Train/infer consistency stays healthy the whole run: train_rollout_logprob_abs_diff 0.002–0.02.
  • Audio ablation on the post-epoch server (greedy, 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.

@yxs
yxs force-pushed the yxs/miles-omni-thinker-grpo branch 4 times, most recently from 4629316 to 6c60ccd Compare June 17, 2026 10:38
@yxs yxs changed the title [omni] Qwen3-Omni thinker RL bridge + thin rollout glue [omni] Qwen3-Omni thinker: HF bridge + /generate rollout adapter Jun 17, 2026
@yxs
yxs requested review from Hayden727 and JingwenGu0829 June 17, 2026 15:11
@yxs yxs changed the title [omni] Qwen3-Omni thinker: HF bridge + /generate rollout adapter [Omni] Qwen3-Omni thinker: HF bridge + /generate rollout adapter Jul 22, 2026
yxs and others added 17 commits July 23, 2026 12:38
…>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)
…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.
@yxs
yxs force-pushed the yxs/miles-omni-thinker-grpo branch from 35735df to 3d2e9e6 Compare July 23, 2026 19:24
@yxs yxs changed the title [Omni] Qwen3-Omni thinker: HF bridge + /generate rollout adapter [Omni] Qwen3-Omni thinker: audio-input GRPO (frozen-tower injection + external omni server) Jul 23, 2026
yxs added 4 commits July 26, 2026 03:09
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
yxs force-pushed the yxs/miles-omni-thinker-grpo branch from 2d1f16c to 2a20bf7 Compare July 25, 2026 19:40
@yxs yxs mentioned this pull request Jul 25, 2026
6 tasks
yxs added 2 commits July 26, 2026 10:47
…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.
@yxs yxs changed the title [Omni] Qwen3-Omni thinker: audio-input GRPO (frozen-tower injection + external omni server) [RL] Qwen3-Omni thinker: audio-input GRPO Jul 27, 2026
- 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 added a commit that referenced this pull request Jul 27, 2026
…from #1)

The pseudo-Qwen3-VL extraction path (map_thinker_param_name_vl, synthesize_vl_config,
fuse_layer_experts_vl, _VlExpertAccumulator) belongs to this PR, not the audio PR #1;
carry it here explicitly so #1 stays the clean audio milestone and #6 owns the VL delta.
@yxs
yxs force-pushed the main branch 2 times, most recently from bb21444 to 6c2fe27 Compare August 1, 2026 16:13
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.

2 participants