Skip to content

Long-form voice cloning drift: prompt/reference conditioning may become self-generated latent conditioning #302

Description

@Lee-take

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:

    • initializes:
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:

  1. In src/voxcpm/model/voxcpm2.py, add helper methods:
_build_voice_anchor_feat(...)
_blend_voice_anchor_condition(...)
  1. Add parameters to _generate_with_prompt_cache(...):
voice_anchor_strength: float = 0.15
voice_anchor_tail_size: int = 4
  1. Build a voice anchor from the tail mean of ref_audio_feat and/or audio_feat.

  2. Pass that anchor into _inference(...).

  3. 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.

  1. In src/voxcpm/core.py, expose:
voice_anchor_strength=0.15
voice_anchor_tail_size=4
  1. 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:

Ran 4 tests
OK

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions