EuuIia commited on
Commit
cd93d5b
·
verified ·
1 Parent(s): eee1c29

Update api/ltx_server.py

Browse files
Files changed (1) hide show
  1. api/ltx_server.py +95 -404
api/ltx_server.py CHANGED
@@ -396,55 +396,22 @@ class VideoService:
396
  return out
397
 
398
 
399
- def _dividir_latentes_em_partes(self, latents_brutos, quantidade: int):
400
- """
401
- Divide um tensor de latentes em `quantidade` partes e retorna uma lista de clones.
402
-
403
- Args:
404
- latents_brutos: tensor [B, C, T, H, W]
405
- quantidade: número de partes que queremos dividir
406
 
407
- Returns:
408
- List[Tensor]: lista de `quantidade` partes, cada uma cloneada
409
- """
410
- total = latents_brutos.shape[2] # dimensão temporal
411
- partes = []
412
-
413
- if quantidade <= 1 or quantidade > total:
414
- return [latents_brutos.clone()]
415
-
416
- # calcular tamanho aproximado de cada parte
417
- step = total // quantidade
418
- overlap = 0 # sobreposição mínima de 1 frame entre partes
419
-
420
- for i in range(quantidade):
421
- start = i * step
422
- end = start + step
423
- if i == quantidade - 1:
424
- end = total # última parte vai até o final
425
- else:
426
- end += overlap # sobreposição
427
- parte = latents_brutos[:, :, start-1:end+1, :, :].clone()
428
- partes.append(parte)
429
-
430
- return partes
431
-
432
-
433
- def dividir_latentes(latents_brutos):
434
- total = latents_brutos.shape[2] # dimensão temporal (latentes)
435
-
436
  if total % 2 == 1: # ÍMPAR
437
- cut = total // 2
438
- primeira = latents_brutos[:, :, :cut+1, :, :].clone()
439
- segunda = latents_brutos[:, :, cut:, :, :].clone()
 
 
440
  else: # PAR
441
- cut = total // 2
442
- # primeira parte até o meio, mas o último frame deve ser ajustado
443
- primeira = latents_brutos[:, :, :cut+1, :, :].clone()
444
- segunda = latents_brutos[:, :, cut:, :, :].clone()
445
-
446
  return primeira, segunda
447
-
448
 
449
  def _concat_mp4s_no_reencode(self, mp4_list: List[str], out_path: str):
450
  """
@@ -472,7 +439,6 @@ class VideoService:
472
  pass
473
 
474
 
475
-
476
  def generate(
477
  self,
478
  prompt,
@@ -575,41 +541,50 @@ class VideoService:
575
  print(f"[DEBUG] media_items shape={tuple(media.shape)}")
576
 
577
  latents = None
578
-
579
- try:
580
- ctx = torch.autocast(device_type="cuda", dtype=self.runtime_autocast_dtype) if self.device == "cuda" else contextlib.nullcontext()
581
 
 
582
  if improve_texture:
583
  if not self.latent_upsampler:
584
  raise ValueError("Upscaler espacial não carregado.")
585
 
 
 
586
  # --- PASSO 1: GERAÇÃO DE LATENTES EM BAIXA RESOLUÇÃO ---
587
  print("[DEBUG] Multi-escala: Iniciando Passo 1 (geração de latentes base).")
588
 
589
  first_pass_args = self.config.get("first_pass", {}).copy()
590
  first_pass_kwargs = call_kwargs.copy()
 
 
591
  first_pass_kwargs.update({
592
- "guidance_scale": float(guidance_scale),
593
  "stg_scale": first_pass_args.get("stg_scale"),
594
  "rescaling_scale": first_pass_args.get("rescaling_scale"),
595
  "skip_block_list": first_pass_args.get("skip_block_list"),
 
 
596
  })
597
- schedule = first_pass_args.get("timesteps") or first_pass_args.get("guidance_timesteps")
598
- if schedule:
599
- first_pass_kwargs["timesteps"] = schedule
600
- first_pass_kwargs["guidance_timesteps"] = schedule
601
-
602
  downscale_factor = self.config.get("downscale_factor", 2)
603
  original_height = first_pass_kwargs["height"]
604
  original_width = first_pass_kwargs["width"]
605
  divisor = 24
606
-
607
- target_height_p1 = original_height // downscale_factor
 
 
 
 
 
 
 
608
  height_p1 = round(target_height_p1 / divisor) * divisor
609
  if height_p1 == 0: height_p1 = divisor
610
  first_pass_kwargs["height"] = height_p1
611
 
612
- target_width_p1 = original_width // downscale_factor
613
  width_p1 = round(target_width_p1 / divisor) * divisor
614
  if width_p1 == 0: width_p1 = divisor
615
  first_pass_kwargs["width"] = width_p1
@@ -622,7 +597,7 @@ class VideoService:
622
  latents_low_res = first_pass_result.images
623
  log_tensor_info(latents_low_res, "Latentes (Passo 1)")
624
 
625
- del first_pass_result
626
  gc.collect()
627
  if self.device == "cuda": torch.cuda.empty_cache()
628
 
@@ -641,48 +616,40 @@ class VideoService:
641
  second_pass_args = self.config.get("second_pass", {}).copy()
642
  second_pass_kwargs = call_kwargs.copy()
643
 
644
- height_p2 = height_p1 * 2
645
- width_p2 = width_p1 * 2
646
- second_pass_kwargs["height"] = height_p2
647
- second_pass_kwargs["width"] = width_p2
648
- print(f"[DEBUG] Passo 2: Dimensões definidas para {height_p2}x{width_p2} para corresponder ao upscale.")
649
-
650
  second_pass_kwargs.update({
651
- "guidance_scale": float(guidance_scale),
652
  "stg_scale": second_pass_args.get("stg_scale"),
653
  "rescaling_scale": second_pass_args.get("rescaling_scale"),
654
  "skip_block_list": second_pass_args.get("skip_block_list"),
 
 
655
  })
 
656
 
657
- schedule_p2 = second_pass_args.get("timesteps") or second_pass_args.get("guidance_timesteps")
658
- if schedule_p2:
659
- timesteps_para_refinamento = schedule_p2
660
- print(f"[DEBUG] Passo 2: Usando {len(timesteps_para_refinamento)} timesteps pré-definidos do config para refinamento.")
661
- else:
662
- strength_p2 = second_pass_args.get("strength", second_pass_args.get("denoising_strength", 0.4))
663
- num_steps_passo2_total = second_pass_args.get("num_inference_steps", 20)
664
-
665
- self.pipeline.scheduler.set_timesteps(num_steps_passo2_total, device=self.device)
666
- todos_os_timesteps_p2 = self.pipeline.scheduler.timesteps
667
-
668
- ponto_de_corte = int(len(todos_os_timesteps_p2) * (1.0 - strength_p2))
669
- timesteps_para_refinamento = todos_os_timesteps_p2[ponto_de_corte:]
670
- print(f"[DEBUG] Passo 2: Calculando {len(timesteps_para_refinamento)} timesteps manuais (strength ≈ {strength_p2})")
671
-
672
- second_pass_kwargs["timesteps"] = timesteps_para_refinamento
673
 
674
- if "strength" in second_pass_kwargs: del second_pass_kwargs["strength"]
675
-
676
  second_pass_kwargs["latents"] = latents_high_res
677
 
 
 
 
 
678
  with ctx:
679
  second_pass_result = self.pipeline(**second_pass_kwargs)
680
 
681
  latents = second_pass_result.images
682
  log_tensor_info(latents, "Latentes Finais (Passo 2)")
683
-
684
- else:
685
- # --- PASSO ÚNICO (SINGLE-PASS) ---
686
  single_pass_kwargs = call_kwargs.copy()
687
  first_pass_config = self.config.get("first_pass", {})
688
  single_pass_kwargs.update(
@@ -702,22 +669,30 @@ class VideoService:
702
  print(f"[DEBUG] Single-pass: timesteps_len={len(schedule) if schedule else 0}")
703
 
704
  print("\n[INFO] Executando pipeline de etapa única...")
 
 
705
  with ctx:
706
  result = self.pipeline(**single_pass_kwargs)
707
-
708
- latents = result.images
 
 
 
 
 
 
709
  print(f"[DEBUG] Latentes (single-pass): shape={tuple(latents.shape)}")
710
 
711
- # --- DECODIFICAÇÃO E CODIFICAÇÃO DE VÍDEO FINAL ---
712
 
713
  latents_cpu = latents.detach().to("cpu", non_blocking=True)
714
- if self.device == "cuda":
715
- torch.cuda.empty_cache()
716
- try:
717
- torch.cuda.ipc_collect()
718
- except Exception:
719
- pass
720
 
 
721
  lat_a, lat_b = self._dividir_latentes(latents_cpu)
722
  lat_a1, lat_a2 = self._dividir_latentes(lat_a)
723
  lat_b1, lat_b2 = self._dividir_latentes(lat_b)
@@ -730,19 +705,22 @@ class VideoService:
730
  partes_mp4 = []
731
  par = 0
732
 
733
- for part in latents_parts:
734
- print(f"[DEBUG] Partição {par}: {tuple(part.shape)}")
 
735
  par = par + 1
736
  output_video_path = os.path.join(temp_dir, f"output_{used_seed}_{par}.mp4")
 
737
 
738
  print("[DEBUG] Decodificando bloco de latentes com VAE → tensor de pixels...")
 
739
  pixel_tensor = vae_manager_singleton.decode(
740
- part.to(self.device, non_blocking=True),
741
  decode_timestep=float(self.config.get("decode_timestep", 0.05))
742
  )
743
  log_tensor_info(pixel_tensor, "Pixel tensor (VAE saída)")
744
 
745
- print("[DEBUG] Codificando MP4 a partir do tensor de pixels...")
746
  video_encode_tool_singleton.save_video_from_tensor(
747
  pixel_tensor,
748
  output_video_path,
@@ -756,7 +734,9 @@ class VideoService:
756
  final_output_path = candidate
757
  print(f"[DEBUG] MP4 parte {par} movido para {final_output_path}")
758
  partes_mp4.append(final_output_path)
 
759
  except Exception as e:
 
760
  print(f"[DEBUG] Falha no move; usando tmp como final: {e}")
761
 
762
  final_concat = os.path.join(results_dir, f"concat_fim_{used_seed}.mp4")
@@ -765,320 +745,31 @@ class VideoService:
765
  self._log_gpu_memory("Fim da Geração")
766
  return final_concat, used_seed
767
 
768
- except Exception as e:
769
- print("[DEBUG] EXCEÇÃO NA GERAÇÃO:")
770
- print("".join(traceback.format_exception(type(e), e, e.__traceback__)))
771
- raise
772
-
773
- # ltx_server.py
774
-
775
- def generate(
776
- self,
777
- prompt,
778
- negative_prompt,
779
- mode="text-to-video",
780
- start_image_filepath=None,
781
- middle_image_filepath=None,
782
- middle_frame_number=None,
783
- middle_image_weight=1.0,
784
- end_image_filepath=None,
785
- end_image_weight=1.0,
786
- input_video_filepath=None,
787
- height=512,
788
- width=704,
789
- duration=2.0,
790
- frames_to_use=9,
791
- seed=42,
792
- randomize_seed=True,
793
- guidance_scale=3.0, # Valor de referência/fallback
794
- improve_texture=True,
795
- progress_callback=None,
796
- external_decode=True,
797
- ):
798
- t_all = time.perf_counter()
799
- print(f"[DEBUG] generate() begin mode={mode} external_decode={external_decode} improve_texture={improve_texture}")
800
- if self.device == "cuda":
801
- torch.cuda.empty_cache(); torch.cuda.reset_peak_memory_stats()
802
- self._log_gpu_memory("Início da Geração")
803
-
804
- if mode == "image-to-video" and not start_image_filepath:
805
- raise ValueError("A imagem de início é obrigatória para o modo image-to-video")
806
- if mode == "video-to-video" and not input_video_filepath:
807
- raise ValueError("O vídeo de entrada é obrigatório para o modo video-to-video")
808
-
809
- used_seed = random.randint(0, 2**32 - 1) if randomize_seed else int(seed)
810
- seed_everething(used_seed); print(f"[DEBUG] Seed usado: {used_seed}")
811
-
812
- FPS = 24.0; MAX_NUM_FRAMES = 2570
813
- target_frames_rounded = round(duration * FPS)
814
- n_val = round((float(target_frames_rounded) - 1.0) / 8.0)
815
- actual_num_frames = max(9, min(MAX_NUM_FRAMES, int(n_val * 8 + 1)))
816
- print(f"[DEBUG] Frames alvo: {actual_num_frames} (dur={duration}s @ {FPS}fps)")
817
-
818
- height_padded = ((height - 1) // 32 + 1) * 32
819
- width_padded = ((width - 1) // 32 + 1) * 32
820
- padding_values = calculate_padding(height, width, height_padded, width_padded)
821
- print(f"[DEBUG] Dimensões: ({height},{width}) -> pad ({height_padded},{width_padded}); padding={padding_values}")
822
-
823
- generator = torch.Generator(device=self.device).manual_seed(used_seed)
824
- conditioning_items = []
825
-
826
- if mode == "image-to-video":
827
- start_tensor = self._prepare_conditioning_tensor(start_image_filepath, height, width, padding_values)
828
- conditioning_items.append(ConditioningItem(start_tensor, 0, 1.0))
829
- if middle_image_filepath and middle_frame_number is not None:
830
- middle_tensor = self._prepare_conditioning_tensor(middle_image_filepath, height, width, padding_values)
831
- safe_middle_frame = max(0, min(int(middle_frame_number), actual_num_frames - 1))
832
- conditioning_items.append(ConditioningItem(middle_tensor, safe_middle_frame, float(middle_image_weight)))
833
- if end_image_filepath:
834
- end_tensor = self._prepare_conditioning_tensor(end_image_filepath, height, width, padding_values)
835
- last_frame_index = actual_num_frames - 1
836
- conditioning_items.append(ConditioningItem(end_tensor, last_frame_index, float(end_image_weight)))
837
- print(f"[DEBUG] Conditioning items: {len(conditioning_items)}")
838
-
839
- # --- LÓGICA DE CONVERSÃO DO STG_MODE ---
840
- stg_mode_str = self.config.get("stg_mode", "attention_values")
841
- stg_mode_map = {
842
- "attention_values": "AttentionValues",
843
- "attention_skip": "AttentionSkip",
844
- "residual": "Residual",
845
- "transformer_block": "TransformerBlock"
846
- }
847
- stg_mode_enum_key = stg_mode_map.get(stg_mode_str.lower(), "AttentionValues")
848
- # --- FIM DA LÓGICA DE CONVERSÃO ---
849
-
850
- call_kwargs = {
851
- "prompt": prompt,
852
- "negative_prompt": negative_prompt,
853
- "height": height_padded,
854
- "width": width_padded,
855
- "num_frames": actual_num_frames,
856
- "frame_rate": int(FPS),
857
- "generator": generator,
858
- "output_type": "latent",
859
- "conditioning_items": conditioning_items if conditioning_items else None,
860
- "media_items": None,
861
- "decode_timestep": self.config.get("decode_timestep"),
862
- "decode_noise_scale": self.config.get("decode_noise_scale"),
863
- "stochastic_sampling": self.config.get("stochastic_sampling"),
864
- "image_cond_noise_scale": self.config.get("image_cond_noise_scale", 0.01),
865
- "is_video": True,
866
- "vae_per_channel_normalize": self.config.get("vae_per_channel_normalize", True),
867
- "mixed_precision": (self.config.get("precision") == "mixed_precision"),
868
- "offload_to_cpu": False,
869
- "enhance_prompt": False,
870
- "skip_layer_strategy": SkipLayerStrategy[stg_mode_enum_key],
871
- }
872
- print(f"[DEBUG] output_type={call_kwargs['output_type']} skip_layer_strategy={call_kwargs['skip_layer_strategy']}")
873
-
874
- if mode == "video-to-video":
875
- media = load_media_file(
876
- media_path=input_video_filepath,
877
- height=height,
878
- width=width,
879
- max_frames=int(frames_to_use),
880
- padding=padding_values,
881
- ).to(self.device)
882
- call_kwargs["media_items"] = media
883
- print(f"[DEBUG] media_items shape={tuple(media.shape)}")
884
-
885
- latents = None
886
-
887
- try:
888
- ctx = torch.autocast(device_type="cuda", dtype=self.runtime_autocast_dtype) if self.device == "cuda" else contextlib.nullcontext()
889
-
890
- if improve_texture:
891
- if not self.latent_upsampler:
892
- raise ValueError("Upscaler espacial não carregado.")
893
-
894
- # --- PASSO 1: GERAÇ��O DE LATENTES EM BAIXA RESOLUÇÃO ---
895
- print("[DEBUG] Multi-escala: Iniciando Passo 1 (geração de latentes base).")
896
-
897
- first_pass_args = self.config.get("first_pass", {}).copy()
898
- first_pass_kwargs = call_kwargs.copy()
899
-
900
- first_pass_kwargs.update({
901
- "guidance_scale": first_pass_args.get("guidance_scale", guidance_scale),
902
- "stg_scale": first_pass_args.get("stg_scale"),
903
- "rescaling_scale": first_pass_args.get("rescaling_scale"),
904
- "skip_block_list": first_pass_args.get("skip_block_list"),
905
- "guidance_timesteps": first_pass_args.get("guidance_timesteps"),
906
- "timesteps": first_pass_args.get("timesteps")
907
- })
908
- print(f"[DEBUG] Passo 1: Parâmetros do config carregados: guidance_scale={first_pass_kwargs['guidance_scale']}, stg_scale={first_pass_kwargs['stg_scale']}")
909
-
910
- downscale_factor = self.config.get("downscale_factor", 2)
911
- original_height = first_pass_kwargs["height"]
912
- original_width = first_pass_kwargs["width"]
913
- divisor = 24
914
- target_height_p1 = original_height // downscale_factor
915
- height_p1 = round(target_height_p1 / divisor) * divisor
916
- if height_p1 == 0: height_p1 = divisor
917
- first_pass_kwargs["height"] = height_p1
918
- target_width_p1 = original_width // downscale_factor
919
- width_p1 = round(target_width_p1 / divisor) * divisor
920
- if width_p1 == 0: width_p1 = divisor
921
- first_pass_kwargs["width"] = width_p1
922
- print(f"[DEBUG] Passo 1: Dimensões reduzidas e ajustadas para {height_p1}x{width_p1}")
923
-
924
- with ctx:
925
- first_pass_result = self.pipeline(**first_pass_kwargs)
926
-
927
- latents_low_res = first_pass_result.images
928
- log_tensor_info(latents_low_res, "Latentes (Passo 1)")
929
-
930
- del first_pass_result
931
- gc.collect()
932
- if self.device == "cuda": torch.cuda.empty_cache()
933
-
934
- # --- PASSO INTERMEDIÁRIO: UPSCALE DOS LATENTES ---
935
- print("[DEBUG] Multi-escala: Fazendo upscale dos latentes com latent_upsampler.")
936
- with ctx:
937
- latents_high_res = self.latent_upsampler(latents_low_res)
938
-
939
- log_tensor_info(latents_high_res, "Latentes (Pós-Upscale)")
940
- del latents_low_res
941
- gc.collect()
942
- if self.device == "cuda": torch.cuda.empty_cache()
943
-
944
- # --- PASSO 2: REFINAMENTO EM ALTA RESOLUÇÃO ---
945
- print("[DEBUG] Multi-escala: Iniciando Passo 2 (refinamento em alta resolução).")
946
- second_pass_args = self.config.get("second_pass", {}).copy()
947
- second_pass_kwargs = call_kwargs.copy()
948
-
949
- second_pass_kwargs.update({
950
- "guidance_scale": second_pass_args.get("guidance_scale", guidance_scale),
951
- "stg_scale": second_pass_args.get("stg_scale"),
952
- "rescaling_scale": second_pass_args.get("rescaling_scale"),
953
- "skip_block_list": second_pass_args.get("skip_block_list"),
954
- "guidance_timesteps": second_pass_args.get("guidance_timesteps"),
955
- "timesteps": second_pass_args.get("timesteps")
956
- })
957
- print(f"[DEBUG] Passo 2: Parâmetros do config carregados: guidance_scale={second_pass_kwargs['guidance_scale']}, stg_scale={second_pass_kwargs['stg_scale']}")
958
-
959
- height_p2 = height_p1 * 2
960
- width_p2 = width_p1 * 2
961
- second_pass_kwargs["height"] = height_p2
962
- second_pass_kwargs["width"] = width_p2
963
- print(f"[DEBUG] Passo 2: Dimensões definidas para {height_p2}x{width_p2}")
964
-
965
- second_pass_kwargs["latents"] = latents_high_res
966
-
967
- with ctx:
968
- second_pass_result = self.pipeline(**second_pass_kwargs)
969
-
970
- latents = second_pass_result.images
971
- log_tensor_info(latents, "Latentes Finais (Passo 2)")
972
-
973
- else:
974
- # --- PASSO ÚNICO (SINGLE-PASS) ---
975
- single_pass_kwargs = call_kwargs.copy()
976
-
977
- single_pass_kwargs.update({
978
- "guidance_scale": self.config.get("guidance_scale", guidance_scale),
979
- "stg_scale": self.config.get("stg_scale"),
980
- "rescaling_scale": self.config.get("rescaling_scale"),
981
- "skip_block_list": self.config.get("skip_block_list"),
982
- "guidance_timesteps": self.config.get("guidance_timesteps"),
983
- "timesteps": self.config.get("timesteps"),
984
- "num_inference_steps": self.config.get("num_inference_steps", 20)
985
- })
986
-
987
- print("\n[INFO] Executando pipeline de etapa única...")
988
- with ctx:
989
- result = self.pipeline(**single_pass_kwargs)
990
-
991
- latents = result.images
992
- print(f"[DEBUG] Latentes (single-pass): shape={tuple(latents.shape)}")
993
-
994
- # --- DECODIFICAÇÃO E CODIFICAÇÃO DE VÍDEO FINAL ---
995
- latents_cpu = latents.detach().to("cpu", non_blocking=True)
996
- if self.device == "cuda":
997
- torch.cuda.empty_cache()
998
- try: torch.cuda.ipc_collect()
999
- except Exception: pass
1000
-
1001
- lat_a, lat_b = self._dividir_latentes(latents_cpu)
1002
- if lat_a is not None:
1003
- lat_a1, lat_a2 = self._dividir_latentes(lat_a)
1004
- else:
1005
- lat_a1, lat_a2 = None, None
1006
- if lat_b is not None:
1007
- lat_b1, lat_b2 = self._dividir_latentes(lat_b)
1008
- else:
1009
- lat_b1, lat_b2 = None, None
1010
-
1011
- latents_parts = [p for p in [lat_a1, lat_a2, lat_b1, lat_b2] if p is not None and p.shape[2] > 1]
1012
- if not latents_parts:
1013
- latents_parts = [latents_cpu]
1014
 
1015
- temp_dir = tempfile.mkdtemp(prefix="ltxv_"); self._register_tmp_dir(temp_dir)
1016
- results_dir = "/app/output"; os.makedirs(results_dir, exist_ok=True)
1017
- partes_mp4 = []
1018
- par = 0
1019
-
1020
- for part in latents_parts:
1021
- par += 1
1022
- print(f"[DEBUG] Partição {par}: {tuple(part.shape)}")
1023
- output_video_path = os.path.join(temp_dir, f"output_{used_seed}_{par}.mp4")
1024
-
1025
- print("[DEBUG] Decodificando bloco de latentes com VAE → tensor de pixels...")
1026
- pixel_tensor = vae_manager_singleton.decode(
1027
- part.to(self.device, non_blocking=True),
1028
- decode_timestep=float(self.config.get("decode_timestep", 0.05))
1029
- )
1030
- log_tensor_info(pixel_tensor, "Pixel tensor (VAE saída)")
1031
-
1032
- print("[DEBUG] Codificando MP4 a partir do tensor de pixels...")
1033
- video_encode_tool_singleton.save_video_from_tensor(
1034
- pixel_tensor,
1035
- output_video_path,
1036
- fps=call_kwargs["frame_rate"],
1037
- progress_callback=progress_callback
1038
- )
1039
-
1040
- candidate = os.path.join(results_dir, f"output_par_{par}.mp4")
1041
- try:
1042
- shutil.move(output_video_path, candidate)
1043
- print(f"[DEBUG] MP4 parte {par} movido para {candidate}")
1044
- partes_mp4.append(candidate)
1045
- except Exception as e:
1046
- print(f"[DEBUG] Falha no move; usando tmp como final: {e}")
1047
- partes_mp4.append(output_video_path)
1048
-
1049
- final_concat = os.path.join(results_dir, f"concat_fim_{used_seed}.mp4")
1050
- if partes_mp4:
1051
- if len(partes_mp4) == 1:
1052
- shutil.move(partes_mp4[0], final_concat)
1053
- print(f"[DEBUG] Apenas uma parte, movida para {final_concat}")
1054
- else:
1055
- self._concat_mp4s_no_reencode(partes_mp4, final_concat)
1056
- else:
1057
- print("[WARN] Nenhuma parte de vídeo foi gerada para concatenar.")
1058
- return None, used_seed
1059
-
1060
- self._log_gpu_memory("Fim da Geração")
1061
- return final_concat, used_seed
1062
-
1063
  except Exception as e:
1064
  print("[DEBUG] EXCEÇÃO NA GERAÇÃO:")
1065
  print("".join(traceback.format_exception(type(e), e, e.__traceback__)))
1066
  raise
1067
  finally:
1068
  try:
1069
- del latents, latents_low_res, latents_high_res, second_pass_result, first_pass_result, result
1070
- except NameError:
 
 
 
 
1071
  pass
1072
- except Exception as e:
1073
- print(f"[DEBUG] Erro na limpeza de variáveis: {e}")
1074
 
1075
  gc.collect()
1076
- if self.device == "cuda":
1077
- try:
1078
  torch.cuda.empty_cache()
1079
- torch.cuda.ipc_collect()
1080
- except Exception as e:
1081
- print(f"[DEBUG] Limpeza GPU no finally falhou: {e}")
 
 
 
1082
 
1083
  try:
1084
  self.finalize(keep_paths=[])
@@ -1086,4 +777,4 @@ class VideoService:
1086
  print(f"[DEBUG] finalize() no finally falhou: {e}")
1087
 
1088
  print("Criando instância do VideoService. O carregamento do modelo começará agora...")
1089
- video_generation_service = VideoService()
 
396
  return out
397
 
398
 
399
+ def _dividir_latentes(self, latents_brutos):
400
+ total = latents_brutos.shape[2] # dimensão temporal (número de latentes)
 
 
 
 
 
401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  if total % 2 == 1: # ÍMPAR
403
+ # Ex: 11 primeira 0..5, segunda 5..10
404
+ cut = total // 2
405
+ primeira = latents_brutos[:, :, :cut+1, :, :].clone()
406
+ segunda = latents_brutos[:, :, cut:, :, :].clone()
407
+
408
  else: # PAR
409
+ # Ex: 12 primeira 0..5, segunda 5..11
410
+ cut = total // 2
411
+ primeira = latents_brutos[:, :, :cut, :, :].clone()
412
+ segunda = latents_brutos[:, :, cut-1:, :, :].clone()
413
+
414
  return primeira, segunda
 
415
 
416
  def _concat_mp4s_no_reencode(self, mp4_list: List[str], out_path: str):
417
  """
 
439
  pass
440
 
441
 
 
442
  def generate(
443
  self,
444
  prompt,
 
541
  print(f"[DEBUG] media_items shape={tuple(media.shape)}")
542
 
543
  latents = None
544
+ multi_scale_pipeline = None
 
 
545
 
546
+ try:
547
  if improve_texture:
548
  if not self.latent_upsampler:
549
  raise ValueError("Upscaler espacial não carregado.")
550
 
551
+ # --- INÍCIO DA IMPLEMENTAÇÃO LIMPA DOS 3 PASSOS ---
552
+
553
  # --- PASSO 1: GERAÇÃO DE LATENTES EM BAIXA RESOLUÇÃO ---
554
  print("[DEBUG] Multi-escala: Iniciando Passo 1 (geração de latentes base).")
555
 
556
  first_pass_args = self.config.get("first_pass", {}).copy()
557
  first_pass_kwargs = call_kwargs.copy()
558
+
559
+ # Carrega os parâmetros do config, incluindo listas de timesteps e guidance
560
  first_pass_kwargs.update({
561
+ "guidance_scale": first_pass_args.get("guidance_scale", guidance_scale),
562
  "stg_scale": first_pass_args.get("stg_scale"),
563
  "rescaling_scale": first_pass_args.get("rescaling_scale"),
564
  "skip_block_list": first_pass_args.get("skip_block_list"),
565
+ "guidance_timesteps": first_pass_args.get("guidance_timesteps"),
566
+ "timesteps": first_pass_args.get("timesteps")
567
  })
568
+ print(f"[DEBUG] Passo 1: Parâmetros do config carregados.")
569
+
570
+ # Calcula as dimensões de baixa resolução
 
 
571
  downscale_factor = self.config.get("downscale_factor", 2)
572
  original_height = first_pass_kwargs["height"]
573
  original_width = first_pass_kwargs["width"]
574
  divisor = 24
575
+
576
+ # Para downscale_factor < 1 (ex: 0.666), a lógica é multiplicar
577
+ if downscale_factor < 1.0:
578
+ target_height_p1 = original_height * downscale_factor
579
+ target_width_p1 = original_width * downscale_factor
580
+ else: # Para downscale_factor >= 1, a lógica é dividir
581
+ target_height_p1 = original_height // downscale_factor
582
+ target_width_p1 = original_width // downscale_factor
583
+
584
  height_p1 = round(target_height_p1 / divisor) * divisor
585
  if height_p1 == 0: height_p1 = divisor
586
  first_pass_kwargs["height"] = height_p1
587
 
 
588
  width_p1 = round(target_width_p1 / divisor) * divisor
589
  if width_p1 == 0: width_p1 = divisor
590
  first_pass_kwargs["width"] = width_p1
 
597
  latents_low_res = first_pass_result.images
598
  log_tensor_info(latents_low_res, "Latentes (Passo 1)")
599
 
600
+ del first_pass_result, first_pass_kwargs
601
  gc.collect()
602
  if self.device == "cuda": torch.cuda.empty_cache()
603
 
 
616
  second_pass_args = self.config.get("second_pass", {}).copy()
617
  second_pass_kwargs = call_kwargs.copy()
618
 
619
+ # Carrega os parâmetros do config para o segundo passo
 
 
 
 
 
620
  second_pass_kwargs.update({
621
+ "guidance_scale": second_pass_args.get("guidance_scale", guidance_scale),
622
  "stg_scale": second_pass_args.get("stg_scale"),
623
  "rescaling_scale": second_pass_args.get("rescaling_scale"),
624
  "skip_block_list": second_pass_args.get("skip_block_list"),
625
+ "guidance_timesteps": second_pass_args.get("guidance_timesteps"),
626
+ "timesteps": second_pass_args.get("timesteps")
627
  })
628
+ print(f"[DEBUG] Passo 2: Parâmetros do config carregados.")
629
 
630
+ # Define as dimensões de alta resolução com base no upscale
631
+ # O upsampler espacial dobra a resolução, então multiplicamos por 2
632
+ height_p2 = height_p1 * 2
633
+ width_p2 = width_p1 * 2
634
+ second_pass_kwargs["height"] = height_p2
635
+ second_pass_kwargs["width"] = width_p2
636
+ print(f"[DEBUG] Passo 2: Dimensões definidas para {height_p2}x{width_p2}")
 
 
 
 
 
 
 
 
 
637
 
638
+ # A entrada para o refinamento são os latentes que sofreram upscale
 
639
  second_pass_kwargs["latents"] = latents_high_res
640
 
641
+ # Garante que 'strength' não seja passado, pois estamos controlando via timesteps
642
+ if "strength" in second_pass_kwargs:
643
+ del second_pass_kwargs["strength"]
644
+
645
  with ctx:
646
  second_pass_result = self.pipeline(**second_pass_kwargs)
647
 
648
  latents = second_pass_result.images
649
  log_tensor_info(latents, "Latentes Finais (Passo 2)")
650
+
651
+
652
+ else:
653
  single_pass_kwargs = call_kwargs.copy()
654
  first_pass_config = self.config.get("first_pass", {})
655
  single_pass_kwargs.update(
 
669
  print(f"[DEBUG] Single-pass: timesteps_len={len(schedule) if schedule else 0}")
670
 
671
  print("\n[INFO] Executando pipeline de etapa única...")
672
+ t_sp = time.perf_counter()
673
+ ctx = torch.autocast(device_type="cuda", dtype=self.runtime_autocast_dtype) if self.device == "cuda" else contextlib.nullcontext()
674
  with ctx:
675
  result = self.pipeline(**single_pass_kwargs)
676
+ print(f"[DEBUG] single-pass tempo={time.perf_counter()-t_sp:.3f}s")
677
+
678
+ if hasattr(result, "latents"):
679
+ latents = result.latents
680
+ elif hasattr(result, "images") and isinstance(result.images, torch.Tensor):
681
+ latents = result.images
682
+ else:
683
+ latents = result
684
  print(f"[DEBUG] Latentes (single-pass): shape={tuple(latents.shape)}")
685
 
686
+ # Staging e escrita MP4 (simples: VAE pixels → MP4)
687
 
688
  latents_cpu = latents.detach().to("cpu", non_blocking=True)
689
+ torch.cuda.empty_cache()
690
+ try:
691
+ torch.cuda.ipc_collect()
692
+ except Exception:
693
+ pass
 
694
 
695
+ # 2) Divide em duas partes
696
  lat_a, lat_b = self._dividir_latentes(latents_cpu)
697
  lat_a1, lat_a2 = self._dividir_latentes(lat_a)
698
  lat_b1, lat_b2 = self._dividir_latentes(lat_b)
 
705
  partes_mp4 = []
706
  par = 0
707
 
708
+ for latents in latents_parts:
709
+ print(f"[DEBUG] Partição {par}: {tuple(latents.shape)}")
710
+
711
  par = par + 1
712
  output_video_path = os.path.join(temp_dir, f"output_{used_seed}_{par}.mp4")
713
+ final_output_path = None
714
 
715
  print("[DEBUG] Decodificando bloco de latentes com VAE → tensor de pixels...")
716
+ # Usar manager com timestep por item; previne target_shape e rota NoneType.decode
717
  pixel_tensor = vae_manager_singleton.decode(
718
+ latents.to(self.device, non_blocking=True),
719
  decode_timestep=float(self.config.get("decode_timestep", 0.05))
720
  )
721
  log_tensor_info(pixel_tensor, "Pixel tensor (VAE saída)")
722
 
723
+ print("[DEBUG] Codificando MP4 a partir do tensor de pixels (bloco inteiro)...")
724
  video_encode_tool_singleton.save_video_from_tensor(
725
  pixel_tensor,
726
  output_video_path,
 
734
  final_output_path = candidate
735
  print(f"[DEBUG] MP4 parte {par} movido para {final_output_path}")
736
  partes_mp4.append(final_output_path)
737
+
738
  except Exception as e:
739
+ final_output_path = output_video_path
740
  print(f"[DEBUG] Falha no move; usando tmp como final: {e}")
741
 
742
  final_concat = os.path.join(results_dir, f"concat_fim_{used_seed}.mp4")
 
745
  self._log_gpu_memory("Fim da Geração")
746
  return final_concat, used_seed
747
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
748
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
749
  except Exception as e:
750
  print("[DEBUG] EXCEÇÃO NA GERAÇÃO:")
751
  print("".join(traceback.format_exception(type(e), e, e.__traceback__)))
752
  raise
753
  finally:
754
  try:
755
+ del latents
756
+ except Exception:
757
+ pass
758
+ try:
759
+ del multi_scale_pipeline
760
+ except Exception:
761
  pass
 
 
762
 
763
  gc.collect()
764
+ try:
765
+ if self.device == "cuda":
766
  torch.cuda.empty_cache()
767
+ try:
768
+ torch.cuda.ipc_collect()
769
+ except Exception:
770
+ pass
771
+ except Exception as e:
772
+ print(f"[DEBUG] Limpeza GPU no finally falhou: {e}")
773
 
774
  try:
775
  self.finalize(keep_paths=[])
 
777
  print(f"[DEBUG] finalize() no finally falhou: {e}")
778
 
779
  print("Criando instância do VideoService. O carregamento do modelo começará agora...")
780
+ video_generation_service = VideoService()