Skip to content

Commit 64cc2b7

Browse files
committed
Merge fix/multispeaker-voice-drift into main
2 parents e53bcb1 + ce884d3 commit 64cc2b7

4 files changed

Lines changed: 36 additions & 3 deletions

File tree

‎.env.example‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ FISH_STREAM_CHUNK_TOKENS=32
4343
# Silence at each text-chunk boundary (ms). Chunks otherwise join with ~0.15 s
4444
# of pause versus ~0.4 s between sentences inside a chunk. 0 disables.
4545
FISH_CHUNK_PAUSE_MS=400
46+
# Multi-speaker: start a new generation at each speaker change (prevents voices
47+
# drifting into the first speaker's). 0 = old grouping.
48+
FISH_SPLIT_SPEAKERS=1
4649

4750
# --- Auth ---
4851
# Require this bearer token on requests (leave blank for an open server).

‎OPENAI_API_README.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,11 @@ accepted via `/v1/tts`.
196196
its last word, so chunks joined edge to edge sound rushed next to the ~0.4 s
197197
the model leaves between sentences within a chunk. The engine measures each
198198
chunk's trailing silence and pads up to this target. `0` disables.
199+
- `FISH_SPLIT_SPEAKERS` (1) — in multi-speaker input, start a new generation at
200+
every speaker change (consecutive lines by one speaker stay together). With
201+
several speakers in one generation, the model drifts other speakers toward the
202+
voice it opened with; a 3-voice scene had wrong-voice lines in 10/24 renders
203+
versus 0/24 with this on, at no speed cost. `0` restores the old grouping.
199204
- `FISH_RAS` (1) — Repetition Aware Sampling; `0` disables it (diagnostics).
200205
- `FISH_REF_CACHE` (on) — reuse encoded reference voices across requests. The
201206
voice add/delete endpoints invalidate it; set `off` if you edit files under

‎compose.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ services:
4242
FISH_ASR_MODEL: "${FISH_ASR_MODEL:-small}" # faster-whisper model (small=multilingual)
4343
FISH_ASR_DEVICE: "${FISH_ASR_DEVICE:-cpu}" # cpu | cuda:0 (cpu avoids VRAM contention)
4444
FISH_CHUNK_PAUSE_MS: "${FISH_CHUNK_PAUSE_MS:-400}" # pause at text-chunk boundaries
45+
FISH_SPLIT_SPEAKERS: "${FISH_SPLIT_SPEAKERS:-1}" # new generation per speaker change
4546
FISH_BATCH_SIZE: "${FISH_BATCH_SIZE:-1}" # >1 = continuous batching (needs FISH_QUANTIZE=none)
4647
FISH_CONCURRENCY: "${FISH_CONCURRENCY:-}" # empty = FISH_BATCH_SIZE
4748
FISH_QUEUE_TIMEOUT: "${FISH_QUEUE_TIMEOUT:-300}"

‎fish_speech/models/text2semantic/inference.py‎

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ def multinomial_sample_one_no_sync(probs_sort):
5050
# FISH_RAS=0 disables Repetition Aware Sampling (resampling a token that
5151
# repeats within the window at a higher temperature).
5252
RAS_ENABLED = os.environ.get("FISH_RAS", "1") != "0"
53+
# Start a new text batch at every speaker change so each generation opens with
54+
# its own speaker's voice. With several speakers in one batch the model drifts
55+
# other speakers toward the batch's opening voice (measured: 10/24 renders of a
56+
# 3-voice scene had wrong-voice lines vs 0/24 when split). FISH_SPLIT_SPEAKERS=0
57+
# restores the old grouping.
58+
SPLIT_ON_SPEAKER_CHANGE = os.environ.get("FISH_SPLIT_SPEAKERS", "1") != "0"
5359
RAS_HIGH_TEMP = 1.0
5460
RAS_HIGH_TOP_P = 0.9
5561

@@ -751,7 +757,10 @@ def split_text_by_speaker(text: str) -> list[str]:
751757

752758

753759
def group_turns_into_batches(
754-
turns: list[str], max_speakers: int = 3, max_bytes: int = 300
760+
turns: list[str],
761+
max_speakers: int = 3,
762+
max_bytes: int = 300,
763+
split_on_speaker_change: bool = False,
755764
) -> list[str]:
756765
"""
757766
Group turns into batches based on speaker count or byte limit.
@@ -768,10 +777,22 @@ def group_turns_into_batches(
768777
current_batch = []
769778
current_bytes = 0
770779

780+
speaker_re = re.compile(r"<\|speaker:(\d+)\|>")
781+
current_speaker = None
782+
771783
for turn in turns:
772784
turn_bytes = len(turn.encode("utf-8"))
785+
m = speaker_re.match(turn)
786+
speaker = m.group(1) if m else None
787+
# Each generation then starts from its own speaker tag; within one
788+
# generation the model tends to carry the opening voice into later
789+
# turns (other speakers drift toward speaker 0's voice).
790+
speaker_changed = (
791+
split_on_speaker_change and current_batch and speaker != current_speaker
792+
)
793+
current_speaker = speaker
773794

774-
would_exceed_speakers = len(current_batch) >= max_speakers
795+
would_exceed_speakers = len(current_batch) >= max_speakers or speaker_changed
775796
would_exceed_bytes = current_bytes + turn_bytes > max_bytes and current_batch
776797

777798
if would_exceed_speakers or would_exceed_bytes:
@@ -941,7 +962,10 @@ def generate_long_steps(
941962
turns = split_text_by_speaker(text)
942963
if turns:
943964
batches = group_turns_into_batches(
944-
turns, max_speakers=5, max_bytes=chunk_length
965+
turns,
966+
max_speakers=5,
967+
max_bytes=chunk_length,
968+
split_on_speaker_change=SPLIT_ON_SPEAKER_CHANGE,
945969
)
946970
else:
947971
batches = [text]

0 commit comments

Comments
 (0)