XCarleX commited on
Commit
e114cb5
·
verified ·
1 Parent(s): 8c6ac45

Update vincie_service.py

Browse files
Files changed (1) hide show
  1. vincie_service.py +139 -44
vincie_service.py CHANGED
@@ -1,70 +1,161 @@
1
  #!/usr/bin/env python3
2
  import os
 
3
  import json
4
- import shlex
5
  import subprocess
6
  from pathlib import Path
7
  from typing import List, Optional
8
- from huggingface_hub import snapshot_download
 
9
 
10
  class VincieService:
11
  """
12
- Serviço mínimo que:
13
- - garante repositório VINCIE clonado e dependências de modelo baixadas
14
- - chama python main.py configs/generate.yaml com overrides oficiais
15
- - expõe funções de alto nível para multi-turn editing e multi-concept composition
 
 
16
  """
17
 
18
- def __init__(self,
19
- repo_dir: str = "/app/VINCIE",
20
- ckpt_dir: str = "/app/ckpt/VINCIE-3B",
21
- python_bin: str = "python"):
 
 
 
22
  self.repo_dir = Path(repo_dir)
23
  self.ckpt_dir = Path(ckpt_dir)
24
  self.python = python_bin
 
 
25
  self.generate_yaml = self.repo_dir / "configs" / "generate.yaml"
26
  self.assets_dir = self.repo_dir / "assets"
27
  self.output_root = Path("/app/outputs")
28
  self.output_root.mkdir(parents=True, exist_ok=True)
29
 
 
 
 
30
  # ---------- Setup ----------
 
31
  def ensure_repo(self, git_url: str = "https://github.com/ByteDance-Seed/VINCIE") -> None:
 
32
  if not self.repo_dir.exists():
33
  subprocess.run(["git", "clone", git_url, str(self.repo_dir)], check=True)
34
- # opcional: garantir submódulos/updates mínimos
35
- # subprocess.run(["git", "pull"], cwd=self.repo_dir, check=True)
36
 
37
- def ensure_model(self, repo_id: str = "ByteDance-Seed/VINCIE-3B") -> None:
38
- self.ckpt_dir.mkdir(parents=True, exist_ok=True)
39
- snapshot_download(
40
- repo_id=repo_id,
41
- local_dir=str(self.ckpt_dir),
42
- local_dir_use_symlinks=False,
43
- resume_download=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  )
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  def ready(self) -> bool:
47
- return self.repo_dir.exists() and self.generate_yaml.exists() and self.ckpt_dir.exists()
 
 
 
 
48
 
49
  # ---------- Core runner ----------
 
50
  def _run_vincie(self, overrides: List[str], work_output: Path) -> None:
 
51
  work_output.mkdir(parents=True, exist_ok=True)
 
52
  cmd = [
53
  self.python,
54
  "main.py",
55
  str(self.generate_yaml),
56
- # overrides list (Hydra/YACS-style) como no README
57
  *overrides,
58
- f"generation.output.dir={str(work_output)}"
59
  ]
60
- # executar dentro do diretório do repo VINCIE
61
- subprocess.run(cmd, cwd=self.repo_dir, check=True)
 
62
 
63
  # ---------- Multi-turn editing ----------
64
- def multi_turn_edit(self,
65
- input_image: str,
66
- turns: List[str],
67
- out_dir_name: Optional[str] = None) -> Path:
 
 
 
68
  """
69
  Equivalente ao exemplo:
70
  python main.py configs/generate.yaml \
@@ -76,41 +167,45 @@ class VincieService:
76
  image_json = json.dumps([str(input_image)])
77
  prompts_json = json.dumps(turns)
78
  overrides = [
79
- f'generation.positive_prompt.image_path={image_json}',
80
- f'generation.positive_prompt.prompts={prompts_json}',
81
- f'ckpt.path={str(self.ckpt_dir)}'
82
  ]
83
  self._run_vincie(overrides, out_dir)
84
  return out_dir
85
 
86
  # ---------- Multi-concept composition ----------
87
- def multi_concept_compose(self,
88
- concept_images: List[str],
89
- concept_prompts: List[str],
90
- final_prompt: str,
91
- out_dir_name: Optional[str] = None) -> Path:
 
 
 
92
  """
93
- Modelo de uso inspirado no README:
94
  - image_path: lista de imagens dos conceitos
95
- - prompts: lista com p1..pN e o prompt final concatenado
96
  """
97
  out_dir = self.output_root / (out_dir_name or "multi_concept")
98
  imgs_json = json.dumps([str(p) for p in concept_images])
99
- # prompts devem alinhar com <IMG0>, <IMG1>, ... e incluir o prompt final no fim
100
  prompts_all = concept_prompts + [final_prompt]
101
  prompts_json = json.dumps(prompts_all)
102
  overrides = [
103
- f'generation.positive_prompt.image_path={imgs_json}',
104
- f'generation.positive_prompt.prompts={prompts_json}',
105
- f"generation.pad_img_placehoder=False", # segue exemplo público
106
- f'ckpt.path={str(self.ckpt_dir)}'
107
  ]
108
  self._run_vincie(overrides, out_dir)
109
  return out_dir
110
 
111
  # ---------- Helpers ----------
 
112
  @staticmethod
113
  def _slug(path_or_text: str) -> str:
114
- base = Path(path_or_text).stem if Path(path_or_text).exists() else path_or_text
 
115
  keep = "".join(c if c.isalnum() or c in "-_." else "_" for c in str(base))
116
  return keep[:64]
 
1
  #!/usr/bin/env python3
2
  import os
3
+ import sys
4
  import json
 
5
  import subprocess
6
  from pathlib import Path
7
  from typing import List, Optional
8
+
9
+ from huggingface_hub import hf_hub_download
10
 
11
  class VincieService:
12
  """
13
+ Serviço que:
14
+ - garante que o repo VINCIE está presente
15
+ - baixa dit.pth e vae.pth de ByteDance-Seed/VINCIE-3B com hf_token (runtime)
16
+ - cria symlink de compatibilidade para ckpt/VINCIE-3B dentro do repo
17
+ - executa main.py com overrides oficiais (Hydra) para edição multi-turn e composição multi-conceito
18
+ - fornece um fallback (shim) para apex.normalization.FusedRMSNorm quando Apex não está instalado
19
  """
20
 
21
+ def __init__(
22
+ self,
23
+ repo_dir: str = "/app/VINCIE",
24
+ ckpt_dir: str = "/app/ckpt/VINCIE-3B",
25
+ python_bin: str = "python",
26
+ repo_id: str = "ByteDance-Seed/VINCIE-3B",
27
+ ):
28
  self.repo_dir = Path(repo_dir)
29
  self.ckpt_dir = Path(ckpt_dir)
30
  self.python = python_bin
31
+ self.repo_id = repo_id
32
+
33
  self.generate_yaml = self.repo_dir / "configs" / "generate.yaml"
34
  self.assets_dir = self.repo_dir / "assets"
35
  self.output_root = Path("/app/outputs")
36
  self.output_root.mkdir(parents=True, exist_ok=True)
37
 
38
+ # Garantir existência de pasta de ckpt relativa no repo para symlink
39
+ (self.repo_dir / "ckpt").mkdir(parents=True, exist_ok=True)
40
+
41
  # ---------- Setup ----------
42
+
43
  def ensure_repo(self, git_url: str = "https://github.com/ByteDance-Seed/VINCIE") -> None:
44
+ """Clona o repositório VINCIE se ainda não existir."""
45
  if not self.repo_dir.exists():
46
  subprocess.run(["git", "clone", git_url, str(self.repo_dir)], check=True)
 
 
47
 
48
+ def ensure_apex(self, enable_shim: bool = True) -> None:
49
+ """
50
+ Garante que apex.normalization.FusedRMSNorm está importável.
51
+ Se Apex não estiver instalado e enable_shim=True, cria um shim baseado em nn.RMSNorm.
52
+ """
53
+ try:
54
+ import importlib
55
+ importlib.import_module("apex.normalization")
56
+ return
57
+ except Exception:
58
+ if not enable_shim:
59
+ return
60
+
61
+ # Criar shim em /app/shims/apex/normalization.py
62
+ shim_root = Path("/app/shims")
63
+ apex_pkg = shim_root / "apex"
64
+ apex_pkg.mkdir(parents=True, exist_ok=True)
65
+
66
+ (apex_pkg / "__init__.py").write_text("from .normalization import *\n")
67
+
68
+ (apex_pkg / "normalization.py").write_text(
69
+ "import torch\n"
70
+ "import torch.nn as nn\n"
71
+ "\n"
72
+ "class FusedRMSNorm(nn.Module):\n"
73
+ " def __init__(self, normalized_shape, eps=1e-6, elementwise_affine=True):\n"
74
+ " super().__init__()\n"
75
+ " self.mod = nn.RMSNorm(normalized_shape, eps=eps, elementwise_affine=elementwise_affine)\n"
76
+ " def forward(self, x):\n"
77
+ " return self.mod(x)\n"
78
  )
79
 
80
+ # Tornar o shim visível neste processo e nos subprocessos
81
+ sys.path.insert(0, str(shim_root))
82
+ os.environ["PYTHONPATH"] = f"{str(shim_root)}:{os.environ.get('PYTHONPATH','')}"
83
+
84
+ def ensure_model(self, hf_token: Optional[str] = None) -> None:
85
+ """
86
+ Baixa apenas os arquivos necessários do repo ByteDance-Seed/VINCIE-3B:
87
+ - dit.pth
88
+ - vae.pth
89
+ Usa hf_token (parâmetro ou variáveis HF_TOKEN/HUGGINGFACE_TOKEN).
90
+ Cria symlink de compatibilidade em /app/VINCIE/ckpt/VINCIE-3B -> /app/ckpt/VINCIE-3B.
91
+ """
92
+ self.ckpt_dir.mkdir(parents=True, exist_ok=True)
93
+
94
+ token = hf_token or os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_TOKEN")
95
+
96
+ def _need(p: Path) -> bool:
97
+ return not (p.exists() and p.stat().st_size > 1_000_000)
98
+
99
+ for fname in ["dit.pth", "vae.pth"]:
100
+ dst = self.ckpt_dir / fname
101
+ if _need(dst):
102
+ print(f"Baixando {fname} de {self.repo_id} ...")
103
+ hf_hub_download(
104
+ repo_id=self.repo_id,
105
+ filename=fname,
106
+ local_dir=str(self.ckpt_dir),
107
+ token=token,
108
+ force_download=False,
109
+ local_files_only=False,
110
+ )
111
+
112
+ # Symlink de compatibilidade para caminhos relativos do repo
113
+ link = self.repo_dir / "ckpt" / "VINCIE-3B"
114
+ try:
115
+ if link.is_symlink() or link.exists():
116
+ # Remover link anterior inválido
117
+ try:
118
+ link.unlink()
119
+ except IsADirectoryError:
120
+ # Se for diretório, não remover conteúdos; só criar link se não existir
121
+ pass
122
+ if not link.exists():
123
+ link.symlink_to(self.ckpt_dir, target_is_directory=True)
124
+ except Exception as e:
125
+ print("Aviso: falha ao criar symlink de ckpt:", e)
126
+
127
  def ready(self) -> bool:
128
+ """Verifica se o repo, config e checkpoints obrigatórios existem."""
129
+ have_repo = self.repo_dir.exists() and self.generate_yaml.exists()
130
+ dit_ok = (self.ckpt_dir / "dit.pth").exists()
131
+ vae_ok = (self.ckpt_dir / "vae.pth").exists()
132
+ return bool(have_repo and dit_ok and vae_ok)
133
 
134
  # ---------- Core runner ----------
135
+
136
  def _run_vincie(self, overrides: List[str], work_output: Path) -> None:
137
+ """Executa main.py com overrides Hydra/YACS do VINCIE dentro do diretório do repo."""
138
  work_output.mkdir(parents=True, exist_ok=True)
139
+
140
  cmd = [
141
  self.python,
142
  "main.py",
143
  str(self.generate_yaml),
 
144
  *overrides,
145
+ f"generation.output.dir={str(work_output)}",
146
  ]
147
+ # Herdar ambiente (inclui PYTHONPATH do shim, se aplicado)
148
+ env = os.environ.copy()
149
+ subprocess.run(cmd, cwd=self.repo_dir, check=True, env=env)
150
 
151
  # ---------- Multi-turn editing ----------
152
+
153
+ def multi_turn_edit(
154
+ self,
155
+ input_image: str,
156
+ turns: List[str],
157
+ out_dir_name: Optional[str] = None,
158
+ ) -> Path:
159
  """
160
  Equivalente ao exemplo:
161
  python main.py configs/generate.yaml \
 
167
  image_json = json.dumps([str(input_image)])
168
  prompts_json = json.dumps(turns)
169
  overrides = [
170
+ f"generation.positive_prompt.image_path={image_json}",
171
+ f"generation.positive_prompt.prompts={prompts_json}",
172
+ f"ckpt.path={str(self.ckpt_dir)}",
173
  ]
174
  self._run_vincie(overrides, out_dir)
175
  return out_dir
176
 
177
  # ---------- Multi-concept composition ----------
178
+
179
+ def multi_concept_compose(
180
+ self,
181
+ concept_images: List[str],
182
+ concept_prompts: List[str],
183
+ final_prompt: str,
184
+ out_dir_name: Optional[str] = None,
185
+ ) -> Path:
186
  """
187
+ Uso inspirado no README do VINCIE:
188
  - image_path: lista de imagens dos conceitos
189
+ - prompts: [p1, p2, ..., final_prompt]
190
  """
191
  out_dir = self.output_root / (out_dir_name or "multi_concept")
192
  imgs_json = json.dumps([str(p) for p in concept_images])
 
193
  prompts_all = concept_prompts + [final_prompt]
194
  prompts_json = json.dumps(prompts_all)
195
  overrides = [
196
+ f"generation.positive_prompt.image_path={imgs_json}",
197
+ f"generation.positive_prompt.prompts={prompts_json}",
198
+ f"generation.pad_img_placehoder=False",
199
+ f"ckpt.path={str(self.ckpt_dir)}",
200
  ]
201
  self._run_vincie(overrides, out_dir)
202
  return out_dir
203
 
204
  # ---------- Helpers ----------
205
+
206
  @staticmethod
207
  def _slug(path_or_text: str) -> str:
208
+ p = Path(path_or_text)
209
+ base = p.stem if p.exists() else str(path_or_text)
210
  keep = "".join(c if c.isalnum() or c in "-_." else "_" for c in str(base))
211
  return keep[:64]