Hello VoxCPM team,
First of all, thank you very much for releasing VoxCPM2. We have been using VoxCPM 2.0.3 for long-form Chinese TTS / voice cloning, and it has been very useful. I would like to share a technical issue we found, the evidence we collected from the source code, and a small inference-time patch that we tested locally.
Problem observed
When generating relatively long audio in a single VoxCPM2 call with prompt/reference audio, the beginning of the output usually follows the reference voice well, but the voice representation can gradually drift as generation continues. In longer generations, the timbre / speaker identity may become less stable or move toward a different voice.
We specifically wanted to understand whether this could be fixed inside the inference path, rather than only working around it by splitting long text into many short segments and concatenating the outputs.
How we analyzed it
We analyzed the VoxCPM 2.0.3 source with GitNexus and Graphify:
- GitNexus helped us confirm the relevant call chain:
VoxCPM2Model._generate_with_prompt_cache(...)
- calls into
VoxCPM2Model._inference(...)
- GitNexus also showed that
merge_prompt_cache(...) exists, but does not appear to be called by the current generation path.
- Graphify was used to connect the source-code evidence with our long-form generation behavior and local test scripts.
Source-code evidence
The current source file appears to be:
src/voxcpm/model/voxcpm2.py
The key locations we found are:
-
build_prompt_cache(...) around line 681:
- encodes
reference_wav_path into ref_audio_feat
- encodes
prompt_wav_path into audio_feat
- sets modes such as
reference, continuation, and ref_continuation
-
merge_prompt_cache(...) around line 742:
- appears designed to merge generated audio features back into a prompt cache
- however, we could not find it being used in the active generation path
-
_generate_with_prompt_cache(...) around line 783:
- builds the text/audio masks and feature tensors
- then calls
_inference(...)
-
_inference(...) around line 975:
prefix_feat_cond = feat[:, -1, ...]
- passes this into the diffusion decoder around line 1068:
cond=prefix_feat_cond.transpose(1, 2).contiguous()
- then after each generated patch, updates the condition around line 1079:
prefix_feat_cond = pred_feat
Our interpretation is that prompt/reference audio acts mainly as the initial conditioning context. During long single-pass generation, the decoder conditioning becomes increasingly dependent on the model's own previously generated latent features. That seems to provide a plausible source-code explanation for cumulative voice drift in long-form generation.
Our diagnosis
Our current judgment is:
- Root cause: prompt/reference voice conditioning is not continuously re-anchored during long single-pass inference.
- Direct trigger: long target text is generated in one call, so later audio is conditioned mostly on self-generated latent features.
- Engineering workaround: split text into short segments, re-inject the same prompt/reference audio for each segment, then concatenate.
- But ideally, a single long-form generation call should have an internal voice-stability mechanism, so users do not have to rely only on external segmentation.
Local source-code modification we tested
We tested a small local inference-time patch that adds a stable voice anchor from the prompt/reference latent features and blends it into the decoder condition during _inference(...).
The local patch did the following:
- In
src/voxcpm/model/voxcpm2.py, add helper methods:
_build_voice_anchor_feat(...)
_blend_voice_anchor_condition(...)
- Add parameters to
_generate_with_prompt_cache(...):
voice_anchor_strength: float = 0.15
voice_anchor_tail_size: int = 4
-
Build a voice anchor from the tail mean of ref_audio_feat and/or audio_feat.
-
Pass that anchor into _inference(...).
-
In _inference(...), before calling feat_decoder, use:
decoder_cond = self._blend_voice_anchor_condition(
prefix_feat_cond,
voice_anchor_feat,
voice_anchor_strength,
)
and then call:
cond=decoder_cond.transpose(1, 2).contiguous()
instead of using prefix_feat_cond directly.
- In
src/voxcpm/core.py, expose:
voice_anchor_strength=0.15
voice_anchor_tail_size=4
- In
src/voxcpm/cli.py, expose:
--voice-anchor-strength
--voice-anchor-tail-size
The default value we tested was voice_anchor_strength=0.15.
Local tests we ran
We added small unit tests that do not require loading the full model:
- prompt-only anchor uses the tail mean of prompt latent features
- reference + prompt mode averages both anchors
- blend strength is clamped to
[0.0, 1.0]
- strength
0.0 disables the anchor and preserves the original condition
The local unit test result was:
We also ran:
- Python syntax compilation for the modified files: passed
- CLI help / argument validation for the new parameters: passed
- a short real
clone smoke test with --voice-anchor-strength 0.15: generated a valid WAV successfully
The smoke test only proves that the patched inference path runs without shape/runtime errors. It is not a full proof that arbitrary-length generation will never drift.
Important limitation
This is an inference-time source-code fix, and it can directly suppress the drift mechanism we identified; however, only retraining the model or changing the model objective can absolutely guarantee that "arbitrarily long text will never drift." The current default strength is 0.15. For long lessons we suggest first trying 0.15; if the latter part still drifts, try 0.25; if the voice becomes muffled or articulation is affected, reduce it to 0.10.
Request
Would you consider adding an official long-form voice-stability mechanism to VoxCPM2, or advising whether this kind of prompt/reference latent anchor is a reasonable direction?
If you are interested, I would be happy to prepare a cleaner PR based on this local patch, with tests adapted to the repository style.
Thank you again for your work on VoxCPM2.
Hello VoxCPM team,
First of all, thank you very much for releasing VoxCPM2. We have been using VoxCPM 2.0.3 for long-form Chinese TTS / voice cloning, and it has been very useful. I would like to share a technical issue we found, the evidence we collected from the source code, and a small inference-time patch that we tested locally.
Problem observed
When generating relatively long audio in a single VoxCPM2 call with prompt/reference audio, the beginning of the output usually follows the reference voice well, but the voice representation can gradually drift as generation continues. In longer generations, the timbre / speaker identity may become less stable or move toward a different voice.
We specifically wanted to understand whether this could be fixed inside the inference path, rather than only working around it by splitting long text into many short segments and concatenating the outputs.
How we analyzed it
We analyzed the VoxCPM 2.0.3 source with GitNexus and Graphify:
VoxCPM2Model._generate_with_prompt_cache(...)VoxCPM2Model._inference(...)merge_prompt_cache(...)exists, but does not appear to be called by the current generation path.Source-code evidence
The current source file appears to be:
src/voxcpm/model/voxcpm2.pyThe key locations we found are:
build_prompt_cache(...)around line 681:reference_wav_pathintoref_audio_featprompt_wav_pathintoaudio_featreference,continuation, andref_continuationmerge_prompt_cache(...)around line 742:_generate_with_prompt_cache(...)around line 783:_inference(...)_inference(...)around line 975:Our interpretation is that prompt/reference audio acts mainly as the initial conditioning context. During long single-pass generation, the decoder conditioning becomes increasingly dependent on the model's own previously generated latent features. That seems to provide a plausible source-code explanation for cumulative voice drift in long-form generation.
Our diagnosis
Our current judgment is:
Local source-code modification we tested
We tested a small local inference-time patch that adds a stable voice anchor from the prompt/reference latent features and blends it into the decoder condition during
_inference(...).The local patch did the following:
src/voxcpm/model/voxcpm2.py, add helper methods:_generate_with_prompt_cache(...):Build a voice anchor from the tail mean of
ref_audio_featand/oraudio_feat.Pass that anchor into
_inference(...).In
_inference(...), before callingfeat_decoder, use:and then call:
instead of using
prefix_feat_conddirectly.src/voxcpm/core.py, expose:src/voxcpm/cli.py, expose:The default value we tested was
voice_anchor_strength=0.15.Local tests we ran
We added small unit tests that do not require loading the full model:
[0.0, 1.0]0.0disables the anchor and preserves the original conditionThe local unit test result was:
We also ran:
clonesmoke test with--voice-anchor-strength 0.15: generated a valid WAV successfullyThe smoke test only proves that the patched inference path runs without shape/runtime errors. It is not a full proof that arbitrary-length generation will never drift.
Important limitation
This is an inference-time source-code fix, and it can directly suppress the drift mechanism we identified; however, only retraining the model or changing the model objective can absolutely guarantee that "arbitrarily long text will never drift." The current default strength is
0.15. For long lessons we suggest first trying0.15; if the latter part still drifts, try0.25; if the voice becomes muffled or articulation is affected, reduce it to0.10.Request
Would you consider adding an official long-form voice-stability mechanism to VoxCPM2, or advising whether this kind of prompt/reference latent anchor is a reasonable direction?
If you are interested, I would be happy to prepare a cleaner PR based on this local patch, with tests adapted to the repository style.
Thank you again for your work on VoxCPM2.