"""npu-forge-convert: GGUF -> Q4NX on Modal (the remote leg of forge convert). Clones the official FLM_Q4NX_Converter, pulls a GGUF from HF, converts, and parks the .q4nx on a volume for the local box to download. modal run convert_q4nx.py --repo-id bartowski/Llama-3.2-1B-Instruct-GGUF \ --filename Llama-3.2-1B-Instruct-Q4_K_M.gguf --out-name forge-llama32-1b """ import modal app = modal.App("npu-forge-convert") image = ( modal.Image.debian_slim(python_version="3.11") .apt_install("git") .pip_install("torch==2.4.1", "transformers==4.44.2", "numpy", "gguf", "amd-quark", "huggingface_hub", "safetensors", "sentencepiece", "einops", "tqdm") .run_commands( "git clone --depth 1 https://github.com/FastFlowLM/FLM_Q4NX_Converter /converter", "if [ -f /converter/requirements.txt ]; then pip install -r /converter/requirements.txt; fi", ) ) vol = modal.Volume.from_name("npu-forge-out", create_if_missing=True) @app.function(image=image, cpu=8, memory=16384, timeout=3600, volumes={"/out": vol}) def convert(repo_id: str, filename: str, out_name: str, family: str = ""): import subprocess, os, time from huggingface_hub import hf_hub_download t0 = time.time() gguf = hf_hub_download(repo_id=repo_id, filename=filename) t1 = time.time() print(f"[download] {filename} in {t1-t0:.0f}s ({os.path.getsize(gguf)/1e9:.2f} GB)") outdir = f"/out/{out_name}" os.makedirs(outdir, exist_ok=True) # call the converter's Python API directly — upstream convert.py HEAD has # an uncommented debug sys.argv override that hijacks every CLI invocation import sys sys.path.insert(0, "/converter") os.chdir("/converter") # converter loads configs/.json relative to its repo root from q4nx import create_converter print(f"[convert] create_converter({gguf}, family={family!r})") model = create_converter(gguf, family or "") model.convert(q4nx_path=outdir, weights_type="language") t2 = time.time() files = {f: os.path.getsize(os.path.join(outdir, f)) for f in os.listdir(outdir)} vol.commit() return {"convert_seconds": round(t2 - t1), "download_seconds": round(t1 - t0), "outputs": files} @app.local_entrypoint() def main(repo_id: str = "bartowski/Llama-3.2-1B-Instruct-GGUF", filename: str = "Llama-3.2-1B-Instruct-Q4_K_M.gguf", out_name: str = "forge-llama32-1b", family: str = ""): import json print(json.dumps(convert.remote(repo_id, filename, out_name, family), indent=2))