Coverage for wrapper/vibevoice/modeling_vibevoice.py: 30%

234 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-09 04:47 +0000

1# mypy: ignore-errors 

2# Copy from: https://github.com/microsoft/VibeVoice/blob/main/vibevoice/modular/modeling_vibevoice.py 

3 

4from dataclasses import dataclass 

5 

6from typing import Any 

7from typing import List 

8from typing import Optional 

9from typing import Tuple 

10from typing import Union 

11 

12import torch 

13import torch.nn as nn 

14import torch.nn.functional as F 

15import torch.distributed as dist 

16 

17from transformers import AutoModel 

18from transformers import AutoModelForCausalLM 

19 

20from transformers.modeling_outputs import BaseModelOutputWithPast 

21from transformers.modeling_outputs import ModelOutput 

22from transformers.models.llama.modeling_llama import LlamaRMSNorm 

23from transformers.modeling_utils import PreTrainedModel 

24from transformers.utils import logging 

25 

26from configuration_vibevoice import VibeVoiceConfig 

27from modular_vibevoice_diffusion_head import VibeVoiceDiffusionHead 

28from schedule.dpm_solver import DPMSolverMultistepScheduler 

29 

30 

31logger = logging.get_logger(__name__) 

32 

33 

34@dataclass 

35class VibeVoiceCausalLMOutputWithPast(ModelOutput): 

36 loss: Optional[torch.FloatTensor] = None 

37 diffusion_loss: Optional[torch.FloatTensor] = None 

38 speech_token_num: Optional[int] = None 

39 logits: torch.FloatTensor = None 

40 past_key_values: Optional[Tuple[Tuple[torch.FloatTensor]]] = None 

41 hidden_states: Optional[Tuple[torch.FloatTensor, ...]] = None 

42 attentions: Optional[Tuple[torch.FloatTensor, ...]] = None 

43 

44 

45@dataclass 

46class VibeVoiceGenerationOutput(ModelOutput): 

47 """ 

48 Output type for VibeVoice generation. 

49 

50 Args: 

51 sequences (`torch.LongTensor` of shape `(batch_size, sequence_length)`): 

52 The generated sequences. 

53 speech_outputs (`List[torch.FloatTensor]`, *optional*): 

54 List of generated speech waveforms or latents for each speech segment. 

55 """ 

56 sequences: torch.LongTensor = None 

57 speech_outputs: Optional[List[torch.FloatTensor]] = None 

58 

59 

60class SpeechConnector(nn.Module): 

61 def __init__(self, input_dim: int, output_dim: int) -> None: 

62 super().__init__() 

63 self.fc1 = nn.Linear(input_dim, output_dim) 

64 self.norm = LlamaRMSNorm(output_dim, eps=1e-6) 

65 self.fc2 = nn.Linear(output_dim, output_dim) 

66 

67 def forward(self, features: Any, **kwargs: Any) -> Any: 

68 x = self.fc1(features) 

69 x = self.norm(x) 

70 x = self.fc2(x) 

71 return x 

72 

73 

74# @auto_docstring 

75class VibeVoicePreTrainedModel(PreTrainedModel): 

76 config_class = VibeVoiceConfig 

77 base_model_prefix = "model" 

78 supports_gradient_checkpointing = True 

79 _skip_keys_device_placement = "past_key_values" 

80 _supports_cache_class = True 

81 _supports_flash_attn_2 = True 

82 _supports_sdpa = True 

83 _supports_quantized_cache = True 

84 _supports_static_cache = True 

85 _supports_attention_backend = True 

86 

87 def _init_weights(self, module: nn.Module) -> None: 

88 if isinstance(module, VibeVoiceDiffusionHead): 

89 module.initialize_weights() 

90 return 

91 

92 # Use the language model's initializer_range if available 

93 if hasattr( 

94 self.config, 'language_model_config' 

95 ) and hasattr( 

96 self.config.language_model_config, 'initializer_range' 

97 ): 

98 std = self.config.language_model_config.initializer_range 

99 elif hasattr(self.config, 'decoder_config') and hasattr(self.config.decoder_config, 'initializer_range'): 

100 std = self.config.decoder_config.initializer_range 

101 else: 

102 std = 0.02 # Default value 

103 

104 if isinstance(module, nn.Linear): 

105 module.weight.data.normal_(mean=0.0, std=std) 

106 if module.bias is not None: 

107 module.bias.data.zero_() 

108 elif isinstance(module, nn.LayerNorm): 

109 module.weight.data.fill_(1.0) 

110 module.bias.data.zero_() 

111 

112 

113# @auto_docstring 

114class VibeVoiceModel(VibeVoicePreTrainedModel): 

115 def __init__(self, config: VibeVoiceConfig) -> None: 

116 super().__init__(config) 

117 

118 if hasattr(config, 'torch_dtype') and config.torch_dtype is not None: 

119 if isinstance(config.torch_dtype, str): 

120 dtype = getattr(torch, config.torch_dtype) 

121 else: 

122 dtype = config.torch_dtype 

123 else: 

124 dtype = torch.float32 

125 

126 # Initialize Qwen2 model for language modeling 

127 lm_config = config.decoder_config 

128 self.language_model = AutoModel.from_config(lm_config) 

129 

130 # Initialize speech components if needed 

131 self.acoustic_tokenizer = AutoModel.from_config(config.acoustic_tokenizer_config).to(dtype) 

132 self.semantic_tokenizer = AutoModel.from_config(config.semantic_tokenizer_config).to(dtype) 

133 

134 self.acoustic_connector = SpeechConnector(config.acoustic_vae_dim, lm_config.hidden_size).to(dtype) 

135 self.semantic_connector = SpeechConnector(config.semantic_vae_dim, lm_config.hidden_size).to(dtype) 

136 

137 # Register scaling factors as buffers - use 1D tensors for FSDP compatibility 

138 self.register_buffer('speech_scaling_factor', torch.tensor(float('nan'))) 

139 self.register_buffer('speech_bias_factor', torch.tensor(float('nan'))) 

140 

141 # Initialize prediction head for speech generation 

142 self.prediction_head = AutoModel.from_config(config.diffusion_head_config).to(dtype) 

143 

144 # Initialize noise scheduler 

145 self.noise_scheduler = DPMSolverMultistepScheduler( 

146 num_train_timesteps=config.diffusion_head_config.ddpm_num_steps, 

147 beta_schedule=config.diffusion_head_config.ddpm_beta_schedule, 

148 prediction_type=config.diffusion_head_config.prediction_type 

149 ) 

150 

151 def get_input_embeddings(self) -> nn.Module: 

152 if hasattr(self.language_model, 'embed_tokens'): 

153 # If the language model has an embed_tokens attribute, return it 

154 return self.language_model.embed_tokens 

155 

156 for name, attr in self.language_model.fullmap.items(): # parallel by nnscaler, the name is changed 

157 if attr.orig_name == 'embed_tokens.weight': 

158 return getattr(self.language_model, name) 

159 assert False, 'should not arrive here' 

160 

161 def set_input_embeddings(self, value: nn.Module) -> None: 

162 self.language_model.embed_tokens = value 

163 

164 def set_speech_tokenizers( 

165 self, acoustic_tokenizer: Optional[Any] = None, semantic_tokenizer: Optional[Any] = None 

166 ) -> None: 

167 """Set the speech tokenizers used for encoding and decoding speech.""" 

168 self.acoustic_tokenizer = acoustic_tokenizer 

169 self.semantic_tokenizer = semantic_tokenizer 

170 

171 # Reset the encoder to evaluation mode 

172 if self.acoustic_tokenizer is not None: 

173 self.acoustic_tokenizer.eval() 

174 

175 if self.semantic_tokenizer is not None: 

176 self.semantic_tokenizer.eval() 

177 

178 def forward( 

179 self, 

180 input_ids: torch.LongTensor = None, 

181 attention_mask: Optional[torch.Tensor] = None, 

182 position_ids: Optional[torch.LongTensor] = None, 

183 past_key_values: Optional[Tuple[Tuple[torch.FloatTensor]]] = None, 

184 inputs_embeds: Optional[torch.FloatTensor] = None, 

185 use_cache: Optional[bool] = None, 

186 output_attentions: Optional[bool] = None, 

187 output_hidden_states: Optional[bool] = None, 

188 return_dict: Optional[bool] = None, 

189 cache_position: Optional[torch.LongTensor] = None, 

190 **kwargs: Any, 

191 ) -> Union[Tuple, BaseModelOutputWithPast]: 

192 

193 return_dict = return_dict if return_dict is not None else self.config.use_return_dict 

194 

195 # Forward through language model 

196 outputs = self.language_model( 

197 input_ids=input_ids, 

198 attention_mask=attention_mask, 

199 position_ids=position_ids, 

200 past_key_values=past_key_values, 

201 inputs_embeds=inputs_embeds, 

202 use_cache=use_cache, 

203 output_attentions=output_attentions, 

204 output_hidden_states=output_hidden_states, 

205 return_dict=return_dict, 

206 cache_position=cache_position, 

207 **kwargs, 

208 ) 

209 

210 if not return_dict: 

211 return outputs 

212 

213 return BaseModelOutputWithPast( 

214 last_hidden_state=outputs.last_hidden_state, 

215 past_key_values=outputs.past_key_values, 

216 hidden_states=outputs.hidden_states, 

217 attentions=outputs.attentions, 

218 ) 

219 

220 

221class VibeVoiceForConditionalGeneration(VibeVoicePreTrainedModel): 

222 _tied_weights_keys = ["lm_head.weight"] 

223 _tp_plan = {"lm_head": "colwise_rep"} 

224 

225 def __init__( 

226 self, 

227 config: VibeVoiceConfig, 

228 ) -> None: 

229 super().__init__(config) 

230 self.model = VibeVoiceModel(config) 

231 self.vocab_size = config.decoder_config.vocab_size 

232 self.lm_head = nn.Linear(config.decoder_config.hidden_size, self.vocab_size, bias=False) 

233 

234 self.post_init() 

235 

236 def get_input_embeddings(self) -> nn.Module: 

237 return self.model.get_input_embeddings() 

238 

239 def set_input_embeddings( 

240 self, 

241 value: nn.Module, 

242 ) -> None: 

243 self.model.set_input_embeddings(value) 

244 

245 def get_output_embeddings(self) -> nn.Module: 

246 return self.lm_head 

247 

248 def set_decoder(self, decoder: nn.Module) -> None: 

249 self.model.language_model = decoder 

250 

251 def get_decoder(self) -> nn.Module: 

252 return self.model.language_model 

253 

254 def tie_weights(self) -> None: 

255 """ 

256 Tie the weights between the input embeddings and the output embeddings. 

257 """ 

258 if getattr(self.config.decoder_config, 'tie_word_embeddings', False): 

259 # The standard PreTrainedModel method will handle the tying. 

260 # It typically does a simple parameter object assignment, which is 

261 # CORRECT to do BEFORE FSDP wraps the model. 

262 output_embeddings = self.get_output_embeddings() 

263 input_embeddings = self.get_input_embeddings() 

264 if hasattr(input_embeddings, 'weight'): 

265 output_embeddings.weight = input_embeddings.weight 

266 else: 

267 # maybe returned input_embeddings a tensor directly 

268 output_embeddings.weight = input_embeddings 

269 

270 if getattr(output_embeddings, "bias", None) is not None: 

271 output_embeddings.bias.data = nn.functional.pad( 

272 output_embeddings.bias.data, 

273 (0, output_embeddings.weight.shape[0] - output_embeddings.bias.shape[0]), 

274 "constant", 

275 0, 

276 ) 

277 logger.info("✅ Tied input and output embeddings using standard assignment.") 

278 else: 

279 logger.info("ℹ️ tie_word_embeddings is False, not tying weights.") 

280 

281 # Also, ensure set_output_embeddings is safe, though your implementation looks okay. 

282 # The key is to avoid calling it after accelerator.prepare(). 

283 def set_output_embeddings(self, new_embeddings: nn.Module) -> None: 

284 # Your current implementation using data.copy_ is good practice, 

285 # but the best way is to not call this after prepare(). 

286 self.lm_head = new_embeddings 

287 

288 def forward_speech_features( 

289 self, 

290 speech_tensors: Optional[torch.Tensor] = None, 

291 speech_masks: Optional[torch.Tensor] = None, 

292 speech_type: str = "audio", 

293 return_unmask: bool = False 

294 ) -> Union[Tuple[torch.Tensor, torch.Tensor], Tuple[torch.Tensor, torch.Tensor]]: 

295 if speech_tensors is None: 

296 # Use config to get vae_dim instead of non-existent self.args 

297 vae_dim = self.config.acoustic_tokenizer_config.vae_dim 

298 audio_features = torch.zeros(1, 1, vae_dim).to(self.get_input_embeddings().weight) 

299 connect_features = self.model.acoustic_connector(audio_features) 

300 return audio_features, connect_features 

301 else: 

302 with torch.no_grad(): 

303 if speech_type == "audio": 

304 with torch.no_grad(): 

305 frames = self.model.acoustic_tokenizer.encode(speech_tensors.unsqueeze(1))[0][0] 

306 audio_tokens = frames.sample(self.model.acoustic_tokenizer.std_dist_type)[0] 

307 

308 elif speech_type == "vae": 

309 # Use config to get vae_dim instead of non-existent self.args 

310 vae_dim = self.config.acoustic_tokenizer_config.vae_dim 

311 speech_mode = speech_tensors.reshape(speech_tensors.size(0), -1, vae_dim) 

312 

313 # gaussian sample from the speech_mode 

314 batch_size = speech_mode.size(0) 

315 value = self.model.acoustic_tokenizer.fix_std / 0.8 

316 std = torch.randn(batch_size, dtype=speech_mode.dtype, device=speech_mode.device) * value 

317 std = std.view(-1, *[1] * (speech_mode.dim() - 1)) 

318 audio_tokens = speech_mode + std * torch.randn(speech_mode.shape).to(speech_mode) 

319 else: 

320 raise NotImplementedError(f"Speech type {speech_type} not implemented") 

321 

322 if torch.isnan(self.model.speech_scaling_factor) or torch.isnan(self.model.speech_bias_factor): 

323 scaling_factor = 1. / audio_tokens[speech_masks].flatten().std() 

324 bias_factor = -audio_tokens[speech_masks].flatten().mean() 

325 

326 # Only use distributed operations if the process group is initialized 

327 if dist.is_available() and dist.is_initialized(): 

328 dist.all_reduce(scaling_factor, op=dist.ReduceOp.SUM) 

329 dist.all_reduce(bias_factor, op=dist.ReduceOp.SUM) 

330 world_size = dist.get_world_size() 

331 self.model.speech_scaling_factor.copy_(scaling_factor / world_size) 

332 self.model.speech_bias_factor.copy_(bias_factor / world_size) 

333 logger.info( 

334 f"Speech scaling factor (distributed): {self.model.speech_scaling_factor}, " 

335 f"bias factor: {self.model.speech_bias_factor}", flush=True) 

336 else: 

337 # Single process case 

338 self.model.speech_scaling_factor.copy_(scaling_factor) 

339 self.model.speech_bias_factor.copy_(bias_factor) 

340 logger.info( 

341 f"Speech scaling factor (single process): {self.model.speech_scaling_factor}, " 

342 f"bias factor: {self.model.speech_bias_factor}", flush=True) 

343 

344 audio_features = (audio_tokens + self.model.speech_bias_factor) * self.model.speech_scaling_factor 

345 

346 connect_features = self.model.acoustic_connector(audio_features) 

347 if return_unmask: 

348 return audio_features, connect_features 

349 return audio_features[speech_masks], connect_features[speech_masks] 

350 

351 def forward( 

352 self, 

353 input_ids: torch.LongTensor = None, 

354 attention_mask: Optional[torch.Tensor] = None, 

355 position_ids: Optional[torch.LongTensor] = None, 

356 past_key_values: Optional[List[torch.FloatTensor]] = None, 

357 inputs_embeds: Optional[torch.FloatTensor] = None, 

358 labels: Optional[torch.LongTensor] = None, 

359 use_cache: Optional[bool] = False, 

360 output_attentions: Optional[bool] = None, 

361 output_hidden_states: Optional[bool] = None, 

362 return_dict: Optional[bool] = None, 

363 cache_position: Optional[torch.LongTensor] = None, 

364 # New arguments for speech processing and loss calculation 

365 speech_tensors: Optional[torch.FloatTensor] = None, 

366 speech_masks: Optional[torch.BoolTensor] = None, 

367 speeches_loss_input: Optional[torch.FloatTensor] = None, 

368 speech_semantic_tensors: Optional[torch.FloatTensor] = None, 

369 acoustic_input_mask: Optional[torch.BoolTensor] = None, 

370 acoustic_loss_mask: Optional[torch.BoolTensor] = None, 

371 ddpm_batch_mul: int = 1, 

372 **kwargs: Any, 

373 ) -> Union[Tuple, VibeVoiceCausalLMOutputWithPast]: 

374 

375 return_dict = return_dict if return_dict is not None else self.config.use_return_dict 

376 

377 x = self.get_input_embeddings()(input_ids) 

378 

379 semantic_speech_all_connect_features = self.model.semantic_connector(speech_semantic_tensors) 

380 if speeches_loss_input is not None: 

381 # only part audio need diffuse 

382 speech_all_features, speech_all_connect_features = self.forward_speech_features( 

383 speech_tensors=speech_tensors.type_as(x) if speech_tensors is not None else None, 

384 speech_masks=speech_masks, 

385 speech_type=kwargs.get("speech_type", "audio"), 

386 return_unmask=True 

387 ) 

388 if speech_tensors is not None: 

389 if semantic_speech_all_connect_features is not None: 

390 x[acoustic_input_mask] = speech_all_connect_features[speech_masks] + \ 

391 semantic_speech_all_connect_features[speech_masks] 

392 else: 

393 x[acoustic_input_mask] = speech_all_connect_features[speech_masks] 

394 mask = speeches_loss_input.unsqueeze(-1) & speech_masks 

395 speech_features = speech_all_features[mask] # only part audio need diffuse 

396 speech_connect_features = speech_all_connect_features[mask] 

397 else: 

398 speech_features, speech_connect_features = self.forward_speech_features( 

399 speech_tensors=speech_tensors.type_as(x) if speech_tensors is not None else None, 

400 speech_masks=speech_masks, 

401 speech_type=kwargs.get("speech_type", "audio"), 

402 ) 

403 if speech_tensors is not None: 

404 x[acoustic_input_mask] = speech_connect_features 

405 

406 outputs = self.model( 

407 input_ids=None, 

408 attention_mask=attention_mask, 

409 position_ids=position_ids, 

410 past_key_values=past_key_values, 

411 inputs_embeds=x, 

412 use_cache=use_cache, 

413 output_attentions=output_attentions, 

414 output_hidden_states=False, 

415 return_dict=return_dict, 

416 cache_position=cache_position, 

417 ) 

418 

419 hidden_states = outputs.last_hidden_state 

420 logits = self.lm_head(hidden_states) 

421 # logits = logits.float() 

422 

423 loss = None 

424 if labels is not None: 

425 # The custom CE loss with masking is calculated in the training script. 

426 # We leave the standard loss calculation here as None. 

427 pass 

428 

429 # --- Diffusion Loss Calculation --- 

430 diffusion_loss = None 

431 # This block is executed only if we are in a context that involves speech. 

432 if speech_tensors is not None and acoustic_loss_mask is not None and acoustic_loss_mask.sum().item() > 0: 

433 condition_features = hidden_states[acoustic_loss_mask] 

434 

435 speech_len, latent_size = speech_features.shape 

436 

437 noise = torch.randn( 

438 (speech_len * ddpm_batch_mul, latent_size), 

439 device=hidden_states.device, 

440 dtype=hidden_states.dtype 

441 ) 

442 

443 timesteps = torch.multinomial( 

444 torch.ones(self.config.diffusion_head_config.ddpm_num_steps), 

445 speech_len * ddpm_batch_mul, 

446 replacement=True, 

447 ).to(hidden_states.device) 

448 

449 speech_features_repeated = speech_features.repeat_interleave(ddpm_batch_mul, dim=0) 

450 condition_features_repeated = condition_features.repeat_interleave(ddpm_batch_mul, dim=0) 

451 

452 noisy_speech_features = self.model.noise_scheduler.add_noise( 

453 speech_features_repeated, noise, timesteps 

454 ) 

455 

456 model_output = self.model.prediction_head( 

457 noisy_speech_features, 

458 timesteps.type_as(x), 

459 condition_features_repeated 

460 ) 

461 

462 prediction_type = self.config.diffusion_head_config.prediction_type 

463 if prediction_type == "epsilon": 

464 target_for_loss = noise 

465 elif prediction_type == "v_prediction": 

466 target_for_loss = self.model.noise_scheduler.get_velocity( 

467 speech_features_repeated, noise, timesteps 

468 ) 

469 else: 

470 raise NotImplementedError(f"Prediction type {prediction_type} not implemented") 

471 

472 diffusion_loss = F.mse_loss(model_output.float(), target_for_loss.float(), reduction='sum') 

473 if latent_size > 0 and ddpm_batch_mul > 0: 

474 diffusion_loss = diffusion_loss / latent_size / ddpm_batch_mul 

475 else: 

476 diffusion_loss = torch.tensor(0.0, device=diffusion_loss.device) 

477 

478 else: 

479 # Dummy loss for DDP to work when there are no speech samples in a batch, 

480 # but we are in a speech context. 

481 diffusion_loss = sum(p.sum() for p in self.model.prediction_head.parameters()) * 0.0 

482 diffusion_loss += sum(p.sum() for p in self.model.acoustic_connector.parameters()) * 0.0 

483 diffusion_loss += sum(p.sum() for p in self.model.semantic_connector.parameters()) * 0.0 

484 # --- End Diffusion Loss Calculation --- 

485 

486 if not return_dict: 

487 output = (logits, speech_len) + outputs.to_tuple()[1:] 

488 return (loss, diffusion_loss) + output 

489 

490 return VibeVoiceCausalLMOutputWithPast( 

491 loss=loss, 

492 diffusion_loss=diffusion_loss, 

493 speech_token_num=speech_len if speech_tensors is not None else 0, 

494 logits=logits, 

495 past_key_values=outputs.past_key_values, 

496 hidden_states=outputs.hidden_states, 

497 attentions=outputs.attentions, 

498 ) 

499 

500 

501AutoModel.register(VibeVoiceConfig, VibeVoiceModel) 

502AutoModelForCausalLM.register(VibeVoiceConfig, VibeVoiceForConditionalGeneration) 

503 

504__all__ = [ 

505 "VibeVoiceModel", 

506 "VibeVoicePreTrainedModel", 

507 "VibeVoiceForConditionalGeneration", 

508 "VibeVoiceCausalLMOutputWithPast", 

509 "VibeVoiceGenerationOutput", 

510]