Spaces:
Running on Zero
Running on Zero
Length-gate text-CFG, hide CFG knobs, cap max_frames
Browse files- Runner disables CFG (cfg_scale->1.0) when text exceeds
generation.cfg_max_text_tokens (default 10): CFG helps short phrases
but rushes/flattens long ones.
- AppConfig parses generation.cfg_max_text_tokens; engine passes it to
the runner.
- Remove CFG scale / CFG frames sliders from the UI; strength now comes
from config defaults, application from the length gate. app.py sources
both from config.defaults.
- Default max_frames 2000 -> 1075 (~50s at 21.5 fps).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- app.py +4 -4
- config.yaml +8 -1
- gepard_inference/engine.py +8 -0
- gepard_inference/runner.py +21 -2
- interface.py +6 -15
app.py
CHANGED
|
@@ -48,8 +48,6 @@ def synthesize(
|
|
| 48 |
ref_audio: str,
|
| 49 |
text: str,
|
| 50 |
temperature: float,
|
| 51 |
-
cfg_scale: float,
|
| 52 |
-
cfg_frames: float,
|
| 53 |
top_k: float,
|
| 54 |
stop_threshold: float,
|
| 55 |
max_frames: float,
|
|
@@ -76,8 +74,10 @@ def synthesize(
|
|
| 76 |
params = GenerationParams(
|
| 77 |
temperature=temperature,
|
| 78 |
top_k=int(top_k),
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
| 81 |
stop_threshold=stop_threshold,
|
| 82 |
max_frames=int(max_frames),
|
| 83 |
repetition_penalty=repetition_penalty,
|
|
|
|
| 48 |
ref_audio: str,
|
| 49 |
text: str,
|
| 50 |
temperature: float,
|
|
|
|
|
|
|
| 51 |
top_k: float,
|
| 52 |
stop_threshold: float,
|
| 53 |
max_frames: float,
|
|
|
|
| 74 |
params = GenerationParams(
|
| 75 |
temperature=temperature,
|
| 76 |
top_k=int(top_k),
|
| 77 |
+
# CFG is not exposed in the UI: strength comes from config defaults and
|
| 78 |
+
# the runner's length gate decides whether it applies at all.
|
| 79 |
+
cfg_scale=config.defaults.cfg_scale,
|
| 80 |
+
cfg_frames=config.defaults.cfg_frames,
|
| 81 |
stop_threshold=stop_threshold,
|
| 82 |
max_frames=int(max_frames),
|
| 83 |
repetition_penalty=repetition_penalty,
|
config.yaml
CHANGED
|
@@ -28,10 +28,17 @@ defaults:
|
|
| 28 |
cfg_scale: 3.0
|
| 29 |
cfg_frames: 0
|
| 30 |
stop_threshold: 0.5
|
| 31 |
-
max_frames:
|
| 32 |
repetition_penalty: 1.0
|
| 33 |
repetition_window: 32
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
app:
|
| 36 |
max_ref_seconds: 60 # uploaded/recorded reference audio is truncated to this
|
| 37 |
gpu_duration: 120 # ZeroGPU seconds budget per request
|
|
|
|
| 28 |
cfg_scale: 3.0
|
| 29 |
cfg_frames: 0
|
| 30 |
stop_threshold: 0.5
|
| 31 |
+
max_frames: 1075
|
| 32 |
repetition_penalty: 1.0
|
| 33 |
repetition_window: 32
|
| 34 |
|
| 35 |
+
generation:
|
| 36 |
+
# Text-CFG (cfg_scale > 1) rescues short phrases from running away, but on
|
| 37 |
+
# longer text it rushes delivery (~2x) and flattens prosody. So CFG is only
|
| 38 |
+
# applied when the input has <= this many text tokens; past it, cfg_scale is
|
| 39 |
+
# forced to 1.0 (off) regardless of the slider. Set to null to never gate.
|
| 40 |
+
cfg_max_text_tokens: 10
|
| 41 |
+
|
| 42 |
app:
|
| 43 |
max_ref_seconds: 60 # uploaded/recorded reference audio is truncated to this
|
| 44 |
gpu_duration: 120 # ZeroGPU seconds budget per request
|
gepard_inference/engine.py
CHANGED
|
@@ -68,6 +68,9 @@ class AppConfig:
|
|
| 68 |
max_ref_seconds: Recorded/uploaded reference audio is truncated to
|
| 69 |
this many seconds before encoding.
|
| 70 |
gpu_duration: ZeroGPU time budget per request (seconds).
|
|
|
|
|
|
|
|
|
|
| 71 |
root: Directory of the config file; relative paths resolve against it.
|
| 72 |
"""
|
| 73 |
|
|
@@ -79,6 +82,7 @@ class AppConfig:
|
|
| 79 |
defaults: GenerationParams = field(default_factory=GenerationParams)
|
| 80 |
max_ref_seconds: float = 60.0
|
| 81 |
gpu_duration: int = 120
|
|
|
|
| 82 |
root: Path = field(default_factory=Path.cwd)
|
| 83 |
|
| 84 |
@classmethod
|
|
@@ -90,8 +94,10 @@ class AppConfig:
|
|
| 90 |
model = raw.get("model") or {}
|
| 91 |
codec = raw.get("codec") or {}
|
| 92 |
app = raw.get("app") or {}
|
|
|
|
| 93 |
if not model.get("checkpoint"):
|
| 94 |
raise ValueError(f"{path}: model.checkpoint is required")
|
|
|
|
| 95 |
return cls(
|
| 96 |
checkpoint=str(model["checkpoint"]),
|
| 97 |
attn_implementation=str(model.get("attn_implementation", "eager")),
|
|
@@ -101,6 +107,7 @@ class AppConfig:
|
|
| 101 |
defaults=GenerationParams(**(raw.get("defaults") or {})),
|
| 102 |
max_ref_seconds=float(app.get("max_ref_seconds", 60.0)),
|
| 103 |
gpu_duration=int(app.get("gpu_duration", 120)),
|
|
|
|
| 104 |
root=path.parent.resolve(),
|
| 105 |
)
|
| 106 |
|
|
@@ -212,6 +219,7 @@ class GepardEngine:
|
|
| 212 |
tokens = self.runner.generate(
|
| 213 |
text,
|
| 214 |
ref_codes=ref_codes.to(self.device) if ref_codes is not None else None,
|
|
|
|
| 215 |
**params.to_generate_kwargs(),
|
| 216 |
) # (num_heads, T)
|
| 217 |
|
|
|
|
| 68 |
max_ref_seconds: Recorded/uploaded reference audio is truncated to
|
| 69 |
this many seconds before encoding.
|
| 70 |
gpu_duration: ZeroGPU time budget per request (seconds).
|
| 71 |
+
cfg_max_text_tokens: Disable text-CFG when the input has more than this
|
| 72 |
+
many text tokens (CFG helps short phrases but rushes/flattens long
|
| 73 |
+
ones). None = never gate.
|
| 74 |
root: Directory of the config file; relative paths resolve against it.
|
| 75 |
"""
|
| 76 |
|
|
|
|
| 82 |
defaults: GenerationParams = field(default_factory=GenerationParams)
|
| 83 |
max_ref_seconds: float = 60.0
|
| 84 |
gpu_duration: int = 120
|
| 85 |
+
cfg_max_text_tokens: Optional[int] = None
|
| 86 |
root: Path = field(default_factory=Path.cwd)
|
| 87 |
|
| 88 |
@classmethod
|
|
|
|
| 94 |
model = raw.get("model") or {}
|
| 95 |
codec = raw.get("codec") or {}
|
| 96 |
app = raw.get("app") or {}
|
| 97 |
+
generation = raw.get("generation") or {}
|
| 98 |
if not model.get("checkpoint"):
|
| 99 |
raise ValueError(f"{path}: model.checkpoint is required")
|
| 100 |
+
cfg_max = generation.get("cfg_max_text_tokens")
|
| 101 |
return cls(
|
| 102 |
checkpoint=str(model["checkpoint"]),
|
| 103 |
attn_implementation=str(model.get("attn_implementation", "eager")),
|
|
|
|
| 107 |
defaults=GenerationParams(**(raw.get("defaults") or {})),
|
| 108 |
max_ref_seconds=float(app.get("max_ref_seconds", 60.0)),
|
| 109 |
gpu_duration=int(app.get("gpu_duration", 120)),
|
| 110 |
+
cfg_max_text_tokens=int(cfg_max) if cfg_max is not None else None,
|
| 111 |
root=path.parent.resolve(),
|
| 112 |
)
|
| 113 |
|
|
|
|
| 219 |
tokens = self.runner.generate(
|
| 220 |
text,
|
| 221 |
ref_codes=ref_codes.to(self.device) if ref_codes is not None else None,
|
| 222 |
+
cfg_max_text_tokens=self.config.cfg_max_text_tokens,
|
| 223 |
**params.to_generate_kwargs(),
|
| 224 |
) # (num_heads, T)
|
| 225 |
|
gepard_inference/runner.py
CHANGED
|
@@ -178,6 +178,7 @@ class GepardRunner:
|
|
| 178 |
force_stop_frames: Optional[int] = None,
|
| 179 |
cfg_scale: float = 1.0,
|
| 180 |
cfg_frames: Optional[int] = None,
|
|
|
|
| 181 |
cfg_uncond_mode: str = "empty_text",
|
| 182 |
) -> torch.LongTensor:
|
| 183 |
"""Generate audio tokens for ``text``, optionally with a reference voice
|
|
@@ -206,6 +207,13 @@ class GepardRunner:
|
|
| 206 |
first N frames; later frames use the cond branch alone (the
|
| 207 |
uncond branch is then skipped to save compute). None = guide
|
| 208 |
every frame.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
cfg_uncond_mode: How to build the text-free uncond branch:
|
| 210 |
"empty_text" → [BOS_text, EOT, BOS_audio] (cleanest contrast);
|
| 211 |
"audio_only" → [BOS_audio] (most aggressive; more OOD).
|
|
@@ -215,10 +223,21 @@ class GepardRunner:
|
|
| 215 |
compatible with ``UnfoldedCodecModel.decode_from_codes()`` after
|
| 216 |
adding a batch dim: ``tokens.unsqueeze(0)``.
|
| 217 |
"""
|
| 218 |
-
cfg_on = cfg_scale != 1.0
|
| 219 |
-
|
| 220 |
# 1. Build cond text_ids (with adaptive repetition).
|
| 221 |
text_token_ids = self.tokenizer.encode(text, add_special_tokens=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
cond_ids = self.repeater.expand(text_token_ids)
|
| 223 |
|
| 224 |
# 1b. Build uncond text_ids (SAME prefix later, text removed).
|
|
|
|
| 178 |
force_stop_frames: Optional[int] = None,
|
| 179 |
cfg_scale: float = 1.0,
|
| 180 |
cfg_frames: Optional[int] = None,
|
| 181 |
+
cfg_max_text_tokens: Optional[int] = None,
|
| 182 |
cfg_uncond_mode: str = "empty_text",
|
| 183 |
) -> torch.LongTensor:
|
| 184 |
"""Generate audio tokens for ``text``, optionally with a reference voice
|
|
|
|
| 207 |
first N frames; later frames use the cond branch alone (the
|
| 208 |
uncond branch is then skipped to save compute). None = guide
|
| 209 |
every frame.
|
| 210 |
+
cfg_max_text_tokens: Length gate for text-CFG. If set and the input
|
| 211 |
+
has MORE than this many text tokens, CFG is turned off
|
| 212 |
+
(``cfg_scale`` forced to 1.0) for this call. CFG rescues short
|
| 213 |
+
phrases from running away, but on longer text it rushes
|
| 214 |
+
delivery (~2x) and flattens prosody — so it is only worth
|
| 215 |
+
applying to short inputs. None = no gate (always honour
|
| 216 |
+
``cfg_scale``).
|
| 217 |
cfg_uncond_mode: How to build the text-free uncond branch:
|
| 218 |
"empty_text" → [BOS_text, EOT, BOS_audio] (cleanest contrast);
|
| 219 |
"audio_only" → [BOS_audio] (most aggressive; more OOD).
|
|
|
|
| 223 |
compatible with ``UnfoldedCodecModel.decode_from_codes()`` after
|
| 224 |
adding a batch dim: ``tokens.unsqueeze(0)``.
|
| 225 |
"""
|
|
|
|
|
|
|
| 226 |
# 1. Build cond text_ids (with adaptive repetition).
|
| 227 |
text_token_ids = self.tokenizer.encode(text, add_special_tokens=False)
|
| 228 |
+
|
| 229 |
+
# Length gate: CFG only helps short phrases latch onto the speech
|
| 230 |
+
# manifold. On longer text it rushes delivery (~2x) and flattens
|
| 231 |
+
# prosody, so disable it past the threshold.
|
| 232 |
+
if cfg_max_text_tokens is not None and len(text_token_ids) > cfg_max_text_tokens:
|
| 233 |
+
if cfg_scale != 1.0:
|
| 234 |
+
print(
|
| 235 |
+
f"[GepardRunner] text has {len(text_token_ids)} tokens "
|
| 236 |
+
f"(> cfg_max_text_tokens={cfg_max_text_tokens}); disabling CFG."
|
| 237 |
+
)
|
| 238 |
+
cfg_scale = 1.0
|
| 239 |
+
|
| 240 |
+
cfg_on = cfg_scale != 1.0
|
| 241 |
cond_ids = self.repeater.expand(text_token_ids)
|
| 242 |
|
| 243 |
# 1b. Build uncond text_ids (SAME prefix later, text removed).
|
interface.py
CHANGED
|
@@ -85,9 +85,10 @@ class GepardInterface:
|
|
| 85 |
config: Application config (slider defaults, speaker names, limits).
|
| 86 |
speaker_names: Preset speaker display names for the dropdown.
|
| 87 |
synthesize_fn: Callable with the signature
|
| 88 |
-
``(mode, speaker, ref_audio, text, temperature,
|
| 89 |
-
|
| 90 |
-
|
|
|
|
| 91 |
Wrapped with the ZeroGPU decorator by the caller.
|
| 92 |
"""
|
| 93 |
|
|
@@ -124,7 +125,7 @@ class GepardInterface:
|
|
| 124 |
"""
|
| 125 |
return self.synthesize_fn(
|
| 126 |
MODE_PRESET, speaker_name, None, example_text,
|
| 127 |
-
d.temperature, d.
|
| 128 |
d.stop_threshold, d.max_frames, d.repetition_penalty,
|
| 129 |
d.repetition_window,
|
| 130 |
)
|
|
@@ -175,16 +176,6 @@ class GepardInterface:
|
|
| 175 |
label="Temperature",
|
| 176 |
info="Sampling temperature; lower = more stable, higher = more varied.",
|
| 177 |
)
|
| 178 |
-
cfg_scale = gr.Slider(
|
| 179 |
-
1.0, 6.0, value=d.cfg_scale, step=0.25,
|
| 180 |
-
label="CFG scale",
|
| 181 |
-
info="Text guidance weight. 1 = off; 2–3 sharpens text/voice adherence.",
|
| 182 |
-
)
|
| 183 |
-
cfg_frames = gr.Slider(
|
| 184 |
-
0, 200, value=d.cfg_frames, step=5,
|
| 185 |
-
label="CFG frames",
|
| 186 |
-
info="Guide only the first N frames (0 = guide the whole utterance).",
|
| 187 |
-
)
|
| 188 |
top_k = gr.Slider(
|
| 189 |
0, 200, value=d.top_k, step=1,
|
| 190 |
label="Top-k",
|
|
@@ -243,7 +234,7 @@ class GepardInterface:
|
|
| 243 |
fn=self.synthesize_fn,
|
| 244 |
inputs=[
|
| 245 |
mode, speaker, ref_audio, text,
|
| 246 |
-
temperature,
|
| 247 |
stop_threshold, max_frames, repetition_penalty, repetition_window,
|
| 248 |
],
|
| 249 |
outputs=[output_audio],
|
|
|
|
| 85 |
config: Application config (slider defaults, speaker names, limits).
|
| 86 |
speaker_names: Preset speaker display names for the dropdown.
|
| 87 |
synthesize_fn: Callable with the signature
|
| 88 |
+
``(mode, speaker, ref_audio, text, temperature, top_k,
|
| 89 |
+
stop_threshold, max_frames, repetition_penalty,
|
| 90 |
+
repetition_window) -> (sr, np.ndarray)``. CFG is not a UI knob;
|
| 91 |
+
it is driven by config defaults + the runner's length gate.
|
| 92 |
Wrapped with the ZeroGPU decorator by the caller.
|
| 93 |
"""
|
| 94 |
|
|
|
|
| 125 |
"""
|
| 126 |
return self.synthesize_fn(
|
| 127 |
MODE_PRESET, speaker_name, None, example_text,
|
| 128 |
+
d.temperature, d.top_k,
|
| 129 |
d.stop_threshold, d.max_frames, d.repetition_penalty,
|
| 130 |
d.repetition_window,
|
| 131 |
)
|
|
|
|
| 176 |
label="Temperature",
|
| 177 |
info="Sampling temperature; lower = more stable, higher = more varied.",
|
| 178 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
top_k = gr.Slider(
|
| 180 |
0, 200, value=d.top_k, step=1,
|
| 181 |
label="Top-k",
|
|
|
|
| 234 |
fn=self.synthesize_fn,
|
| 235 |
inputs=[
|
| 236 |
mode, speaker, ref_audio, text,
|
| 237 |
+
temperature, top_k,
|
| 238 |
stop_threshold, max_frames, repetition_penalty, repetition_window,
|
| 239 |
],
|
| 240 |
outputs=[output_audio],
|