diff --git a/app.py b/app.py index 15448281..2f1b2c29 100644 --- a/app.py +++ b/app.py @@ -225,10 +225,17 @@ class VoxCPMDemo: - def __init__(self, model_id: str = "openbmb/VoxCPM2", device: str = "auto") -> None: + def __init__( + self, + model_id: str = "openbmb/VoxCPM2", + device: str = "auto", + optimize: bool = False, + ) -> None: self.device = resolve_runtime_device(device, "cuda") logger.info(f"Running VoxCPM on device: {self.device}") - self.optimize = self.device.startswith("cuda") + # Allow enabling torch.compile via param or VOXCPM_OPTIMIZE=1 + env_optimize = os.environ.get("VOXCPM_OPTIMIZE", "0").lower() in ("1", "true") + self.optimize = (optimize or env_optimize) and self.device.startswith("cuda") self.asr_model_id = "iic/SenseVoiceSmall" self.asr_device = "cuda:0" if self.device.startswith("cuda") else "cpu" @@ -560,8 +567,9 @@ def run_demo( show_error: bool = True, model_id: str = "openbmb/VoxCPM2", device: str = "auto", + optimize: bool = False, ): - demo = VoxCPMDemo(model_id=model_id, device=device) + demo = VoxCPMDemo(model_id=model_id, device=device, optimize=optimize) interface = create_demo_interface(demo) interface.queue(max_size=10, default_concurrency_limit=1).launch( server_name=server_name, @@ -597,10 +605,17 @@ def run_demo( default="auto", help="Runtime device: auto, cpu, mps, cuda, or cuda:N (default: auto)", ) + parser.add_argument( + "--optimize", + action="store_true", + default=False, + help="Enable torch.compile optimization (requires compatible GPU/PyTorch)", + ) args = parser.parse_args() run_demo( model_id=args.model_id, server_name=args.host, server_port=args.port, device=args.device, + optimize=args.optimize, ) diff --git a/src/voxcpm/model/utils.py b/src/voxcpm/model/utils.py index f6d7463c..37ae1e96 100644 --- a/src/voxcpm/model/utils.py +++ b/src/voxcpm/model/utils.py @@ -240,3 +240,43 @@ def resolve_runtime_device(device: Optional[str], configured_device: str = "cuda f"Unsupported device '{device}'. Supported values are 'auto', 'cpu', 'mps', " "'cuda', or indexed CUDA devices like 'cuda:0'." ) + + +def load_audio_file(file_path: str, target_sr: int = 16000) -> torch.Tensor: + """Load any audio file (WAV, MP3, M4A, AAC, FLAC, OGG, WEBM, etc.) and return a mono 1D float32 tensor.""" + # Try librosa / soundfile first + try: + import librosa + audio, _ = librosa.load(file_path, sr=target_sr, mono=True) + return torch.from_numpy(audio.astype(torch.float32.numpy_dtype() if hasattr(torch.float32, 'numpy_dtype') else "float32")) + except Exception: + pass + + # Fallback to ffmpeg stream conversion + try: + import io + import subprocess + import soundfile as sf + + try: + import imageio_ffmpeg + ffmpeg_exe = imageio_ffmpeg.get_ffmpeg_exe() + except ImportError: + ffmpeg_exe = "ffmpeg" + + cmd = [ + ffmpeg_exe, + "-nostdin", + "-threads", "0", + "-i", str(file_path), + "-f", "wav", + "-ar", str(target_sr), + "-ac", "1", + "pipe:1", + ] + proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) + audio_np, _ = sf.read(io.BytesIO(proc.stdout), dtype="float32") + return torch.from_numpy(audio_np) + except Exception as e: + raise RuntimeError(f"Failed to load audio file '{file_path}': {e}") from e + diff --git a/src/voxcpm/model/voxcpm2.py b/src/voxcpm/model/voxcpm2.py index 174dea3b..49f65696 100644 --- a/src/voxcpm/model/voxcpm2.py +++ b/src/voxcpm/model/voxcpm2.py @@ -48,6 +48,7 @@ from .utils import ( apply_generation_seed, get_dtype, + load_audio_file, materialize_generation_seed, mask_multichar_chinese_tokens, next_and_close, @@ -414,8 +415,7 @@ def _encode_wav( Returns: audio_feat: (T, P, D) tensor of latent patches. """ - audio, _ = librosa.load(wav_path, sr=self._encode_sample_rate, mono=True) - audio = torch.from_numpy(audio).unsqueeze(0) + audio = load_audio_file(wav_path, target_sr=self._encode_sample_rate).unsqueeze(0) if trim_silence_vad: audio = _trim_audio_silence_vad(audio, self._encode_sample_rate, max_silence_ms=200.0) patch_len = self.patch_size * self.chunk_size