jhonier23 commited on
Commit
1e476c0
·
1 Parent(s): 09bd849
Files changed (4) hide show
  1. README.md +14 -15
  2. app.py +175 -113
  3. requirements.txt +10 -8
  4. requirements_local.txt +9 -5
README.md CHANGED
@@ -4,8 +4,9 @@ emoji: 🧞
4
  colorFrom: blue
5
  colorTo: purple
6
  sdk: gradio
7
- sdk_version: 4.44.1
8
  app_file: app.py
 
9
  pinned: true
10
  license: mit
11
  fullWidth: true
@@ -25,23 +26,21 @@ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-
25
 
26
  This fork trains LoRA adapters for `stabilityai/stable-diffusion-xl-base-1.0`.
27
  The maintained Hugging Face Diffusers v0.39.0 advanced SDXL trainer is downloaded
28
- from a pinned commit the first time training starts. Captions are generated by a
29
- dedicated Mage-VL Space, while the face-prior dataset is loaded only when needed.
30
 
31
  ## Deploy as a Hugging Face Space
32
 
33
- Create or duplicate a Gradio Space and push these files to it. The UI itself can
34
- remain on CPU Basic: when a user starts training, AutoTrain SpaceRunner creates a
35
- temporary L40S training Space under that user's account and publishes the finished
36
- LoRA to their model repository.
37
-
38
- Captioning defaults to the public `microsoft/mage-vl-demo`, which uses the
39
- Apache-2.0 `microsoft/Mage-VL` model. No repository token is required for reading
40
- its API. `MAGE_VL_SPACE_ID` and `MAGE_VL_API_NAME` can be set as Space variables
41
- if you use a different duplicate or endpoint. `MAGE_VL_BASE_URL` can override the
42
- derived `https://OWNER-SPACE.hf.space` URL. If that override is private, add a token
43
- with access to it as the `MAGE_VL_TOKEN` Space secret. Uploaded images are sent to
44
- the configured captioning Space, so use a Space you trust.
45
 
46
  The user must:
47
 
 
4
  colorFrom: blue
5
  colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.29.0
8
  app_file: app.py
9
+ startup_duration_timeout: 1h
10
  pinned: true
11
  license: mit
12
  fullWidth: true
 
26
 
27
  This fork trains LoRA adapters for `stabilityai/stable-diffusion-xl-base-1.0`.
28
  The maintained Hugging Face Diffusers v0.39.0 advanced SDXL trainer is downloaded
29
+ from a pinned commit the first time training starts. Captions are generated in this
30
+ Space by the embedded Mage-VL model; the face-prior dataset is loaded only when needed.
31
 
32
  ## Deploy as a Hugging Face Space
33
 
34
+ Create or duplicate a Gradio Space, enable **ZeroGPU** hardware, and push these
35
+ files to it. `microsoft/Mage-VL` is loaded directly into this Space and the
36
+ captioning function is decorated with `spaces.GPU`; images never need to be sent
37
+ to a second Space or account. The first startup downloads the model and can take
38
+ several minutes.
39
+
40
+ When a user starts training, the app directly creates a private dataset repository
41
+ and a temporary private L40S Docker Space under that user's account. This keeps
42
+ AutoTrain's conflicting dependency stack out of the captioning/UI container while
43
+ preserving the same trainer runtime and finished-model upload behavior.
 
 
44
 
45
  The user must:
46
 
app.py CHANGED
@@ -1,12 +1,14 @@
1
- import gradio as gr
2
- import subprocess
3
  import os
4
- import requests
5
  is_spaces = True if os.environ.get('SPACE_ID') else False
6
  if is_spaces:
7
  import spaces
 
 
 
 
8
  from gradio_client import utils as gradio_client_utils
9
  from huggingface_hub import snapshot_download, HfApi
 
10
  import uuid
11
  import shutil
12
  import json
@@ -29,13 +31,9 @@ TRAINING_SCRIPT = Path("train_dreambooth_lora_sdxl_advanced.py")
29
  training_script_url = f"https://raw.githubusercontent.com/huggingface/diffusers/{DIFFUSERS_COMMIT}/examples/advanced_diffusion_training/{TRAINING_SCRIPT.name}"
30
  orchestrator_script_url = "https://huggingface.co/datasets/multimodalart/lora-ease-helper/raw/main/script.py"
31
 
32
- MAGE_VL_SPACE_ID = os.environ.get("MAGE_VL_SPACE_ID", "microsoft/mage-vl-demo")
33
- MAGE_VL_API_NAME = os.environ.get("MAGE_VL_API_NAME", "/ask_image")
34
- MAGE_VL_BASE_URL = os.environ.get(
35
- "MAGE_VL_BASE_URL",
36
- f"https://{MAGE_VL_SPACE_ID.replace('/', '-').lower()}.hf.space",
37
- ).rstrip("/")
38
- mage_vl_client = None
39
  caption_cache = {}
40
 
41
 
@@ -54,16 +52,6 @@ def _safe_schema_to_python_type(schema, defs):
54
  gradio_client_utils._json_schema_to_python_type = _safe_schema_to_python_type
55
 
56
 
57
- # A Space configured for ZeroGPU must register at least one GPU function during
58
- # startup. Captioning itself runs in the separate Mage-VL Space, so this probe is
59
- # intentionally not placed around run_captioning (which would waste local quota
60
- # while waiting for remote API requests).
61
- if is_spaces:
62
- @spaces.GPU(duration=1)
63
- def zero_gpu_probe():
64
- return True
65
-
66
-
67
  def ensure_file(url, destination):
68
  """Download a pinned helper only when it is actually needed."""
69
  destination = Path(destination)
@@ -81,90 +69,78 @@ def get_face_prior_dataset():
81
  return dataset_path
82
 
83
 
84
- class MageVLClient:
85
- """Minimal Gradio HTTP client, independent of the local Gradio version."""
86
-
87
- def __init__(self, base_url, api_name, token=None):
88
- self.base_url = base_url
89
- self.api_name = api_name.strip("/")
90
- self.session = requests.Session()
91
- if token:
92
- self.session.headers.update({"Authorization": f"Bearer {token}"})
93
-
94
- response = self.session.get(f"{self.base_url}/gradio_api/info", timeout=30)
95
- response.raise_for_status()
96
- endpoints = response.json().get("named_endpoints", {})
97
- if f"/{self.api_name}" not in endpoints:
98
- raise RuntimeError(
99
- f"Mage-VL endpoint '/{self.api_name}' was not found at {self.base_url}."
100
- )
101
-
102
- def predict(self, image, instruction, max_new_tokens):
103
- with open(image, "rb") as image_file:
104
- upload = self.session.post(
105
- f"{self.base_url}/gradio_api/upload",
106
- files={"files": (os.path.basename(image), image_file)},
107
- timeout=60,
108
- )
109
- upload.raise_for_status()
110
- uploaded_path = upload.json()[0]
111
-
112
- payload = {
113
- "data": [
114
- {
115
- "path": uploaded_path,
116
- "orig_name": os.path.basename(image),
117
- "meta": {"_type": "gradio.FileData"},
118
- },
119
- instruction,
120
- int(max_new_tokens),
121
- ]
122
- }
123
- start = self.session.post(
124
- f"{self.base_url}/gradio_api/call/{self.api_name}",
125
- json=payload,
126
- timeout=30,
127
- )
128
- start.raise_for_status()
129
- event_id = start.json()["event_id"]
130
-
131
- result = self.session.get(
132
- f"{self.base_url}/gradio_api/call/{self.api_name}/{event_id}",
133
- timeout=180,
134
  )
135
- result.raise_for_status()
136
- event = None
137
- for line in result.text.splitlines():
138
- if line.startswith("event:"):
139
- event = line.partition(":")[2].strip()
140
- elif line.startswith("data:") and event == "complete":
141
- return json.loads(line.partition(":")[2].strip())
142
- elif line.startswith("data:") and event == "error":
143
- raise RuntimeError(line.partition(":")[2].strip())
144
- raise RuntimeError(f"Mage-VL returned no completed result: {result.text[:500]}")
145
 
146
 
147
- def get_captioner():
148
- """Connect to the dedicated Mage-VL Space without loading a VLM here."""
149
- global mage_vl_client
150
- if mage_vl_client is None:
151
- token = os.environ.get("MAGE_VL_TOKEN")
152
- mage_vl_client = MageVLClient(MAGE_VL_BASE_URL, MAGE_VL_API_NAME, token)
153
- return mage_vl_client
 
 
 
 
 
 
154
 
155
 
156
- def caption_with_mage(client, image, instruction):
157
  with open(image, "rb") as image_file:
158
  cache_key = hashlib.sha256(image_file.read() + instruction.encode("utf-8")).hexdigest()
159
  if cache_key in caption_cache:
160
  return caption_cache[cache_key]
161
 
162
- result = client.predict(image, instruction, 160)
163
- caption = result[0] if isinstance(result, (list, tuple)) else result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  caption = str(caption).strip().rstrip(" .,")
165
  caption_cache[cache_key] = caption
166
  return caption
167
 
 
 
 
 
 
 
168
  training_option_settings = {
169
  "face": {
170
  "rank": 32,
@@ -311,6 +287,88 @@ def create_dataset(*inputs):
311
 
312
  return destination_folder
313
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  def start_training(
315
  lora_name,
316
  training_option,
@@ -448,8 +506,6 @@ def start_training(
448
  if use_adam_weight_decay_text_encoder:
449
  commands.append(f"adam_weight_decay_text_encoder={adam_weight_decay_text_encoder}")
450
  print(commands)
451
- # Joining the commands with ';' separator for spacerunner format
452
- spacerunner_args = ';'.join(commands)
453
  if not os.path.exists(spacerunner_folder):
454
  os.makedirs(spacerunner_folder)
455
  ensure_file(training_script_url, TRAINING_SCRIPT)
@@ -475,18 +531,19 @@ sentencepiece'''
475
  file_path = f'{spacerunner_folder}/requirements.txt'
476
  with open(file_path, 'w') as file:
477
  file.write(requirements)
478
- # The subprocess call for autotrain spacerunner
479
- api = HfApi(token=token)
480
- username = api.whoami()["name"]
481
- subprocess_command = ["autotrain", "spacerunner", "--project-name", slugged_lora_name, "--script-path", spacerunner_folder, "--username", username, "--token", token, "--backend", "spaces-l40sx1", "--env",f"HF_TOKEN={token};HF_HUB_ENABLE_HF_TRANSFER=1", "--args", spacerunner_args]
482
- outcome = subprocess.run(subprocess_command)
483
- if(outcome.returncode == 0):
484
- return f"""# Your training has started.
485
- ## - Training Status: <a href='https://huggingface.co/spaces/{username}/autotrain-{slugged_lora_name}?logs=container'>{username}/autotrain-{slugged_lora_name}</a> <small>(in the logs tab)</small>
 
 
 
 
486
  ## - Model page: <a href='https://huggingface.co/{username}/{slugged_lora_name}'>{username}/{slugged_lora_name}</a> <small>(will be available when training finishes)</small>"""
487
- else:
488
- print("Error: ", outcome.stderr)
489
- raise gr.Error("Something went wrong. Make sure the name of your LoRA is unique and try again")
490
 
491
  def calculate_price(iterations, with_prior_preservation):
492
  if(with_prior_preservation):
@@ -636,24 +693,16 @@ def start_training_og(
636
 
637
  return f"Your model has finished training and has been saved to the `{slugged_lora_name}` folder"
638
 
639
- def run_captioning(*inputs):
640
  images = inputs[0]
641
  training_option = inputs[-2]
642
  caption_instruction = inputs[-1].strip()
643
  final_captions = [""] * MAX_IMAGES
644
- try:
645
- caption_client = get_captioner()
646
- except Exception as exc:
647
- raise gr.Error(
648
- f"Could not connect to Mage-VL Space '{MAGE_VL_SPACE_ID}'. "
649
- "If it is private, add MAGE_VL_TOKEN as a Hugging Face Space secret. "
650
- f"Details: {exc}"
651
- ) from exc
652
 
653
  for index, image in enumerate(images):
654
  concept_caption = inputs[index + 1].strip()
655
  try:
656
- generated_text = caption_with_mage(caption_client, image, caption_instruction)
657
  except Exception as exc:
658
  raise gr.Error(
659
  f"Mage-VL failed while captioning image {index + 1}: {exc}"
@@ -666,6 +715,19 @@ def run_captioning(*inputs):
666
  yield final_captions
667
 
668
 
 
 
 
 
 
 
 
 
 
 
 
 
 
669
  def export_captions(images, *captions):
670
  if not images:
671
  raise gr.Error("Upload images before exporting captions.")
 
 
 
1
  import os
 
2
  is_spaces = True if os.environ.get('SPACE_ID') else False
3
  if is_spaces:
4
  import spaces
5
+ import gradio as gr
6
+ import torch
7
+ from PIL import Image
8
+ from transformers import AutoModelForCausalLM, AutoProcessor
9
  from gradio_client import utils as gradio_client_utils
10
  from huggingface_hub import snapshot_download, HfApi
11
+ import io
12
  import uuid
13
  import shutil
14
  import json
 
31
  training_script_url = f"https://raw.githubusercontent.com/huggingface/diffusers/{DIFFUSERS_COMMIT}/examples/advanced_diffusion_training/{TRAINING_SCRIPT.name}"
32
  orchestrator_script_url = "https://huggingface.co/datasets/multimodalart/lora-ease-helper/raw/main/script.py"
33
 
34
+ MAGE_VL_MODEL_ID = "microsoft/Mage-VL"
35
+ mage_vl_processor = None
36
+ mage_vl_model = None
 
 
 
 
37
  caption_cache = {}
38
 
39
 
 
52
  gradio_client_utils._json_schema_to_python_type = _safe_schema_to_python_type
53
 
54
 
 
 
 
 
 
 
 
 
 
 
55
  def ensure_file(url, destination):
56
  """Download a pinned helper only when it is actually needed."""
57
  destination = Path(destination)
 
69
  return dataset_path
70
 
71
 
72
+ def get_captioner():
73
+ """Load the embedded Mage-VL model once per Space process."""
74
+ global mage_vl_processor, mage_vl_model
75
+ if mage_vl_processor is None or mage_vl_model is None:
76
+ target_device = "cuda" if is_spaces or torch.cuda.is_available() else "cpu"
77
+ dtype = torch.bfloat16 if target_device == "cuda" else torch.float32
78
+ mage_vl_processor = AutoProcessor.from_pretrained(
79
+ MAGE_VL_MODEL_ID, trust_remote_code=True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  )
81
+ mage_vl_model = AutoModelForCausalLM.from_pretrained(
82
+ MAGE_VL_MODEL_ID,
83
+ trust_remote_code=True,
84
+ dtype=dtype,
85
+ attn_implementation="sdpa",
86
+ ).to(target_device).eval()
87
+ return mage_vl_processor, mage_vl_model
 
 
 
88
 
89
 
90
+ def _mage_prompt(processor, instruction):
91
+ messages = [
92
+ {
93
+ "role": "user",
94
+ "content": [
95
+ {"type": "image"},
96
+ {"type": "text", "text": instruction},
97
+ ],
98
+ }
99
+ ]
100
+ return processor.apply_chat_template(
101
+ messages, tokenize=False, add_generation_prompt=True
102
+ )
103
 
104
 
105
+ def caption_with_mage(image, instruction):
106
  with open(image, "rb") as image_file:
107
  cache_key = hashlib.sha256(image_file.read() + instruction.encode("utf-8")).hexdigest()
108
  if cache_key in caption_cache:
109
  return caption_cache[cache_key]
110
 
111
+ processor, model = get_captioner()
112
+ with Image.open(image) as source_image:
113
+ pil_image = source_image.convert("RGB")
114
+ inputs = processor(
115
+ text=[_mage_prompt(processor, instruction)],
116
+ images=[pil_image],
117
+ return_tensors="pt",
118
+ )
119
+ device_inputs = {}
120
+ for key, value in inputs.items():
121
+ if not hasattr(value, "to"):
122
+ continue
123
+ device_inputs[key] = value.to(model.device)
124
+ if key == "pixel_values":
125
+ device_inputs[key] = device_inputs[key].to(model.dtype)
126
+ with torch.inference_mode():
127
+ output = model.generate(
128
+ **device_inputs,
129
+ max_new_tokens=160,
130
+ do_sample=False,
131
+ )
132
+ new_tokens = output[0, device_inputs["input_ids"].shape[1]:]
133
+ caption = processor.tokenizer.decode(new_tokens, skip_special_tokens=True)
134
  caption = str(caption).strip().rstrip(" .,")
135
  caption_cache[cache_key] = caption
136
  return caption
137
 
138
+
139
+ if is_spaces:
140
+ # ZeroGPU emulates CUDA during startup, so the weights can be placed once and
141
+ # transparently backed by a real GPU inside the decorated captioning call.
142
+ get_captioner()
143
+
144
  training_option_settings = {
145
  "face": {
146
  "rank": 32,
 
287
 
288
  return destination_folder
289
 
290
+
291
+ AUTOTRAIN_DOCKERFILE = """FROM huggingface/autotrain-advanced:latest
292
+
293
+ CMD pip uninstall -y autotrain-advanced && pip install -U autotrain-advanced && autotrain api --port 7860 --host 0.0.0.0
294
+ """
295
+
296
+
297
+ def _commands_to_args(commands):
298
+ """Convert SpaceRunner's old semicolon arguments into its JSON form."""
299
+ args = {}
300
+ for command in commands:
301
+ key, separator, value = command.partition("=")
302
+ args[key] = value if separator else ""
303
+ return args
304
+
305
+
306
+ def _launch_training_space(folder, project_name, commands, token):
307
+ """Create the dataset and L40S trainer Space without installing AutoTrain here."""
308
+ api = HfApi(token=token)
309
+ username = api.whoami()["name"]
310
+ repo_name = f"autotrain-{project_name}"
311
+ dataset_id = f"{username}/{repo_name}"
312
+ space_id = f"{username}/{repo_name}"
313
+
314
+ api.create_repo(repo_id=dataset_id, repo_type="dataset", private=True)
315
+ api.upload_folder(folder_path=folder, repo_id=dataset_id, repo_type="dataset")
316
+
317
+ params = {
318
+ "project_name": project_name,
319
+ "data_path": dataset_id,
320
+ "username": username,
321
+ "token": token,
322
+ "script_path": folder,
323
+ "env": {},
324
+ "args": _commands_to_args(commands),
325
+ }
326
+ api.create_repo(
327
+ repo_id=space_id,
328
+ repo_type="space",
329
+ space_sdk="docker",
330
+ space_hardware="l40sx1",
331
+ private=True,
332
+ )
333
+ secrets = {
334
+ "HF_TOKEN": token,
335
+ "HF_HUB_ENABLE_HF_TRANSFER": "1",
336
+ "AUTOTRAIN_USERNAME": username,
337
+ "PROJECT_NAME": project_name,
338
+ "TASK_ID": "27",
339
+ "PARAMS": json.dumps(params),
340
+ "DATA_PATH": dataset_id,
341
+ }
342
+ for key, value in secrets.items():
343
+ api.add_space_secret(repo_id=space_id, key=key, value=value)
344
+ api.set_space_sleep_time(repo_id=space_id, sleep_time=604800)
345
+
346
+ space_readme = f"""---
347
+ title: {project_name}
348
+ emoji: 🚀
349
+ colorFrom: green
350
+ colorTo: indigo
351
+ sdk: docker
352
+ pinned: false
353
+ tags:
354
+ - autotrain
355
+ duplicated_from: autotrain-projects/autotrain-advanced
356
+ ---
357
+ """
358
+ api.upload_file(
359
+ path_or_fileobj=io.BytesIO(space_readme.encode("utf-8")),
360
+ path_in_repo="README.md",
361
+ repo_id=space_id,
362
+ repo_type="space",
363
+ )
364
+ api.upload_file(
365
+ path_or_fileobj=io.BytesIO(AUTOTRAIN_DOCKERFILE.encode("utf-8")),
366
+ path_in_repo="Dockerfile",
367
+ repo_id=space_id,
368
+ repo_type="space",
369
+ )
370
+ return username, space_id
371
+
372
  def start_training(
373
  lora_name,
374
  training_option,
 
506
  if use_adam_weight_decay_text_encoder:
507
  commands.append(f"adam_weight_decay_text_encoder={adam_weight_decay_text_encoder}")
508
  print(commands)
 
 
509
  if not os.path.exists(spacerunner_folder):
510
  os.makedirs(spacerunner_folder)
511
  ensure_file(training_script_url, TRAINING_SCRIPT)
 
531
  file_path = f'{spacerunner_folder}/requirements.txt'
532
  with open(file_path, 'w') as file:
533
  file.write(requirements)
534
+ try:
535
+ username, space_id = _launch_training_space(
536
+ spacerunner_folder, slugged_lora_name, commands, token
537
+ )
538
+ except Exception as exc:
539
+ raise gr.Error(
540
+ "Could not create the private L40S training Space. Make sure the "
541
+ f"LoRA name is unique and the token can create repositories. Details: {exc}"
542
+ ) from exc
543
+
544
+ return f"""# Your training has started.
545
+ ## - Training Status: <a href='https://huggingface.co/spaces/{space_id}?logs=container'>{space_id}</a> <small>(in the logs tab)</small>
546
  ## - Model page: <a href='https://huggingface.co/{username}/{slugged_lora_name}'>{username}/{slugged_lora_name}</a> <small>(will be available when training finishes)</small>"""
 
 
 
547
 
548
  def calculate_price(iterations, with_prior_preservation):
549
  if(with_prior_preservation):
 
693
 
694
  return f"Your model has finished training and has been saved to the `{slugged_lora_name}` folder"
695
 
696
+ def _run_captioning(*inputs):
697
  images = inputs[0]
698
  training_option = inputs[-2]
699
  caption_instruction = inputs[-1].strip()
700
  final_captions = [""] * MAX_IMAGES
 
 
 
 
 
 
 
 
701
 
702
  for index, image in enumerate(images):
703
  concept_caption = inputs[index + 1].strip()
704
  try:
705
+ generated_text = caption_with_mage(image, caption_instruction)
706
  except Exception as exc:
707
  raise gr.Error(
708
  f"Mage-VL failed while captioning image {index + 1}: {exc}"
 
715
  yield final_captions
716
 
717
 
718
+ def captioning_duration(*inputs):
719
+ images = inputs[0] or []
720
+ return min(180, max(30, 20 + len(images) * 3))
721
+
722
+
723
+ if is_spaces:
724
+ @spaces.GPU(duration=captioning_duration)
725
+ def run_captioning(*inputs):
726
+ yield from _run_captioning(*inputs)
727
+ else:
728
+ run_captioning = _run_captioning
729
+
730
+
731
  def export_captions(images, *captions):
732
  if not images:
733
  raise gr.Error("Upload images before exporting captions.")
requirements.txt CHANGED
@@ -1,10 +1,12 @@
1
- autotrain-advanced
2
- numpy==1.26.4
3
- huggingface-hub==0.27.0
4
  python-slugify
5
- gradio==4.44.1
6
- gradio_client==1.3.0
7
- fastapi==0.115.6
8
- starlette==0.41.3
9
- jinja2==3.1.4
10
  spaces
 
 
 
 
 
 
 
1
+ numpy<2
2
+ huggingface-hub>=1.5.0,<2
 
3
  python-slugify
4
+ gradio==5.29.0
5
+ gradio_client==1.10.0
 
 
 
6
  spaces
7
+ transformers==5.7.0
8
+ accelerate
9
+ safetensors
10
+ pillow
11
+ torchvision
12
+ opencv-python-headless<4.12
requirements_local.txt CHANGED
@@ -1,13 +1,17 @@
1
  torch
2
  torchvision
 
3
  python-slugify
4
- gradio>=4.37,<6
 
 
 
 
 
 
 
5
  diffusers==0.39.0
6
  peft>=0.11.1
7
- huggingface-hub>=0.34.0
8
- transformers>=4.41.2
9
- accelerate>=0.31.0
10
- safetensors>=0.4.3
11
  prodigyopt==1.0
12
  datasets>=2.20.0
13
  ftfy
 
1
  torch
2
  torchvision
3
+ numpy<2
4
  python-slugify
5
+ gradio==5.29.0
6
+ gradio_client==1.10.0
7
+ huggingface-hub>=1.5.0,<2
8
+ transformers==5.7.0
9
+ accelerate
10
+ safetensors
11
+ pillow
12
+ opencv-python-headless<4.12
13
  diffusers==0.39.0
14
  peft>=0.11.1
 
 
 
 
15
  prodigyopt==1.0
16
  datasets>=2.20.0
17
  ftfy