Skip to content

[Perf][MiniCPM-o] MiniCPM-o 4.5 full-duplex serving optimization on H100 - #8428

Draft
BeatSeat wants to merge 103 commits into
vllm-project:mainfrom
BeatSeat:perf/minicpmo45-duplex-h100
Draft

BeatSeat wants to merge 103 commits into
vllm-project:mainfrom
BeatSeat:perf/minicpmo45-duplex-h100

Conversation

@BeatSeat

@BeatSeat BeatSeat commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR introduces end-to-end full-duplex serving optimizations for MiniCPM-o 4.5 on single-GPU NVIDIA H100 (and SM80+ architectures), achieving real-time duplex serving up to 20 concurrent sessions (d20).

Note: This PR builds upon and depends on #7929 (feat/port-opt-to-main: Talker multi-frame decode and NPU pipeline optimizations).

Key Optimizations

1. Stage 0 (Thinker)

  • Streaming Audio Encoder Batching & In-Place KV: Batched multi-session audio encoding with in-place cache updates.
  • CUDA Graph Capture with Resident Slot Pool: Caches reside in graph slot buffers; capture grid extended up to 128 tokens; cache buckets extended with 1200 and 1450 tokens to eliminate eager fallbacks.
  • Incremental Streaming Fbank: Optimized mel-spectrogram feature extraction (8.53 ms -> 1.60 ms on 29s buffer).
  • Fused Vision Layers & Graph (Opt-in): Packed SDPA and fused layer-norm operators for multi-modal duplex input.

2. Stage 1 (Talker)

  • CUDA Multi-Frame Decode (K-Step): Multi-frame autoregressive Talker decoding via model-level hook.
  • Frame Sampling Tail CUDA Graphs: Replays per-frame logits adjustments and sampling tail as private CUDA graphs interleaved between Talker forward steps, reducing step latency from 22.4 ms to 12-14 ms.

3. Stage 2 (Code2Wav)

  • Whole-Euler Slot Pool: Decouples memory ownership and caps attention cache memory (from 22 GiB down to 15 GiB), eliminating OOM at high concurrency (d20).
  • Fused DiT Body & Tiled TF32 Attention: Fused residual LayerNorm and QKV LayerNorm, reducing block execution time from 65.4 ms to 38.7 ms per audio second.
  • Exact-Shape CUDA Graphs: Flow encoder continuation chunks and HiFT vocoder captured as exact-shape graphs without GEMM-perturbing row padding.
  • Deadline-Driven Request Scheduling: Dynamic batch release policy (StepReleasePolicy) replacing fixed sleep windows.
  • Async Scheduling Fix: Prevents spurious chunk queuing latency in the generation runner.

4. Duplex Orchestration & Pacing

  • Decoupled Unit Continuation: Configurable unit continuation depth and trigger conditions.
  • Pacing & Adaptive Barge-in: Onset lead regulation (target 1.1s) and low-latency audio cutoff on user interruptions.
  • Raw Bytes Inter-Stage Transport: Eliminates Python serialization overhead between Thinker and Talker.
  • CUMULATIVE Output In-Place Growth: Amortized capacity growth for cumulative audio/text streams.

Scope & Non-Regression Guarantees

  • Zero modifications to unrelated models (Qwen3-Omni, AURA, PersonaPlex, CosyVoice).
  • Fused operators isolated under models/minicpmo_4_5/ops/.
  • All shared-framework extensions are opt-in and default to no-op.
  • Zero new mandatory environment variables; all runtime settings consolidated into minicpmo_4_5_h100.yaml.

Signed-off-by: BeatSeat wendavid552@gmail.com

The NPU override block only covered stages 0 and 2, so stage 1 kept the base
max_num_seqs of 4 and gated the top tier.

Measured on 910B3 (r16 against the r14 baseline): c8 RTF -15.1%, c4 -3.5%,
c1 -1.1%. Concurrency only; the numerical path is untouched.

Signed-off-by: y-null <y-null@users.noreply.github.com>
Once the 32 captured shapes are exhausted, later shapes permanently degrade to
eager. The CFM mel bucketing already in main (cfm_graph_bucket_frames: 16)
keeps the captured shape count at 19, which is what makes a pool this size
sustainable.

Measured on 910B3 (r34 against r22): c4 RTF -17.0%, c8 -23.5%, c1 +5.8%.

Signed-off-by: y-null <y-null@users.noreply.github.com>
The stage input processor learns a first-window size: the first payload uses
initial_codec_chunk_frames while the steady-state window is unchanged, and a
deployment that leaves the knob unset is bit-identical to the previous
behaviour. It only takes effect when initial < codec_chunk_frames.

Measured on 910B3 (r17 against r16): c1 RTF -7.4%; Seed-TTS WER 1.2179%,
unchanged from the baseline.

Signed-off-by: y-null <y-null@users.noreply.github.com>
Behind VLLM_OMNI_C2W_SINGLE_ROW (default off), the code2wav stage reads the
switch and _iter_decode_batches gains a single-row bypass that skips wave
splitting and the min-batch gate. The numerical path is unchanged (the even
path is pre-existing code).

Measured (A49/A49P against r34): c1 ~0, c4 -3~8%, c8 -4~7%.

The item is not kept: the gain sits inside the run-to-run drift band, so a
later commit in this branch removes the switch again.

Signed-off-by: y-null <y-null@users.noreply.github.com>
Stage-level enable_prefix_caching on stage 0; the top level stays false for
duplex safety. The other two parts of the same source item (the HiFT/CFM graph
modes and the stage-2 eager override) are already in main and are not carried.

Measured on the source line (same hardware class): c1 -22.0%, c4 -3.9%,
c8 +1.1%.

Signed-off-by: y-null <y-null@users.noreply.github.com>
Stage 0 gains async_scheduling: false plus a speculative_config (method ngram,
14 speculative tokens). async_scheduling has to be false: vLLM's VllmConfig
validation rejects the async-scheduling and n-gram-proposer combination.

Measured on the source line: RTF -11.8% / -3.7% / -5.9%, TTFT -69%.

It is dropped again by the next commit: vLLM-omni refuses prefix caching and
speculative decoding on the same stage, and prefix caching is the
configuration this port validates.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…hain

The Talker emits 8 codec frames per scheduler step instead of one. The stop
chain that stalled inside the K loop is fixed end to end:

* the outer model class forwards the four multi-frame entry points again
  (supports_multi_frame_decode, take/set_batch_stop_logits, merge_frame_outputs
  -- without them the multi-frame loop never engaged, and silently so);
* the stop head stays the two-wide continue/stop row, and its vocabulary width
  is repaired for the rejection sampler;
* the native rejection sampler is restored for the armed path;
* the scheduler gains a K-frame guard.

Without the stop fix a request ran to max_tokens (~143 s instead of ~2 s per
request); with it the multi-frame decode is a net win (c1 RTF -19.1%,
c8 -10.6% on the source line).

Verified on 910B3: warm 3-tier 0.4434 / 1.0867 / 2.1644 with 0 ERROR, the
multi-frame loop engaged on the first request, audio non-empty.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…hing; tidy comments

- The deploy config enabled stage-0 prefix caching and n-gram speculative
  decoding together, which vLLM rejects at startup
  (OmniPrefixCacheUnmatchError). Keep prefix caching (the validated
  configuration) and remove the n-gram block.
- Replace internal notes (item numbers, measurement references, Chinese
  prose) with concise upstream-style comments.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…ti-frame feedback chain

Pins the frame-code feedback (request-state last_code), the decode
preprocess priority, the scheduler K-frame guard and the multi-frame
gate matrix. Pure CPU; no device required.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…ame decode is off

The multi-frame work moved codec sampling into a device-side state machine and,
in doing so, dropped the host-state contract the default single-frame path
relies on:

- make_omni_output no longer recorded state step/recent_codes/finished or the
  force/mask codec-EOS routing, so empty-speech segments, chunk budgets and the
  duplex turn-end drain window lost their effect;
- compute_logits no longer forced or masked EOS, and sample() no longer
  restored an EOS blanked by vLLM's min_tokens processor;
- the offline budget recorded min_tokens as 0 instead of None.

Split make_omni_output into a single-frame path (multi-frame off, the default)
that keeps the upstream contract, plus the multi-frame path, and restore the
codec-EOS routing in compute_logits/sample. The 12 upstream tests in
test_talker_batching.py pass again; test_pipeline expectations follow the new
stage-1 concurrency cap and Code2Wav graph pool.

Signed-off-by: y-null <y-null@users.noreply.github.com>
- ruff check: 21 findings (15 auto-fixed; manual: N814 import alias,
  F841 unused variable, E501 long line, 4x UP031 percent formatting)
- ruff format: 12 files reformatted

Signed-off-by: y-null <y-null@users.noreply.github.com>
codec_chunk_frames 25 -> 75 so initial_codec_chunk_frames: 25 takes effect; stage-1 stop_token_ids: [1] for the multi-frame stop head (nothing injects it for stage 1); stage0/stage2 max_num_seqs 8 so the top tier is not gated by a single stage; NPU cudagraph_mode PIECEWISE -> FULL_DECODE_ONLY, which keeps in-graph sampling and multi-frame expansion.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…the deploy YAML

Make stage 1's speculative_config in the deploy YAML the single source of truth for the multi-frame decode and remove every code-side default and switch around it.

* the deploy YAML carries stage 1's speculative_config (8 codec frames per step) and async_scheduling: false explicitly; no code-side injection or default remains.
* the environment switches VLLM_OMNI_MINICPMO_TALKER_FRAMES, OMNI_K_STEP and VLLM_OMNI_C2W_SINGLE_ROW are gone, and the 910B SoC gate is dropped: the rejection-verifier limit it guarded against was the missing multi-frame wiring this branch fixes, and 910B3 is validated end to end.
* the model-side and scheduler-side gates derive K from the engine config (model_stage == tts plus the n-gram fingerprint), so the two cannot disagree.
* the stage-2 single-row even-batch item is reverted: its measured gain sat inside the run-to-run drift band and a default-off switch adds configuration without value.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…the deploy YAML

Make stage 1's speculative_config in the deploy YAML the single source of truth for the multi-frame decode, and keep this port to the optimizations plus their tests only.

* the deploy YAML carries stage 1's speculative_config (8 codec frames per step) and async_scheduling: false explicitly; no code-side default remains.
* the environment switches VLLM_OMNI_MINICPMO_TALKER_FRAMES, OMNI_K_STEP, VLLM_OMNI_C2W_SINGLE_ROW and VLLM_OMNI_MINICPMO_NARROW_REPLAY are gone, and the 910B SoC gate is dropped: the rejection-verifier limit it guarded against was the missing multi-frame wiring this branch fixes, and 910B3 is validated end to end.
* the model-side and scheduler-side gates derive K from the engine config (model_stage == tts plus the n-gram fingerprint), so the two cannot disagree.
* the per-step/per-frame timers, the stop and bookkeeping traces, the guard view and utils/step_prof.py are removed: they were bring-up instrumentation, not optimizations.
* the stage-2 single-row even-batch item is reverted: its measured gain sat inside the run-to-run drift band and a default-off switch adds configuration without value.
* tests follow the new deploy values (chunk 75, FULL_DECODE_ONLY, the multi-frame stop id, all-stage concurrency 8).

Signed-off-by: y-null <y-null@users.noreply.github.com>
`cfm_graph_bucket_frames` (deploy YAML: 16) only applied when the CUDA-only
`CFMGraphWrapper` existed, so on NPU the bucket width stayed 0: the CFM capture
key space never collapsed (64 shapes) and, once the graph pool filled, later
shapes permanently degraded to eager.

- batched_token2wav.py: track "graphs requested" separately
  (`_cfm_graph_enabled`, from the deploy YAML's `enable_cfm_graph`) and key
  bucketing/padding off "CUDA wrapper present OR graphs requested". The CUDA path
  is unchanged: the wrapper is built whenever the config is enabled.
- platforms/npu/models/minicpmo_4_5_code2wav.py: stop routing calls that carry an
  attention mask to eager; only the ragged path (`valid_lengths`) stays eager. A
  bucketing pad mask has a shape fixed by the bucket, so it travels as a graph
  input (its presence is part of the capture key) like any other tensor.
  `attn_mask` is forwarded to the graphable body only when present, so graphable
  bodies written before this feature keep their old signature.

Motivation: this is the memory prerequisite for the single-device two-replica
profile; without it the pool fills and the second replica does not fit. Bucketing
itself measures neutral for RTF -- it is a shape/memory lever.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…the deploy YAML

Make stage 1's speculative_config in the deploy YAML the single source of truth for the multi-frame decode, and keep this port to the optimizations plus their tests only.

* the deploy YAML carries stage 1's speculative_config (8 codec frames per step) and async_scheduling: false explicitly; no code-side default remains.
* the environment switches VLLM_OMNI_MINICPMO_TALKER_FRAMES, OMNI_K_STEP, VLLM_OMNI_C2W_SINGLE_ROW and VLLM_OMNI_MINICPMO_NARROW_REPLAY are gone, and the 910B SoC gate is dropped.
* the model-side and scheduler-side gates derive K from the engine config (model_stage == tts plus the n-gram fingerprint), so the two cannot disagree.
* the per-step/per-frame timers, the stop and bookkeeping traces, the guard view and utils/step_prof.py are removed: they were bring-up instrumentation, not optimizations.
* the stage-2 single-row even-batch item is reverted, along with the four orphan comment lines it left in code2wav.
* the 910C/A3 Triton warmup guard (ascend_warmup_patch.py and its install sites) is dropped: it is a platform adaptation, not an optimization, and the 910B baseline ran the stock warmup either way. Its only remaining consumer, native_rejection_sampler, now carries the SoC probe and K-step check inline.
* tests follow the new deploy values.

Signed-off-by: y-null <y-null@users.noreply.github.com>
The bucketing change lets a pad mask travel as a graph input; add the regression
test that pins the contract: a call carrying an attention mask still dispatches
to the platform graph runner (only the ragged per-request-length path stays
eager), the mask is one of the captured inputs, and its presence is part of the
capture key (constants == (with_cache, True)). Also drop one stray blank line so
the NPU code2wav file stays byte-identical with the validated branch.

Signed-off-by: y-null <y-null@users.noreply.github.com>
Removing ascend_warmup_patch.py makes the service fail on 910C/A3 (confirmed by testing): it is a required platform adaptation, not dead weight. Restore the module and its three install sites, and point native_rejection_sampler back at its helpers instead of the inlined copies.

This reverts the platform part of 58e513a.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…rofile

The validated set measures stage-0 n-gram speculative decoding as part of the
optimization set, but the profile generated from the base config inherited the
base's deliberate omission of it (upstream fix fff0b0f, on the grounds that
vLLM rejects prefix caching together with speculative decoding).

That ground does not hold here: vLLM 0.29.0 in this container has no such
mutual-exclusion check, and the validated branch ran exactly this pair -- prefix
caching on with `async_scheduling: false` -- in both arms of today's 14-vs-15 A/B
(two full three-tier runs). So the profile enables it with the base's 15 tokens
(a 14-vs-15 A/B on 910B3 landed inside the run-to-run drift band: c1 -4.5%,
c4 -7.5%, c8 +9.3% -> equivalent), while the base config keeps it off.

With this, the profile differs from the base config only by the single-device
dual-replica settings plus this speculation block.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…t is the tail-draft gate

An earlier version of this comment said 14 and 15 were equivalent because the RTF
difference sat near the run-to-run drift band. That reading was incomplete: the
stage-0 tail-draft path is gated on `num_speculative_tokens == 15`
(stage0_tail_draft.applies()), so k=14 switches it off silently. The 14-vs-15 A/B
measured 22-37% worse TTFT in the k=14 arm, matching the tail-draft item's own
account (TTFT -10%, RTF unchanged); RTF was inconsistent (-4.5% / -7.5% / +9.3%).
The width itself is still untested in isolation.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…ofile

The omni prefix cache refuses the combination outright: stage_prefix_cache_config()
-> check_prefix_cache_token_accounting() raises OmniPrefixCacheUnmatchError
('omni prefix caching is not supported together with speculative decoding') as soon
as a stage has enable_prefix_caching and a speculative_config, and stage 0 of this
profile has the former. The runner call sites (NPU initialize_kv_cache, GPU
initialize_metadata_builders) are not wrapped in try/except, so the engine would
fail at kv-cache init.

The base config keeps stage 0 free of speculation for the same reason. Reverts
ad6ff944f (which added the block) and d7de6d4ee (its comment).

Signed-off-by: y-null <y-null@users.noreply.github.com>
…h speculation

The comment blamed vLLM. It is not vLLM: prefix caching and speculative decoding are
allowed together by vLLM itself (no such validation in the installed 0.29.0, and
upstream main even has disable_eagle_block_drop for the interaction). The refusal is
ours: stage_prefix_cache_config() -> check_prefix_cache_token_accounting() raises
OmniPrefixCacheUnmatchError whenever a stage has enable_prefix_caching and any
speculative_config -- regardless of async_scheduling and of the speculative method.

Comment-only change; the configs are unchanged.

Signed-off-by: y-null <y-null@users.noreply.github.com>
… needs

The deploy-config port dropped vllm_omni/platforms/npu/attention/ and its
install sites, and with them the only decode-capture path the 910B family
has. With the K-step configured, base and prof both fail in capture_model()
with a vector-core exception (acl 507035) on 910B3, so the service never
comes up; the same config on the pre-port tree reaches "Stage 1 engine
startup completed", so the gap is the module, not the part.

Restore the module and its three hooks:
* platforms/npu/attention/{__init__,fixed_kv_backend,fixed_kv_decode,fixed_kv_prefill}.py
  -- whole. fixed_kv_prefill is imported by fixed_kv_backend, and narrow
  replay reads captured_seq_lens/captured_slot_mapping, so nothing in it is
  dead weight: it is the fixed-KV item's own delivery.
* NPUOmniPlatform.get_attn_backend_cls: route short-context decoders onto
  OmniFixedKVAttentionBackend.
* NPUARModelRunner: the enable_hamming_sparse default, install_into_ascend_backend()
  before parent init, and the per-capacity _capture_cudagraphs override.

Signed-off-by: y-null <y-null@users.noreply.github.com>
With the K-step armed, the stock rejection_sampler_triton_warmup faults the
910B vector core (acl 507035) during its dummy-token setup, which is why the
guard in ascend_warmup_patch exists. The pre-port tree could tell from the
environment; this tree asks the engine config instead, and the stage worker
does not have it yet at warmup time -- the engine hands the runner its
vllm_config only after load_model, and a deploy-layer record does not survive
the spawn that starts these processes. The warmup therefore ran and faulted
inside capture_model(), so stage 1 never came up.

* _skipped_names(): on Ascend910B return {"rejection_sampler"} without asking
  _kstep_armed(). The torch-native sampler serves the armed path, and the
  stock path only pays a first-request JIT of that kernel.
* _kstep_armed(): keep the engine-config answer first, fall back to the deploy
  layer, so callers that do have the runtime config keep their precise answer.
* stage_config: record stage 1's declared frame count when a deploy YAML is
  parsed and expose talker_frames_per_step() for those out-of-band readers.

Verified on 910B3: the prof deploy config now reaches "Application startup
complete" with stage 2's two replicas, and stage 1 logs the per-capacity
fixed-KV captures.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…kers

Stage workers are spawned, so they do not inherit the recorded value; the comment claimed a fork. The warmup guard on that side decides without it (see ascend_warmup_patch._skipped_names).

Signed-off-by: y-null <y-null@users.noreply.github.com>
Same facts, shorter: the deploy-layer fallback, the 910B warmup skip and the fixed-KV route each lose a paragraph of restatement.

Signed-off-by: y-null <y-null@users.noreply.github.com>
pre-commit failed on ruff-format (7 files) and ruff-check (6 findings). Run ruff format and ruff check --fix over vllm_omni/, and drop the unused prev_code assignment in minicpmo_4_5_omni_tts.py that F841 flagged.

Signed-off-by: y-null <y-null@users.noreply.github.com>
Review pass over this branch's own additions.

Comments and docstrings still described removed environment switches,
internal item numbers, measurement rounds and out-of-tree design notes.
They now describe what the code does, in the wording the rest of the
repo uses.

Dead code removed:

* the unused ``MINICPMO_FAST_CODEC_PENALTY`` read (nothing consumed it);
* ``fixed_kv_prefill.is_enabled()`` / ``STAGE0_BUCKETS`` / the
  ``VLLM_OMNI_FIXED_KV_PREFILL`` gate, which nothing called;
* the scheduler's per-request ``states`` list, built and never read;
* the unused ``_CODEC_EOS_TOKEN_ID`` left behind by the stop-chain fix;
* the frame-count clamp whose only reader compares against 1.

Behavior changes are limited to the two that make a gate match its
reader: the vocab gate now requires the same arming predicate as the
multi-frame path itself (``num_spec_tokens > 0``) rather than the
wrapper class alone, and ``get_attn_backend_cls`` logs which of the two
fixed-KV outcomes it took. The environment variables this branch
introduces are registered in the inventory and the configuration docs.

Signed-off-by: y-null <y-null@users.noreply.github.com>
``VLLM_OMNI_FIXED_KV_DECODE_MAX_LEN`` decided the same thing the deploy
YAML already decides: the fixed-KV path engages when a stage's
``max_model_len`` is small enough to have fixed-capacity graphs. A second
knob for that rule can only be set to a value the captured shapes do not
support, so the ceiling is a code constant now and the stage's own
``max_model_len`` is what selects the outcome (Talker 4096 engages, a
32768-token Thinker keeps the stock path).

No behavior change at the default: the constant is the old default 8192.
The inventory, the configuration reference and the snapshot counts are
updated with it.

Signed-off-by: y-null <y-null@users.noreply.github.com>
``fixed_kv_*`` named one decode-side op argument, and the prefill module
does not fix a KV length at all -- it buckets the prefill by token count.
What all three do is capture a step whose shapes are static so that a
replay needs nothing from the host, so they are named for that:

  fixed_kv_decode.py  -> static_shape_decode.py
  fixed_kv_prefill.py -> static_shape_prefill.py
  fixed_kv_backend.py -> static_shape_backend.py

The symbols follow the files: OmniStaticShapeAttentionBackend and its
impl, OmniStaticShapeMetadataBuilder, the ``_static_shape_*`` helpers and
the ``static_shape_*`` attention-metadata attributes. The reference to
vllm-ascend's own ``vllm_ascend.attention.fixed_kv_decode`` module stays
as it is.

No behavior change.

Signed-off-by: y-null <y-null@users.noreply.github.com>
y-null and others added 25 commits September 29, 2026 21:38
FULL_DECODE_ONLY cannot run on the NPU stage-0 engine under
vLLM 0.30.0, so the single-card deploy now pins stage 0 to
PIECEWISE. The Talker (stage 1) keeps its full decode graph on
the single-card deploy (in-graph sampling + multi-frame K
expansion); multi-card variants stay PIECEWISE. Align the
stage-scoped topology test with the per-stage configuration.

Signed-off-by: y_null <y_null@qq.com>
- P2-1: reject logit_bias/allowed_token_ids at admission when the K-step
  codec stream is armed (OmniSchedulerMixin.add_request -> finish_requests
  FINISHED_ERROR); the in-model filter has no reader for them, and the
  talker_codec_sample module docstring no longer claims support
- P2-2: gate the empty-row draft rollback on _talker_kstep_armed()
  (omni_ar_scheduler.py) -- rollback semantics verified for the Talker
  K-step loop only
- P2-3: pack sampled_id/finished/emit into one D2H per frame in the
  minicpmo_4_5_omni_tts.py K loop (3 device syncs per frame -> 1)
- P3-5: native_rejection_sampler._RESTORED latches only on a confirmed
  non-target SoC; an empty probe name retries instead of locking out the
  restore for the whole process lifetime
- P3-6: single transition source _advance_classified shared by
  advance_codec_device_state and codec_sample_result (emit/finished
  computed once per frame); rep_counts histogram preallocated in the
  device state and zeroed in place per frame
- claims-2: minicpmo_4_5.yaml stage0 comment attributes the K-step
  FULL-only RTF win to stage 1

Validation: py_compile all touched files; pytest tests/core/sched +
tests/model_executor/models/minicpmo_4_5/test_talker_kstep_feedback.py
= 255 passed (3 logprobs tests fixed by adding _talker_kstep_armed to
the SimpleNamespace stub fixture)

Signed-off-by: y-null <y-null@users.noreply.github.com>
…_config overlay

The profiling profile was a 193-line copy of minicpmo_4_5.yaml and had
already drifted from it (prefix caching, code2wav_max_npu_graphs,
captured-shape counts). It also armed enable_prefix_caching on the NPU
stage-0 engine of a duplex deploy, which validate_duplex_window_install
rejects at startup.

Replace it with a thin minicpmo_4_5_stage2_replicas.yaml overlay
(base_config: minicpmo_4_5.yaml) that spells out only the two-replica
topology and its sizing, inheriting the duplex contracts (including
enable_prefix_caching=false) from the base config. The rename follows
the *_stage1_replicas naming and the file is marked as a non-production
profiling entrypoint.

Signed-off-by: y_null <y_null@qq.com>
CI's pre-commit run fails because ruff-format would reformat the kstep
unsupported-params comprehension in omni_scheduler_mixin and reformat
talker_multiframe and test_talker_kstep_feedback. Apply the formatter
output verbatim; no functional changes.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…ts.py

Keep both execution paths:
- K-step (HEAD): resolve_codec_sampling_params family, K-step request
  state, gpu_resident_buffer_keys, _merge_request_codec_params in the
  K-step make_omni_output loop.
- MRv2 (upstream): _CodecWindowPenaltiesState, _install_mrv2_talker_sampler,
  MiniCPMO45TalkerSampler, _force_eos_on_sampled_ids (index_to_device
  variant kept, duplicate torch.tensor variant dropped),
  preprocess_decode_batch / make_omni_output_mrv2 / _HostCodecIds.
Graft: pending codec-id consumption (deferred .item() EOS check from
preprocess_decode_batch) into _make_omni_output_single_frame, with
empty_speech computed after the pending pop so a pending-EOS row still
forces EOS. _flush_deferred_cleanup now clears both sides' state dicts.

Signed-off-by: y-null <y-null@users.noreply.github.com>
pre-commit (ruff format) flagged two double blank lines introduced by
the merge conflict resolution; collapse both to single blank lines.

Signed-off-by: y-null <y-null@users.noreply.github.com>
CachedRequestState in vLLM 0.30.0 has no num_output_tokens attribute
(only num_tokens as prompt+output); this made every decode step fall
back to uniform budget splitting. Use len(req.output_token_ids), the
equivalent generated-token count, instead.

Signed-off-by: y-null <y-null@users.noreply.github.com>
…o main

Vendor the open vllm-project#7929 head so the MiniCPM-o duplex work that builds on its
Talker K-step plumbing can be reviewed against the code it depends on.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
Outputs: grow CUMULATIVE dim-0 streams of matrices in place

A CUMULATIVE stream re-consolidates the stream from the start on every
step. Reusing capacity behind the previous view avoids O(T^2) recopying
over long duplex epochs while returning value-identical views.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
…ge idle wait

Framework hooks for pacing, unit continuation, and stage idle wait

Add minimal opt-in hooks for duplex pacing, decoupled unit continuation,
event-driven orchestrator loop, and stage idle waiting. Default behaviors
for existing non-duplex and turn-based pipelines are preserved.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
Worker multi-frame decode hook for Talker K-step

Expose a model-level multi_frame_decode_hook on GPU runners and waiting-safe
dispatch in the AR scheduler. Default single-frame execution paths are unchanged.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
Talker K-step multi-frame decode on CUDA

Implement CUDA Talker multi-frame decoding using the vllm-project#7929 hook interface,
with native sampling controls, repetition penalty cache, and pinned copies.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
…A graphs

Replay Talker K-step per-frame sampling tail as CUDA graphs

Capture the per-frame logits adjustment and sampling tail into private CUDA
graphs interleaved between Talker forward replays, eliminating per-frame host launch overhead.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
…KV, and CUDA graphs

Stage 0 streaming audio encoder batching, in-place KV, and CUDA graphs

Batch streaming audio encoding across sessions, maintain KV caches in place,
capture audio encoder CUDA graphs with resident cache slots, and add incremental fbank.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
Stage 0 fused vision encoder and CUDA graphs

Add packed SDPA vision encoding, cross-session frame batching, fused layer norm ops,
and vision CUDA graph capture for multi-modal duplex serving.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
Code2Wav Whole-Euler slot pool and fused DiT body

Implement Whole-Euler slot pool memory management for high-concurrency duplex Code2Wav,
fused DiT block execution with tiled TF32 attention, and single-projection speaker embeddings.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
…HiFT, and deadline scheduling

Code2Wav exact-shape CUDA graphs for flow encoder, HiFT, and deadline scheduling

Add exact-shape CUDA graphs for the flow encoder continuation chunks and HiFT vocoder,
along with deadline-driven request scheduling to maximize concurrency and minimize latency.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
…trim

HiFT zero-width mel safeguard and Stage 2 memory trim

Return empty HiFT outputs for zero-width mels to prevent conv padding failure,
and drop unused upstream flow attention buffers in Step-Audio2 token2wav.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
Consolidated single recommended deployment profile

Consolidate high-concurrency full duplex tuning into minicpmo_4_5_h100.yaml
and update deploy config test expectations.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
…aphs into minicpmo_4_5_h100.yaml

Consolidate Stage 0 multi-modal vision and streaming audio CUDA graphs,
resident slots, incremental fbank, duplex pacing, and codec deadline
scheduling into minicpmo_4_5_h100.yaml.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
Signed-off-by: BeatSeat <wendavid552@gmail.com>
Signed-off-by: BeatSeat <wendavid552@gmail.com>
@hsliuustc0106 hsliuustc0106 added omni code related to omni models enhancement New feature or request core related to core module: cache, scheduler, engine, worker, modelrunner labels Oct 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core related to core module: cache, scheduler, engine, worker, modelrunner enhancement New feature or request omni code related to omni models

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants