comdoleger commited on
Commit
548f069
·
verified ·
1 Parent(s): c9445d0

Upload run_modal.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. run_modal.py +175 -0
run_modal.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ '''
2
+
3
+ ostris/ai-toolkit on https://modal.com
4
+ Run training with the following command:
5
+ modal run run_modal.py --config-file-list-str=/root/ai-toolkit/config/whatever_you_want.yml
6
+
7
+ '''
8
+
9
+ import os
10
+ os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1"
11
+ import sys
12
+ import modal
13
+ from dotenv import load_dotenv
14
+ # Load the .env file if it exists
15
+ load_dotenv()
16
+
17
+ sys.path.insert(0, "/root/ai-toolkit")
18
+ # must come before ANY torch or fastai imports
19
+ # import toolkit.cuda_malloc
20
+
21
+ # turn off diffusers telemetry until I can figure out how to make it opt-in
22
+ os.environ['DISABLE_TELEMETRY'] = 'YES'
23
+
24
+ # define the volume for storing model outputs, using "creating volumes lazily": https://modal.com/docs/guide/volumes
25
+ # you will find your model, samples and optimizer stored in: https://modal.com/storage/your-username/main/flux-lora-models
26
+ model_volume = modal.Volume.from_name("flux-lora-models", create_if_missing=True)
27
+
28
+ # modal_output, due to "cannot mount volume on non-empty path" requirement
29
+ MOUNT_DIR = "/root/ai-toolkit/modal_output" # modal_output, due to "cannot mount volume on non-empty path" requirement
30
+
31
+ # define modal app
32
+ image = (
33
+ modal.Image.debian_slim(python_version="3.11")
34
+ # install required system and pip packages, more about this modal approach: https://modal.com/docs/examples/dreambooth_app
35
+ .apt_install("libgl1", "libglib2.0-0")
36
+ .pip_install(
37
+ "python-dotenv",
38
+ "torch",
39
+ "diffusers[torch]",
40
+ "transformers",
41
+ "ftfy",
42
+ "torchvision",
43
+ "oyaml",
44
+ "opencv-python",
45
+ "albumentations",
46
+ "safetensors",
47
+ "lycoris-lora==1.8.3",
48
+ "flatten_json",
49
+ "pyyaml",
50
+ "tensorboard",
51
+ "kornia",
52
+ "invisible-watermark",
53
+ "einops",
54
+ "accelerate",
55
+ "toml",
56
+ "pydantic",
57
+ "omegaconf",
58
+ "k-diffusion",
59
+ "open_clip_torch",
60
+ "timm",
61
+ "prodigyopt",
62
+ "controlnet_aux==0.0.7",
63
+ "bitsandbytes",
64
+ "hf_transfer",
65
+ "lpips",
66
+ "pytorch_fid",
67
+ "optimum-quanto",
68
+ "sentencepiece",
69
+ "huggingface_hub",
70
+ "peft"
71
+ )
72
+ )
73
+
74
+ # mount for the entire ai-toolkit directory
75
+ # example: "/Users/username/ai-toolkit" is the local directory, "/root/ai-toolkit" is the remote directory
76
+ code_mount = modal.Mount.from_local_dir("/Users/username/ai-toolkit", remote_path="/root/ai-toolkit")
77
+
78
+ # create the Modal app with the necessary mounts and volumes
79
+ app = modal.App(name="flux-lora-training", image=image, mounts=[code_mount], volumes={MOUNT_DIR: model_volume})
80
+
81
+ # Check if we have DEBUG_TOOLKIT in env
82
+ if os.environ.get("DEBUG_TOOLKIT", "0") == "1":
83
+ # Set torch to trace mode
84
+ import torch
85
+ torch.autograd.set_detect_anomaly(True)
86
+
87
+ import argparse
88
+ from toolkit.job import get_job
89
+
90
+ def print_end_message(jobs_completed, jobs_failed):
91
+ failure_string = f"{jobs_failed} failure{'' if jobs_failed == 1 else 's'}" if jobs_failed > 0 else ""
92
+ completed_string = f"{jobs_completed} completed job{'' if jobs_completed == 1 else 's'}"
93
+
94
+ print("")
95
+ print("========================================")
96
+ print("Result:")
97
+ if len(completed_string) > 0:
98
+ print(f" - {completed_string}")
99
+ if len(failure_string) > 0:
100
+ print(f" - {failure_string}")
101
+ print("========================================")
102
+
103
+
104
+ @app.function(
105
+ # request a GPU with at least 24GB VRAM
106
+ # more about modal GPU's: https://modal.com/docs/guide/gpu
107
+ gpu="A100", # gpu="H100"
108
+ # more about modal timeouts: https://modal.com/docs/guide/timeouts
109
+ timeout=7200 # 2 hours, increase or decrease if needed
110
+ )
111
+ def main(config_file_list_str: str, recover: bool = False, name: str = None):
112
+ # convert the config file list from a string to a list
113
+ config_file_list = config_file_list_str.split(",")
114
+
115
+ jobs_completed = 0
116
+ jobs_failed = 0
117
+
118
+ print(f"Running {len(config_file_list)} job{'' if len(config_file_list) == 1 else 's'}")
119
+
120
+ for config_file in config_file_list:
121
+ try:
122
+ job = get_job(config_file, name)
123
+
124
+ job.config['process'][0]['training_folder'] = MOUNT_DIR
125
+ os.makedirs(MOUNT_DIR, exist_ok=True)
126
+ print(f"Training outputs will be saved to: {MOUNT_DIR}")
127
+
128
+ # run the job
129
+ job.run()
130
+
131
+ # commit the volume after training
132
+ model_volume.commit()
133
+
134
+ job.cleanup()
135
+ jobs_completed += 1
136
+ except Exception as e:
137
+ print(f"Error running job: {e}")
138
+ jobs_failed += 1
139
+ if not recover:
140
+ print_end_message(jobs_completed, jobs_failed)
141
+ raise e
142
+
143
+ print_end_message(jobs_completed, jobs_failed)
144
+
145
+ if __name__ == "__main__":
146
+ parser = argparse.ArgumentParser()
147
+
148
+ # require at least one config file
149
+ parser.add_argument(
150
+ 'config_file_list',
151
+ nargs='+',
152
+ type=str,
153
+ help='Name of config file (eg: person_v1 for config/person_v1.json/yaml), or full path if it is not in config folder, you can pass multiple config files and run them all sequentially'
154
+ )
155
+
156
+ # flag to continue if a job fails
157
+ parser.add_argument(
158
+ '-r', '--recover',
159
+ action='store_true',
160
+ help='Continue running additional jobs even if a job fails'
161
+ )
162
+
163
+ # optional name replacement for config file
164
+ parser.add_argument(
165
+ '-n', '--name',
166
+ type=str,
167
+ default=None,
168
+ help='Name to replace [name] tag in config file, useful for shared config file'
169
+ )
170
+ args = parser.parse_args()
171
+
172
+ # convert list of config files to a comma-separated string for Modal compatibility
173
+ config_file_list_str = ",".join(args.config_file_list)
174
+
175
+ main.call(config_file_list_str=config_file_list_str, recover=args.recover, name=args.name)