aducsdr commited on
Commit
05033ed
·
verified ·
1 Parent(s): c9b21bd

Update aduc_framework/managers/seedvr_manager.py

Browse files
aduc_framework/managers/seedvr_manager.py CHANGED
@@ -2,13 +2,11 @@
2
  #
3
  # Copyright (C) 2025 Carlos Rodrigues dos Santos
4
  #
5
- # Version: 4.0.0 (Root Installer & Executor)
6
  #
7
- # This version fully adopts the logic from the functional hd_specialist.py example.
8
- # It acts as a setup manager: it clones the SeedVR repo and then copies all
9
- # necessary directories (projects, common, models, configs, ckpts) to the
10
- # application root. It also handles the pip installation of the Apex dependency.
11
- # This ensures that the SeedVR code runs in the exact file structure it expects.
12
 
13
  import torch
14
  import torch.distributed as dist
@@ -17,6 +15,7 @@ import gc
17
  import logging
18
  import sys
19
  import subprocess
 
20
  from pathlib import Path
21
  from urllib.parse import urlparse
22
  from torch.hub import download_url_to_file
@@ -25,6 +24,10 @@ import mediapy
25
  from einops import rearrange
26
  import shutil
27
  from omegaconf import OmegaConf
 
 
 
 
28
 
29
  logger = logging.getLogger(__name__)
30
 
@@ -34,200 +37,289 @@ DEPS_DIR = APP_ROOT / "deps"
34
  SEEDVR_SPACE_DIR = DEPS_DIR / "SeedVR_Space"
35
  SEEDVR_SPACE_URL = "https://huggingface.co/spaces/ByteDance-Seed/SeedVR2-3B"
36
 
37
- class SeedVrManager:
38
- def __init__(self, workspace_dir="deformes_workspace"):
39
- self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
 
40
  self.runner = None
41
- self.workspace_dir = workspace_dir
42
  self.is_initialized = False
43
- self._original_barrier = None
44
- self.setup_complete = False # Flag para rodar o setup apenas uma vez
45
- logger.info("SeedVrManager initialized. Setup will run on first use.")
46
 
47
- def _full_setup(self):
48
- """
49
- Executa todo o processo de setup uma única vez.
50
- """
51
- if self.setup_complete:
52
- return
 
53
 
54
- logger.info("--- Starting Full SeedVR Setup ---")
55
 
56
- # 1. Clonar o repositório se não existir
57
  if not SEEDVR_SPACE_DIR.exists():
58
- logger.info(f"Cloning SeedVR Space repo to {SEEDVR_SPACE_DIR}...")
59
  DEPS_DIR.mkdir(exist_ok=True, parents=True)
60
- subprocess.run(
61
- ["git", "clone", "--depth", "1", SEEDVR_SPACE_URL, str(SEEDVR_SPACE_DIR)],
62
- check=True, capture_output=True, text=True
63
- )
64
 
65
- # 2. Copiar as pastas necessárias para a raiz da aplicação
66
  required_dirs = ["projects", "common", "models", "configs_3b", "configs_7b"]
67
  for dirname in required_dirs:
68
  source = SEEDVR_SPACE_DIR / dirname
69
  target = APP_ROOT / dirname
70
  if not target.exists():
71
- logger.info(f"Copying '{dirname}' to application root...")
72
  shutil.copytree(source, target)
73
 
74
- # 3. Adicionar a raiz ao sys.path para garantir que os imports funcionem
75
  if str(APP_ROOT) not in sys.path:
76
  sys.path.insert(0, str(APP_ROOT))
77
- logger.info(f"Added '{APP_ROOT}' to sys.path.")
78
 
79
- # 4. Instalar dependências complexas como Apex
80
  try:
81
  import apex
82
- logger.info("Apex is already installed.")
83
  except ImportError:
84
- logger.info("Installing Apex dependency...")
85
  apex_url = 'https://huggingface.co/ByteDance-Seed/SeedVR2-3B/resolve/main/apex-0.1-cp310-cp310-linux_x86_64.whl'
86
  apex_wheel_path = _load_file_from_url(url=apex_url, model_dir=str(DEPS_DIR))
87
  subprocess.run(f"pip install {apex_wheel_path}", check=True, shell=True)
88
- logger.info("Apex installed successfully.")
89
 
90
- # 5. Baixar os modelos para a pasta ./ckpts na raiz
91
  ckpt_dir = APP_ROOT / 'ckpts'
92
  ckpt_dir.mkdir(exist_ok=True)
93
- pretrain_model_urls = {
94
  'vae': 'https://huggingface.co/ByteDance-Seed/SeedVR2-3B/resolve/main/ema_vae.pth',
95
  'dit_3b': 'https://huggingface.co/ByteDance-Seed/SeedVR2-3B/resolve/main/seedvr2_ema_3b.pth',
96
- #'dit_7b': 'https://huggingface.co/ByteDance-Seed/SeedVR2-7B/resolve/main/seedvr2_ema_7b.pth',
97
  'pos_emb': 'https://huggingface.co/ByteDance-Seed/SeedVR2-3B/resolve/main/pos_emb.pt',
98
  'neg_emb': 'https://huggingface.co/ByteDance-Seed/SeedVR2-3B/resolve/main/neg_emb.pt'
99
  }
100
- for name, url in pretrain_model_urls.items():
101
  _load_file_from_url(url=url, model_dir=str(ckpt_dir))
102
-
103
- self.setup_complete = True
104
- logger.info("--- Full SeedVR Setup Complete ---")
 
105
 
106
- def _initialize_runner(self, model_version: str):
 
107
  if self.runner is not None: return
108
 
109
- # Garante que todo o ambiente está configurado antes de prosseguir
110
- self._full_setup()
111
-
112
  # Agora que o setup está feito, podemos importar os módulos
113
  from projects.video_diffusion_sr.infer import VideoDiffusionInfer
114
  from common.config import load_config
115
- from common.seed import set_seed
116
 
117
- if dist.is_available() and not dist.is_initialized():
118
- os.environ["MASTER_ADDR"] = "127.0.0.1"
119
- os.environ["MASTER_PORT"] = "12355"
120
- os.environ["RANK"] = str(0)
121
- os.environ["WORLD_SIZE"] = str(1)
122
- dist.init_process_group(backend='gloo')
123
- logger.info("Initialized torch.distributed process group.")
124
-
125
- logger.info(f"Initializing SeedVR2 {model_version} runner...")
126
- #if model_version == '3B':
127
- config_path = APP_ROOT / 'configs_3b' / 'main.yaml'
128
- checkpoint_path = APP_ROOT / 'ckpts' / 'seedvr2_ema_3b.pth'
129
- #else: # Assumimos 7B
130
- # config_path = APP_ROOT / 'configs_7b' / 'main.yaml'
131
- # checkpoint_path = APP_ROOT / 'ckpts' / 'seedvr2_ema_7b.pth'
132
 
133
  config = load_config(str(config_path))
134
 
135
  self.runner = VideoDiffusionInfer(config)
136
  OmegaConf.set_readonly(self.runner.config, False)
137
 
138
- self.runner.configure_dit_model(device=self.device, checkpoint=str(checkpoint_path))
139
  self.runner.configure_vae_model()
140
 
141
- if hasattr(self.runner.vae, "set_memory_limit"):
142
- self.runner.vae.set_memory_limit(**self.runner.config.vae.memory_limit)
143
-
144
  self.is_initialized = True
145
- logger.info(f"Runner for SeedVR2 {model_version} initialized and ready.")
146
 
147
- def _unload_runner(self):
 
148
  if self.runner is not None:
 
 
 
149
  del self.runner
150
  self.runner = None
151
  gc.collect()
152
  torch.cuda.empty_cache()
153
  self.is_initialized = False
154
- logger.info("Runner do SeedVR2 descarregado da VRAM.")
155
- if dist.is_initialized():
156
- dist.destroy_process_group()
157
- logger.info("Destroyed torch.distributed process group.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
  def process_video(self, input_video_path: str, output_video_path: str, prompt: str,
160
- model_version: str = '3B', steps: int = 100, seed: int = 666,
161
  progress: gr.Progress = None) -> str:
 
162
  try:
163
- self._initialize_runner(model_version)
164
-
165
- # Precisamos importar aqui, pois o sys.path é modificado no setup
166
- from common.seed import set_seed
167
- from data.image.transforms.divisible_crop import DivisibleCrop
168
- from data.image.transforms.na_resize import NaResize
169
- from data.video.transforms.rearrange import Rearrange
170
- from projects.video_diffusion_sr.color_fix import wavelet_reconstruction
171
- from torchvision.transforms import Compose, Lambda, Normalize
172
- from torchvision.io.video import read_video
173
-
174
- set_seed(seed, same_across_ranks=True)
175
- self.runner.config.diffusion.timesteps.sampling.steps = steps
176
- self.runner.configure_diffusion()
177
-
178
- video_tensor = read_video(input_video_path, output_format="TCHW")[0] / 255.0
179
- res_h, res_w = video_tensor.shape[-2:]
180
- video_transform = Compose([
181
- NaResize(resolution=(res_h * res_w) ** 0.5, mode="area", downsample_only=False),
182
- Lambda(lambda x: torch.clamp(x, 0.0, 1.0)),
183
- DivisibleCrop((16, 16)),
184
- Normalize(0.5, 0.5),
185
- Rearrange("t c h w -> c t h w"),
186
- ])
187
- cond_latents = [video_transform(video_tensor.to(self.device))]
188
- input_videos = cond_latents
189
- self.runner.dit.to("cpu")
190
- self.runner.vae.to(self.device)
191
- cond_latents = self.runner.vae_encode(cond_latents)
192
- self.runner.vae.to("cpu"); gc.collect(); torch.cuda.empty_cache()
193
- self.runner.dit.to(self.device)
194
-
195
- pos_emb = torch.load(APP_ROOT / 'pos_emb.pt').to(self.device)
196
- neg_emb = torch.load(APP_ROOT / 'neg_emb.pt').to(self.device)
197
- text_embeds_dict = {"texts_pos": [pos_emb], "texts_neg": [neg_emb]}
198
-
199
- noises = [torch.randn_like(latent) for latent in cond_latents]
200
- conditions = [self.runner.get_condition(noise, latent_blur=latent, task="sr") for noise, latent in zip(noises, cond_latents)]
201
-
202
- with torch.no_grad(), torch.autocast("cuda", torch.bfloat16, enabled=True):
203
- video_tensors = self.runner.inference(noises=noises, conditions=conditions, dit_offload=True, **text_embeds_dict)
204
 
205
- self.runner.dit.to("cpu"); gc.collect(); torch.cuda.empty_cache()
206
- self.runner.vae.to(self.device)
207
- samples = self.runner.vae_decode(video_tensors)
208
- final_sample = samples[0]
209
- input_video_sample = input_videos[0]
210
- if final_sample.shape[1] < input_video_sample.shape[1]:
211
- input_video_sample = input_video_sample[:, :final_sample.shape[1]]
212
 
213
- final_sample = wavelet_reconstruction(rearrange(final_sample, "c t h w -> t c h w"), rearrange(input_video_sample, "c t h w -> t c h w"))
214
- final_sample = rearrange(final_sample, "t c h w -> t h w c")
215
- final_sample = final_sample.clip(-1, 1).mul_(0.5).add_(0.5).mul_(255).round()
216
- final_sample_np = final_sample.to(torch.uint8).cpu().numpy()
217
 
218
- mediapy.write_video(output_video_path, final_sample_np, fps=24)
219
- logger.info(f"HD Mastered video saved to: {output_video_path}")
220
- return output_path
 
 
 
 
221
  finally:
222
- self._unload_runner()
 
223
 
224
  def _load_file_from_url(url, model_dir='./', file_name=None):
225
  os.makedirs(model_dir, exist_ok=True)
226
  filename = file_name or os.path.basename(urlparse(url).path)
227
  cached_file = os.path.abspath(os.path.join(model_dir, filename))
228
  if not os.path.exists(cached_file):
229
- logger.info(f'Downloading: "{url}" to {cached_file}')
230
  download_url_to_file(url, cached_file, hash_prefix=None, progress=True)
231
  return cached_file
232
 
233
- seedvr_manager_singleton = SeedVrManager()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  #
3
  # Copyright (C) 2025 Carlos Rodrigues dos Santos
4
  #
5
+ # Version: 5.0.0 (GPU Pool Manager)
6
  #
7
+ # Esta versão transforma o SeedVrManager em um Pool Manager, similar ao LTX,
8
+ # para gerenciar múltiplos workers em GPUs dedicadas e fazer rodízio,
9
+ # otimizando o uso de VRAM e o tempo de carregamento/descarregamento.
 
 
10
 
11
  import torch
12
  import torch.distributed as dist
 
15
  import logging
16
  import sys
17
  import subprocess
18
+ import threading
19
  from pathlib import Path
20
  from urllib.parse import urlparse
21
  from torch.hub import download_url_to_file
 
24
  from einops import rearrange
25
  import shutil
26
  from omegaconf import OmegaConf
27
+ import yaml
28
+
29
+ # Imports relativos para o hardware_manager
30
+ from ..tools.hardware_manager import hardware_manager
31
 
32
  logger = logging.getLogger(__name__)
33
 
 
37
  SEEDVR_SPACE_DIR = DEPS_DIR / "SeedVR_Space"
38
  SEEDVR_SPACE_URL = "https://huggingface.co/spaces/ByteDance-Seed/SeedVR2-3B"
39
 
40
+ class SeedVrWorker:
41
+ """Representa uma única instância do pipeline SeedVR em um dispositivo."""
42
+ def __init__(self, device_id: str):
43
+ self.device = torch.device(device_id)
44
  self.runner = None
 
45
  self.is_initialized = False
46
+ self.setup_complete = self._check_and_run_global_setup()
47
+ logger.info(f"SeedVR Worker inicializado para o dispositivo {self.device}.")
 
48
 
49
+ @staticmethod
50
+ def _check_and_run_global_setup():
51
+ """Executa o setup de arquivos uma única vez para toda a aplicação."""
52
+ # Usa um arquivo de flag para garantir que o setup só rode uma vez.
53
+ setup_flag = DEPS_DIR / "seedvr.setup.complete"
54
+ if setup_flag.exists():
55
+ return True
56
 
57
+ logger.info("--- Iniciando Setup Global do SeedVR (primeira execução) ---")
58
 
59
+ # 1. Clonar o repositório
60
  if not SEEDVR_SPACE_DIR.exists():
61
+ logger.info(f"Clonando repositório SeedVR Space para {SEEDVR_SPACE_DIR}...")
62
  DEPS_DIR.mkdir(exist_ok=True, parents=True)
63
+ subprocess.run(["git", "clone", "--depth", "1", SEEDVR_SPACE_URL, str(SEEDVR_SPACE_DIR)], check=True)
 
 
 
64
 
65
+ # 2. Copiar as pastas necessárias
66
  required_dirs = ["projects", "common", "models", "configs_3b", "configs_7b"]
67
  for dirname in required_dirs:
68
  source = SEEDVR_SPACE_DIR / dirname
69
  target = APP_ROOT / dirname
70
  if not target.exists():
71
+ logger.info(f"Copiando '{dirname}' para a raiz da aplicação...")
72
  shutil.copytree(source, target)
73
 
74
+ # 3. Adicionar a raiz ao sys.path
75
  if str(APP_ROOT) not in sys.path:
76
  sys.path.insert(0, str(APP_ROOT))
77
+ logger.info(f"Adicionado '{APP_ROOT}' ao sys.path.")
78
 
79
+ # 4. Instalar Apex
80
  try:
81
  import apex
 
82
  except ImportError:
83
+ logger.info("Instalando dependência Apex...")
84
  apex_url = 'https://huggingface.co/ByteDance-Seed/SeedVR2-3B/resolve/main/apex-0.1-cp310-cp310-linux_x86_64.whl'
85
  apex_wheel_path = _load_file_from_url(url=apex_url, model_dir=str(DEPS_DIR))
86
  subprocess.run(f"pip install {apex_wheel_path}", check=True, shell=True)
 
87
 
88
+ # 5. Baixar os modelos
89
  ckpt_dir = APP_ROOT / 'ckpts'
90
  ckpt_dir.mkdir(exist_ok=True)
91
+ model_urls = {
92
  'vae': 'https://huggingface.co/ByteDance-Seed/SeedVR2-3B/resolve/main/ema_vae.pth',
93
  'dit_3b': 'https://huggingface.co/ByteDance-Seed/SeedVR2-3B/resolve/main/seedvr2_ema_3b.pth',
94
+ 'dit_7b': 'https://huggingface.co/ByteDance-Seed/SeedVR2-7B/resolve/main/seedvr2_ema_7b.pth',
95
  'pos_emb': 'https://huggingface.co/ByteDance-Seed/SeedVR2-3B/resolve/main/pos_emb.pt',
96
  'neg_emb': 'https://huggingface.co/ByteDance-Seed/SeedVR2-3B/resolve/main/neg_emb.pt'
97
  }
98
+ for name, url in model_urls.items():
99
  _load_file_from_url(url=url, model_dir=str(ckpt_dir))
100
+
101
+ setup_flag.touch() # Cria o arquivo de flag
102
+ logger.info("--- Setup Global do SeedVR Concluído ---")
103
+ return True
104
 
105
+ def initialize_runner(self, model_version: str):
106
+ """Carrega os modelos para a VRAM do dispositivo deste worker."""
107
  if self.runner is not None: return
108
 
 
 
 
109
  # Agora que o setup está feito, podemos importar os módulos
110
  from projects.video_diffusion_sr.infer import VideoDiffusionInfer
111
  from common.config import load_config
 
112
 
113
+ # O torch.distributed é complicado em multi-GPU, vamos desativá-lo para SeedVR
114
+ # que cada worker é independente.
115
+ logger.info(f"Worker {self.device}: Inicializando runner SeedVR2 {model_version}...")
116
+ if model_version == '3B':
117
+ config_path = APP_ROOT / 'configs_3b' / 'main.yaml'
118
+ checkpoint_path = APP_ROOT / 'ckpts' / 'seedvr2_ema_3b.pth'
119
+ else: # 7B
120
+ config_path = APP_ROOT / 'configs_7b' / 'main.yaml'
121
+ checkpoint_path = APP_ROOT / 'ckpts' / 'seedvr2_ema_7b.pth'
 
 
 
 
 
 
122
 
123
  config = load_config(str(config_path))
124
 
125
  self.runner = VideoDiffusionInfer(config)
126
  OmegaConf.set_readonly(self.runner.config, False)
127
 
128
+ self.runner.configure_dit_model(device=str(self.device), checkpoint=str(checkpoint_path))
129
  self.runner.configure_vae_model()
130
 
 
 
 
131
  self.is_initialized = True
132
+ logger.info(f"Worker {self.device}: Runner para SeedVR2 {model_version} inicializado e pronto na VRAM.")
133
 
134
+ def unload_runner(self):
135
+ """Descarrega os modelos da VRAM."""
136
  if self.runner is not None:
137
+ # Mova todos os componentes para a CPU antes de deletar
138
+ self.runner.dit.to('cpu')
139
+ self.runner.vae.to('cpu')
140
  del self.runner
141
  self.runner = None
142
  gc.collect()
143
  torch.cuda.empty_cache()
144
  self.is_initialized = False
145
+ logger.info(f"Worker {self.device}: Runner do SeedVR2 descarregado da VRAM.")
146
+
147
+ def process_video_internal(self, *args, **kwargs):
148
+ # A lógica de processamento de vídeo em si.
149
+ # Precisamos importar aqui, pois o sys.path é modificado no setup
150
+ from common.seed import set_seed
151
+ from data.image.transforms.divisible_crop import DivisibleCrop
152
+ from data.image.transforms.na_resize import NaResize
153
+ from data.video.transforms.rearrange import Rearrange
154
+ from projects.video_diffusion_sr.color_fix import wavelet_reconstruction
155
+ from torchvision.transforms import Compose, Lambda, Normalize
156
+ from torchvision.io.video import read_video
157
+
158
+ input_video_path, output_video_path, prompt, model_version, steps, seed = args
159
+
160
+ set_seed(seed, same_across_ranks=True)
161
+ self.runner.config.diffusion.timesteps.sampling.steps = steps
162
+ self.runner.configure_diffusion()
163
+
164
+ video_tensor = read_video(input_video_path, output_format="TCHW")[0] / 255.0
165
+ res_h, res_w = video_tensor.shape[-2:]
166
+ video_transform = Compose([
167
+ NaResize(resolution=(res_h * res_w) ** 0.5, mode="area", downsample_only=False),
168
+ Lambda(lambda x: torch.clamp(x, 0.0, 1.0)),
169
+ DivisibleCrop((16, 16)),
170
+ Normalize(0.5, 0.5),
171
+ Rearrange("t c h w -> c t h w"),
172
+ ])
173
+ cond_latents = [video_transform(video_tensor.to(self.device))]
174
+ input_videos = cond_latents
175
+ self.runner.dit.to("cpu")
176
+ self.runner.vae.to(self.device)
177
+ cond_latents = self.runner.vae_encode(cond_latents)
178
+ self.runner.vae.to("cpu"); gc.collect(); torch.cuda.empty_cache()
179
+ self.runner.dit.to(self.device)
180
+
181
+ pos_emb = torch.load(APP_ROOT / 'ckpts' / 'pos_emb.pt').to(self.device)
182
+ neg_emb = torch.load(APP_ROOT / 'ckpts' / 'neg_emb.pt').to(self.device)
183
+ text_embeds_dict = {"texts_pos": [pos_emb], "texts_neg": [neg_emb]}
184
+
185
+ noises = [torch.randn_like(latent) for latent in cond_latents]
186
+ conditions = [self.runner.get_condition(noise, latent_blur=latent, task="sr") for noise, latent in zip(noises, cond_latents)]
187
+
188
+ with torch.no_grad(), torch.autocast("cuda", torch.bfloat16, enabled=True):
189
+ video_tensors = self.runner.inference(noises=noises, conditions=conditions, dit_offload=True, **text_embeds_dict)
190
+
191
+ self.runner.dit.to("cpu"); gc.collect(); torch.cuda.empty_cache()
192
+ self.runner.vae.to(self.device)
193
+ samples = self.runner.vae_decode(video_tensors)
194
+ final_sample = samples[0]
195
+ input_video_sample = input_videos[0]
196
+ if final_sample.shape[1] < input_video_sample.shape[1]:
197
+ input_video_sample = input_video_sample[:, :final_sample.shape[1]]
198
+
199
+ final_sample = wavelet_reconstruction(rearrange(final_sample, "c t h w -> t c h w"), rearrange(input_video_sample, "c t h w -> t c h w"))
200
+ final_sample = rearrange(final_sample, "t c h w -> t h w c")
201
+ final_sample = final_sample.clip(-1, 1).mul_(0.5).add_(0.5).mul_(255).round()
202
+ final_sample_np = final_sample.to(torch.uint8).cpu().numpy()
203
+
204
+ mediapy.write_video(output_video_path, final_sample_np, fps=24)
205
+ return output_video_path
206
+
207
+ class SeedVrPoolManager:
208
+ def __init__(self, device_ids: list[str]):
209
+ logger.info(f"SEEDVR POOL MANAGER: Criando workers para os dispositivos: {device_ids}")
210
+ if not device_ids or device_ids == ['cpu']:
211
+ raise ValueError("SeedVrPoolManager requer GPUs dedicadas. Verifique a configuração.")
212
+ self.workers = [SeedVrWorker(device_id) for device_id in device_ids]
213
+ self.current_worker_index = 0
214
+ self.lock = threading.Lock()
215
+ self.last_cleanup_thread = None
216
+
217
+ def _cleanup_worker_thread(self, worker: SeedVrWorker):
218
+ """Thread para descarregar o worker em segundo plano."""
219
+ logger.info(f"SEEDVR CLEANUP THREAD: Iniciando limpeza de {worker.device} em background...")
220
+ worker.unload_runner()
221
 
222
  def process_video(self, input_video_path: str, output_video_path: str, prompt: str,
223
+ model_version: str = '7B', steps: int = 100, seed: int = 666,
224
  progress: gr.Progress = None) -> str:
225
+ worker_to_use = None
226
  try:
227
+ with self.lock:
228
+ # Espera a limpeza do worker anterior terminar, se houver
229
+ if self.last_cleanup_thread and self.last_cleanup_thread.is_alive():
230
+ logger.info("Aguardando a thread de limpeza anterior terminar...")
231
+ self.last_cleanup_thread.join()
232
+
233
+ # Seleciona o worker atual e o próximo a ser limpo
234
+ worker_to_use = self.workers[self.current_worker_index]
235
+ previous_worker_index = (self.current_worker_index - 1 + len(self.workers)) % len(self.workers)
236
+ worker_to_cleanup = self.workers[previous_worker_index]
237
+
238
+ # Inicia a limpeza do worker anterior em uma nova thread
239
+ cleanup_thread = threading.Thread(target=self._cleanup_worker_thread, args=(worker_to_cleanup,))
240
+ cleanup_thread.start()
241
+ self.last_cleanup_thread = cleanup_thread
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
243
+ # Carrega o modelo no worker atual
244
+ worker_to_use.initialize_runner(model_version)
 
 
 
 
 
245
 
246
+ # Avança o índice para o próximo rodízio
247
+ self.current_worker_index = (self.current_worker_index + 1) % len(self.workers)
 
 
248
 
249
+ logger.info(f"SEEDVR POOL MANAGER: Processando vídeo em {worker_to_use.device}...")
250
+ return worker_to_use.process_video_internal(
251
+ input_video_path, output_video_path, prompt, model_version, steps, seed
252
+ )
253
+ except Exception as e:
254
+ logger.error(f"SEEDVR POOL MANAGER: Erro durante o processamento de vídeo: {e}", exc_info=True)
255
+ raise e
256
  finally:
257
+ # A limpeza agora é feita pela thread, não mais aqui.
258
+ pass
259
 
260
  def _load_file_from_url(url, model_dir='./', file_name=None):
261
  os.makedirs(model_dir, exist_ok=True)
262
  filename = file_name or os.path.basename(urlparse(url).path)
263
  cached_file = os.path.abspath(os.path.join(model_dir, filename))
264
  if not os.path.exists(cached_file):
265
+ logger.info(f'Baixando: "{url}" para {cached_file}')
266
  download_url_to_file(url, cached_file, hash_prefix=None, progress=True)
267
  return cached_file
268
 
269
+ # --- Instanciação Singleton ---
270
+ with open("config.yaml", 'r') as f:
271
+ config = yaml.safe_load(f)
272
+
273
+ seedvr_gpus_required = config['specialists'].get('seedvr', {}).get('gpus_required', 0)
274
+ seedvr_device_ids = hardware_manager.allocate_gpus('SeedVR', seedvr_gpus_required)
275
+
276
+ # Verificação crucial para garantir que não tente instanciar um pool sem GPUs
277
+ if seedvr_gpus_required > 0 and 'cpu' not in seedvr_device_ids:
278
+ seedvr_manager_singleton = SeedVrPoolManager(device_ids=seedvr_device_ids)
279
+ logger.info("Especialista de Masterização HD (SeedVR Pool) pronto.")
280
+ else:
281
+ # Se nenhuma GPU for alocada, criamos um placeholder para evitar erros.
282
+ class SeedVrPlaceholder:
283
+ def process_video(self, *args, **kwargs):
284
+ logger.error("SeedVR não foi inicializado pois nenhuma GPU foi alocada na config.yaml. Pulando etapa de masterização.")
285
+ # Retorna o vídeo de entrada para não quebrar o fluxo
286
+ return kwargs.get('input_video_path') or args[0]
287
+
288
+ seedvr_manager_singleton = SeedVrPlaceholder()
289
+ logger.warning("SeedVR Pool Manager não foi inicializado. Nenhuma GPU foi requisitada na config.yaml.")```
290
+
291
+ ### Passo 3: Ajustar o `AducOrchestrator`
292
+
293
+ Agora, o `orchestrator` não precisa mais se preocupar em descarregar os modelos LTX, pois eles estão em GPUs diferentes do SeedVR. A chamada para a masterização HD se torna mais limpa.
294
+
295
+ No arquivo `aduc_framework/orchestrator.py`:
296
+
297
+ ```python
298
+ # Em aduc_framework/orchestrator.py
299
+
300
+ # ... (outros imports)
301
+ from .managers.seedvr_manager import seedvr_manager_singleton # Garanta que está importando o novo singleton
302
+
303
+ class AducOrchestrator:
304
+ # ... (outras funções)
305
+
306
+ def task_run_hd_mastering(self, source_video_path: str, model_version: str, steps: int, prompt: str, progress_callback: ProgressCallback = None) -> Generator[Dict[str, Any], None, None]:
307
+ if not self.director.workspace_dir: raise RuntimeError("Orchestrator não inicializado corretamente.")
308
+ logger.info(f"--- ORQUESTRADOR: Tarefa de Masterização HD com SeedVR {model_version} ---")
309
+
310
+ # <<< --- REMOVA O BLOCO DE DESCARREGAMENTO DE MODELOS DAQUI --- >>>
311
+ # Não precisamos mais descarregar LTX e VAE, pois estão em GPUs diferentes.
312
+
313
+ run_timestamp = int(time.time())
314
+ output_path = os.path.join(self.director.workspace_dir, f"hd_mastered_movie_{model_version}_{run_timestamp}.mp4")
315
+
316
+ # A chamada para process_video agora usa o Pool Manager, que gerencia o rodízio internamente.
317
+ final_path = seedvr_manager_singleton.process_video(
318
+ input_video_path=source_video_path,
319
+ output_video_path=output_path,
320
+ prompt=prompt,
321
+ model_version=model_version,
322
+ steps=steps
323
+ )
324
+ logger.info(f"Masterização HD completa! Vídeo final em: {final_path}")
325
+ yield {"final_path": final_path}