euIaxs22 commited on
Commit
20ef566
·
verified ·
1 Parent(s): ef8cbb1

Update services/vincie.py

Browse files
Files changed (1) hide show
  1. services/vincie.py +121 -138
services/vincie.py CHANGED
@@ -1,7 +1,20 @@
1
  #!/usr/bin/env python3
2
  """
3
- VincieService (CLI runner, singleton-friendly)
 
 
 
 
 
 
 
 
 
 
 
 
4
  """
 
5
  import os
6
  import json
7
  import subprocess
@@ -16,128 +29,93 @@ class VincieService:
16
  self,
17
  repo_dir: str = "/app/VINCIE",
18
  python_bin: str = "python",
19
- repo_id: str = "ByteDance-Seed/VINCIE-3B",
 
20
  output_root: str = "/app/outputs",
21
  ):
22
  self.repo_dir = Path(repo_dir)
23
  self.python = python_bin
24
- self.repo_id = repo_id
25
- self.generate_yaml = self.repo_dir / "configs" / "generate.yaml"
26
  self.output_root = Path(output_root)
27
  self.output_root.mkdir(parents=True, exist_ok=True)
 
 
 
 
 
28
  self.ckpt_dir: Optional[Path] = None
29
  self._env = os.environ.copy()
30
- (self.repo_dir / "ckpt").mkdir(parents=True, exist_ok=True)
31
 
32
- # ---------- Repositório e modelo ----------
 
 
 
 
33
 
34
- def ensure_repo(self, git_url: str = "https://github.com/ByteDance-Seed/VINCIE") -> None:
35
- """
36
- Garante repositório íntegro:
37
- - Se válido (.git e main.py), mantém.
38
- - Se quebrado/ausente, clona em diretório temporário e substitui atomicamente,
39
- recriando ckpt/ e o symlink conforme necessário.
40
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  self.repo_dir.mkdir(parents=True, exist_ok=True)
42
  main_py = self.repo_dir / "main.py"
43
  git_dir = self.repo_dir / ".git"
44
-
45
- repo_ok = main_py.exists() and git_dir.exists()
46
- if repo_ok:
47
  return
48
-
49
- # Preserva alvo do symlink ckpt/VINCIE-3B, se houver
50
- ckpt_link = self.repo_dir / "ckpt" / "VINCIE-3B"
51
- preserved_target = None
52
- if ckpt_link.is_symlink():
53
- try:
54
- preserved_target = ckpt_link.resolve()
55
- except Exception:
56
- preserved_target = None
57
-
58
- # Clona em diretório temporário
59
- tmp_dir = self.repo_dir.with_name(self.repo_dir.name + ".tmp")
60
- try:
61
- if tmp_dir.exists():
62
- subprocess.run(["rm", "-rf", str(tmp_dir)], check=True)
63
- subprocess.run(["git", "clone", git_url, str(tmp_dir)], check=True)
64
- except subprocess.CalledProcessError as e:
65
- print("Error: git clone failed:", e)
66
- raise
67
-
68
- # Garante ckpt/ no temp e relinka symlink se possível
69
- (tmp_dir / "ckpt").mkdir(parents=True, exist_ok=True)
70
- if preserved_target and preserved_target.exists():
71
- try:
72
- tmp_link = tmp_dir / "ckpt" / "VINCIE-3B"
73
- if tmp_link.exists() or tmp_link.is_symlink():
74
- tmp_link.unlink()
75
- tmp_link.symlink_to(preserved_target, target_is_directory=True)
76
- except Exception as e:
77
- print("Warning: failed to link ckpt in temp repo:", e)
78
-
79
- # Remove repo antigo e move temp para destino
80
- try:
81
- subprocess.run(["rm", "-rf", str(self.repo_dir)], check=True)
82
- tmp_dir.rename(self.repo_dir)
83
- except Exception as e:
84
- print("Error: failed to swap repo directories:", e)
85
- raise
86
-
87
- def ensure_model(self, hf_token: Optional[str] = None, revision: Optional[str] = None) -> None:
88
- """
89
- Baixa snapshot completo no cache (honra HF_HUB_CACHE) e cria symlink no repo.
90
- """
91
- token = hf_token or os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_TOKEN")
92
  cache_dir = os.environ.get("HF_HUB_CACHE")
93
  snapshot_path = snapshot_download(
94
- repo_id=self.repo_id,
95
  revision=revision,
 
96
  cache_dir=cache_dir,
97
- token=token,
98
- local_files_only=False,
99
  )
100
  self.ckpt_dir = Path(snapshot_path)
101
-
102
- link = self.repo_dir / "ckpt" / "VINCIE-3B"
103
- try:
104
- if link.is_symlink() or link.exists():
105
- try:
106
- link.unlink()
107
- except Exception:
108
- pass
109
- link.symlink_to(self.ckpt_dir, target_is_directory=True)
110
- except Exception as e:
111
- print("Warning: failed to create checkpoint symlink:", e)
112
-
113
- def ready(self) -> bool:
114
- have_repo = (self.repo_dir / "main.py").exists() and (self.repo_dir / "configs" / "generate.yaml").exists()
115
- ckpt_ok = False
116
- if getattr(self, "ckpt_dir", None):
117
- ckpt_ok = (self.ckpt_dir / "dit.pth").exists() and (self.ckpt_dir / "vae.pth").exists()
118
- if not ckpt_ok:
119
- link = self.repo_dir / "ckpt" / "VINCIE-3B"
120
- if link.exists():
121
- try:
122
- tgt = link.resolve()
123
- ckpt_ok = (tgt / "dit.pth").exists() and (tgt / "vae.pth").exists()
124
- except Exception:
125
- ckpt_ok = False
126
- return bool(have_repo and ckpt_ok)
127
-
128
- # ---------- GPUs dedicadas e persistência ----------
129
-
130
- def pin_gpus(self, device_indices: List[int]) -> None:
131
- visible = ",".join(str(i) for i in device_indices)
132
- self._env["CUDA_VISIBLE_DEVICES"] = visible
133
-
134
- def enable_persistence_mode(self) -> None:
135
- try:
136
- subprocess.run(["nvidia-smi", "-pm", "1"], check=True)
137
- except Exception as e:
138
- print("Warning: failed to enable persistence mode:", e)
139
-
140
- # ---------- Execução ----------
141
 
142
  def _build_overrides(
143
  self,
@@ -148,8 +126,7 @@ class VincieService:
148
  steps: Optional[int] = None,
149
  ) -> List[str]:
150
  overrides = list(extra_overrides or [])
151
- if self.ckpt_dir is not None:
152
- overrides.append(f"ckpt.path={str(self.ckpt_dir)}")
153
  if cfg_scale is not None:
154
  overrides.append(f"generation.cfg_scale={cfg_scale}")
155
  if resolution_input is not None:
@@ -160,20 +137,8 @@ class VincieService:
160
  overrides.append(f"generation.steps={steps}")
161
  return overrides
162
 
163
- def _run_vincie_once(self, overrides: List[str], work_output: Path) -> None:
164
- work_output.mkdir(parents=True, exist_ok=True)
165
- cmd = [
166
- self.python,
167
- "main.py",
168
- str(self.generate_yaml),
169
- *overrides,
170
- f"generation.output.dir={str(work_output)}",
171
- ]
172
- subprocess.run(cmd, cwd=self.repo_dir, check=True, env=self._env)
173
-
174
  def _clean_gpu_memory(self) -> None:
175
- try:
176
- code = r"""
177
  import torch, gc
178
  try:
179
  torch.cuda.synchronize()
@@ -186,9 +151,7 @@ try:
186
  except Exception:
187
  pass
188
  """
189
- subprocess.run([self.python, "-c", code], check=True, env=self._env)
190
- except Exception as e:
191
- print("Warning: GPU cleanup failed:", e)
192
 
193
  # ---------- APIs ----------
194
 
@@ -202,17 +165,21 @@ except Exception:
202
  resolution_input: Optional[int] = None,
203
  aspect_ratio_input: Optional[str] = None,
204
  steps: Optional[int] = None,
205
- pad_img_placeholder: Optional[bool] = None,
206
- ) -> Path:
207
- out_dir = self.output_root / (out_dir_name or f"multi_turn_{self._slug(input_image)}")
 
 
 
 
 
208
  image_json = json.dumps([str(input_image)])
209
  prompts_json = json.dumps(turns)
 
210
  base_overrides = [
211
  f"generation.positive_prompt.image_path={image_json}",
212
  f"generation.positive_prompt.prompts={prompts_json}",
213
  ]
214
- if pad_img_placeholder is not None:
215
- base_overrides.append(f"generation.pad_img_placehoder={str(bool(pad_img_placeholder)).lower()}")
216
  overrides = self._build_overrides(
217
  extra_overrides=base_overrides,
218
  cfg_scale=cfg_scale,
@@ -220,9 +187,17 @@ except Exception:
220
  aspect_ratio_input=aspect_ratio_input,
221
  steps=steps,
222
  )
223
- self._run_vincie_once(overrides, out_dir)
 
 
 
 
 
 
 
 
224
  self._clean_gpu_memory()
225
- return out_dir
226
 
227
  def multi_concept_compose(
228
  self,
@@ -235,11 +210,18 @@ except Exception:
235
  resolution_input: Optional[int] = None,
236
  aspect_ratio_input: Optional[str] = None,
237
  steps: Optional[int] = None,
238
- ) -> Path:
 
 
 
 
239
  out_dir = self.output_root / (out_dir_name or "multi_concept")
 
 
240
  imgs_json = json.dumps([str(p) for p in concept_images])
241
  prompts_all = concept_prompts + [final_prompt]
242
  prompts_json = json.dumps(prompts_all)
 
243
  base_overrides = [
244
  f"generation.positive_prompt.image_path={imgs_json}",
245
  f"generation.positive_prompt.prompts={prompts_json}",
@@ -252,13 +234,14 @@ except Exception:
252
  aspect_ratio_input=aspect_ratio_input,
253
  steps=steps,
254
  )
255
- self._run_vincie_once(overrides, out_dir)
256
- self._clean_gpu_memory()
257
- return out_dir
258
 
259
- @staticmethod
260
- def _slug(path_or_text: str) -> str:
261
- p = Path(path_or_text)
262
- base = p.stem if p.exists() else str(path_or_text)
263
- keep = "".join(c if c.isalnum() or c in "-_." else "_" for c in str(base))
264
- return keep[:64]
 
 
 
 
 
1
  #!/usr/bin/env python3
2
  """
3
+ services/vincie.py
4
+
5
+ VincieService — preparação e execução CLI do VINCIE (upstream)
6
+ - Garante repositório íntegro (clona/repara se faltarem main.py/.git).
7
+ - Baixa snapshot completo do modelo no HF_HUB_CACHE.
8
+ - Cria symlink idempotente ckpt/VINCIE-3B (no repo e em /app/ckpt) apontando para o snapshot (contém dit.pth, vae.pth, llm14b).
9
+ - Valida artefatos esperados pelo generate.yaml.
10
+ - Executa main.py do upstream com overrides de geração (sem mexer em ckpt.path).
11
+ - Limpa VRAM levemente após cada job.
12
+
13
+ Observação:
14
+ - Para latência mínima, preferir o vince_server in-process (pipeline aquecida).
15
+ - Este serviço via subprocess é fiel ao upstream e útil como fallback/diag.
16
  """
17
+
18
  import os
19
  import json
20
  import subprocess
 
29
  self,
30
  repo_dir: str = "/app/VINCIE",
31
  python_bin: str = "python",
32
+ repo_url: str = "https://github.com/ByteDance-Seed/VINCIE",
33
+ model_repo: str = "ByteDance-Seed/VINCIE-3B",
34
  output_root: str = "/app/outputs",
35
  ):
36
  self.repo_dir = Path(repo_dir)
37
  self.python = python_bin
38
+ self.repo_url = repo_url
39
+ self.model_repo = model_repo
40
  self.output_root = Path(output_root)
41
  self.output_root.mkdir(parents=True, exist_ok=True)
42
+
43
+ self.generate_yaml = self.repo_dir / "configs" / "generate.yaml"
44
+ self.ckpt_link_repo = self.repo_dir / "ckpt" / "VINCIE-3B"
45
+ self.ckpt_link_app = Path("/app/ckpt") / "VINCIE-3B"
46
+
47
  self.ckpt_dir: Optional[Path] = None
48
  self._env = os.environ.copy()
 
49
 
50
+ # ---------- util ----------
51
+
52
+ @staticmethod
53
+ def _run(cmd: List[str], cwd: Optional[Path] = None, env=None):
54
+ subprocess.run(cmd, cwd=str(cwd) if cwd else None, check=True, env=env)
55
 
56
+ @staticmethod
57
+ def _ensure_symlink(link: Path, target: Path):
58
+ link.parent.mkdir(parents=True, exist_ok=True)
59
+ if link.is_symlink():
60
+ try:
61
+ if link.resolve() != target:
62
+ link.unlink()
63
+ link.symlink_to(target, target_is_directory=True)
64
+ except Exception:
65
+ # relinka a partir do zero
66
+ link.unlink(missing_ok=True)
67
+ link.symlink_to(target, target_is_directory=True)
68
+ elif link.exists():
69
+ VincieService._run(["rm", "-rf", str(link)])
70
+ link.symlink_to(target, target_is_directory=True)
71
+ else:
72
+ link.symlink_to(target, target_is_directory=True)
73
+
74
+ # ---------- repo/modelo ----------
75
+
76
+ def ensure_repo(self) -> None:
77
  self.repo_dir.mkdir(parents=True, exist_ok=True)
78
  main_py = self.repo_dir / "main.py"
79
  git_dir = self.repo_dir / ".git"
80
+ if main_py.exists() and git_dir.exists():
 
 
81
  return
82
+ tmp = self.repo_dir.with_name(self.repo_dir.name + ".tmp")
83
+ if tmp.exists():
84
+ self._run(["rm", "-rf", str(tmp)])
85
+ self._run(["git", "clone", self.repo_url, str(tmp)])
86
+ # swap atômico simples
87
+ if self.repo_dir.exists():
88
+ self._run(["rm", "-rf", str(self.repo_dir)])
89
+ tmp.rename(self.repo_dir)
90
+
91
+ def ensure_model(self, revision: Optional[str] = None, token: Optional[str] = None) -> None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  cache_dir = os.environ.get("HF_HUB_CACHE")
93
  snapshot_path = snapshot_download(
94
+ repo_id=self.model_repo,
95
  revision=revision,
96
+ token=token or os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_TOKEN"),
97
  cache_dir=cache_dir,
98
+ resume_download=True,
 
99
  )
100
  self.ckpt_dir = Path(snapshot_path)
101
+ # symlinks idempotentes
102
+ self._ensure_symlink(self.ckpt_link_repo, self.ckpt_dir)
103
+ self._ensure_symlink(self.ckpt_link_app, self.ckpt_dir)
104
+
105
+ def validate_assets(self) -> None:
106
+ # exige generate.yaml/main.py e conteúdo essencial no snapshot
107
+ if not self.generate_yaml.exists() or not (self.repo_dir / "main.py").exists():
108
+ raise RuntimeError("VINCIE repo inválido (faltando generate.yaml ou main.py)")
109
+ target = self.ckpt_dir or self.ckpt_link_repo
110
+ need = [target / "dit.pth", target / "vae.pth", target / "llm14b"]
111
+ missing = [str(p) for p in need if not p.exists()]
112
+ if missing:
113
+ raise RuntimeError(f"Snapshot incompleto: {missing}")
114
+ # também requer que o link repo exista (a config usa ckpt/ relativo)
115
+ if not self.ckpt_link_repo.exists():
116
+ raise RuntimeError("ckpt link ausente no repo: ckpt/VINCIE-3B")
117
+
118
+ # ---------- execução ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
  def _build_overrides(
121
  self,
 
126
  steps: Optional[int] = None,
127
  ) -> List[str]:
128
  overrides = list(extra_overrides or [])
129
+ # não altera ckpt.path; respeita o YAML
 
130
  if cfg_scale is not None:
131
  overrides.append(f"generation.cfg_scale={cfg_scale}")
132
  if resolution_input is not None:
 
137
  overrides.append(f"generation.steps={steps}")
138
  return overrides
139
 
 
 
 
 
 
 
 
 
 
 
 
140
  def _clean_gpu_memory(self) -> None:
141
+ code = r"""
 
142
  import torch, gc
143
  try:
144
  torch.cuda.synchronize()
 
151
  except Exception:
152
  pass
153
  """
154
+ self._run([self.python, "-c", code], env=self._env)
 
 
155
 
156
  # ---------- APIs ----------
157
 
 
165
  resolution_input: Optional[int] = None,
166
  aspect_ratio_input: Optional[str] = None,
167
  steps: Optional[int] = None,
168
+ ) -> str:
169
+ self.ensure_repo()
170
+ self.ensure_model()
171
+ self.validate_assets()
172
+
173
+ out_dir = self.output_root / (out_dir_name or f"multi_turn_{Path(input_image).stem}")
174
+ out_dir.mkdir(parents=True, exist_ok=True)
175
+
176
  image_json = json.dumps([str(input_image)])
177
  prompts_json = json.dumps(turns)
178
+
179
  base_overrides = [
180
  f"generation.positive_prompt.image_path={image_json}",
181
  f"generation.positive_prompt.prompts={prompts_json}",
182
  ]
 
 
183
  overrides = self._build_overrides(
184
  extra_overrides=base_overrides,
185
  cfg_scale=cfg_scale,
 
187
  aspect_ratio_input=aspect_ratio_input,
188
  steps=steps,
189
  )
190
+
191
+ cmd = [
192
+ self.python,
193
+ "main.py",
194
+ str(self.generate_yaml),
195
+ *overrides,
196
+ f"generation.output.dir={str(out_dir)}",
197
+ ]
198
+ self._run(cmd, cwd=self.repo_dir, env=self._env)
199
  self._clean_gpu_memory()
200
+ return str(out_dir)
201
 
202
  def multi_concept_compose(
203
  self,
 
210
  resolution_input: Optional[int] = None,
211
  aspect_ratio_input: Optional[str] = None,
212
  steps: Optional[int] = None,
213
+ ) -> str:
214
+ self.ensure_repo()
215
+ self.ensure_model()
216
+ self.validate_assets()
217
+
218
  out_dir = self.output_root / (out_dir_name or "multi_concept")
219
+ out_dir.mkdir(parents=True, exist_ok=True)
220
+
221
  imgs_json = json.dumps([str(p) for p in concept_images])
222
  prompts_all = concept_prompts + [final_prompt]
223
  prompts_json = json.dumps(prompts_all)
224
+
225
  base_overrides = [
226
  f"generation.positive_prompt.image_path={imgs_json}",
227
  f"generation.positive_prompt.prompts={prompts_json}",
 
234
  aspect_ratio_input=aspect_ratio_input,
235
  steps=steps,
236
  )
 
 
 
237
 
238
+ cmd = [
239
+ self.python,
240
+ "main.py",
241
+ str(self.generate_yaml),
242
+ *overrides,
243
+ f"generation.output.dir={str(out_dir)}",
244
+ ]
245
+ self._run(cmd, cwd=self.repo_dir, env=self._env)
246
+ self._clean_gpu_memory()
247
+ return str(out_dir)