diff --git a/builder/modal-builder/src/main.py b/builder/modal-builder/src/main.py index 2493a37..c0e0170 100644 --- a/builder/modal-builder/src/main.py +++ b/builder/modal-builder/src/main.py @@ -312,7 +312,9 @@ async def build_logic(item: Item): config = { "name": item.name, "deploy_test": os.environ.get("DEPLOY_TEST_FLAG", "False"), - "gpu": item.gpu + "gpu": item.gpu, + "public_checkpoint_volume": "model-store", + "private_checkpoint_volume": "private-model-store" } with open(f"{folder_path}/config.py", "w") as f: f.write("config = " + json.dumps(config)) diff --git a/builder/modal-builder/src/template/app.py b/builder/modal-builder/src/template/app.py index ab6a1fb..f3af844 100644 --- a/builder/modal-builder/src/template/app.py +++ b/builder/modal-builder/src/template/app.py @@ -7,6 +7,7 @@ import urllib.parse from pydantic import BaseModel from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse +from volume import volumes # deploy_test = False @@ -28,9 +29,6 @@ web_app = FastAPI() print(config) print("deploy_test ", deploy_test) stub = Stub(name=config["name"]) -volume = modal.Volume.persisted("model-store") -MODEL_DIR = "/comfyui/models/checkpoints/" -# print(stub.app_id) if not deploy_test: # dockerfile_image = Image.from_dockerfile(f"{current_directory}/Dockerfile", context_mount=Mount.from_local_dir(f"{current_directory}/data", remote_path="/data")) @@ -58,7 +56,7 @@ if not deploy_test: # # Install comfy deploy # "cd /comfyui/custom_nodes && git clone https://github.com/BennyKok/comfyui-deploy.git", # ) - # .copy_local_file(f"{current_directory}/data/extra_model_paths.yaml", "/comfyui") + .copy_local_file(f"{current_directory}/data/extra_model_paths.yaml", "/comfyui") .copy_local_file(f"{current_directory}/data/start.sh", "/start.sh") .run_commands("chmod +x /start.sh") @@ -74,7 +72,6 @@ if not deploy_test: .copy_local_file(f"{current_directory}/data/deps.json", "/") .run_commands("python install_deps.py") - .run_commands(f"rm -rf {MODEL_DIR}") # clear model dir so volume can mount, NOTE: could instead use the extra_model_paths ) # Time to wait between API check attempts in milliseconds @@ -156,10 +153,9 @@ image = Image.debian_slim() target_image = image if deploy_test else dockerfile_image - @stub.function(image=target_image, gpu=config["gpu"] - , volumes={MODEL_DIR: volume} - ) + ,volumes=volumes +) def run(input: Input): import subprocess import time @@ -168,6 +164,7 @@ def run(input: Input): command = ["python", "main.py", "--disable-auto-launch", "--disable-metadata"] + server_process = subprocess.Popen(command, cwd="/comfyui") check_server( @@ -241,8 +238,8 @@ async def bar(request_input: RequestInput): @stub.function(image=image - , volumes={MODEL_DIR: volume} - ) + ,volumes=volumes +) @asgi_app() def comfyui_api(): return web_app @@ -292,7 +289,7 @@ def spawn_comfyui_in_background(): # to be on a single container. concurrency_limit=1, timeout=10 * 60, - volumes={MODEL_DIR: volume} + volumes=volumes, ) @asgi_app() def comfyui_app(): diff --git a/builder/modal-builder/src/template/config.py b/builder/modal-builder/src/template/config.py index e59020b..a651642 100644 --- a/builder/modal-builder/src/template/config.py +++ b/builder/modal-builder/src/template/config.py @@ -1 +1,7 @@ -config = {"name": "my-app", "deploy_test": "True", "gpu": "T4"} \ No newline at end of file +config = { + "name": "my-app", + "deploy_test": "True", + "gpu": "T4", + "public_checkpoint_volume": "model-store", + "private_checkpoint_volume": "private-model-store" +} diff --git a/builder/modal-builder/src/template/data/extra_model_paths.yaml b/builder/modal-builder/src/template/data/extra_model_paths.yaml index 6e07d7b..311a949 100644 --- a/builder/modal-builder/src/template/data/extra_model_paths.yaml +++ b/builder/modal-builder/src/template/data/extra_model_paths.yaml @@ -1,11 +1,30 @@ comfyui: - base_path: /runpod-volume/ComfyUI/ - checkpoints: models/checkpoints/ - clip: models/clip/ - clip_vision: models/clip_vision/ - configs: models/configs/ - controlnet: models/controlnet/ - embeddings: models/embeddings/ - loras: models/loras/ - upscale_models: models/upscale_models/ - vae: models/vae/ \ No newline at end of file + base_path: /extra_models/ + checkpoints: | + checkpoints + private_checkpoints + clip: | + clip + private_clip + clip_vision: | + clip_vision + private_clip_vision + configs: | + configs + private_configs + controlnet: | + controlnet + private_controlnet + embeddings: | + embeddings + private_embeddings + loras: | + loras + private_loras + upscale_models: | + upscale_models + private_upscale_models + vae: | + vae + private_vae + diff --git a/builder/modal-builder/src/template/data/insert_models.py b/builder/modal-builder/src/template/data/insert_models.py index c6571d9..c1792b3 100644 --- a/builder/modal-builder/src/template/data/insert_models.py +++ b/builder/modal-builder/src/template/data/insert_models.py @@ -1,3 +1,15 @@ +""" +This is a standalone script to download models into a modal Volume using civitai + +Example Usage +`modal run insert_models::insert_model --civitai-url https://civitai.com/models/36520/ghostmix` +This inserts an individual model from a civitai url (public not API url) + +`modal run insert_models::insert_models` +This inserts a bunch of models based on the models retrieved by civitai + +civitai's API reference https://github.com/civitai/civitai/wiki/REST-API-Reference +""" import modal import subprocess import requests @@ -5,7 +17,7 @@ import requests stub = modal.Stub() # NOTE: volume name can be variable -volume = modal.Volume.persisted("model-store") +volume = modal.Volume.persisted("private-model-store") model_store_path = "/vol/models" MODEL_ROUTE = "models" image = ( @@ -15,8 +27,10 @@ image = ( @stub.function(volumes={model_store_path: volume}, gpu="any", image=image, timeout=600) def download_model(model): # wget https://civitai.com/api/download/models/{modelVersionId} --content-disposition - model_id = model['modelVersions'][0]['id'] - download_url = f"https://civitai.com/api/download/models/{model_id}" + # model_id = model['modelVersions'][0]['id'] + # download_url = f"https://civitai.com/api/download/models/{model_id}" + + download_url = model['modelVersions'][0]['downloadUrl'] subprocess.run(["wget", download_url, "--content-disposition", "-P", model_store_path]) subprocess.run(["ls", "-la", model_store_path]) volume.commit() @@ -34,11 +48,44 @@ def get_civitai_models(model_type: str, sort: str = "Highest Rated", page: int = print(f"Error fetching models: {e}") return None + +@stub.function() +def get_civitai_model_url(civitai_url: str): + # Validate the URL + if not civitai_url.startswith("https://civitai.com/models/"): + return "Error: URL must be from civitai.com and contain /models/" + + # Extract the model ID + try: + model_id = civitai_url.split("/")[4] + int(model_id) # Check if the ID is an integer + except (IndexError, ValueError): + return None #Error: Invalid model ID in URL + + # Make the API request + api_url = f"https://civitai.com/api/v1/models/{model_id}" + response = requests.get(api_url) + + # Check for successful response + if response.status_code != 200: + return f"Error: Unable to fetch data from {api_url}" + + # Return the response data + return response.json() + + + @stub.local_entrypoint() -def insert_model(type: str = "Checkpoint", sort = "Highest Rated", page: int = 1): +def insert_models(type: str = "Checkpoint", sort = "Highest Rated", page: int = 1): civitai_models = get_civitai_models.local(type, sort, page) if civitai_models: for _ in download_model.map(civitai_models['items'][1:]): pass else: print("Failed to retrieve models.") + +@stub.local_entrypoint() +def insert_model(civitai_url: str): + civitai_model = get_civitai_model_url.local(civitai_url) + if civitai_model: + download_model.remote(civitai_model) diff --git a/builder/modal-builder/src/template/volume.py b/builder/modal-builder/src/template/volume.py new file mode 100644 index 0000000..a6f1e0e --- /dev/null +++ b/builder/modal-builder/src/template/volume.py @@ -0,0 +1,10 @@ +import modal +from config import config + +public_model_volume = modal.Volume.persisted(config["public_checkpoint_volume"]) +private_volume = modal.Volume.persisted(config["private_checkpoint_volume"]) + +BASEMODEL_DIR = "/extra_models/" +MODEL_DIR = BASEMODEL_DIR + "checkpoints" +PRIVATE_MODEL_DIR = BASEMODEL_DIR + "private_checkpoints" +volumes = {MODEL_DIR: public_model_volume, PRIVATE_MODEL_DIR: private_volume}