File size: 6,047 Bytes
a5720bf
 
 
 
 
 
 
c96bb7c
a5720bf
 
 
 
 
c96bb7c
a5720bf
 
 
c96bb7c
 
 
a5720bf
c96bb7c
a5720bf
 
c96bb7c
a5720bf
 
 
 
 
 
 
 
 
 
c96bb7c
 
a5720bf
 
 
 
 
 
c96bb7c
 
 
 
a5720bf
 
 
 
c96bb7c
 
 
 
 
 
 
a5720bf
c96bb7c
a5720bf
 
 
 
 
 
 
 
c96bb7c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a5720bf
1209cf5
 
 
c96bb7c
1209cf5
c96bb7c
a5720bf
1209cf5
a5720bf
1209cf5
 
 
a5720bf
c96bb7c
 
 
 
 
1209cf5
a5720bf
 
 
1209cf5
 
a5720bf
c96bb7c
a5720bf
c96bb7c
a5720bf
c96bb7c
 
 
 
 
 
 
 
1209cf5
 
c96bb7c
 
a5720bf
c96bb7c
 
 
 
 
 
 
 
 
a5720bf
c96bb7c
 
a5720bf
c96bb7c
a5720bf
c96bb7c
 
a5720bf
 
c96bb7c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import os
import shutil
import subprocess
import sys
import time
import mimetypes
from pathlib import Path
from typing import Optional, Tuple
from huggingface_hub import hf_hub_download

class SeedVRServer:
    def __init__(self, **kwargs):
        self.SEEDVR_ROOT = Path(os.getenv("SEEDVR_ROOT", "/data/SeedVR"))
        self.CKPTS_ROOT = Path("/data/seedvr_models_fp16")
        self.OUTPUT_ROOT = Path(os.getenv("OUTPUT_ROOT", "/app/outputs"))
        self.INPUT_ROOT = Path(os.getenv("INPUT_ROOT", "/app/inputs"))
        self.HF_HOME_CACHE = Path(os.getenv("HF_HOME", "/data/.cache/huggingface"))
        # Use 8 por padrão, mas nunca maior que o visível no container/host
        self.NUM_GPUS_TOTAL = min(int(os.getenv("NUM_GPUS", "8")),
                                  int(os.getenv("MAX_VISIBLE_GPUS", "8")))
        print("🚀 SeedVRServer (FP16) inicializando e preparando o ambiente...")

        for p in [self.SEEDVR_ROOT.parent, self.CKPTS_ROOT, self.OUTPUT_ROOT, self.INPUT_ROOT, self.HF_HOME_CACHE]:
            p.mkdir(parents=True, exist_ok=True)

        self.setup_dependencies()
        print("✅ SeedVRServer (FP16) pronto.")

    def setup_dependencies(self):
        self._ensure_repo()
        self._ensure_model()

    def _ensure_repo(self) -> None:
        if not (self.SEEDVR_ROOT / ".git").exists():
            print(f"[SeedVRServer] Clonando repositório para {self.SEEDVR_ROOT}...")
            subprocess.run(["git", "clone", "--depth", "1", os.getenv("SEEDVR_GIT_URL",
                            "https://github.com/numz/ComfyUI-SeedVR2_VideoUpscaler"), str(self.SEEDVR_ROOT)], check=True)
        else:
            print("[SeedVRServer] Repositório SeedVR já existe.")

    def _ensure_model(self) -> None:
        print(f"[SeedVRServer] Verificando checkpoints (FP16) em {self.CKPTS_ROOT}...")
        model_files = {
            "seedvr2_ema_3b_fp16.safetensors": "MonsterMMORPG/SeedVR2_SECourses",
            "ema_vae_fp16.safetensors": "MonsterMMORPG/SeedVR2_SECourses",
            "pos_emb.pt": "ByteDance-Seed/SeedVR2-3B",
            "neg_emb.pt": "ByteDance-Seed/SeedVR2-3B",
        }
        for filename, repo_id in model_files.items():
            if not (self.CKPTS_ROOT / filename).exists():
                print(f"Baixando {filename} de {repo_id}...")
                hf_hub_download(
                    repo_id=repo_id,
                    filename=filename,
                    local_dir=str(self.CKPTS_ROOT),
                    cache_dir=str(self.HF_HOME_CACHE),
                    token=os.getenv("HF_TOKEN"),
                )
        print("[SeedVRServer] Checkpoints (FP16) estão no local correto.")

    def _prepare_job(self, input_file: str) -> Tuple[Path, Path]:
        ts = f"{int(time.time())}_{os.urandom(4).hex()}"
        job_input_dir = self.INPUT_ROOT / f"job_{ts}"
        out_dir = self.OUTPUT_ROOT / f"run_{ts}"
        job_input_dir.mkdir(parents=True, exist_ok=True)
        out_dir.mkdir(parents=True, exist_ok=True)
        shutil.copy2(input_file, job_input_dir / Path(input_file).name)
        return job_input_dir, out_dir

    def _visible_devices_for(self, nproc: int) -> str:
        # Mapeia 0..nproc-1 (lógico) para o espaço visível do container
        return ",".join(str(i) for i in range(nproc))

    def run_inference(
        self,
        file_path: str,
        *,
        seed: int,
        res_h: int,
        res_w: int,
        sp_size: int,
        fps: Optional[float] = None,
    ) -> Tuple[Optional[str], Optional[str], Path]:

        script = self.SEEDVR_ROOT / "inference_cli.py"
        job_input_dir, out_dir = self._prepare_job(file_path)

        media_type, _ = mimetypes.guess_type(file_path)
        is_image = bool(media_type and media_type.startswith("image"))

        # Política: 1 GPU para imagem, 8 GPUs (ou NUM_GPUS_TOTAL) para vídeo
        effective_nproc = 1 if is_image else self.NUM_GPUS_TOTAL
        effective_sp_size = 1 if is_image else sp_size

        output_filename = f"result_{Path(file_path).stem}.mp4"
        output_filepath = out_dir / output_filename

        cmd = [
            "torchrun",
            "--standalone",
            "--nnodes=1",
            f"--nproc-per-node={effective_nproc}",
            str(script),
            "--video_path", str(file_path),
            "--output", str(output_filepath),
            "--model_dir", str(self.CKPTS_ROOT),
            "--seed", str(seed),
            "--resolution", str(res_h),
            "--batch_size", str(effective_sp_size),
            "--model", "seedvr2_ema_3b_fp16.safetensors",
            "--preserve_vram",
            "--debug",
            "--output_format", "video",
        ]
        # Removido: --cuda_device ... (torchrun + LOCAL_RANK fará o binding correto)

        env = os.environ.copy()
        # Alinhar espaço lógico de devices com nproc
        env["CUDA_VISIBLE_DEVICES"] = self._visible_devices_for(effective_nproc)
        # Dicas úteis de debug (opcional):
        # env["NCCL_DEBUG"] = "WARN"
        # env["CUDA_LAUNCH_BLOCKING"] = "1"

        print("[SeedVRServer] Comando:", " ".join(cmd))
        print("[SeedVRServer] CUDA_VISIBLE_DEVICES:", env.get("CUDA_VISIBLE_DEVICES", ""))

        try:
            subprocess.run(
                cmd,
                cwd=str(self.SEEDVR_ROOT),
                check=True,
                env=env,
                stdout=sys.stdout,
                stderr=sys.stderr,
            )

            if is_image:
                # Se output_format=png no CLI, aqui poderia ser diretório; com "video" mantemos mp4, mas
                # preservamos compatibilidade caso o CLI mude:
                image_dir = output_filepath if output_filepath.suffix == "" else output_filepath.with_suffix("")
                return str(image_dir), None, out_dir
            else:
                return None, str(output_filepath), out_dir

        except Exception as e:
            print(f"[UI ERROR] A inferência falhou: {e}")
            return None, None, out_dir