YaTharThShaRma999 commited on
Commit
04837b5
·
verified ·
1 Parent(s): ed9ee9b

Create tts.py

Browse files
Files changed (1) hide show
  1. tts.py +512 -0
tts.py ADDED
@@ -0,0 +1,512 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import argparse
2
+ from dataclasses import dataclass, field
3
+ from enum import Enum
4
+ import huggingface_hub
5
+ from pathlib import Path
6
+ import random
7
+ import time
8
+ import typing as tp
9
+
10
+ import numpy as np
11
+ from safetensors.torch import load_file
12
+ import torch
13
+
14
+ from moshi.conditioners import ConditionAttributes, dropout_all_conditions, TensorCondition
15
+ from moshi.models import loaders
16
+ from moshi.models.lm import _LMGenState, LMGen
17
+ from moshi.models.tts import TTSModel, Entry, State, StateMachine, DEFAULT_DSM_TTS_REPO
18
+ from moshi.modules.transformer import StreamingMultiheadAttention
19
+ from pydantic import BaseModel
20
+
21
+
22
+ class MaskFlags(Enum):
23
+ # Output PCM is ready
24
+ HAS_PCM = 1
25
+ # Generation is done, no need to step again.
26
+ IS_EOS = 2
27
+ # One word was consumed in the text stream.
28
+ WORD_FINISHED = 4
29
+ # One AR step was performed.
30
+ AR_STEP = 8
31
+ # AR step was skipped because the client is not sending words fast enough.
32
+ MISSING_WORDS = 16
33
+
34
+
35
+ def flags_out_from_mask_(flags_out: np.ndarray, mask: torch.Tensor, value: int):
36
+ flags_out[mask.numpy()] |= value
37
+
38
+
39
+ def split_at_specific_separator(text: str, separator: str, index_of_separator: int) -> tuple[str, str]:
40
+ """ kyutai/tts-voices/unmute-prod-website/*.safetensors
41
+ becomes
42
+ ('kyutai/tts-voices', 'unmute-prod-website/*.safetensors)
43
+ with index_of_separator=1.
44
+ """
45
+ if text.count(separator) <= index_of_separator:
46
+ raise ValueError(f"Separator '{separator}' not found {index_of_separator + 1} times in `{text}`.")
47
+ parts = text.split(separator, index_of_separator + 1)
48
+ return separator.join(parts[:-1]), parts[-1]
49
+
50
+
51
+ class Config(BaseModel):
52
+ log_folder: Path = Path.home() / 'tmp/tts-service'
53
+ hf_repo: str = DEFAULT_DSM_TTS_REPO
54
+ mimi_weight: Path | None = None
55
+ moshi_weight: Path | None = None
56
+ config_path: Path | None = None
57
+ tokenizer: Path | None = None
58
+ device: str = 'cuda'
59
+
60
+ n_q: int = 4
61
+ # This can have multiple formats:
62
+ # - A path to a folder with voices, e.g. `models/tts`
63
+ # - A huggingface snapshot, e.g. `hf-snapshot://kyutai/tts-voices`
64
+ # - A huggingface snapshot with a pattern,
65
+ # e.g. `hf-snapshot://kyutai/tts-voices/unmute-prod-website/*.safetensors`
66
+ voice_folder: str = str(Path.home() / 'models/tts-voices')
67
+ default_voice: str = "barack_demo.wav"
68
+
69
+ temp: float = 0.6
70
+ cfg_coef: float = 2.
71
+
72
+ max_padding: int = 8
73
+ initial_padding: int = 2
74
+ final_padding: int = 4
75
+ padding_between: int = 1
76
+
77
+ interleaved_text_only: int = 2
78
+ debug: bool = False
79
+
80
+
81
+ def init(batch_size: int, config_override: dict) -> 'TTSService':
82
+ config = Config(**config_override)
83
+ config.log_folder.mkdir(parents=True, exist_ok=True)
84
+ print("ADJUSTED CODE")
85
+ print("retrieving checkpoint")
86
+ checkpoint_info = loaders.CheckpointInfo.from_hf_repo(
87
+ config.hf_repo, moshi_weights=config.moshi_weight, mimi_weights=config.mimi_weight,
88
+ config_path=config.config_path, tokenizer=config.tokenizer)
89
+
90
+ cfg_condition = None
91
+ tts_model = TTSModel.from_checkpoint_info(
92
+ checkpoint_info, n_q=config.n_q, temp=config.temp, cfg_coef=config.cfg_coef,
93
+ max_padding=config.max_padding, initial_padding=config.initial_padding, final_padding=config.final_padding,
94
+ device=config.device, dtype=torch.float16)
95
+ if tts_model.valid_cfg_conditionings:
96
+ # Model was trained with CFG distillation.
97
+ cfg_condition = tts_model.cfg_coef
98
+ tts_model.cfg_coef = 1.
99
+ cfg_is_no_text = False
100
+ else:
101
+ cfg_is_no_text = True
102
+
103
+ voice_suffix = tts_model.voice_suffix
104
+ print(f"loading voices from {config.voice_folder}, with suffix {voice_suffix}.")
105
+ all_attributes = {}
106
+ voice_folder = config.voice_folder
107
+ if voice_folder.startswith("hf-snapshot://"):
108
+ voice_folder = voice_folder.removeprefix("hf-snapshot://")
109
+ # We detect if there is a pattern in the voice folder.
110
+ if voice_folder.count("/") > 1:
111
+ voice_folder, pattern = split_at_specific_separator(voice_folder, '/', 1)
112
+ else:
113
+ pattern = None
114
+ print(f"retrieving voices from {voice_folder}")
115
+ voice_folder = huggingface_hub.snapshot_download(voice_folder, allow_patterns=pattern)
116
+ voice_folder = Path(voice_folder)
117
+
118
+ for file in voice_folder.glob(f'**/*{voice_suffix}'):
119
+ relative = file.relative_to(voice_folder)
120
+ name = str(relative.with_name(relative.name.removesuffix(voice_suffix)))
121
+ try:
122
+ attributes = tts_model.make_condition_attributes([file, file], cfg_coef=cfg_condition)
123
+ except Exception:
124
+ print(f"[WARNING] failed to load voice {name}")
125
+ else:
126
+ all_attributes[name] = attributes
127
+
128
+ if not all_attributes:
129
+ raise RuntimeError(
130
+ "No voices found, please check your voice folder. "
131
+ f"Searched for files matching {voice_folder}/**/*{voice_suffix}"
132
+ )
133
+
134
+ if config.default_voice not in all_attributes:
135
+ raise RuntimeError(
136
+ f"Default voice {config.default_voice}, please check your voice folder. "
137
+ f"Expected {voice_folder}/{config.default_voice}{voice_suffix} to exist"
138
+ )
139
+
140
+ service = TTSService(
141
+ batch_size=batch_size, default_attribute_name=config.default_voice,
142
+ all_attributes=all_attributes,
143
+ tts_model=tts_model,
144
+ cfg_condition=cfg_condition,
145
+ cfg_is_no_text=cfg_is_no_text,
146
+ padding_between=config.padding_between,
147
+ debug=config.debug,
148
+ interleaved_text_only=config.interleaved_text_only)
149
+
150
+ return service
151
+
152
+
153
+ @dataclass
154
+ class ClientState:
155
+ is_complete: bool = False
156
+ state: State | None = None
157
+ offset: int = 0
158
+
159
+ def reset(self, state_machine: StateMachine) -> None:
160
+ self.is_complete = False
161
+ self.offset = 0
162
+ self.state = state_machine.new_state([])
163
+
164
+
165
+ @dataclass
166
+ class TTSService:
167
+ batch_size: int
168
+ default_attribute_name: str
169
+ all_attributes: dict[str, ConditionAttributes]
170
+
171
+ tts_model: TTSModel
172
+
173
+ cfg_is_no_text: bool = True
174
+ cfg_condition: float | None = None
175
+ padding_between: int = 1
176
+ n_q: int = 4
177
+ debug: bool = False
178
+ interleaved_text_only: int = 0
179
+
180
+ flags_out: np.ndarray | None = None
181
+ clients: list[ClientState] = field(default_factory=list)
182
+ cross_attention_cache: dict[str, torch.Tensor] = field(default_factory=dict)
183
+ cross_attentions: list[StreamingMultiheadAttention] = field(default_factory=list)
184
+
185
+ def __post_init__(self):
186
+ print(self.n_q)
187
+ print("NQ AMOUNT")
188
+
189
+ lm = self.tts_model.lm
190
+ tts_model = self.tts_model
191
+ mimi = self.tts_model.mimi
192
+ machine = self.tts_model.machine
193
+
194
+ self.device = lm.device
195
+ self.dtype = lm.dtype
196
+ self.remaining_text_only = self.interleaved_text_only
197
+
198
+ for _ in range(self.batch_size):
199
+ client = ClientState()
200
+ self.clients.append(client)
201
+
202
+ print("Filling cross attention cache.")
203
+ for name, attributes in self.all_attributes.items():
204
+ self.cross_attention_cache[name] = self._get_cross_attention_source([attributes])
205
+
206
+ assert lm.condition_provider is not None
207
+
208
+ cas = [self.all_attributes[self.default_attribute_name]] * self.batch_size
209
+ if self.tts_model.cfg_coef != 1.0:
210
+ nulled = make_null(cas)
211
+ cas = cas + nulled
212
+ prepared = lm.condition_provider.prepare(cas)
213
+ condition_tensors = lm.condition_provider(prepared)
214
+
215
+ for module in lm.modules():
216
+ if isinstance(module, StreamingMultiheadAttention) and module.cross_attention:
217
+ self.cross_attentions.append(module)
218
+
219
+ self.lm_gen = LMGen(
220
+ lm, temp=tts_model.temp, temp_text=tts_model.temp, cfg_coef=tts_model.cfg_coef,
221
+ condition_tensors=condition_tensors, on_text_hook=self._on_text_hook,
222
+ on_audio_hook=self._on_audio_hook, cfg_is_no_text=self.cfg_is_no_text,
223
+ support_out_of_sync=True)
224
+ self.lm_gen.streaming_forever(self.batch_size)
225
+ mimi.streaming_forever(self.batch_size)
226
+
227
+ missing = lm.n_q - lm.dep_q
228
+ self.input_tokens = torch.full(
229
+ (self.batch_size, missing, 1), machine.token_ids.zero,
230
+ dtype=torch.long, device=self.device)
231
+ self.no_depformer_tokens = torch.full(
232
+ (self.batch_size, lm.dep_q, 1), machine.token_ids.zero,
233
+ dtype=torch.long, device=self.device)
234
+ self.last_actives: list[bool] = [False] * self.batch_size
235
+ print("warming up.")
236
+ for _ in range(3):
237
+ mimi.set_exec_mask(torch.ones(self.batch_size, dtype=torch.bool))
238
+ self.lm_gen.set_exec_mask(torch.ones(self.batch_size, dtype=torch.bool))
239
+ frame = self.lm_gen.step(self.input_tokens)
240
+ assert frame is not None
241
+ mimi.decode(frame[:, 1:].clamp(min=0))
242
+ print("ready to roll.")
243
+
244
+ def _get_cross_attention_source(self, all_attributes: list[ConditionAttributes]) -> torch.Tensor:
245
+ lm = self.tts_model.lm
246
+ assert lm.condition_provider is not None
247
+ assert lm.fuser is not None
248
+ prepared = lm.condition_provider.prepare(all_attributes)
249
+ condition_tensors = lm.condition_provider(prepared)
250
+ cross = lm.fuser.get_cross(condition_tensors)
251
+ assert cross is not None
252
+ return cross.to(device=self.device, dtype=self.dtype)
253
+
254
+ @property
255
+ def _lm_gen_state(self) -> _LMGenState:
256
+ assert self.lm_gen._streaming_state is not None
257
+ return self.lm_gen._streaming_state
258
+
259
+ def _on_audio_hook(self, audio_tokens: torch.Tensor) -> None:
260
+ delays = self.lm_gen.delays_cuda[1: 1 + self.tts_model.lm.dep_q]
261
+ mask = self._lm_gen_state.offsets[:, None] < delays + self.tts_model.delay_steps
262
+ audio_tokens.masked_fill_(mask, self.tts_model.machine.token_ids.zero)
263
+
264
+ def _on_text_hook(self, text_tokens) -> None:
265
+ tokens = text_tokens.tolist()
266
+ out_tokens = []
267
+ for b, (token, client) in enumerate(zip(tokens, self.clients)):
268
+ if not self.last_actives[b]:
269
+ out_tokens.append(token)
270
+ continue
271
+ assert client.state is not None
272
+ out_token, consumed_new_word = self.tts_model.machine.process(client.offset, client.state, token)
273
+
274
+ if self.flags_out is not None and consumed_new_word:
275
+ self.flags_out[b] |= MaskFlags.WORD_FINISHED.value
276
+ out_tokens.append(out_token)
277
+ text_tokens[:] = torch.tensor(out_tokens, dtype=torch.long, device=text_tokens.device)
278
+
279
+ def _print(self, *args, **kwargs):
280
+ if self.debug:
281
+ print(*args, **kwargs)
282
+
283
+ @torch.no_grad()
284
+ def step(self, updates: list[tuple[int, list[int], np.ndarray | str | None]], pcm_out: np.ndarray,
285
+ flags_out: np.ndarray, code_out: np.ndarray) -> None:
286
+ mimi = self.tts_model.mimi
287
+ machine = self.tts_model.machine
288
+ delay_steps = self.tts_model.delay_steps
289
+
290
+ self.flags_out = flags_out
291
+ flags_out[:] = 0
292
+
293
+ reset_mask = torch.zeros(self.batch_size, dtype=torch.bool)
294
+ # List of pre computed cross attention values.
295
+ new_cross_sources: list[torch.Tensor] = []
296
+ new_cross_indexes: list[int] = []
297
+ # List of new dynamic conditioning that we need to compute.
298
+ new_voice_indexes: list[int] = []
299
+ new_voice_sources: list[torch.Tensor] = []
300
+ for b, new_entry, voice in updates:
301
+ client = self.clients[b]
302
+ if not new_entry:
303
+ self._print(f"[{b}] NO TOKENS REALLY LAURENT.")
304
+ if new_entry[0] == -1:
305
+ client.reset(machine)
306
+ reset_mask[b] = True
307
+ new_entry = new_entry[1:]
308
+ if isinstance(voice, np.ndarray):
309
+ new_voice_indexes.append(b)
310
+ new_voice_sources.append(torch.from_numpy(voice))
311
+ else:
312
+ cross_source = self.cross_attention_cache.get(voice or '', None)
313
+ if cross_source is None:
314
+ cross_source = self.cross_attention_cache[self.default_attribute_name]
315
+ new_cross_sources.append(cross_source)
316
+ new_cross_indexes.append(b)
317
+ self._print(f"[{b}] Reset, voice is {voice}.")
318
+ if client.state is None:
319
+ self._print(f"[{b}] Trying to push {new_entry}, but not assigned.")
320
+ elif new_entry == [-2]:
321
+ self._print(f"[{b}] Done.")
322
+ client.is_complete = True
323
+ else:
324
+ self._print(f"[{b}] Pushing {new_entry}.")
325
+ padding = 0
326
+ if self.padding_between > 0:
327
+ padding = max(0, self.padding_between + len(new_entry) - 1)
328
+ client.state.entries.append(Entry(new_entry, '', padding=padding))
329
+
330
+ actives = []
331
+ mimi_actives = []
332
+ in_text_onlys = []
333
+ for b, client in enumerate(self.clients):
334
+ if client.state is None:
335
+ # client is not currently assigned.
336
+ active = False
337
+ elif client.is_complete:
338
+ # We got all the words from the client and are wrapping up.
339
+ active = True
340
+ elif client.state.forced_padding > 0:
341
+ # We are sure we won't try to consume a word at this point.
342
+ active = True
343
+ elif len(client.state.entries) > self.tts_model.machine.second_stream_ahead:
344
+ # We have some words ready to be consumed.
345
+ active = True
346
+ else:
347
+ flags_out[b] |= MaskFlags.MISSING_WORDS.value
348
+ active = False
349
+ actives.append(active)
350
+
351
+ real_offset = client.offset - self.lm_gen.max_delay
352
+
353
+ mimi_active = active and (real_offset >= delay_steps)
354
+ mimi_actives.append(mimi_active)
355
+
356
+ in_text_only = active and (client.offset < delay_steps)
357
+ in_text_onlys.append(in_text_only)
358
+
359
+ in_text_only_mask = torch.tensor(in_text_onlys, dtype=torch.bool)
360
+ run_in_text_only = self.remaining_text_only > 0 and in_text_only_mask.any()
361
+
362
+ if run_in_text_only:
363
+ self.remaining_text_only -= 1
364
+ mimi_exec_mask = torch.zeros(self.batch_size, dtype=torch.bool)
365
+ exec_mask = in_text_only_mask
366
+ actives = in_text_onlys
367
+ else:
368
+ self.remaining_text_only = self.interleaved_text_only
369
+ exec_mask = torch.tensor(actives, dtype=torch.bool)
370
+ mimi_exec_mask = torch.tensor(mimi_actives, dtype=torch.bool)
371
+ del mimi_actives
372
+ self.last_actives = actives
373
+
374
+ flags_out_from_mask_(flags_out, exec_mask, MaskFlags.AR_STEP.value)
375
+ flags_out_from_mask_(flags_out, mimi_exec_mask, MaskFlags.HAS_PCM.value)
376
+
377
+ # We check on exec_mask whether we actually need to run anything, before we move it to CUDA.
378
+ # However, we still need to perform the reset and update of cross attention for models
379
+ # with a text lookahead stream.
380
+ skip_exec = not exec_mask.any()
381
+
382
+ exec_mask = exec_mask.to(self.device)
383
+ mimi_exec_mask = mimi_exec_mask.to(self.device)
384
+ need_reset = reset_mask.any()
385
+ reset_mask = reset_mask.to(self.device)
386
+
387
+ if new_voice_sources:
388
+ all_attributes = [make_condition_attributes([voice_source], cfg_condition=self.cfg_condition)
389
+ for voice_source in new_voice_sources]
390
+ new_cross_sources += self._get_cross_attention_source(all_attributes).split(1)
391
+ new_cross_indexes += new_voice_indexes
392
+ if new_cross_sources:
393
+ cross_source = torch.cat(new_cross_sources)
394
+ cross_indexes = torch.tensor(new_cross_indexes, dtype=torch.long, device=self.device)
395
+ for attention in self.cross_attentions:
396
+ k, v = attention._compute_cross_attention(cross_source, cross_source)
397
+ state = attention._streaming_state
398
+ assert state is not None
399
+ assert state.k_cross is not None
400
+ assert state.v_cross is not None
401
+ state.k_cross.index_copy_(0, cross_indexes, k)
402
+ state.v_cross.index_copy_(0, cross_indexes, v)
403
+
404
+ if need_reset:
405
+ self.lm_gen.reset_streaming(reset_mask=reset_mask)
406
+ mimi.reset_streaming(reset_mask=reset_mask)
407
+
408
+ if skip_exec:
409
+ return
410
+
411
+ self.lm_gen.set_exec_mask(exec_mask)
412
+ mimi.set_exec_mask(mimi_exec_mask)
413
+
414
+ depformer_replace_tokens = self.no_depformer_tokens if run_in_text_only else None
415
+ frame = self.lm_gen.step(self.input_tokens, depformer_replace_tokens=depformer_replace_tokens)
416
+ assert frame is not None
417
+ audio_frame = frame[:, 1:]
418
+ audio_frame.clamp_(min=0)
419
+
420
+ if run_in_text_only:
421
+ pcm = None
422
+ else:
423
+ pcm = mimi.decode(audio_frame)
424
+ pcm.clamp_(-0.99, 0.99)
425
+
426
+ for b, client in enumerate(self.clients):
427
+ if actives[b]:
428
+ assert client.state is not None
429
+ client.offset += 1
430
+ self._print(f"[{b}] Offset {client.offset: 3d}, pendings={len(client.state.entries): 3d}.")
431
+ if client.is_complete and client.state.end_step is not None:
432
+ # We were waiting for the end of the generation.
433
+ real_end = (
434
+ client.state.end_step + delay_steps + self.tts_model.final_padding + self.lm_gen.max_delay)
435
+ if client.offset >= real_end:
436
+ self._print(f"[{b}] Done.")
437
+ client.reset(machine)
438
+ flags_out[b] |= MaskFlags.IS_EOS.value
439
+ if pcm is not None:
440
+ pcm_out[:] = pcm[:, 0].cpu().numpy()
441
+ code_out[:, :frame.shape[1]] = frame[:, :, 0].int().cpu().numpy()
442
+ code_out[:, frame.shape[1]:] = 0
443
+ self.flags_out = None
444
+
445
+
446
+ class Profiler:
447
+ """Context manager wrapper for xformers profiler.
448
+ """
449
+ def __init__(self, enabled: bool = False):
450
+ self.profiler: tp.Optional[tp.Any] = None
451
+ if enabled:
452
+ from xformers.profiler import profile
453
+ from xformers.profiler.api import PyTorchProfiler
454
+ output_dir = './profiler_data'
455
+ schedule = (
456
+ (PyTorchProfiler, 6, 12),
457
+ )
458
+ self.profiler = profile(output_dir=output_dir, schedule=schedule)
459
+
460
+ def step(self):
461
+ if self.profiler is not None:
462
+ self.profiler.step() # type: ignore
463
+
464
+ def __enter__(self):
465
+ if self.profiler is not None:
466
+ return self.profiler.__enter__() # type: ignore
467
+
468
+ def __exit__(self, exc_type, exc_value, exc_tb):
469
+ if self.profiler is not None:
470
+ return self.profiler.__exit__(exc_type, exc_value, exc_tb) # type: ignore
471
+
472
+
473
+ def make_condition_attributes(voices: list[Path | torch.Tensor],
474
+ max_speakers: int = 5,
475
+ cfg_condition: float | None = None) -> ConditionAttributes:
476
+ assert voices
477
+ voice_tensor = None
478
+ mask = None
479
+ for idx in range(5):
480
+ if idx < len(voices):
481
+ voice = voices[idx]
482
+ if isinstance(voice, Path):
483
+ emb = load_file(voice, device='cuda')['speaker_wavs']
484
+ else:
485
+ emb = voice
486
+ assert emb.dim() == 3
487
+ if voice_tensor is None:
488
+ voice_tensor = torch.zeros(1, max_speakers, emb.shape[2], emb.shape[1], device='cuda')
489
+ if mask is None:
490
+ mask = torch.zeros(1, max_speakers, emb.shape[2], dtype=torch.bool, device='cuda')
491
+ voice_tensor[:, idx, :, :] = emb.transpose(1, 2)
492
+ mask[:, idx, :] = True
493
+ assert voice_tensor is not None
494
+ assert mask is not None
495
+ voice_tensor = voice_tensor.view(1, -1, voice_tensor.shape[-1])
496
+ mask = mask.view(1, -1)
497
+ tensors = {
498
+ 'speaker_wavs': TensorCondition(voice_tensor, mask)
499
+ }
500
+ text: dict[str, str | None] = {
501
+ 'control': 'ok',
502
+ }
503
+ if cfg_condition is None:
504
+ text['cfg'] = None
505
+ else:
506
+ text['cfg'] = format(cfg_condition, '.1f')
507
+ return ConditionAttributes(text=dict(text), tensor=tensors)
508
+
509
+
510
+ def make_null(all_attributes: tp.Sequence[ConditionAttributes]) -> list[ConditionAttributes]:
511
+ return dropout_all_conditions(all_attributes)
512
+
Free AI Image Generator No sign-up. Instant results. Open Now