Update api/ltx_server.py
Browse files- api/ltx_server.py +90 -72
api/ltx_server.py
CHANGED
|
@@ -366,31 +366,57 @@ class VideoService:
|
|
| 366 |
return yaml.safe_load(file)
|
| 367 |
|
| 368 |
def _load_models(self):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
t0 = time.perf_counter()
|
| 370 |
LTX_REPO = "Lightricks/LTX-Video"
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 378 |
)
|
| 379 |
self.config["checkpoint_path"] = distilled_model_path
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
spatial_upscaler_path =
|
| 384 |
-
|
| 385 |
-
filename=self.config["spatial_upscaler_model_path"],
|
| 386 |
-
local_dir=os.getenv("HF_HOME"),
|
| 387 |
-
cache_dir=os.getenv("HF_HOME_CACHE"),
|
| 388 |
-
token=os.getenv("HF_TOKEN")
|
| 389 |
)
|
| 390 |
self.config["spatial_upscaler_model_path"] = spatial_upscaler_path
|
| 391 |
-
print(f"[DEBUG] Upscaler em: {spatial_upscaler_path}")
|
| 392 |
|
| 393 |
-
|
|
|
|
| 394 |
pipeline = create_ltx_video_pipeline(
|
| 395 |
ckpt_path=self.config["checkpoint_path"],
|
| 396 |
precision=self.config["precision"],
|
|
@@ -408,6 +434,7 @@ class VideoService:
|
|
| 408 |
print("[DEBUG] Construindo latent_upsampler...")
|
| 409 |
latent_upsampler = create_latent_upsampler(self.config["spatial_upscaler_model_path"], device="cpu")
|
| 410 |
print("[DEBUG] Upsampler pronto.")
|
|
|
|
| 411 |
print(f"[DEBUG] _load_models() tempo total={time.perf_counter()-t0:.3f}s")
|
| 412 |
return pipeline, latent_upsampler
|
| 413 |
|
|
@@ -435,8 +462,6 @@ class VideoService:
|
|
| 435 |
pass
|
| 436 |
print(f"[DEBUG] FP8→BF16: params_promoted={p_cnt}, buffers_promoted={b_cnt}")
|
| 437 |
|
| 438 |
-
|
| 439 |
-
|
| 440 |
@torch.no_grad()
|
| 441 |
def _upsample_latents_internal(self, latents: torch.Tensor) -> torch.Tensor:
|
| 442 |
"""
|
|
@@ -453,11 +478,8 @@ class VideoService:
|
|
| 453 |
upsampled_latents = self.latent_upsampler(latents)
|
| 454 |
upsampled_latents = normalize_latents(upsampled_latents, self.pipeline.vae, vae_per_channel_normalize=True)
|
| 455 |
print(f"[DEBUG-UPSAMPLE] Shape de saída: {tuple(upsampled_latents.shape)}")
|
| 456 |
-
|
| 457 |
return upsampled_latents
|
| 458 |
|
| 459 |
-
|
| 460 |
-
|
| 461 |
def _apply_precision_policy(self):
|
| 462 |
prec = str(self.config.get("precision", "")).lower()
|
| 463 |
self.runtime_autocast_dtype = torch.float32
|
|
@@ -491,7 +513,6 @@ class VideoService:
|
|
| 491 |
print(f"[DEBUG] Cond shape={tuple(out.shape)} dtype={out.dtype} device={out.device}")
|
| 492 |
return out
|
| 493 |
|
| 494 |
-
|
| 495 |
def _dividir_latentes_por_tamanho(self, latents_brutos, num_latente_por_chunk: int, overlap: int = 1):
|
| 496 |
"""
|
| 497 |
Divide o tensor de latentes em chunks com tamanho definido em número de latentes.
|
|
@@ -626,7 +647,6 @@ class VideoService:
|
|
| 626 |
print(f"[DEBUG] Video podado {i+1} adicionado {self._get_total_frames(video_podado)} frames ✅")
|
| 627 |
|
| 628 |
|
| 629 |
-
|
| 630 |
print("===========CONCATECAO CAUSAL=============")
|
| 631 |
print(f"[DEBUG] {nova_lista}")
|
| 632 |
return nova_lista
|
|
@@ -804,7 +824,7 @@ class VideoService:
|
|
| 804 |
except Exception:
|
| 805 |
pass
|
| 806 |
|
| 807 |
-
latents_parts_up = self._dividir_latentes_por_tamanho(latents_cpu_up,15,
|
| 808 |
|
| 809 |
for latents in latents_parts_up:
|
| 810 |
|
|
@@ -832,8 +852,8 @@ class VideoService:
|
|
| 832 |
"output_type": "latent",
|
| 833 |
"width": second_pass_width,
|
| 834 |
"height": second_pass_height,
|
| 835 |
-
|
| 836 |
-
"latents":
|
| 837 |
"guidance_scale": float(guidance_scale),
|
| 838 |
**second_pass_config
|
| 839 |
})
|
|
@@ -861,54 +881,52 @@ class VideoService:
|
|
| 861 |
|
| 862 |
# --- ETAPA FINAL: DECODIFICAÇÃO E CODIFICAÇÃO MP4 ---
|
| 863 |
print("\n--- INICIANDO ETAPA FINAL: DECODIFICAÇÃO E MONTAGEM ---")
|
|
|
|
|
|
|
| 864 |
|
| 865 |
-
|
| 866 |
-
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
for latents in latents_list:
|
| 874 |
-
latents_parts.append(self._dividir_latentes_por_tamanho(latents,15,1))
|
| 875 |
|
|
|
|
|
|
|
| 876 |
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 883 |
-
|
| 884 |
-
|
| 885 |
-
|
| 886 |
-
|
| 887 |
-
|
| 888 |
-
|
| 889 |
-
|
| 890 |
-
|
| 891 |
-
|
| 892 |
-
|
| 893 |
-
|
| 894 |
-
|
| 895 |
-
|
| 896 |
-
|
| 897 |
-
|
| 898 |
-
|
| 899 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 900 |
|
| 901 |
-
candidate = os.path.join(results_dir, f"output_par_{par}.mp4")
|
| 902 |
-
try:
|
| 903 |
-
shutil.move(output_video_path, candidate)
|
| 904 |
-
final_output_path = candidate
|
| 905 |
-
print(f"[DEBUG] MP4 parte {par} movido para {final_output_path}")
|
| 906 |
-
partes_mp4.append(final_output_path)
|
| 907 |
-
|
| 908 |
-
except Exception as e:
|
| 909 |
-
final_output_path = output_video_path
|
| 910 |
-
print(f"[DEBUG] Falha no move; usando tmp como final: {e}")
|
| 911 |
-
|
| 912 |
total_partes = len(partes_mp4)
|
| 913 |
if (total_partes>1):
|
| 914 |
final_vid = os.path.join(results_dir, f"concat_fim_{used_seed}.mp4")
|
|
|
|
| 366 |
return yaml.safe_load(file)
|
| 367 |
|
| 368 |
def _load_models(self):
|
| 369 |
+
"""
|
| 370 |
+
Carrega os modelos de forma inteligente:
|
| 371 |
+
1. Tenta resolver o caminho do cache local (rápido, sem rede).
|
| 372 |
+
2. Se o arquivo não for encontrado localmente, baixa como fallback.
|
| 373 |
+
Garante que o serviço possa iniciar mesmo que o setup.py não tenha sido executado.
|
| 374 |
+
"""
|
| 375 |
t0 = time.perf_counter()
|
| 376 |
LTX_REPO = "Lightricks/LTX-Video"
|
| 377 |
+
|
| 378 |
+
print("[DEBUG] Resolvendo caminhos dos modelos de forma inteligente...")
|
| 379 |
+
|
| 380 |
+
# --- Função Auxiliar para Carregamento Inteligente ---
|
| 381 |
+
def get_or_download_model(repo_id, filename, description):
|
| 382 |
+
try:
|
| 383 |
+
# hf_hub_download é a ferramenta certa aqui. Ela verifica o cache PRIMEIRO.
|
| 384 |
+
# Se o arquivo estiver no cache, retorna o caminho instantaneamente (após uma verificação rápida de metadados).
|
| 385 |
+
# Se não estiver no cache, ela o baixa.
|
| 386 |
+
print(f"[DEBUG] Verificando {description}: {filename}...")
|
| 387 |
+
model_path = hf_hub_download(
|
| 388 |
+
repo_id=repo_id,
|
| 389 |
+
filename=filename,
|
| 390 |
+
# Forçar o uso de um cache específico se necessário
|
| 391 |
+
cache_dir=os.getenv("HF_HOME_CACHE"),
|
| 392 |
+
token=os.getenv("HF_TOKEN")
|
| 393 |
+
)
|
| 394 |
+
print(f"[DEBUG] Caminho do {description} resolvido com sucesso.")
|
| 395 |
+
return model_path
|
| 396 |
+
except Exception as e:
|
| 397 |
+
print("\n" + "="*80)
|
| 398 |
+
print(f"[ERRO CRÍTICO] Falha ao obter o modelo '{filename}'.")
|
| 399 |
+
print(f"Detalhe do erro: {e}")
|
| 400 |
+
print("Verifique sua conexão com a internet ou o estado do cache do Hugging Face.")
|
| 401 |
+
print("="*80 + "\n")
|
| 402 |
+
sys.exit(1)
|
| 403 |
+
|
| 404 |
+
# --- Checkpoint Principal ---
|
| 405 |
+
checkpoint_filename = self.config["checkpoint_path"]
|
| 406 |
+
distilled_model_path = get_or_download_model(
|
| 407 |
+
LTX_REPO, checkpoint_filename, "checkpoint principal"
|
| 408 |
)
|
| 409 |
self.config["checkpoint_path"] = distilled_model_path
|
| 410 |
+
|
| 411 |
+
# --- Upscaler Espacial ---
|
| 412 |
+
upscaler_filename = self.config["spatial_upscaler_model_path"]
|
| 413 |
+
spatial_upscaler_path = get_or_download_model(
|
| 414 |
+
LTX_REPO, upscaler_filename, "upscaler espacial"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 415 |
)
|
| 416 |
self.config["spatial_upscaler_model_path"] = spatial_upscaler_path
|
|
|
|
| 417 |
|
| 418 |
+
# --- Construção dos Pipelines ---
|
| 419 |
+
print("\n[DEBUG] Construindo pipeline a partir dos caminhos resolvidos...")
|
| 420 |
pipeline = create_ltx_video_pipeline(
|
| 421 |
ckpt_path=self.config["checkpoint_path"],
|
| 422 |
precision=self.config["precision"],
|
|
|
|
| 434 |
print("[DEBUG] Construindo latent_upsampler...")
|
| 435 |
latent_upsampler = create_latent_upsampler(self.config["spatial_upscaler_model_path"], device="cpu")
|
| 436 |
print("[DEBUG] Upsampler pronto.")
|
| 437 |
+
|
| 438 |
print(f"[DEBUG] _load_models() tempo total={time.perf_counter()-t0:.3f}s")
|
| 439 |
return pipeline, latent_upsampler
|
| 440 |
|
|
|
|
| 462 |
pass
|
| 463 |
print(f"[DEBUG] FP8→BF16: params_promoted={p_cnt}, buffers_promoted={b_cnt}")
|
| 464 |
|
|
|
|
|
|
|
| 465 |
@torch.no_grad()
|
| 466 |
def _upsample_latents_internal(self, latents: torch.Tensor) -> torch.Tensor:
|
| 467 |
"""
|
|
|
|
| 478 |
upsampled_latents = self.latent_upsampler(latents)
|
| 479 |
upsampled_latents = normalize_latents(upsampled_latents, self.pipeline.vae, vae_per_channel_normalize=True)
|
| 480 |
print(f"[DEBUG-UPSAMPLE] Shape de saída: {tuple(upsampled_latents.shape)}")
|
|
|
|
| 481 |
return upsampled_latents
|
| 482 |
|
|
|
|
|
|
|
| 483 |
def _apply_precision_policy(self):
|
| 484 |
prec = str(self.config.get("precision", "")).lower()
|
| 485 |
self.runtime_autocast_dtype = torch.float32
|
|
|
|
| 513 |
print(f"[DEBUG] Cond shape={tuple(out.shape)} dtype={out.dtype} device={out.device}")
|
| 514 |
return out
|
| 515 |
|
|
|
|
| 516 |
def _dividir_latentes_por_tamanho(self, latents_brutos, num_latente_por_chunk: int, overlap: int = 1):
|
| 517 |
"""
|
| 518 |
Divide o tensor de latentes em chunks com tamanho definido em número de latentes.
|
|
|
|
| 647 |
print(f"[DEBUG] Video podado {i+1} adicionado {self._get_total_frames(video_podado)} frames ✅")
|
| 648 |
|
| 649 |
|
|
|
|
| 650 |
print("===========CONCATECAO CAUSAL=============")
|
| 651 |
print(f"[DEBUG] {nova_lista}")
|
| 652 |
return nova_lista
|
|
|
|
| 824 |
except Exception:
|
| 825 |
pass
|
| 826 |
|
| 827 |
+
latents_parts_up = self._dividir_latentes_por_tamanho(latents_cpu_up,15,0)
|
| 828 |
|
| 829 |
for latents in latents_parts_up:
|
| 830 |
|
|
|
|
| 852 |
"output_type": "latent",
|
| 853 |
"width": second_pass_width,
|
| 854 |
"height": second_pass_height,
|
| 855 |
+
"num_frames": num_pixel_frames_part,
|
| 856 |
+
"latents": latents, # O tensor upscaled
|
| 857 |
"guidance_scale": float(guidance_scale),
|
| 858 |
**second_pass_config
|
| 859 |
})
|
|
|
|
| 881 |
|
| 882 |
# --- ETAPA FINAL: DECODIFICAÇÃO E CODIFICAÇÃO MP4 ---
|
| 883 |
print("\n--- INICIANDO ETAPA FINAL: DECODIFICAÇÃO E MONTAGEM ---")
|
| 884 |
+
|
| 885 |
+
for latents_vae in latents_list:
|
| 886 |
|
| 887 |
+
latents_cpu_vae = latents_vae.detach().to("cpu", non_blocking=True)
|
| 888 |
+
torch.cuda.empty_cache()
|
| 889 |
+
try:
|
| 890 |
+
torch.cuda.ipc_collect()
|
| 891 |
+
except Exception:
|
| 892 |
+
pass
|
| 893 |
+
|
| 894 |
+
latents_parts_vae = self._dividir_latentes_por_tamanho(latents_cpu_vae,4,1)
|
|
|
|
|
|
|
| 895 |
|
| 896 |
+
for latents in latents_parts_vae:
|
| 897 |
+
print(f"[DEBUG] Partição {par}: {tuple(latents.shape)}")
|
| 898 |
|
| 899 |
+
par = par + 1
|
| 900 |
+
output_video_path = os.path.join(temp_dir, f"output_{used_seed}_{par}.mp4")
|
| 901 |
+
final_output_path = None
|
| 902 |
+
|
| 903 |
+
print("[DEBUG] Decodificando bloco de latentes com VAE → tensor de pixels...")
|
| 904 |
+
# Usar manager com timestep por item; previne target_shape e rota NoneType.decode
|
| 905 |
+
pixel_tensor = vae_manager_singleton.decode(
|
| 906 |
+
latents.to(self.device, non_blocking=True),
|
| 907 |
+
decode_timestep=float(self.config.get("decode_timestep", 0.05))
|
| 908 |
+
)
|
| 909 |
+
log_tensor_info(pixel_tensor, "Pixel tensor (VAE saída)")
|
| 910 |
+
|
| 911 |
+
print("[DEBUG] Codificando MP4 a partir do tensor de pixels (bloco inteiro)...")
|
| 912 |
+
video_encode_tool_singleton.save_video_from_tensor(
|
| 913 |
+
pixel_tensor,
|
| 914 |
+
output_video_path,
|
| 915 |
+
fps=call_kwargs["frame_rate"],
|
| 916 |
+
progress_callback=progress_callback
|
| 917 |
+
)
|
| 918 |
+
|
| 919 |
+
candidate = os.path.join(results_dir, f"output_par_{par}.mp4")
|
| 920 |
+
try:
|
| 921 |
+
shutil.move(output_video_path, candidate)
|
| 922 |
+
final_output_path = candidate
|
| 923 |
+
print(f"[DEBUG] MP4 parte {par} movido para {final_output_path}")
|
| 924 |
+
partes_mp4.append(final_output_path)
|
| 925 |
+
|
| 926 |
+
except Exception as e:
|
| 927 |
+
final_output_path = output_video_path
|
| 928 |
+
print(f"[DEBUG] Falha no move; usando tmp como final: {e}")
|
| 929 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 930 |
total_partes = len(partes_mp4)
|
| 931 |
if (total_partes>1):
|
| 932 |
final_vid = os.path.join(results_dir, f"concat_fim_{used_seed}.mp4")
|