ylankgz Claude Opus 4.8 (1M context) commited on
Commit
18101d9
Β·
1 Parent(s): 0ec888c

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 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
- cfg_scale=cfg_scale,
80
- cfg_frames=int(cfg_frames),
 
 
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: 2000
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, cfg_scale,
89
- cfg_frames, top_k, stop_threshold, max_frames,
90
- repetition_penalty, repetition_window) -> (sr, np.ndarray)``.
 
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.cfg_scale, d.cfg_frames, d.top_k,
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, cfg_scale, cfg_frames, top_k,
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],