"""BC-250 Full GPU Fix: Verify ROCm, diagnose VRAM, start ComfyUI on GPU, generate image.""" import paramiko import time import json import sys SSH_HOST = '192.168.178.150' SSH_USER = 'fabian' SSH_KEY = r'C:\Users\fabia\.ssh\id_ed25519' def ssh_connect(): k = paramiko.Ed25519Key.from_private_key_file(SSH_KEY) c = paramiko.SSHClient() c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) c.connect(SSH_HOST, username=SSH_USER, pkey=k, timeout=15) return c def run(c, cmd, timeout=30): """Run command via fish shell, return stdout.""" wrapped = f'bash -c {repr(cmd)}' _, o, e = c.exec_command(wrapped, timeout=timeout) return o.read().decode(errors='replace').strip() def run_full(c, cmd, timeout=30): """Run command, return (stdout, stderr).""" wrapped = f'bash -c {repr(cmd)}' _, o, e = c.exec_command(wrapped, timeout=timeout) return o.read().decode(errors='replace').strip(), e.read().decode(errors='replace').strip() # ============================================================ # PHASE 1: Kill any remnants # ============================================================ print("="*60) print("PHASE 1: Clean slate") print("="*60) c = ssh_connect() run(c, 'pkill -f "python.*main.py" 2>/dev/null; pkill -f comfyui 2>/dev/null') time.sleep(2) leftover = run(c, 'pgrep -af "python.*main.py" 2>/dev/null') if leftover: print(f"WARNING: Still running: {leftover}") run(c, 'pkill -9 -f "python.*main.py" 2>/dev/null') time.sleep(1) print("ComfyUI killed. Clean slate.") # ============================================================ # PHASE 2: Verify ROCm + PyTorch GPU # ============================================================ print("\n" + "="*60) print("PHASE 2: Verify ROCm + PyTorch GPU access") print("="*60) # Set GPU env vars for ALL subsequent commands GPU_ENV = ( '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; ' 'export PYTORCH_HIP_ALLOC_CONF=expandable_segments:False; ' ) # Check rocminfo out = run(c, f'{GPU_ENV} rocminfo 2>&1 | grep -E "Name:|Marketing Name:|gfx" | head -10') print(f"ROCm devices:\n{out}") # Check PyTorch GPU gpu_test = f'''{GPU_ENV} cd ~/ComfyUI && source ~/comfyui-env/bin/activate.fish 2>/dev/null; . ~/comfyui-env/bin/activate 2>/dev/null; python3 -c " import torch print(f'PyTorch: {{torch.__version__}}') print(f'CUDA available: {{torch.cuda.is_available()}}') print(f'Device count: {{torch.cuda.device_count()}}') if torch.cuda.is_available(): print(f'Device name: {{torch.cuda.get_device_name(0)}}') free, total = torch.cuda.mem_get_info(0) print(f'VRAM: {{free//1024//1024}}MB free / {{total//1024//1024}}MB total') # Quick GPU compute test x = torch.randn(1024, 1024, device='cuda', dtype=torch.float16) y = torch.mm(x, x) print(f'GPU compute test: OK (result sum={{y.sum().item():.1f}})') del x, y torch.cuda.empty_cache() else: print('ERROR: GPU NOT AVAILABLE') import sys; sys.exit(1) "''' out, err = run_full(c, gpu_test, timeout=60) print(out) if err: print(f"STDERR: {err}") if 'ERROR: GPU NOT AVAILABLE' in out or 'CUDA available: False' in out: print("\n*** FATAL: PyTorch cannot see the GPU! ***") c.close() sys.exit(1) print("\nGPU verified OK!") # ============================================================ # PHASE 3: Start ComfyUI with correct GPU flags # ============================================================ print("\n" + "="*60) print("PHASE 3: Start ComfyUI with GPU") print("="*60) # The key insight: --novram was offloading EVERYTHING to CPU (0 MB on GPU) # For this APU with shared memory, --lowvram is better: # it keeps compute on GPU but swaps model layers in/out # We also use --force-fp16 to reduce memory pressure # --cpu-vae to avoid the known VAE decode hang on this GPU COMFYUI_CMD = ( f'{GPU_ENV} ' 'export OMP_NUM_THREADS=12; ' 'export MKL_NUM_THREADS=12; ' 'export OPENBLAS_NUM_THREADS=12; ' 'export MIOPEN_FIND_MODE=1; ' # Fast MIOpen kernel search 'cd ~/ComfyUI && ' 'source ~/comfyui-env/bin/activate 2>/dev/null; . ~/comfyui-env/bin/activate 2>/dev/null; ' 'nohup python3 main.py ' '--listen 0.0.0.0 --port 8188 ' '--lowvram ' '--force-fp16 ' '--cpu-vae ' '--disable-smart-memory ' '> /tmp/comfyui.log 2>&1 &' ) # Truncate old log first run(c, 'truncate -s 0 /tmp/comfyui.log 2>/dev/null; touch /tmp/comfyui.log') print("Starting ComfyUI with: --lowvram --force-fp16 --cpu-vae --disable-smart-memory") print("(--lowvram keeps compute on GPU, swaps layers; --novram was wrong - it put everything on CPU)") run(c, COMFYUI_CMD) time.sleep(3) # Verify it started pid = run(c, 'pgrep -f "python.*main.py" 2>/dev/null') if not pid: print("ERROR: ComfyUI failed to start!") log = run(c, 'cat /tmp/comfyui.log') print(f"Log:\n{log}") c.close() sys.exit(1) print(f"ComfyUI started, PID: {pid}") # Wait for server ready print("Waiting for server ready...") for i in range(60): try: resp = run(c, 'curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8188/ 2>/dev/null', timeout=5) if resp == '200': print(f"Server ready after {i*3}s!") break except: pass # Also check for crash log_tail = run(c, 'tail -3 /tmp/comfyui.log 2>/dev/null') if 'Traceback' in log_tail or 'Error' in log_tail: print(f"Server log issue: {log_tail}") if i % 5 == 0 and i > 0: print(f" [{i*3}s] Still waiting... log: {log_tail[-80:]}") time.sleep(3) else: print("TIMEOUT waiting for ComfyUI!") log = run(c, 'tail -30 /tmp/comfyui.log') print(f"Log:\n{log}") c.close() sys.exit(1) # Print startup log to confirm flags log = run(c, 'head -20 /tmp/comfyui.log') print(f"\nStartup log:\n{log}") # ============================================================ # PHASE 4: Submit workflow via SFTP # ============================================================ print("\n" + "="*60) print("PHASE 4: Submit workflow") print("="*60) 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, highly detailed", "clip": ["2", 0] } }, "5": { "class_type": "EmptyLatentImage", "inputs": { "width": 512, "height": 512, "batch_size": 1 } }, "6": { "class_type": "KSampler", "inputs": { "model": ["1", 0], "positive": ["4", 0], "negative": ["4", 0], "latent_image": ["5", 0], "seed": 42, "steps": 8, "cfg": 1.0, "sampler_name": "euler", "scheduler": "simple", "denoise": 1.0 } }, "7": { "class_type": "VAEDecode", "inputs": { "samples": ["6", 0], "vae": ["3", 0] } }, "8": { "class_type": "SaveImage", "inputs": { "images": ["7", 0], "filename_prefix": "ZImageTurbo_GPU" } } } } # Write via SFTP sftp = c.open_sftp() wf_json = json.dumps(workflow) with sftp.open('/tmp/wf.json', 'w') as f: f.write(wf_json) sftp.close() print("Workflow written to /tmp/wf.json via SFTP") # Verify JSON verify = run(c, 'python3 -c "import json; d=json.load(open(\'/tmp/wf.json\')); print(f\'Nodes: {list(d[chr(34)+chr(34) if False else \"prompt\"].keys())}\')"') print(f"Verify: {verify}") # Submit resp = run(c, 'curl -s -X POST http://127.0.0.1:8188/prompt -H "Content-Type: application/json" -d @/tmp/wf.json 2>/dev/null') print(f"Submit response: {resp}") if 'error' in resp.lower() and 'prompt_id' not in resp.lower(): print(f"\n*** SUBMISSION ERROR ***") # Check what went wrong log = run(c, 'tail -10 /tmp/comfyui.log') print(f"Log: {log}") c.close() sys.exit(1) try: resp_data = json.loads(resp) prompt_id = resp_data.get('prompt_id', 'unknown') print(f"Prompt ID: {prompt_id}") except: print("Could not parse response, continuing anyway...") # ============================================================ # PHASE 5: Monitor generation with GPU tracking # ============================================================ print("\n" + "="*60) print("PHASE 5: Monitor generation (GPU must be active!)") print("="*60) start_time = time.time() last_log_len = 0 for i in range(120): # Up to 30 minutes elapsed = int(time.time() - start_time) # GPU metrics gpu_pct = run(c, 'cat /sys/class/drm/card0/device/gpu_busy_percent 2>/dev/null') gpu_temp = run(c, 'cat /sys/class/drm/card0/device/hwmon/hwmon*/temp1_input 2>/dev/null') temp_c = int(gpu_temp) // 1000 if gpu_temp.isdigit() else '?' # GPU power gpu_power = run(c, f'{GPU_ENV} rocm-smi -P 2>&1 | grep "Graphics Package" | grep -oP "[\\d.]+" | head -1') # Process info proc = run(c, 'ps -p $(pgrep -f "python.*main.py" | head -1) -o %cpu,%mem,rss --no-headers 2>/dev/null') # Log tail log = run(c, 'tail -5 /tmp/comfyui.log 2>/dev/null') last_line = log.split('\n')[-1] if log else '' # Output files files = run(c, 'ls ~/ComfyUI/output/*.png 2>/dev/null') # Queue queue = run(c, 'curl -s http://127.0.0.1:8188/queue 2>/dev/null') status = f"[{elapsed:>4}s] GPU:{gpu_pct:>3}% {temp_c}C {gpu_power}W | proc:{proc} | {last_line[-100:]}" print(status) # SUCCESS: Image generated! if files: print(f"\n{'='*60}") print(f"*** SUCCESS! IMAGE GENERATED! ***") print(f"Files: {files}") print(f"Total time: {elapsed}s") print(f"{'='*60}") # Print final log final_log = run(c, 'tail -20 /tmp/comfyui.log 2>/dev/null') print(f"\nFinal log:\n{final_log}") break # Check if queue is empty (job done or failed) try: qdata = json.loads(queue) running = len(qdata.get('queue_running', [])) pending = len(qdata.get('queue_pending', [])) if running == 0 and pending == 0 and elapsed > 30: print(f"\nQueue empty after {elapsed}s. Checking if image was saved...") time.sleep(2) files = run(c, 'ls ~/ComfyUI/output/*.png 2>/dev/null') if files: print(f"*** SUCCESS! {files}") else: print("No image. Checking log for errors:") err_log = run(c, 'tail -30 /tmp/comfyui.log 2>/dev/null') print(err_log) break except: pass # Check for process death alive = run(c, 'pgrep -f "python.*main.py" 2>/dev/null') if not alive: print("\n*** ComfyUI process died! ***") crash_log = run(c, 'tail -40 /tmp/comfyui.log 2>/dev/null') print(f"Crash log:\n{crash_log}") break time.sleep(15) c.close() print("\nDone.")