EuuIia commited on
Commit
bda9780
·
verified ·
1 Parent(s): 4747f61

Rename video_service.py to api/ltx_server.py

Browse files
video_service.py → api/ltx_server.py RENAMED
@@ -63,9 +63,9 @@ def _query_gpu_processes_via_nvidiasmi(device_index: int) -> List[Dict]:
63
  parts = [p.strip() for p in line.split(",")]
64
  if len(parts) >= 3:
65
  try:
66
- pid = int(parts[0])
67
- name = parts[1]
68
- used_mb = int(parts[2])
69
  user = "unknown"
70
  try:
71
  import psutil
@@ -163,7 +163,7 @@ class VideoService:
163
  if self.latent_upsampler:
164
  self.latent_upsampler.to(self.device)
165
 
166
- # Política de precisão (inclui promoção FP8->BF16 e dtype de autocast)
167
  self._apply_precision_policy()
168
 
169
  if self.device == "cuda":
@@ -171,7 +171,6 @@ class VideoService:
171
  self._log_gpu_memory("Após carregar modelos")
172
  print("VideoService pronto para uso.")
173
 
174
- # Método de log de GPU como parte da classe
175
  def _log_gpu_memory(self, stage_name: str):
176
  if self.device != "cuda":
177
  return
@@ -209,15 +208,9 @@ class VideoService:
209
  pass
210
 
211
  def finalize(self, keep_paths=None, extra_paths=None, clear_gpu=True):
212
- """
213
- Remove temporários e coleta memória.
214
- keep_paths: caminhos que não devem ser removidos (ex.: vídeo final).
215
- extra_paths: caminhos adicionais para tentar remover (opcional).
216
- """
217
  keep = set(keep_paths or [])
218
  extras = set(extra_paths or [])
219
 
220
- # Remoção de arquivos
221
  for f in list(self._tmp_files | extras):
222
  try:
223
  if f not in keep and os.path.isfile(f):
@@ -227,7 +220,6 @@ class VideoService:
227
  finally:
228
  self._tmp_files.discard(f)
229
 
230
- # Remoção de diretórios
231
  for d in list(self._tmp_dirs):
232
  try:
233
  if d not in keep and os.path.isdir(d):
@@ -237,7 +229,6 @@ class VideoService:
237
  finally:
238
  self._tmp_dirs.discard(d)
239
 
240
- # Coleta de GC e limpeza de VRAM
241
  gc.collect()
242
  try:
243
  if clear_gpu and torch.cuda.is_available():
@@ -249,26 +240,23 @@ class VideoService:
249
  except Exception:
250
  pass
251
 
252
- # Log opcional pós-limpeza
253
  try:
254
  self._log_gpu_memory("Após finalize")
255
  except Exception:
256
  pass
257
 
258
  def _load_config(self):
259
- # Prioriza configs FP8 se presentes, mantendo compatibilidade
260
  base = LTX_VIDEO_REPO_DIR / "configs"
261
  candidates = [
262
  base / "ltxv-13b-0.9.8-dev-fp8.yaml",
263
  base / "ltxv-13b-0.9.8-distilled-fp8.yaml",
264
  base / "ltxv-13b-0.9.8-dev-fp8.yaml.txt",
265
- base / "ltxv-13b-0.9.8-distilled.yaml", # fallback não-FP8
266
  ]
267
  for cfg in candidates:
268
  if cfg.exists():
269
  with open(cfg, "r") as file:
270
  return yaml.safe_load(file)
271
- # Fallback rígido para caminho clássico se nada acima existir
272
  config_file_path = base / "ltxv-13b-0.9.8-distilled.yaml"
273
  with open(config_file_path, "r") as file:
274
  return yaml.safe_load(file)
@@ -311,9 +299,7 @@ class VideoService:
311
 
312
  return pipeline, latent_upsampler
313
 
314
- # Precisão: promove FP8->BF16 e define dtype de autocast (versão segura)
315
  def _promote_fp8_weights_to_bf16(self, module):
316
- # Só promova se for realmente um nn.Module; Pipelines não são nn.Module
317
  if not isinstance(module, torch.nn.Module):
318
  return
319
  f8 = getattr(torch, "float8_e4m3fn", None)
@@ -332,16 +318,14 @@ class VideoService:
332
  b.data = b.data.to(torch.bfloat16)
333
  except Exception:
334
  pass
335
-
336
  def _apply_precision_policy(self):
337
  prec = str(self.config.get("precision", "")).lower()
338
  self.runtime_autocast_dtype = torch.float32
339
  if prec == "float8_e4m3fn":
340
- # FP8: kernels nativos da LTX podem estar ativos; por padrão, não promover pesos
341
  self.runtime_autocast_dtype = torch.bfloat16
342
  force_promote = os.getenv("LTXV_FORCE_BF16_ON_FP8", "0") == "1"
343
  if force_promote and hasattr(torch, "float8_e4m3fn"):
344
- # Promove apenas módulos reais; ignora objetos Pipeline
345
  try:
346
  self._promote_fp8_weights_to_bf16(self.pipeline)
347
  except Exception:
@@ -357,7 +341,7 @@ class VideoService:
357
  self.runtime_autocast_dtype = torch.float16
358
  else:
359
  self.runtime_autocast_dtype = torch.float32
360
-
361
  def _prepare_conditioning_tensor(self, filepath, height, width, padding_values):
362
  tensor = load_image_to_tensor_with_resize_and_crop(filepath, height, width)
363
  tensor = torch.nn.functional.pad(tensor, padding_values)
@@ -458,7 +442,6 @@ class VideoService:
458
  ).to(self.device)
459
 
460
  result_tensor = None
461
- video_np = None
462
  multi_scale_pipeline = None
463
 
464
  if improve_texture:
@@ -496,32 +479,31 @@ class VideoService:
496
  "skip_block_list": first_pass_config.get("skip_block_list"),
497
  }
498
  )
499
-
500
- # EVITAR guidance_timesteps no single-pass para não acionar guidance_mapping na lib
501
- # Preferir 'timesteps' se existir; caso contrário, deixar sem e usar defaults do pipeline.
502
- config_timesteps = first_pass_config.get("timesteps")
 
503
  if mode == "video-to-video":
504
- single_pass_kwargs["timesteps"] = [0.7]
505
  print("[INFO] Modo video-to-video (etapa única): definindo timesteps (força) para [0.7]")
506
- elif isinstance(config_timesteps, (list, tuple)) and len(config_timesteps) > 0:
507
- single_pass_kwargs["timesteps"] = config_timesteps
508
- # IMPORTANTE: não usar first_pass_config.get("guidance_timesteps") aqui
509
-
510
  print("\n[INFO] Executando pipeline de etapa única...")
511
  ctx = contextlib.nullcontext()
512
  if self.device == "cuda":
513
  ctx = torch.autocast(device_type="cuda", dtype=self.runtime_autocast_dtype)
514
  with ctx:
515
  result_tensor = self.pipeline(**single_pass_kwargs).images
516
-
517
  pad_left, pad_right, pad_top, pad_bottom = padding_values
518
  slice_h_end = -pad_bottom if pad_bottom > 0 else None
519
  slice_w_end = -pad_right if pad_right > 0 else None
520
  result_tensor = result_tensor[:, :, :actual_num_frames, pad_top:slice_h_end, pad_left:slice_w_end]
521
  log_tensor_info(result_tensor, "Tensor Final (Após Pós-processamento, Antes de Salvar)")
522
 
523
- video_np = (result_tensor[0].permute(1, 2, 3, 0).cpu().float().numpy() * 255).astype(np.uint8)
524
-
525
  # Staging seguro em tmp e move para diretório persistente
526
  temp_dir = tempfile.mkdtemp(prefix="ltxv_")
527
  self._register_tmp_dir(temp_dir)
@@ -531,12 +513,20 @@ class VideoService:
531
  final_output_path = None
532
  output_video_path = os.path.join(temp_dir, f"output_{used_seed}.mp4")
533
  try:
 
534
  with imageio.get_writer(output_video_path, fps=call_kwargs["frame_rate"], codec="libx264", quality=8) as writer:
535
- total_frames = len(video_np)
536
- for i, frame in enumerate(video_np):
537
- writer.append_data(frame)
 
 
 
 
 
 
 
538
  if progress_callback:
539
- progress_callback(i + 1, total_frames)
540
 
541
  candidate_final = os.path.join(results_dir, f"output_{used_seed}.mp4")
542
  try:
@@ -549,15 +539,10 @@ class VideoService:
549
  self._log_gpu_memory("Fim da Geração")
550
  return final_output_path, used_seed
551
  finally:
552
- # Libera tensores/objetos grandes antes de limpar VRAM
553
  try:
554
  del result_tensor
555
  except Exception:
556
  pass
557
- try:
558
- del video_np
559
- except Exception:
560
- pass
561
  try:
562
  del multi_scale_pipeline
563
  except Exception:
@@ -574,11 +559,10 @@ class VideoService:
574
  except Exception:
575
  pass
576
 
577
- # Limpeza de temporários preservando o vídeo final
578
  try:
579
  self.finalize(keep_paths=[final_output_path] if final_output_path else [])
580
  except Exception:
581
  pass
582
 
583
  print("Criando instância do VideoService. O carregamento do modelo começará agora...")
584
- video_generation_service = VideoService()
 
63
  parts = [p.strip() for p in line.split(",")]
64
  if len(parts) >= 3:
65
  try:
66
+ pid = int(parts[^18_0])
67
+ name = parts[^18_1]
68
+ used_mb = int(parts[^18_2])
69
  user = "unknown"
70
  try:
71
  import psutil
 
163
  if self.latent_upsampler:
164
  self.latent_upsampler.to(self.device)
165
 
166
+ # Política de precisão (FP8 opcional + autocast coerente)
167
  self._apply_precision_policy()
168
 
169
  if self.device == "cuda":
 
171
  self._log_gpu_memory("Após carregar modelos")
172
  print("VideoService pronto para uso.")
173
 
 
174
  def _log_gpu_memory(self, stage_name: str):
175
  if self.device != "cuda":
176
  return
 
208
  pass
209
 
210
  def finalize(self, keep_paths=None, extra_paths=None, clear_gpu=True):
 
 
 
 
 
211
  keep = set(keep_paths or [])
212
  extras = set(extra_paths or [])
213
 
 
214
  for f in list(self._tmp_files | extras):
215
  try:
216
  if f not in keep and os.path.isfile(f):
 
220
  finally:
221
  self._tmp_files.discard(f)
222
 
 
223
  for d in list(self._tmp_dirs):
224
  try:
225
  if d not in keep and os.path.isdir(d):
 
229
  finally:
230
  self._tmp_dirs.discard(d)
231
 
 
232
  gc.collect()
233
  try:
234
  if clear_gpu and torch.cuda.is_available():
 
240
  except Exception:
241
  pass
242
 
 
243
  try:
244
  self._log_gpu_memory("Após finalize")
245
  except Exception:
246
  pass
247
 
248
  def _load_config(self):
 
249
  base = LTX_VIDEO_REPO_DIR / "configs"
250
  candidates = [
251
  base / "ltxv-13b-0.9.8-dev-fp8.yaml",
252
  base / "ltxv-13b-0.9.8-distilled-fp8.yaml",
253
  base / "ltxv-13b-0.9.8-dev-fp8.yaml.txt",
254
+ base / "ltxv-13b-0.9.8-distilled.yaml",
255
  ]
256
  for cfg in candidates:
257
  if cfg.exists():
258
  with open(cfg, "r") as file:
259
  return yaml.safe_load(file)
 
260
  config_file_path = base / "ltxv-13b-0.9.8-distilled.yaml"
261
  with open(config_file_path, "r") as file:
262
  return yaml.safe_load(file)
 
299
 
300
  return pipeline, latent_upsampler
301
 
 
302
  def _promote_fp8_weights_to_bf16(self, module):
 
303
  if not isinstance(module, torch.nn.Module):
304
  return
305
  f8 = getattr(torch, "float8_e4m3fn", None)
 
318
  b.data = b.data.to(torch.bfloat16)
319
  except Exception:
320
  pass
321
+
322
  def _apply_precision_policy(self):
323
  prec = str(self.config.get("precision", "")).lower()
324
  self.runtime_autocast_dtype = torch.float32
325
  if prec == "float8_e4m3fn":
 
326
  self.runtime_autocast_dtype = torch.bfloat16
327
  force_promote = os.getenv("LTXV_FORCE_BF16_ON_FP8", "0") == "1"
328
  if force_promote and hasattr(torch, "float8_e4m3fn"):
 
329
  try:
330
  self._promote_fp8_weights_to_bf16(self.pipeline)
331
  except Exception:
 
341
  self.runtime_autocast_dtype = torch.float16
342
  else:
343
  self.runtime_autocast_dtype = torch.float32
344
+
345
  def _prepare_conditioning_tensor(self, filepath, height, width, padding_values):
346
  tensor = load_image_to_tensor_with_resize_and_crop(filepath, height, width)
347
  tensor = torch.nn.functional.pad(tensor, padding_values)
 
442
  ).to(self.device)
443
 
444
  result_tensor = None
 
445
  multi_scale_pipeline = None
446
 
447
  if improve_texture:
 
479
  "skip_block_list": first_pass_config.get("skip_block_list"),
480
  }
481
  )
482
+
483
+ # Escolha de schedule única para garantir guidance_mapping definido e consistente
484
+ schedule = first_pass_config.get("timesteps")
485
+ if schedule is None:
486
+ schedule = first_pass_config.get("guidance_timesteps")
487
  if mode == "video-to-video":
488
+ schedule = [0.7]
489
  print("[INFO] Modo video-to-video (etapa única): definindo timesteps (força) para [0.7]")
490
+ if isinstance(schedule, (list, tuple)) and len(schedule) > 0:
491
+ single_pass_kwargs["timesteps"] = schedule
492
+ single_pass_kwargs["guidance_timesteps"] = schedule # garante criação de guidance_mapping
493
+
494
  print("\n[INFO] Executando pipeline de etapa única...")
495
  ctx = contextlib.nullcontext()
496
  if self.device == "cuda":
497
  ctx = torch.autocast(device_type="cuda", dtype=self.runtime_autocast_dtype)
498
  with ctx:
499
  result_tensor = self.pipeline(**single_pass_kwargs).images
500
+
501
  pad_left, pad_right, pad_top, pad_bottom = padding_values
502
  slice_h_end = -pad_bottom if pad_bottom > 0 else None
503
  slice_w_end = -pad_right if pad_right > 0 else None
504
  result_tensor = result_tensor[:, :, :actual_num_frames, pad_top:slice_h_end, pad_left:slice_w_end]
505
  log_tensor_info(result_tensor, "Tensor Final (Após Pós-processamento, Antes de Salvar)")
506
 
 
 
507
  # Staging seguro em tmp e move para diretório persistente
508
  temp_dir = tempfile.mkdtemp(prefix="ltxv_")
509
  self._register_tmp_dir(temp_dir)
 
513
  final_output_path = None
514
  output_video_path = os.path.join(temp_dir, f"output_{used_seed}.mp4")
515
  try:
516
+ # Escrita quadro a quadro para evitar array 4D gigante em RAM
517
  with imageio.get_writer(output_video_path, fps=call_kwargs["frame_rate"], codec="libx264", quality=8) as writer:
518
+ T = result_tensor.shape[^18_2] # (B, C, T, H, W)
519
+ for i in range(T):
520
+ frame_chw = result_tensor[0, :, i] # (C,H,W) no device
521
+ frame_hwc_u8 = (frame_chw.permute(1, 2, 0) # (H,W,C)
522
+ .clamp(0, 1)
523
+ .mul(255)
524
+ .to(torch.uint8)
525
+ .cpu()
526
+ .numpy())
527
+ writer.append_data(frame_hwc_u8)
528
  if progress_callback:
529
+ progress_callback(i + 1, T)
530
 
531
  candidate_final = os.path.join(results_dir, f"output_{used_seed}.mp4")
532
  try:
 
539
  self._log_gpu_memory("Fim da Geração")
540
  return final_output_path, used_seed
541
  finally:
 
542
  try:
543
  del result_tensor
544
  except Exception:
545
  pass
 
 
 
 
546
  try:
547
  del multi_scale_pipeline
548
  except Exception:
 
559
  except Exception:
560
  pass
561
 
 
562
  try:
563
  self.finalize(keep_paths=[final_output_path] if final_output_path else [])
564
  except Exception:
565
  pass
566
 
567
  print("Criando instância do VideoService. O carregamento do modelo começará agora...")
568
+ video_generation_service = VideoService()