[Model] Make VoxCPM2 generation settings configurable (#7010)

Signed-off-by: Egor Chernobrovkin <33jasooon33@gmail.com>
This commit is contained in:
Egor Chernobrovkin authored and GitHub committed 2026-09-30 11:26:12 +08:00
1 parent 25bdb38b11
commit c27a52a359
5 files changed
+137 -2

No files matched your search

+6
View File
@@ -141,6 +141,12 @@ The deploy config at
is loaded automatically by the model registry (HF `model_type=voxcpm2`).
Pass `--deploy-config <path>` to override.
The `engine_extras.hf_overrides.voxcpm2_runtime_config` section controls
LocDiT CFM generation. `inference_timesteps`, `cfg_value`, and
`cfg_cutoff_ratio` default to `10`, `2.0`, and `1.0`, respectively.
`inference_timesteps` is clamped to at least `2`: with CFG-Zero*, a single
step would skip all estimator calls and return the initial noise.
#### Verification
**Server cold-start**: ~60 s from `vllm serve` to `Application startup
@@ -0,0 +1,116 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from dataclasses import dataclass
import pytest
import torch
from vllm_omni.model_executor.models.voxcpm2.runtime_config import _VoxCPM2RuntimeConfig
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@dataclass
class _ModelConfig:
voxcpm2_runtime_config: dict[str, object]
@dataclass
class _VllmConfig:
model_config: _ModelConfig
@pytest.mark.parametrize(
("key", "value", "expected"),
[
("inference_timesteps", 4, 4),
("inference_timesteps", "6", 6),
("inference_timesteps", -3, 2),
("inference_timesteps", 0, 2),
("inference_timesteps", 1, 2),
("inference_timesteps", 2, 2),
("cfg_value", "1.5", 1.5),
("cfg_cutoff_ratio", "0.5", 0.5),
("cfg_cutoff_ratio", -1.0, 0.0),
("cfg_cutoff_ratio", 2.0, 1.0),
],
)
def test_generation_config(key: str, value: object, expected: int | float) -> None:
vllm_config = _VllmConfig(_ModelConfig({key: value}))
config = _VoxCPM2RuntimeConfig.from_vllm_config(vllm_config)
assert getattr(config, key) == expected
def test_generation_config_defaults() -> None:
config = _VoxCPM2RuntimeConfig()
assert config.inference_timesteps == 10
assert config.cfg_value == 2.0
assert config.cfg_cutoff_ratio == 1.0
class _CountingEstimator(torch.nn.Module):
def __init__(self) -> None:
super().__init__()
self.calls = 0
def forward(self, x, mu, t, cond, dt):
self.calls += 1
return mu[:, :1, None].expand_as(x)
@pytest.mark.parametrize("value", ["nan", "inf", "-inf"])
def test_cfg_value_rejects_nonfinite(value: str) -> None:
with pytest.raises(ValueError, match="cfg_value must be finite"):
_VoxCPM2RuntimeConfig.from_vllm_config(_VllmConfig(_ModelConfig({"cfg_value": value})))
@pytest.mark.parametrize("timesteps", [-3, 0, 1, 2, 4, 10])
@pytest.mark.parametrize("batch_size", [1, 3])
@pytest.mark.parametrize("cfg_value", [1.5, 2.0])
@pytest.mark.parametrize("cutoff", [0.0, 1.0])
def test_generation_config_runs_estimator(timesteps: int, batch_size: int, cfg_value: float, cutoff: float) -> None:
from vllm_omni.model_executor.models.voxcpm2.voxcpm2_talker import (
_CFMBufferManager,
_optimized_solve_euler_with_noise,
)
config = _VoxCPM2RuntimeConfig.from_vllm_config(
_VllmConfig(
_ModelConfig({"inference_timesteps": timesteps, "cfg_value": cfg_value, "cfg_cutoff_ratio": cutoff})
)
)
cfm = torch.nn.Module()
cfm.estimator = _CountingEstimator()
buffers = _CFMBufferManager(
device=torch.device("cpu"),
dtype=torch.float32,
feat_dim=2,
patch_size=2,
dit_hidden_size=2,
max_batch_size=batch_size,
)
noise = torch.ones(batch_size, 2, 2)
output = _optimized_solve_euler_with_noise(
cfm,
mu=torch.ones(batch_size, 2),
patch_size=2,
cond=torch.zeros_like(noise),
noise=noise,
n_timesteps=config.inference_timesteps,
cfg_value=config.cfg_value,
buffers=buffers,
cfg_cutoff_ratio=config.cfg_cutoff_ratio,
)
assert cfm.estimator.calls == max(2, timesteps) - 1
assert torch.isfinite(output).all()
assert not torch.equal(output, noise)
torch.testing.assert_close(noise, torch.ones_like(noise))
if cutoff == 1.0:
t_span = buffers.get_t_span(config.inference_timesteps)
expected = noise - cfg_value * (t_span[1] - t_span[-1])
torch.testing.assert_close(output, expected)
+6
View File
@@ -31,6 +31,12 @@ stages:
engine_extras:
hf_overrides:
voxcpm2_runtime_config:
# LocDiT generation settings affect synthesis quality. Keep the defaults
# unless alternative values have been validated for your workload.
inference_timesteps: 10
cfg_value: 2.0
cfg_cutoff_ratio: 1.0
audio_emit_every: 1
vae_decode_every: 3
enable_batched_cfm: true
@@ -4,6 +4,7 @@
from __future__ import annotations
import dataclasses
import math
from typing import Any
from vllm.logger import init_logger
@@ -24,6 +25,8 @@ class _VoxCPM2RuntimeConfig:
enable_loc_dit_skip_qkv_contig: bool = True
enable_loc_dit_reduce_overhead_no_cg: bool = False
enable_loc_dit_fullgraph_no_cg: bool = False
inference_timesteps: int = 10
cfg_value: float = 2.0
cfg_cutoff_ratio: float = 1.0
decode_graph_capture_policy: str = "all"
enable_vae_cuda_graph: bool = False
@@ -116,10 +119,14 @@ class _VoxCPM2RuntimeConfig:
return bool(value)
if isinstance(default, int) and not isinstance(default, bool):
value = int(value)
if key == "inference_timesteps":
return max(2, value)
if key in {"audio_emit_every", "vae_decode_every", "batched_fsq_fusion_max_batch"}:
return max(1, value)
return value
if isinstance(default, float):
if key == "cfg_value" and not math.isfinite(float(value)):
raise ValueError("VoxCPM2 cfg_value must be finite.")
if key == "cfg_cutoff_ratio":
return min(1.0, max(0.0, float(value)))
return float(value)
@@ -922,8 +922,8 @@ class VoxCPM2TalkerForConditionalGeneration(nn.Module):
self._tts.residual_lm = None
torch.accelerator.empty_cache()
self._inference_timesteps = 10
self._cfg_value = 2.0
self._inference_timesteps = self._runtime_config.inference_timesteps
self._cfg_value = self._runtime_config.cfg_value
self._cfg_cutoff_ratio = self._runtime_config.cfg_cutoff_ratio
# Number of trailing latent frames to keep as VAE receptive-field context
# for sliding-window streaming decode. 12 matches the nanovllm reference