[Model] Make VoxCPM2 generation settings configurable (#7010)
Signed-off-by: Egor Chernobrovkin <33jasooon33@gmail.com>
This commit is contained in:
1 parent
25bdb38b11
commit
c27a52a359
5 files changed
+137
-2
No files matched your search
@@ -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)
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user