carlex3321 commited on
Commit
4304413
·
verified ·
1 Parent(s): cc3b1e1

Update services/vincie.py

Browse files
Files changed (1) hide show
  1. services/vincie.py +78 -218
services/vincie.py CHANGED
@@ -1,19 +1,9 @@
1
  #!/usr/bin/env python3
2
- import os
3
- import json
4
- import subprocess
5
  from pathlib import Path
6
  from typing import List, Optional
7
- from huggingface_hub import snapshot_download
8
-
9
- def _ls(path: Path, title: str = ""):
10
- try:
11
- print(f"[vince][ls] {title}: {path}")
12
- result = subprocess.run(["bash", "-lc", f"ls -la {str(path)} || true"], capture_output=True, text=True)
13
- print(result.stdout)
14
- print(result.stderr)
15
- except Exception as e:
16
- print(f"[vince][ls] erro listando {path}: {e}")
17
 
18
  class VincieService:
19
  def __init__(
@@ -23,241 +13,111 @@ class VincieService:
23
  python_bin: str = "python3",
24
  repo_id: str = "ByteDance-Seed/VINCIE-3B",
25
  ):
26
- # Executar ls /app/ckpt antes de iniciar
27
- print("[vince][pre-init] Executando ls -la /app/ckpt antes de iniciar o serviço")
28
- _ls(Path("/app/ckpt"), "pre-init /app/ckpt")
29
-
30
  self.repo_dir = Path(repo_dir)
31
  self.ckpt_dir = Path(ckpt_dir)
32
  self.python = python_bin
33
  self.repo_id = repo_id
34
-
35
- # Saídas persistentes
36
- self.output_root = Path("/data/outputs") if Path("/data").exists() else Path("/app/outputs")
37
- self.output_root.mkdir(parents=True, exist_ok=True)
38
-
39
- # Configs do VINCIE
40
  self.generate_yaml = self.repo_dir / "configs" / "generate.yaml"
41
- self.generate_yaml_distributed = self.repo_dir / "configs" / "generate_distributed.yaml"
42
-
43
- # Diretório ckpt dentro do repositório (para symlink)
44
  (self.repo_dir / "ckpt").mkdir(parents=True, exist_ok=True)
45
 
46
- print(f"[vince][init] repo_dir={self.repo_dir} ckpt_dir={self.ckpt_dir} output_root={self.output_root}")
47
- _ls(self.repo_dir, "repo_dir (init)")
48
- _ls(self.ckpt_dir, "ckpt_dir (init)")
49
-
50
- # ---------------------- Utilidades de cache e links ----------------------
51
- def _hf_cache_dir(self) -> Path:
52
- if Path("/data").exists():
53
- base = Path(os.environ.get("HF_HOME", "/data/.cache/huggingface"))
54
- else:
55
- base = Path(os.environ.get("HF_HOME", "/app/.cache/huggingface"))
56
- cache_dir = base / "hub"
57
- cache_dir.mkdir(parents=True, exist_ok=True)
58
- # Symlink de compatibilidade com /app/.cache
59
- try:
60
- Path("/app/.cache").mkdir(parents=True, exist_ok=True)
61
- if not Path("/app/.cache/huggingface").exists():
62
- Path("/app/.cache/huggingface").symlink_to(base)
63
- except Exception as e:
64
- print(f"[vince][cache] aviso ao criar symlink /app/.cache/huggingface -> {base}: {e}")
65
- print(f"[vince][cache] HF_HOME={base} HF_HUB_CACHE={cache_dir}")
66
- _ls(cache_dir, "HF_HUB_CACHE")
67
- return cache_dir
68
-
69
- def _model_cache_path(self, cache_dir: Path) -> Path:
70
- m = cache_dir / f"models--{self.repo_id.replace('/', '--')}"
71
- print(f"[vince][cache] model_cache_path={m}")
72
- return m
73
-
74
- def _ensure_aliases(self) -> None:
75
- print("[vince][aliases] verificando/criando aliases dit.pth e vae.pth")
76
- candidates = {
77
- "dit.pth": ["dit.pth", "VINCIE-3B-dit.pth", "dit.safetensors", "pytorch_model.bin"],
78
- "vae.pth": ["vae.pth", "VINCIE-3B-vae.pth", "vae.safetensors", "vae.ckpt"],
79
- }
80
- for target_name, options in candidates.items():
81
- target = self.ckpt_dir / target_name
82
- if target.exists():
83
- print(f"[vince][aliases] existe {target_name} -> OK")
84
- continue
85
- found = None
86
- for opt in options:
87
- for p in self.ckpt_dir.rglob(opt):
88
- found = p
89
- break
90
- if found:
91
- break
92
- if found:
93
- try:
94
- target.symlink_to(found)
95
- print(f"[vince][aliases] criado {target_name} -> {found}")
96
- except Exception as e:
97
- print(f"[vince][aliases] falha ao criar {target_name}: {e}")
98
- else:
99
- print(f"[vince][aliases] não encontrado candidato para {target_name}")
100
- _ls(self.ckpt_dir, "ckpt_dir após aliases")
101
-
102
- def _link_ckpt(self) -> None:
103
- print("[vince][link] garantindo symlink /app/VINCIE/ckpt/VINCIE-3B -> ckpt_dir")
104
- link_parent = self.repo_dir / "ckpt"
105
- link_parent.mkdir(parents=True, exist_ok=True)
106
- link_path = link_parent / "VINCIE-3B"
107
- if link_path.exists() or link_path.is_symlink():
108
- try:
109
- link_path.unlink()
110
- except Exception as e:
111
- print(f"[vince][link] aviso ao remover link antigo: {e}")
112
- try:
113
- link_path.symlink_to(self.ckpt_dir, target_is_directory=True)
114
- print(f"[vince][link] criado {link_path} -> {self.ckpt_dir}")
115
- except Exception as e:
116
- print(f"[vince][link] falha ao criar link: {e}")
117
- _ls(link_parent, "repo/ckpt")
118
-
119
- # ---------------------- Setup repositório/modelo ----------------------
120
  def ensure_repo(self, git_url: str = "https://github.com/ByteDance-Seed/VINCIE") -> None:
121
  if not self.repo_dir.exists():
122
- print(f"[vince][repo] clonando {git_url} em {self.repo_dir}")
123
  subprocess.run(["git", "clone", "--depth", "1", git_url, str(self.repo_dir)], check=True)
124
- else:
125
- print(f"[vince][repo] já existe {self.repo_dir}")
126
- _ls(self.repo_dir, "repo_dir (ensure_repo)")
127
 
128
  def ensure_model(self, hf_token: Optional[str] = None) -> None:
129
- cache_dir = self._hf_cache_dir()
130
- mcache = self._model_cache_path(cache_dir)
131
- token = hf_token or os.getenv("HF_TOKEN") or os.getenv("HUGGING_FACE_TOKEN")
132
-
133
- # 1) Usa ckpt_dir se já possui conteúdo
134
- if self.ckpt_dir.exists():
135
- try:
136
- if any(self.ckpt_dir.iterdir()):
137
- print(f"[vince][model] dados presentes em {self.ckpt_dir} — pulando download")
138
- self._ensure_aliases()
139
- self._link_ckpt()
140
- return
141
- except PermissionError as e:
142
- print(f"[vince][model] permissão ao listar {self.ckpt_dir}: {e}")
143
 
144
- # 2) Usa cache se existe
145
- if mcache.exists():
146
  try:
147
- if any(mcache.iterdir()):
148
- print(f"[vince][model] cache do Hub em {mcache} — linkando para {self.ckpt_dir}")
149
- self.ckpt_dir.mkdir(parents=True, exist_ok=True)
150
- for item in mcache.iterdir():
151
- target = self.ckpt_dir / item.name
152
- if not target.exists():
153
- try:
154
- target.symlink_to(item)
155
- except Exception as e:
156
- print(f"[vince][model] aviso link {target} -> {item}: {e}")
157
- self._ensure_aliases()
158
- self._link_ckpt()
159
- return
160
- except PermissionError as e:
161
- print(f"[vince][model] permissão ao listar {mcache}: {e}")
162
-
163
- # 3) Baixa snapshot se necessário
164
- print(f"[vince][model] baixando snapshot {self.repo_id} em {cache_dir}")
165
- snapshot_download(
166
- repo_id=self.repo_id,
167
- cache_dir=str(cache_dir),
168
- token=token,
169
- resume_download=True,
170
- local_dir_use_symlinks=False
171
- )
172
-
173
- # 4) Linka do cache recém-baixado
174
- if mcache.exists() and any(mcache.iterdir()):
175
- print(f"[vince][model] pós-download: linkando {mcache} -> {self.ckpt_dir}")
176
- self.ckpt_dir.mkdir(parents=True, exist_ok=True)
177
- for item in mcache.iterdir():
178
- target = self.ckpt_dir / item.name
179
- if not target.exists():
180
- try:
181
- target.symlink_to(item)
182
- except Exception as e:
183
- print(f"[vince][model] aviso link pós-download {target} -> {item}: {e}")
184
- self._ensure_aliases()
185
- self._link_ckpt()
186
- else:
187
- print("[vince][model] aviso: snapshot feito mas diretório do modelo não localizado no cache")
188
- _ls(self.ckpt_dir, "ckpt_dir (final ensure_model)")
189
-
190
- # ---------------------- Execução ----------------------
191
- def run_vincie(self, overrides: List[str], work_output: Path, video_mode: bool = False, num_gpus: int = 1) -> None:
 
192
  work_output.mkdir(parents=True, exist_ok=True)
 
 
 
 
 
 
 
 
 
 
 
 
193
 
194
- # Validação final: dit.pth deve existir no repo/ckpt
195
- need = self.repo_dir / "ckpt" / "VINCIE-3B" / "dit.pth"
196
- if not need.exists():
197
- print(f"[vince][run] {need} ausente — reforçando aliases e link")
198
- self._ensure_aliases()
199
- self._link_ckpt()
200
- if not need.exists():
201
- _ls(self.ckpt_dir, "ckpt_dir (antes do erro)")
202
- _ls(self.repo_dir / "ckpt", "repo/ckpt (antes do erro)")
203
- raise FileNotFoundError(f"Checkpoint ausente: {need}")
204
-
205
- config_file = self.generate_yaml_distributed if num_gpus > 1 else self.generate_yaml
206
- base_cmd = ["main.py", str(config_file)] + overrides + [f"generation.output.dir={str(work_output)}"]
207
- cmd = (["torchrun", f"--nproc_per_node={num_gpus}"] + base_cmd) if num_gpus > 1 else (["python3"] + base_cmd)
208
-
209
- env = os.environ.copy()
210
- if video_mode:
211
- env["VINCIE_GENERATE_VIDEO"] = "1"
212
- else:
213
- env.pop("VINCIE_GENERATE_VIDEO", None)
214
-
215
- print(f"[vince][run] CWD={self.repo_dir}")
216
- print(f"[vince][run] CMD={' '.join(cmd)}")
217
- _ls(self.repo_dir / "ckpt" / "VINCIE-3B", "repo/ckpt/VINCIE-3B (pre-run)")
218
- subprocess.run(cmd, cwd=self.repo_dir, check=True, env=env)
219
-
220
- # ---------------------- Pipelines ----------------------
221
  def multi_turn_edit(self, input_image: str, turns: List[str], **kwargs) -> Path:
222
- num_gpus = int(kwargs.get("num_gpus", 1))
223
- batch_size = int(kwargs.get("batch_size", 1))
224
  out_dir = self.output_root / f"multi_turn_{Path(input_image).stem}"
225
  overrides = [
226
- f'generation.positive_prompt.image_path={json.dumps(str(input_image))}',
227
- f'generation.positive_prompt.prompts={json.dumps(turns)}',
228
  f"generation.seed={int(kwargs.get('seed', 1))}",
229
  f"diffusion.timesteps.sampling.steps={int(kwargs.get('steps', 50))}",
230
  f"diffusion.cfg.scale={float(kwargs.get('cfg_scale', 7.5))}",
231
- f"generation.negative_prompt={json.dumps(kwargs.get('negative_prompt', ''))}",
232
  f"generation.resolution={int(kwargs.get('resolution', 512))}",
233
- f"generation.batch_size={batch_size}",
234
  ]
235
- if kwargs.get("use_vae_slicing", True):
236
- overrides += ["vae.slicing.split_size=1", "vae.slicing.memory_device=same"]
237
- self.run_vincie(overrides, out_dir, video_mode=False, num_gpus=num_gpus)
238
  return out_dir
239
 
240
- def text_to_video(self, input_image: str, prompt: str, **kwargs) -> Path:
241
- num_gpus = int(kwargs.get("num_gpus", 1))
242
- batch_size = int(kwargs.get("batch_size", 1))
243
- out_dir = self.output_root / f"txt2vid_{self._slug(prompt)}"
244
  overrides = [
245
- f'generation.positive_prompt.image_path={json.dumps(str(input_image))}',
246
- f'generation.positive_prompt.prompts={json.dumps([prompt])}',
 
247
  f"generation.seed={int(kwargs.get('seed', 1))}",
248
  f"diffusion.timesteps.sampling.steps={int(kwargs.get('steps', 50))}",
249
  f"diffusion.cfg.scale={float(kwargs.get('cfg_scale', 7.5))}",
250
- f"generation.negative_prompt={json.dumps(kwargs.get('negative_prompt', ''))}",
251
  f"generation.resolution={int(kwargs.get('resolution', 512))}",
252
- f"generation.fps={int(kwargs.get('fps', 2))}",
253
- f"generation.batch_size={batch_size}",
254
  ]
255
- if kwargs.get("use_vae_slicing", True):
256
- overrides += ["vae.slicing.split_size=1", "vae.slicing.memory_device=same"]
257
- self.run_vincie(overrides, out_dir, video_mode=True, num_gpus=num_gpus)
258
  return out_dir
259
-
260
- @staticmethod
261
- def _slug(text: str) -> str:
262
- s = "".join(c if c.isalnum() or c in "-." else "_" for c in text)
263
- return s[:64]
 
1
  #!/usr/bin/env python3
2
+ import os, sys, json, subprocess
 
 
3
  from pathlib import Path
4
  from typing import List, Optional
5
+ from time import time, sleep
6
+ from huggingface_hub import hf_hub_download
 
 
 
 
 
 
 
 
7
 
8
  class VincieService:
9
  def __init__(
 
13
  python_bin: str = "python3",
14
  repo_id: str = "ByteDance-Seed/VINCIE-3B",
15
  ):
 
 
 
 
16
  self.repo_dir = Path(repo_dir)
17
  self.ckpt_dir = Path(ckpt_dir)
18
  self.python = python_bin
19
  self.repo_id = repo_id
 
 
 
 
 
 
20
  self.generate_yaml = self.repo_dir / "configs" / "generate.yaml"
21
+ self.output_root = Path("/app/outputs")
22
+ self.output_root.mkdir(parents=True, exist_ok=True)
 
23
  (self.repo_dir / "ckpt").mkdir(parents=True, exist_ok=True)
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  def ensure_repo(self, git_url: str = "https://github.com/ByteDance-Seed/VINCIE") -> None:
26
  if not self.repo_dir.exists():
 
27
  subprocess.run(["git", "clone", "--depth", "1", git_url, str(self.repo_dir)], check=True)
 
 
 
28
 
29
  def ensure_model(self, hf_token: Optional[str] = None) -> None:
30
+ self.ckpt_dir.mkdir(parents=True, exist_ok=True)
31
+ token = hf_token or os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_TOKEN")
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
+ def _need(p: Path) -> bool:
 
34
  try:
35
+ return not (p.exists() and p.stat().st_size > 1_000_000)
36
+ except FileNotFoundError:
37
+ return True
38
+
39
+ for fname in ["dit.pth", "vae.pth"]:
40
+ dst = self.ckpt_dir / fname
41
+ if _need(dst):
42
+ print(f"[vince] downloading {fname} from {self.repo_id} ...")
43
+ hf_hub_download(
44
+ repo_id=self.repo_id,
45
+ filename=fname,
46
+ local_dir=str(self.ckpt_dir),
47
+ token=token,
48
+ force_download=False,
49
+ local_files_only=False,
50
+ )
51
+
52
+ link = self.repo_dir / "ckpt" / "VINCIE-3B"
53
+ try:
54
+ if link.is_symlink() or link.exists():
55
+ try:
56
+ link.unlink()
57
+ except IsADirectoryError:
58
+ pass
59
+ if not link.exists():
60
+ link.symlink_to(self.ckpt_dir, target_is_directory=True)
61
+ except Exception as e:
62
+ print("[vince] symlink warning:", e)
63
+
64
+ def ready(self) -> bool:
65
+ have_repo = self.repo_dir.exists() and self.generate_yaml.exists()
66
+ dit_ok = (self.ckpt_dir / "dit.pth").exists()
67
+ vae_ok = (self.ckpt_dir / "vae.pth").exists()
68
+ return bool(have_repo and dit_ok and vae_ok)
69
+
70
+ def _wait_until_outputs(self, out_dir: Path, timeout_s: int = 300) -> None:
71
+ exts = (".png", ".jpg", ".jpeg", ".gif", ".mp4")
72
+ deadline = time() + timeout_s
73
+ while time() < deadline:
74
+ if any(p.is_file() and p.suffix.lower() in exts for p in out_dir.rglob("*")):
75
+ print(f"[vince] outputs detected in {out_dir}")
76
+ return
77
+ sleep(1)
78
+ print(f"[vince] warning: no outputs detected in {out_dir} within {timeout_s}s")
79
+
80
+ def _run_vincie(self, overrides: List[str], work_output: Path, wait_outputs: bool = True) -> None:
81
  work_output.mkdir(parents=True, exist_ok=True)
82
+ cmd = [
83
+ self.python,
84
+ "main.py",
85
+ str(self.generate_yaml),
86
+ *overrides,
87
+ f"generation.output.dir={str(work_output)}",
88
+ ]
89
+ print("[vince] CWD=", self.repo_dir)
90
+ print("[vince] CMD=", " ".join(cmd))
91
+ subprocess.run(cmd, cwd=self.repo_dir, check=True, env=os.environ.copy())
92
+ if wait_outputs:
93
+ self._wait_until_outputs(work_output, timeout_s=int(os.getenv("VINCIE_WAIT_OUTPUTS_SEC", "300")))
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  def multi_turn_edit(self, input_image: str, turns: List[str], **kwargs) -> Path:
 
 
96
  out_dir = self.output_root / f"multi_turn_{Path(input_image).stem}"
97
  overrides = [
98
+ f'generation.positive_prompt.image_path="{str(input_image)}"',
99
+ f"generation.positive_prompt.prompts={json.dumps(turns)}",
100
  f"generation.seed={int(kwargs.get('seed', 1))}",
101
  f"diffusion.timesteps.sampling.steps={int(kwargs.get('steps', 50))}",
102
  f"diffusion.cfg.scale={float(kwargs.get('cfg_scale', 7.5))}",
103
+ f'generation.negative_prompt="{kwargs.get("negative_prompt","")}"',
104
  f"generation.resolution={int(kwargs.get('resolution', 512))}",
105
+ f"generation.batch_size={int(kwargs.get('batch_size', 1))}",
106
  ]
107
+ self._run_vincie(overrides, out_dir, wait_outputs=True)
 
 
108
  return out_dir
109
 
110
+ def multi_concept_compose(self, files: List[str], descs: List[str], final_prompt: str, **kwargs) -> Path:
111
+ out_dir = self.output_root / f"multi_concept_{len(files)}"
 
 
112
  overrides = [
113
+ f"generation.concepts.files={json.dumps(files)}",
114
+ f"generation.concepts.descs={json.dumps(descs)}",
115
+ f'generation.final_prompt="{final_prompt}"',
116
  f"generation.seed={int(kwargs.get('seed', 1))}",
117
  f"diffusion.timesteps.sampling.steps={int(kwargs.get('steps', 50))}",
118
  f"diffusion.cfg.scale={float(kwargs.get('cfg_scale', 7.5))}",
 
119
  f"generation.resolution={int(kwargs.get('resolution', 512))}",
120
+ f"generation.batch_size={int(kwargs.get('batch_size', 1))}",
 
121
  ]
122
+ self._run_vincie(overrides, out_dir, wait_outputs=True)
 
 
123
  return out_dir