#!/usr/bin/env python3 """ Fix: Add MIOpen fast-find, pre-warm GPU, restart ComfyUI, generate. All GPU ops confirmed working. The hang is likely cold MIOpen kernel cache. Single SSH connection. """ import paramiko, json, time, sys ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) for attempt in range(5): try: ssh.connect('192.168.178.150', username='fabian', key_filename=r'C:\Users\fabia\.ssh\id_ed25519', timeout=10) break except Exception as e: print(f" SSH attempt {attempt+1}/5: {e}") time.sleep(10) else: print("FATAL: Cannot connect"); sys.exit(1) def run(cmd, timeout=300): _, stdout, stderr = ssh.exec_command(cmd, timeout=timeout) out = stdout.read().decode() err = stderr.read().decode() return out, err try: # 1. Kill any lingering ComfyUI print("=== Kill any ComfyUI ===") run("pkill -9 -f 'python3 main.py' 2>/dev/null; sleep 1") print(" Done") # 2. Pre-warm MIOpen kernel cache with DiT-like operations print("\n=== Pre-warming MIOpen kernel cache ===") print(" This compiles HIP kernels that Z-Image-Turbo will need.") print(" First run after reboot is slow (kernel compilation)...") warmup_code = r""" import torch, torch.nn as nn, time # Simulate Z-Image-Turbo DiT operations device = 'cuda' dtype = torch.float16 print("Warming up HIP kernels for DiT inference...") t0 = time.time() # 1. Linear layers (DiT blocks) print(" Linear layers...", end=" ", flush=True) for size in [(1024,1024), (4096,1024), (1024,4096)]: l = nn.Linear(*size).to(device, dtype) x = torch.randn(1, 64, size[0], device=device, dtype=dtype) y = l(x) del l, x, y torch.cuda.synchronize() print(f"{time.time()-t0:.1f}s") # 2. Attention (SDPA - the core of DiT) print(" Scaled dot-product attention...", end=" ", flush=True) t1 = time.time() for heads in [8, 16, 24]: q = torch.randn(1, heads, 64, 64, device=device, dtype=dtype) k = torch.randn(1, heads, 64, 64, device=device, dtype=dtype) v = torch.randn(1, heads, 64, 64, device=device, dtype=dtype) y = torch.nn.functional.scaled_dot_product_attention(q, k, v) del q, k, v, y torch.cuda.synchronize() print(f"{time.time()-t1:.1f}s") # 3. LayerNorm / RMSNorm print(" Normalization layers...", end=" ", flush=True) t1 = time.time() for dim in [1024, 2048, 4096]: ln = nn.LayerNorm(dim).to(device, dtype) x = torch.randn(1, 64, dim, device=device, dtype=dtype) y = ln(x) del ln, x, y torch.cuda.synchronize() print(f"{time.time()-t1:.1f}s") # 4. Conv2d (VAE-like, but we'll run VAE on CPU) print(" Conv2d layers...", end=" ", flush=True) t1 = time.time() for ch in [64, 128, 256]: c = nn.Conv2d(ch, ch, 3, padding=1).to(device, dtype) x = torch.randn(1, ch, 32, 32, device=device, dtype=dtype) y = c(x) del c, x, y torch.cuda.synchronize() print(f"{time.time()-t1:.1f}s") # 5. Full mini-DiT forward pass simulation print(" Mini-DiT forward pass simulation...", end=" ", flush=True) t1 = time.time() hidden = 1024 seq_len = 256 heads = 16 head_dim = hidden // heads # Simulate a DiT block x = torch.randn(1, seq_len, hidden, device=device, dtype=dtype) norm = nn.LayerNorm(hidden).to(device, dtype) qkv = nn.Linear(hidden, hidden*3).to(device, dtype) proj = nn.Linear(hidden, hidden).to(device, dtype) ff1 = nn.Linear(hidden, hidden*4).to(device, dtype) ff2 = nn.Linear(hidden*4, hidden).to(device, dtype) for step in range(3): h = norm(x) q, k, v = qkv(h).chunk(3, dim=-1) q = q.view(1, seq_len, heads, head_dim).transpose(1,2) k = k.view(1, seq_len, heads, head_dim).transpose(1,2) v = v.view(1, seq_len, heads, head_dim).transpose(1,2) attn = torch.nn.functional.scaled_dot_product_attention(q, k, v) attn = attn.transpose(1,2).contiguous().view(1, seq_len, hidden) x = x + proj(attn) x = x + ff2(torch.nn.functional.gelu(ff1(norm(x)))) torch.cuda.synchronize() print(f"{time.time()-t1:.1f}s") total = time.time() - t0 print(f"\nGPU kernel warmup complete in {total:.1f}s") print(f"VRAM used: {torch.cuda.memory_allocated()//1048576} MB") torch.cuda.empty_cache() print(f"VRAM after cleanup: {torch.cuda.memory_allocated()//1048576} MB") print("WARMUP_DONE") """ # Write warmup script run(f"cat > /tmp/gpu_warmup.py << 'PYEOF'\n{warmup_code}\nPYEOF") out, err = run("bash -c 'source ~/comfyui-env/bin/activate && " "HSA_OVERRIDE_GFX_VERSION=10.1.0 HSA_ENABLE_SDMA=0 " "MIOPEN_FIND_MODE=3 MIOPEN_FIND_ENFORCE=3 " "python3 /tmp/gpu_warmup.py' 2>&1", timeout=300) print(out.strip()) if 'WARMUP_DONE' not in out: print(f" WARNING: Warmup may have failed") print(f" STDERR: {err.strip()[:500]}") # 3. Update startup script with MIOpen settings print("\n=== Update startup script ===") script = """#!/bin/bash # BC-250 ComfyUI Launcher — GPU (ROCm) + CPU VAE # GPU identity (Cyan Skillfish gfx1013 -> gfx1010) export HSA_OVERRIDE_GFX_VERSION=10.1.0 export HIP_VISIBLE_DEVICES=0 export HSA_ENABLE_SDMA=0 export HSA_TOOLS_LIB="" export HSA_TOOLS_REPORT_LOAD_FAILURE=0 # MIOpen: fast kernel selection (avoid long auto-tune on first run) export MIOPEN_FIND_MODE=3 export MIOPEN_FIND_ENFORCE=3 # Use all 12 CPU cores for CPU-side work (dequant, text encoding) export OMP_NUM_THREADS=12 export MKL_NUM_THREADS=12 export OPENBLAS_NUM_THREADS=12 # Activate venv source /home/fabian/comfyui-env/bin/activate cd /home/fabian/ComfyUI # --novram: offload models to RAM, send layers to GPU one at a time # --force-fp16: fp16 diffusion to halve VRAM usage # --cpu-vae: VAE decode on CPU (GPU hangs on full VAE forward pass) exec python3 main.py \\ --listen 0.0.0.0 --port 8188 \\ --novram \\ --force-fp16 \\ --cpu-vae """ run(f"cat > /home/fabian/start_comfyui.sh << 'HEREDOC_END'\n{script}HEREDOC_END\n" f"chmod +x /home/fabian/start_comfyui.sh") print(" Written with MIOpen fast-find + --novram --force-fp16 --cpu-vae") # 4. Launch ComfyUI print("\n=== Launch ComfyUI ===") run("nohup /home/fabian/start_comfyui.sh > /home/fabian/comfyui.log 2>&1 &") time.sleep(2) print(" Waiting for server...") for i in range(50): time.sleep(3) out, _ = run("curl -s -o /dev/null -w '%{http_code}' http://localhost:8188/ 2>/dev/null || echo 0") if out.strip() == '200': print(f" Server ready ({(i+1)*3}s)") break if i % 5 == 4: log, _ = run("tail -2 /home/fabian/comfyui.log 2>/dev/null") last = [l.strip() for l in log.strip().split('\n') if l.strip()] print(f" [{(i+1)*3}s] ... {last[-1][:80] if last else ''}") else: print(" Timeout!") out, _ = run("tail -40 /home/fabian/comfyui.log") print(out) sys.exit(1) # 5. Submit workflow print("\n=== Submit workflow (512x512, 8 steps) ===") workflow = { "prompt": { "1": {"class_type": "UnetLoaderGGUF", "inputs": {"unet_name": "z_image_turbo-Q5_K_S.gguf"}}, "2": {"class_type": "CLIPLoaderGGUF", "inputs": {"clip_name": "Qwen3-4B.i1-Q5_K_S.gguf", "type": "qwen_image"}}, "3": {"class_type": "VAELoader", "inputs": {"vae_name": "ae.safetensors"}}, "4": {"class_type": "CLIPTextEncode", "inputs": {"text": "A red fox in a snowy forest, photorealistic", "clip": ["2", 0]}}, "5": {"class_type": "CLIPTextEncode", "inputs": {"text": "", "clip": ["2", 0]}}, "6": {"class_type": "EmptyLatentImage", "inputs": {"width": 512, "height": 512, "batch_size": 1}}, "7": {"class_type": "KSampler", "inputs": {"model": ["1", 0], "seed": 42, "steps": 8, "cfg": 1.0, "sampler_name": "euler", "scheduler": "simple", "positive": ["4", 0], "negative": ["5", 0], "latent_image": ["6", 0], "denoise": 1.0}}, "8": {"class_type": "VAEDecode", "inputs": {"samples": ["7", 0], "vae": ["3", 0]}}, "9": {"class_type": "SaveImage", "inputs": {"images": ["8", 0], "filename_prefix": "ZImageTurbo_test"}} } } wf_json = json.dumps(workflow) # Write workflow to file to avoid shell escaping issues run(f"cat > /tmp/zimage_wf.json << 'JSONEOF'\n{wf_json}\nJSONEOF") out, _ = run("curl -s -X POST http://localhost:8188/prompt " "-H 'Content-Type: application/json' " "-d @/tmp/zimage_wf.json") try: resp = json.loads(out.strip()) if 'error' in resp: print(f" API ERROR: {resp['error']}") sys.exit(1) print(f" Prompt ID: {resp.get('prompt_id')}") except: print(f" Response: {out.strip()[:500]}") # 6. Monitor — wait up to 15 minutes (first run can be slow due to kernel cache) print("\n=== Monitoring generation (GPU kernels may compile on first step) ===") last_log = "" for i in range(60): # up to 15 minutes time.sleep(15) stats, _ = run("bash -c '" "PID=$(pgrep -f \"python3 main.py\" | head -1); " "if [ -n \"$PID\" ]; then " " CPU=$(ps -p $PID -o %cpu --no-headers); " " RSS=$(ps -p $PID -o rss --no-headers); " " LOAD=$(cat /proc/loadavg | cut -d\" \" -f1); " " GPU_T=$(cat /sys/class/drm/card1/device/hwmon/hwmon*/temp1_input 2>/dev/null || echo 0); " " echo \"CPU:${CPU}% RSS:$((RSS/1024))M LOAD:${LOAD} GPU:$((GPU_T/1000))C\"; " "else echo DEAD; fi'") log, _ = run("tail -12 /home/fabian/comfyui.log 2>/dev/null") log_s = log.strip() elapsed = (i+1) * 15 m, s = divmod(elapsed, 60) stats_s = stats.strip() print(f" [{m}m{s:02d}s] {stats_s}") # Show new log content if log_s != last_log: for line in reversed(log_s.split('\n')): l = line.strip() if l and not l.startswith('FETCH') and not l.startswith('[DEPRECATION') and not l.startswith('[ComfyUI-Manager]'): print(f" LOG: {l[:120]}") break last_log = log_s if 'DEAD' in stats_s: print("\n PROCESS DIED!") out, _ = run("tail -60 /home/fabian/comfyui.log") print(out) break if 'Prompt executed in' in log_s: print(f"\n SUCCESS! Image generated!") out, _ = run("tail -25 /home/fabian/comfyui.log") print(out) break if 'Traceback' in log_s or 'RuntimeError' in log_s: print("\n ERROR detected!") out, _ = run("tail -60 /home/fabian/comfyui.log") print(out) break # 7. Check output print("\n=== Output files ===") out, _ = run("ls -lah ~/ComfyUI/output/ 2>/dev/null") print(out.strip()) # 8. Check history out, _ = run("curl -s http://localhost:8188/history 2>/dev/null") try: h = json.loads(out) for pid, info in h.items(): status = info.get('status', {}) outputs = info.get('outputs', {}) print(f"\n Prompt {pid[:12]}...: status={status}") if outputs: for nid, nout in outputs.items(): if isinstance(nout, dict) and 'images' in nout: for img in nout['images']: print(f" Image: {img.get('filename', 'unknown')}") except: pass finally: ssh.close() print("\nSSH connection closed.")