diff --git a/docs/design/minicpm_o45_mrv2_performance.md b/docs/design/minicpm_o45_mrv2_performance.md index e543f7b1b..11f36bc34 100644 --- a/docs/design/minicpm_o45_mrv2_performance.md +++ b/docs/design/minicpm_o45_mrv2_performance.md @@ -23,3 +23,14 @@ performance or interruption correctness. The shared asynchronous output snapshot and batched Talker preprocessing also apply to V1 when async chunking/scheduling is enabled. Default V1 behavior must therefore be included in end-to-end regression validation. + +CFM graph execution defaults to the fused DiT body on CUDA when TF32 is +allowed. NVIDIA SM80+ uses tiled TF32 attention; older CUDA devices use the +fused body's SDPA fallback. Unsupported DiT layouts or non-FP32 attention +caches retain the original body. Other platforms retain their existing default. +Set `cfm_fused_body: false` in connector `extra` to restore the original body. +Disabling TF32 with `token2wav_allow_tf32: false` or +`MINICPMO_CODE2WAV_TF32=off` also disables the default fusion choice; an explicit +`cfm_fused_body: true` overrides that choice and may use TF32 attention. +Slot pooling and row-offset merging remain opt-in. Fusion changes floating-point +rounding and does not promise identical waveforms. diff --git a/tests/model_executor/models/minicpmo_4_5/test_code2wav_batching.py b/tests/model_executor/models/minicpmo_4_5/test_code2wav_batching.py index 02f5d3c80..dc051655d 100644 --- a/tests/model_executor/models/minicpmo_4_5/test_code2wav_batching.py +++ b/tests/model_executor/models/minicpmo_4_5/test_code2wav_batching.py @@ -172,6 +172,36 @@ def _config(minimum: int = 1, initial: int = 0, *, runtime_prompt_cache_size: in ) +@pytest.mark.parametrize( + ("cuda", "extra", "env", "expected"), + [ + (True, {}, None, True), + (False, {}, None, False), + (True, {"cfm_fused_body": False}, None, False), + (True, {"token2wav_allow_tf32": False}, None, False), + (True, {"code2wav_allow_tf32": False}, None, False), + (True, {}, "off", False), + (True, {"token2wav_allow_tf32": False}, "tf32", True), + (True, {"cfm_fused_body": True, "token2wav_allow_tf32": False}, None, True), + ], +) +def test_cfm_fused_body_default_respects_platform_and_precision(monkeypatch, cuda, extra, env, expected): + from vllm_omni.platforms import current_omni_platform + + monkeypatch.setattr(current_omni_platform, "is_cuda", lambda: cuda) + monkeypatch.delenv("MINICPMO_CODE2WAV_TF32", raising=False) + if env is not None: + monkeypatch.setenv("MINICPMO_CODE2WAV_TF32", env) + config = _config() + config.model_config.stage_connector_config["extra"].update(extra) + + model = MiniCPMO45Code2Wav(vllm_config=config) + + assert model._cfm_graph_config["fused_body"] is expected + assert model._cfm_graph_config["slot_pool"] is False + assert model._cfm_graph_config["row_offset_merge"] is False + + @pytest.mark.parametrize( ("extra", "max_num_seqs", "micro"), [ diff --git a/vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_code2wav.py b/vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_code2wav.py index f5e374446..25a69a1de 100644 --- a/vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_code2wav.py +++ b/vllm_omni/model_executor/models/minicpmo_4_5/minicpmo_4_5_code2wav.py @@ -288,6 +288,8 @@ class MiniCPMO45Code2Wav(nn.Module): self._runtime_prompt_dir = tempfile.TemporaryDirectory( prefix="minicpmo45-runtime-prompts-", ) + from vllm_omni.platforms import current_omni_platform + extra = self._extra_config() self._runtime_prompt_cache_size = int(extra.get("token2wav_runtime_prompt_cache_size", 4)) if self._runtime_prompt_cache_size < 0: @@ -342,7 +344,11 @@ class MiniCPMO45Code2Wav(nn.Module): "max_graph_batch": max_graph_batch, "micro_batch_size": micro_batch_size, "pad_max_rows": extra.get("whole_euler_pad_max_rows"), - "fused_body": bool(extra.get("cfm_fused_body", False)), + # The fused body uses tiled TF32 attention on NVIDIA SM80+; + # older CUDA devices retain its SDPA fallback. Respect TF32 opt-out. + "fused_body": bool( + extra.get("cfm_fused_body", current_omni_platform.is_cuda() and _tf32_mode(extra) != "off") + ), "slot_pool": bool(extra.get("cfm_slot_pool", False)), "row_offset_merge": extra.get("cfm_row_offset_merge", False) is True, }