| |
| """Misst Krea-2-Generierungszeiten ueber die ComfyUI-API. |
| |
| Vergleicht NVFP4 gegen FP8, jeweils mit und ohne das ilore-Style-LoRA. |
| Erster Lauf je Variante ist Aufwaermen (Modell laden) und zaehlt nicht. |
| """ |
| import json, time, urllib.request, urllib.error, statistics, argparse, uuid |
|
|
| HOST = "127.0.0.1:8188" |
|
|
| def build(model, lora, strength, w, h, steps, prompt, seed): |
| g = { |
| "1": {"class_type": "UNETLoader", "inputs": {"unet_name": model, "weight_dtype": "default"}}, |
| "13": {"class_type": "CLIPLoader", "inputs": {"clip_name": "qwen3vl_4b_fp8_scaled.safetensors", |
| "type": "krea2", "device": "default"}}, |
| "4": {"class_type": "VAELoader", "inputs": {"vae_name": "qwen_image_vae.safetensors"}}, |
| "6": {"class_type": "CLIPTextEncode", "inputs": {"text": prompt, "clip": ["13", 0]}}, |
| "8": {"class_type": "ConditioningZeroOut", "inputs": {"conditioning": ["6", 0]}}, |
| "10": {"class_type": "EmptyLatentImage", "inputs": {"width": w, "height": h, "batch_size": 1}}, |
| "3": {"class_type": "VAEDecode", "inputs": {"samples": ["2", 0], "vae": ["4", 0]}}, |
| "9": {"class_type": "SaveImage", "inputs": {"images": ["3", 0], "filename_prefix": "bench"}}, |
| } |
| model_src = ["1", 0] |
| if lora: |
| g["20"] = {"class_type": "LoraLoaderModelOnly", |
| "inputs": {"model": ["1", 0], "lora_name": lora, "strength_model": strength}} |
| model_src = ["20", 0] |
| g["2"] = {"class_type": "KSampler", "inputs": { |
| "model": model_src, "positive": ["6", 0], "negative": ["8", 0], "latent_image": ["10", 0], |
| "seed": seed, "steps": steps, "cfg": 1.0, |
| "sampler_name": "er_sde", "scheduler": "simple", "denoise": 1.0}} |
| return g |
|
|
| def run(graph, cid): |
| body = json.dumps({"prompt": graph, "client_id": cid}).encode() |
| req = urllib.request.Request(f"http://{HOST}/prompt", body, {"Content-Type": "application/json"}) |
| pid = json.load(urllib.request.urlopen(req))["prompt_id"] |
| t0 = time.time() |
| while True: |
| with urllib.request.urlopen(f"http://{HOST}/history/{pid}") as r: |
| hist = json.load(r) |
| if pid in hist: |
| st = hist[pid].get("status", {}) |
| if st.get("status_str") == "error": |
| raise RuntimeError(json.dumps(st)[:400]) |
| return time.time() - t0 |
| time.sleep(0.05) |
|
|
| def main(): |
| ap = argparse.ArgumentParser() |
| ap.add_argument("--runs", type=int, default=5) |
| ap.add_argument("--steps", type=int, default=8) |
| ap.add_argument("--size", default="1024x1024") |
| ap.add_argument("--lora", default="ilore_style_krea2_1000.safetensors") |
| ap.add_argument("--strength", type=float, default=0.8) |
| ap.add_argument("--prompt", default="a weathered iron battleaxe lying on plain white background") |
| a = ap.parse_args() |
| w, h = (int(x) for x in a.size.split("x")) |
| cid = str(uuid.uuid4()) |
|
|
| variants = [ |
| ("NVFP4 ohne LoRA", "krea2_turbo_nvfp4.safetensors", None), |
| ("NVFP4 mit LoRA", "krea2_turbo_nvfp4.safetensors", a.lora), |
| ("FP8 ohne LoRA", "krea2_turbo_fp8_scaled.safetensors", None), |
| ("FP8 mit LoRA", "krea2_turbo_fp8_scaled.safetensors", a.lora), |
| ] |
| print(f"{w}x{h}, {a.steps} steps, {a.runs} Laeufe je Variante (+1 Warmup)\n") |
| results = {} |
| for label, model, lora in variants: |
| try: |
| g = build(model, lora, a.strength, w, h, a.steps, a.prompt, 0) |
| run(g, cid) |
| ts = [] |
| for i in range(a.runs): |
| g = build(model, lora, a.strength, w, h, a.steps, a.prompt, i + 1) |
| ts.append(run(g, cid)) |
| med = statistics.median(ts) |
| results[label] = med |
| print(f" {label:20s} Median {med:6.2f}s (min {min(ts):.2f} / max {max(ts):.2f})") |
| except Exception as e: |
| print(f" {label:20s} FEHLER: {str(e)[:200]}") |
| if "NVFP4 mit LoRA" in results and "FP8 mit LoRA" in results: |
| sp = results["FP8 mit LoRA"] / results["NVFP4 mit LoRA"] |
| print(f"\n NVFP4 ist {sp:.2f}x schneller als FP8 (mit LoRA)") |
|
|
| if __name__ == "__main__": |
| main() |
|
|