Eueuiaa commited on
Commit
d67548c
·
verified ·
1 Parent(s): 8e066ce

Delete api/ltx_server_refactored.py

Browse files
Files changed (1) hide show
  1. api/ltx_server_refactored.py +0 -462
api/ltx_server_refactored.py DELETED
@@ -1,462 +0,0 @@
1
- # ltx_server.py — VideoService (beta 1.2 - Robusto e Completo)
2
- # DESCRIÇÃO:
3
- # - Servidor de geração de vídeo com pipeline de 2 passes para melhoria de textura.
4
- # - Gerenciamento de memória robusto com limpeza garantida via `finalize()`.
5
- # - Cálculo de dimensões inteligente para preservar a proporção e evitar erros.
6
- # - Suporte para divisão de tarefas longas em chunks para evitar OOM (Out of Memory).
7
- # - Concatenação de chunks com transições suaves (crossfade) para um resultado contínuo.
8
-
9
- # --- 0. WARNINGS, IMPORTS E CONFIGURAÇÃO DE AMBIENTE ---
10
-
11
- import warnings
12
- warnings.filterwarnings("ignore", category=UserWarning)
13
- warnings.filterwarnings("ignore", category=FutureWarning)
14
- from huggingface_hub import logging as hf_logging, hf_hub_download
15
- hf_logging.set_verbosity_error()
16
-
17
- import os
18
- import sys
19
- import subprocess
20
- import shlex
21
- import tempfile
22
- import gc
23
- import shutil
24
- import contextlib
25
- import time
26
- import traceback
27
- import json
28
- import yaml
29
- import random
30
- from typing import List, Dict
31
- from pathlib import Path
32
-
33
- import torch
34
- import torch.nn.functional as F
35
- import numpy as np
36
- import imageio
37
- from PIL import Image
38
- from einops import rearrange
39
-
40
- # --- Variáveis de Ambiente e Constantes ---
41
- LTXV_DEBUG = os.getenv("LTXV_DEBUG", "1") == "1"
42
- LTXV_FRAME_LOG_EVERY = int(os.getenv("LTXV_FRAME_LOG_EVERY", "8"))
43
- DEPS_DIR = Path("/data")
44
- LTX_VIDEO_REPO_DIR = DEPS_DIR / "LTX-Video"
45
-
46
- # --- 1. SETUP E GERENCIAMENTO DE DEPENDÊNCIAS ---
47
-
48
- def run_setup():
49
- """Executa o script de setup para clonar dependências se necessário."""
50
- setup_script_path = "setup.py"
51
- if not os.path.exists(setup_script_path):
52
- print("[DEBUG] 'setup.py' não encontrado. Pulando clonagem de dependências.")
53
- return
54
- try:
55
- print("[DEBUG] Executando setup.py para instalar dependências...")
56
- subprocess.run([sys.executable, setup_script_path], check=True, capture_output=True, text=True)
57
- print("[DEBUG] Setup concluído com sucesso.")
58
- except subprocess.CalledProcessError as e:
59
- print(f"[ERROR] Falha crítica ao executar setup.py (código {e.returncode}).\nOutput:\n{e.stdout}\n{e.stderr}")
60
- sys.exit(1)
61
-
62
- def add_deps_to_path():
63
- """Adiciona o diretório do repositório ao sys.path para importação dos módulos."""
64
- repo_path = str(LTX_VIDEO_REPO_DIR.resolve())
65
- if repo_path not in sys.path:
66
- sys.path.insert(0, repo_path)
67
- print(f"[DEBUG] Repositório LTX-Video adicionado ao sys.path: {repo_path}")
68
-
69
- # Executa a configuração inicial ao carregar o script
70
- if not LTX_VIDEO_REPO_DIR.exists():
71
- print(f"[INFO] Repositório não encontrado em {LTX_VIDEO_REPO_DIR}. Executando setup...")
72
- run_setup()
73
- add_deps_to_path()
74
-
75
- # --- Importações que dependem do sys.path modificado ---
76
- from managers.vae_manager import vae_manager_singleton
77
- from tools.video_encode_tool import video_encode_tool_singleton
78
- from ltx_video.pipelines.pipeline_ltx_video import ConditioningItem, LTXMultiScalePipeline, adain_filter_latent
79
- from ltx_video.utils.skip_layer_strategy import SkipLayerStrategy
80
- from ltx_video.models.autoencoders.vae_encode import un_normalize_latents, normalize_latents
81
- from api.ltx.inference import (
82
- create_ltx_video_pipeline, create_latent_upsampler,
83
- load_image_to_tensor_with_resize_and_crop, seed_everething,
84
- calculate_padding, load_media_file
85
- )
86
-
87
- # --- 2. FUNÇÕES UTILITÁRIAS INTELIGENTES ---
88
-
89
- def calculate_new_dimensions(orig_w, orig_h, target_area=512*768, divisor=8):
90
- """
91
- [FUNÇÃO INTELIGENTE]
92
- Calcula novas dimensões mantendo a proporção original, garantindo que ambos
93
- os lados sejam múltiplos do divisor. Visa uma 'área alvo' para manter o
94
- uso de VRAM consistente e previsível.
95
- """
96
- if orig_w <= 0 or orig_h <= 0:
97
- print(f"[WARN] Dimensões originais inválidas ({orig_w}x{orig_h}). Usando padrão 512x768.")
98
- return 512, 768
99
-
100
- aspect_ratio = orig_w / orig_h
101
- new_h = int((target_area / aspect_ratio)**0.5)
102
- new_w = int(new_h * aspect_ratio)
103
-
104
- final_w = round(new_w / divisor) * divisor
105
- final_h = round(new_h / divisor) * divisor
106
-
107
- final_w = max(divisor, final_w)
108
- final_h = max(divisor, final_h)
109
-
110
- if LTXV_DEBUG:
111
- print(f"[Dimension Calc] Original: {orig_w}x{orig_h} (AR: {aspect_ratio:.2f}) -> "
112
- f"Calculado: {new_w}x{new_h} -> Final (múltiplo de {divisor}): {final_w}x{final_h}")
113
- return final_h, final_w
114
-
115
- def log_tensor_info(tensor, name="Tensor"):
116
- """Exibe informações detalhadas sobre um tensor para depuração."""
117
- if not LTXV_DEBUG: return
118
- if not isinstance(tensor, torch.Tensor):
119
- print(f"\n[INFO] '{name}' não é um tensor.")
120
- return
121
- print(f"\n--- Tensor: {name} ---")
122
- print(f" - Shape: {tuple(tensor.shape)}")
123
- print(f" - Dtype: {tensor.dtype}")
124
- print(f" - Device: {tensor.device}")
125
- if tensor.numel() > 0:
126
- try:
127
- print(f" - Stats: Min={tensor.min().item():.4f}, Max={tensor.max().item():.4f}, Mean={tensor.mean().item():.4f}")
128
- except Exception as e:
129
- print(f" - Stats: Falha ao calcular estatísticas - {e}")
130
- print("------------------------------------------\n")
131
-
132
- # --- 3. CLASSE PRINCIPAL DO SERVIÇO DE VÍDEO ---
133
-
134
- class VideoService:
135
- def __init__(self):
136
- t0 = time.perf_counter()
137
- print("[INFO] Inicializando VideoService...")
138
- self.device = "cuda" if torch.cuda.is_available() else "cpu"
139
- self.config = self._load_config()
140
-
141
- print(f"[INFO] Config carregada (precision={self.config.get('precision')}, sampler={self.config.get('sampler')})")
142
- print(f"[INFO] Dispositivo selecionado: {self.device}")
143
-
144
- self._tmp_dirs = set()
145
- self._tmp_files = set()
146
-
147
- self.pipeline, self.latent_upsampler = self._load_models()
148
-
149
- print("[INFO] Movendo modelos para o dispositivo...")
150
- self.pipeline.to(self.device)
151
- if self.latent_upsampler:
152
- self.latent_upsampler.to(self.device)
153
-
154
- self._apply_precision_policy()
155
-
156
- vae_manager_singleton.attach_pipeline(
157
- self.pipeline,
158
- device=self.device,
159
- autocast_dtype=self.runtime_autocast_dtype
160
- )
161
- print("[INFO] VAE manager conectado ao pipeline.")
162
-
163
- if self.device == "cuda":
164
- torch.cuda.empty_cache()
165
-
166
- print(f"[SUCCESS] VideoService pronto. Tempo de inicialização: {time.perf_counter()-t0:.2f}s")
167
-
168
- # --- MÉTODOS INTERNOS: INICIALIZAÇÃO E SETUP ---
169
-
170
- def _load_config(self):
171
- """Carrega o arquivo de configuração YAML do modelo."""
172
- base = LTX_VIDEO_REPO_DIR / "configs"
173
- # Tenta carregar a configuração mais provável, com fallbacks
174
- candidates = [
175
- base / "ltxv-13b-0.9.8-dev-fp8.yaml",
176
- base / "ltxv-13b-0.9.8-distilled-fp8.yaml",
177
- base / "ltxv-13b-0.9.8-distilled.yaml",
178
- ]
179
- for cfg_path in candidates:
180
- if cfg_path.exists():
181
- print(f"[DEBUG] Configuração encontrada e selecionada: {cfg_path}")
182
- with open(cfg_path, "r") as file:
183
- return yaml.safe_load(file)
184
-
185
- raise FileNotFoundError(f"Nenhum arquivo de configuração YAML encontrado em {base}. Verifique a instalação.")
186
-
187
- def _load_models(self):
188
- t0 = time.perf_counter()
189
- LTX_REPO = "Lightricks/LTX-Video"
190
- print("[DEBUG] Baixando checkpoint principal...")
191
- distilled_model_path = hf_hub_download(
192
- repo_id=LTX_REPO,
193
- filename=self.config["checkpoint_path"],
194
- local_dir=os.getenv("HF_HOME"),
195
- cache_dir=os.getenv("HF_HOME_CACHE"),
196
- token=os.getenv("HF_TOKEN"),
197
- )
198
- self.config["checkpoint_path"] = distilled_model_path
199
- print(f"[DEBUG] Checkpoint em: {distilled_model_path}")
200
-
201
- print("[DEBUG] Baixando upscaler espacial...")
202
- spatial_upscaler_path = hf_hub_download(
203
- repo_id=LTX_REPO,
204
- filename=self.config["spatial_upscaler_model_path"],
205
- local_dir=os.getenv("HF_HOME"),
206
- cache_dir=os.getenv("HF_HOME_CACHE"),
207
- token=os.getenv("HF_TOKEN")
208
- )
209
- self.config["spatial_upscaler_model_path"] = spatial_upscaler_path
210
- print(f"[DEBUG] Upscaler em: {spatial_upscaler_path}")
211
-
212
- print("[DEBUG] Construindo pipeline...")
213
- pipeline = create_ltx_video_pipeline(
214
- ckpt_path=self.config["checkpoint_path"],
215
- precision=self.config["precision"],
216
- text_encoder_model_name_or_path=self.config["text_encoder_model_name_or_path"],
217
- sampler=self.config["sampler"],
218
- device="cpu",
219
- enhance_prompt=False,
220
- prompt_enhancer_image_caption_model_name_or_path=self.config["prompt_enhancer_image_caption_model_name_or_path"],
221
- prompt_enhancer_llm_model_name_or_path=self.config["prompt_enhancer_llm_model_name_or_path"],
222
- )
223
- print("[DEBUG] Pipeline pronto.")
224
-
225
- latent_upsampler = None
226
- if self.config.get("spatial_upscaler_model_path"):
227
- print("[DEBUG] Construindo latent_upsampler...")
228
- latent_upsampler = create_latent_upsampler(self.config["spatial_upscaler_model_path"], device="cpu")
229
- print("[DEBUG] Upsampler pronto.")
230
- print(f"[DEBUG] _load_models() tempo total={time.perf_counter()-t0:.3f}s")
231
- return pipeline, latent_upsampler
232
-
233
-
234
- def _apply_precision_policy(self):
235
- """Define o dtype a ser usado pelo autocast com base na configuração."""
236
- prec = str(self.config.get("precision", "")).lower()
237
- self.runtime_autocast_dtype = torch.float32
238
-
239
- if "bfloat16" in prec or "fp8" in prec:
240
- self.runtime_autocast_dtype = torch.bfloat16
241
- elif "mixed_precision" in prec or "fp16" in prec:
242
- self.runtime_autocast_dtype = torch.float16
243
-
244
- print(f"[INFO] Política de precisão aplicada. Dtype para Autocast: {self.runtime_autocast_dtype}")
245
-
246
- # --- MÉTODOS INTERNOS: OPERAÇÕES DE TENSOR E VÍDEO ---
247
-
248
- def _prepare_conditioning_tensor(self, filepath, height, width, padding_values):
249
- """Carrega uma imagem, redimensiona, aplica padding e move para o dispositivo correto."""
250
- tensor = load_image_to_tensor_with_resize_and_crop(filepath, height, width)
251
- tensor = F.pad(tensor, padding_values)
252
- return tensor.to(self.device, dtype=self.runtime_autocast_dtype)
253
-
254
- @torch.no_grad()
255
- def _upsample_latents_internal(self, latents: torch.Tensor) -> torch.Tensor:
256
- """Lógica de upscale de latentes, garantindo que os modelos estejam no dispositivo correto."""
257
- if not self.latent_upsampler:
258
- raise ValueError("Latent Upsampler não está carregado, mas foi solicitado.")
259
-
260
- # Garante que o VAE e o upsampler estejam no dispositivo correto para a operação
261
- self.latent_upsampler.to(self.device)
262
- self.pipeline.vae.to(self.device)
263
-
264
- latents_up = un_normalize_latents(latents, self.pipeline.vae, vae_per_channel_normalize=True)
265
- latents_up = self.latent_upsampler(latents_up)
266
- latents_up = normalize_latents(latents_up, self.pipeline.vae, vae_per_channel_normalize=True)
267
- return latents_up
268
-
269
- # --- MÉTODO PRINCIPAL DE LIMPEZA ---
270
-
271
- def finalize(self, keep_paths=None, clear_gpu=True):
272
- """
273
- [FUNÇÃO INTELIGENTE]
274
- Limpeza robusta para garantir a liberação de recursos de disco e GPU,
275
- mesmo em caso de falhas, prevenindo memory leaks.
276
- """
277
- print("[INFO] Finalize: iniciando limpeza de recursos...")
278
- keep = set(keep_paths or [])
279
- files_to_clean, dirs_to_clean = list(self._tmp_files), list(self._tmp_dirs)
280
- removed_files, removed_dirs = 0, 0
281
-
282
- for f in files_to_clean:
283
- try:
284
- if f not in keep and os.path.isfile(f):
285
- os.remove(f); removed_files += 1
286
- except OSError as e:
287
- print(f"[WARN] Falha ao remover arquivo temporário {f}: {e}")
288
- finally:
289
- self._tmp_files.discard(f)
290
-
291
- for d in dirs_to_clean:
292
- try:
293
- if d not in keep and os.path.isdir(d):
294
- shutil.rmtree(d, ignore_errors=True); removed_dirs += 1
295
- except OSError as e:
296
- print(f"[WARN] Falha ao remover diretório temporário {d}: {e}")
297
- finally:
298
- self._tmp_dirs.discard(d)
299
-
300
- if LTXV_DEBUG:
301
- print(f"[DEBUG] Limpeza de disco: {removed_files} arquivos e {removed_dirs} diretórios removidos.")
302
-
303
- gc.collect()
304
-
305
- if clear_gpu and self.device == "cuda":
306
- try:
307
- torch.cuda.empty_cache()
308
- torch.cuda.ipc_collect()
309
- if LTXV_DEBUG: print("[DEBUG] Limpeza da GPU concluída com sucesso.")
310
- except Exception as e:
311
- print(f"[ERROR] Falha crítica durante a limpeza da GPU: {e}")
312
-
313
- # ==============================================================================
314
- # --- FUNÇÃO PRINCIPAL DE GERAÇÃO (generate) ---
315
- # ==============================================================================
316
-
317
- @torch.no_grad()
318
- def generate(
319
- self,
320
- prompt: str,
321
- negative_prompt: str = "",
322
- mode: str = "text-to-video",
323
- start_image_filepath: str = None,
324
- height: int = 512,
325
- width: int = 704,
326
- duration: float = 2.0,
327
- seed: int = 42,
328
- randomize_seed: bool = True,
329
- guidance_scale: float = 3.0,
330
- improve_texture: bool = True,
331
- ):
332
- output_path, final_seed = None, None
333
- try:
334
- t_all = time.perf_counter()
335
- print(f"\n{'='*20} INICIANDO NOVA GERAÇÃO {'='*20}")
336
-
337
- if self.device == "cuda":
338
- torch.cuda.empty_cache()
339
-
340
- # --- 1. Setup da Geração (parâmetros, seed, dimensões) ---
341
- if mode == "image-to-video" and not start_image_filepath:
342
- raise ValueError("Imagem de início é obrigatória para o modo 'image-to-video'")
343
-
344
- final_seed = random.randint(0, 2**32 - 1) if randomize_seed else int(seed)
345
- seed_everething(final_seed)
346
- print(f"[INFO] Geração com Seed: {final_seed}")
347
-
348
- FPS = 24.0; MAX_NUM_FRAMES = 2570
349
- target_frames_rounded = round(duration * FPS)
350
- n_val = round((float(target_frames_rounded) - 1.0) / 8.0)
351
- actual_num_frames = max(9, min(MAX_NUM_FRAMES, int(n_val * 8 + 1)))
352
-
353
- height_padded = ((height - 1) // 8 + 1) * 8
354
- width_padded = ((width - 1) // 8 + 1) * 8
355
- padding_values = calculate_padding(height, width, height_padded, width_padded)
356
- generator = torch.Generator(device=self.device).manual_seed(final_seed)
357
-
358
- temp_dir = tempfile.mkdtemp(prefix="ltxv_")
359
- self._tmp_dirs.add(temp_dir)
360
-
361
- # --- 2. Preparação dos Tensores de Condicionamento ---
362
- conditioning_items = []
363
- if mode == "image-to-video" and start_image_filepath:
364
- start_tensor = self._prepare_conditioning_tensor(start_image_filepath, height, width, padding_values)
365
- conditioning_items.append(ConditioningItem(start_tensor, 0, 1.0))
366
-
367
- # --- 3. Construção dos Argumentos da Pipeline ---
368
- call_kwargs = self.config.get("pipeline_defaults", {}).copy() # Carrega defaults do YAML
369
- call_kwargs.update({
370
- "prompt": prompt, "negative_prompt": negative_prompt,
371
- "height": height_padded, "width": width_padded,
372
- "num_frames": actual_num_frames, "frame_rate": int(FPS),
373
- "generator": generator, "output_type": "latent",
374
- "conditioning_items": conditioning_items or None,
375
- "guidance_scale": float(guidance_scale),
376
- })
377
-
378
- # --- 4. Lógica de Geração (Pipeline de 1 ou 2 passes) ---
379
- final_latents = None
380
- ctx = torch.autocast(device_type="cuda", dtype=self.runtime_autocast_dtype)
381
-
382
- with ctx:
383
- if improve_texture:
384
- print("[INFO] Iniciando pipeline de 2 passes para melhoria de textura.")
385
-
386
- # ETAPA 1: Geração Base em Baixa Resolução
387
- downscale_factor = self.config.get("downscale_factor", 0.5)
388
- target_low_res_area = (width * height) * (downscale_factor**2)
389
- downscaled_h, downscaled_w = calculate_new_dimensions(width, height, target_area=target_low_res_area)
390
-
391
- first_pass_kwargs = call_kwargs.copy()
392
- first_pass_kwargs.update(self.config.get("first_pass", {}))
393
- first_pass_kwargs.update({"width": downscaled_w, "height": downscaled_h, "guidance_scale": float(guidance_scale)})
394
-
395
- base_latents = self.pipeline(**first_pass_kwargs).images
396
- log_tensor_info(base_latents, "Latentes Base (Passo 1)")
397
-
398
- # ETAPA 2: Upscale e Refinamento
399
- upsampled_latents = self._upsample_latents_internal(base_latents)
400
- del base_latents; gc.collect(); torch.cuda.empty_cache()
401
-
402
- second_pass_kwargs = call_kwargs.copy()
403
- second_pass_kwargs.update(self.config.get("second_pass", {}))
404
- second_pass_kwargs.update({"latents": upsampled_latents, "guidance_scale": float(guidance_scale)})
405
-
406
- final_latents = self.pipeline(**second_pass_kwargs).images
407
- log_tensor_info(final_latents, "Latentes Finais (Passo 2)")
408
-
409
- else:
410
- print("[INFO] Iniciando pipeline de 1 passe.")
411
- final_latents = self.pipeline(**call_kwargs).images
412
- log_tensor_info(final_latents, "Latentes Finais (Passe Único)")
413
-
414
- # --- 5. Decodificação, Codificação de Vídeo e Finalização ---
415
- print("[INFO] Decodificando latentes para pixels com VAE...")
416
- pixel_tensor = vae_manager_singleton.decode(
417
- final_latents.to(self.device),
418
- decode_timestep=float(self.config.get("decode_timestep", 0.05))
419
- )
420
- del final_latents; gc.collect(); torch.cuda.empty_cache()
421
-
422
- output_video_path_tmp = os.path.join(temp_dir, f"output_{final_seed}.mp4")
423
-
424
- print(f"[INFO] Codificando vídeo final para: {output_video_path_tmp}")
425
- video_encode_tool_singleton.save_video_from_tensor(
426
- pixel_tensor, output_video_path_tmp, fps=call_kwargs["frame_rate"]
427
- )
428
- del pixel_tensor
429
-
430
- results_dir = "/app/output"
431
- os.makedirs(results_dir, exist_ok=True)
432
- output_path = os.path.join(results_dir, f"final_video_{final_seed}.mp4")
433
- shutil.move(output_video_path_tmp, output_path)
434
-
435
- print(f"[SUCCESS] Geração concluída em {time.perf_counter() - t_all:.2f}s. Vídeo salvo em: {output_path}")
436
- return output_path, final_seed
437
-
438
- except Exception as e:
439
- print(f"[FATAL ERROR] A geração falhou: {type(e).__name__} - {e}")
440
- traceback.print_exc()
441
- raise
442
-
443
- finally:
444
- print("[INFO] Executando limpeza final da tarefa...")
445
- self.finalize(keep_paths=[output_path] if output_path else [])
446
-
447
-
448
- # --- Ponto de Entrada Principal ---
449
- if __name__ == "__main__":
450
- print("Iniciando carregamento do VideoService...")
451
- video_generation_service = VideoService()
452
- print("\n[INFO] VideoService carregado e pronto para receber tarefas.")
453
- # Exemplo de como chamar a geração (pode ser substituído por uma API)
454
- try:
455
- video_generation_service.generate(
456
- prompt="A cinematic shot of a panda drinking bubble tea in a Tokyo cafe",
457
- negative_prompt="blurry, low quality, cartoon",
458
- duration=3.0,
459
- improve_texture=True
460
- )
461
- except Exception as e:
462
- print("\n[MAIN] Exemplo de geração falhou. O servidor ainda está de pé, mas verifique o erro acima.")