From d65420f34a763aab9f17b0fd7db2a63460a4747e Mon Sep 17 00:00:00 2001 From: Towsty Date: Sun, 20 Sep 2026 15:53:43 -0500 Subject: [PATCH] Add Qwen 2.1 host setup script and document Shared model paths. Co-authored-by: Cursor --- docs/qwen21.md | 34 +++++++ scripts/setup-qwen21.ps1 | 189 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 docs/qwen21.md create mode 100644 scripts/setup-qwen21.ps1 diff --git a/docs/qwen21.md b/docs/qwen21.md new file mode 100644 index 0000000..1c71dd9 --- /dev/null +++ b/docs/qwen21.md @@ -0,0 +1,34 @@ +# Qwen Image 2.1 (engine `qwen21`) — host weights + Comfy graph + +## Host paths (this RTX 5080 box) + +| Role | Resolved path | +|---|---| +| ComfyUI root (Klein / host agent `ComfyUI (1)`) | `C:\Users\ianjm\AppData\Local\Comfy-Desktop\ComfyUI-Installs\ComfyUI (1)\ComfyUI` | +| Models root (Desktop Shared via `shared_model_paths.yaml`) | `C:\Users\ianjm\AppData\Local\Comfy-Desktop\ComfyUI-Shared\models` | + +Weights must land under Shared so every Desktop instance sees them: + +- `diffusion_models\qwen-image-2.1-Q8_0.gguf` (~7.59 GiB) — from `abenzerps/Qwen-Image-2.1-Uncensored-GGUF` +- `text_encoders\qwen3vl_8b_int8_convrot.safetensors` — from `Comfy-Org/Qwen-Image-2.1` (INT8; BF16 8B VL OOMs on 16 GB) +- `vae\qwen_image_2.1_vae_bf16.safetensors` — from `Comfy-Org/Qwen-Image-2.1` (**not** the old Qwen-Image 1.0 VAE) + +Custom node: `custom_nodes\ComfyUI-GGUF` (`UnetLoaderGGUF`). Do not install a second GGUF pack. + +## Setup + +```powershell +powershell -ExecutionPolicy Bypass -File scripts\setup-qwen21.ps1 +``` + +Restart Comfy **only when idle** (`COMFY_CONTROL_URL/status` → `gpu.busy=false`). Confirm `object_info` lists `UnetLoaderGGUF` and the three filenames. + +## App + +- Engine key: `qwen21` · UI label: **Qwen 2.1** +- Generate (T2I) only in this build. Edit / Compose / Iterate / Video disable with: “Qwen 2.1 is T2I in this build”. +- Sampler defaults: euler / simple / cfg **1** / steps **25** · `ModelSamplingAuraFlow` shift **3.1** +- Default canvas **1024×1024** (aspect 16:9 / 9:16 / 1:1 honored, multiples of 32). Drop to 768 if VRAM errors. +- No hero / locks / Klein LoRA stack on this engine. + +Graph: `server/assets/studio2_qwen21_t2i.json`. diff --git a/scripts/setup-qwen21.ps1 b/scripts/setup-qwen21.ps1 new file mode 100644 index 0000000..b64e0f7 --- /dev/null +++ b/scripts/setup-qwen21.ps1 @@ -0,0 +1,189 @@ +# Download Qwen Image 2.1 GGUF + companions into the live Comfy Desktop Shared models tree. +# Also ensures city96/ComfyUI-GGUF is present on the ComfyUI (1) install. +# +# Example: +# powershell -ExecutionPolicy Bypass -File scripts\setup-qwen21.ps1 +# powershell -File scripts\setup-qwen21.ps1 -SkipDownload +# powershell -File scripts\setup-qwen21.ps1 -ComfyRoot "C:\...\ComfyUI (1)\ComfyUI" + +[CmdletBinding()] +param( + [string]$ComfyRoot = '', + [string]$ModelsRoot = '', + [switch]$SkipDownload, + [switch]$SkipNode, + [switch]$Force +) + +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'Continue' + +function Write-Step([string]$Message) { + Write-Host "" + Write-Host "==> $Message" -ForegroundColor Cyan +} + +function Write-Ok([string]$Message) { + Write-Host " $Message" -ForegroundColor Green +} + +function Write-Warn([string]$Message) { + Write-Host " warn $Message" -ForegroundColor Yellow +} + +function Resolve-ComfyRoot([string]$Hint) { + $candidates = @( + $Hint, + $env:COMFY_ROOT, + $env:COMFYUI_ROOT, + (Join-Path $env:LOCALAPPDATA 'Comfy-Desktop\ComfyUI-Installs\ComfyUI (1)\ComfyUI'), + (Join-Path $env:LOCALAPPDATA 'Comfy-Desktop\ComfyUI-Installs\ComfyUI\ComfyUI'), + 'C:\ComfyUI', + (Join-Path $env:USERPROFILE 'ComfyUI') + ) | Where-Object { $_ } | ForEach-Object { $_.TrimEnd('\', '/') } + + foreach ($path in $candidates) { + if (Test-Path (Join-Path $path 'main.py')) { return (Resolve-Path $path).Path } + if (Test-Path (Join-Path $path 'ComfyUI\main.py')) { return (Resolve-Path (Join-Path $path 'ComfyUI')).Path } + } + throw "Could not find ComfyUI root (main.py). Pass -ComfyRoot." +} + +function Resolve-ModelsRoot([string]$Hint, [string]$ComfyRootPath) { + $shared = Join-Path $env:LOCALAPPDATA 'Comfy-Desktop\ComfyUI-Shared\models' + $candidates = @( + $Hint, + $env:COMFY_MODELS_ROOT, + $shared, + (Join-Path $ComfyRootPath 'models') + ) | Where-Object { $_ } | ForEach-Object { $_.TrimEnd('\', '/') } + + foreach ($path in $candidates) { + if (Test-Path $path) { return (Resolve-Path $path).Path } + } + New-Item -ItemType Directory -Force -Path $shared | Out-Null + return (Resolve-Path $shared).Path +} + +function Find-HfCli { + foreach ($name in @('hf', 'huggingface-cli')) { + $cmd = Get-Command $name -ErrorAction SilentlyContinue + if ($cmd) { return $cmd.Source } + } + throw "Hugging Face CLI not found (hf / huggingface-cli). Install with: pip install -U huggingface_hub" +} + +function Ensure-Dir([string]$Path) { + if (-not (Test-Path $Path)) { + New-Item -ItemType Directory -Force -Path $Path | Out-Null + } +} + +function Assert-ModelFile([string]$Path, [long]$MinBytes) { + if (-not (Test-Path $Path)) { throw "Missing model file: $Path" } + $item = Get-Item $Path + if ($item.Length -lt $MinBytes) { + throw "Suspicious size for $Path ($($item.Length) bytes). Expected >= $MinBytes. Delete and re-run." + } + Write-Ok ("{0} {1:N0} bytes" -f $item.FullName, $item.Length) +} + +function Flatten-IfNested([string]$DestDir, [string]$FileName) { + $nested = Get-ChildItem $DestDir -Recurse -File -Filter $FileName -ErrorAction SilentlyContinue | + Where-Object { $_.DirectoryName -ne $DestDir } | + Select-Object -First 1 + if ($nested) { + $target = Join-Path $DestDir $FileName + if ((Test-Path $target) -and -not $Force) { + Write-Warn "Nested copy found but destination already exists: $target" + return + } + Move-Item -Force $nested.FullName $target + Write-Ok "Flattened $($nested.FullName) -> $target" + } +} + +function Invoke-HfFile { + param( + [string]$Cli, + [string]$Repo, + [string]$File, + [string]$LocalDir + ) + Ensure-Dir $LocalDir + $dest = Join-Path $LocalDir (Split-Path $File -Leaf) + if ((Test-Path $dest) -and -not $Force) { + $len = (Get-Item $dest).Length + if ($len -gt 1MB) { + Write-Ok "Already present: $dest ($len bytes)" + return + } + } + Write-Step "hf download $Repo $File" + & $Cli download $Repo $File --local-dir $LocalDir + if ($LASTEXITCODE -ne 0) { throw "hf download failed for $Repo / $File (exit $LASTEXITCODE)" } + Flatten-IfNested -DestDir $LocalDir -FileName (Split-Path $File -Leaf) +} + +# --- resolve paths --- +Write-Step "Resolve ComfyUI root" +$root = Resolve-ComfyRoot $ComfyRoot +Write-Ok $root + +Write-Step "Resolve models root (Desktop Shared preferred)" +$models = Resolve-ModelsRoot $ModelsRoot $root +Write-Ok $models + +$Diff = Join-Path $models 'diffusion_models' +$TE = Join-Path $models 'text_encoders' +$VAE = Join-Path $models 'vae' +Ensure-Dir $Diff +Ensure-Dir $TE +Ensure-Dir $VAE + +if (-not $SkipDownload) { + $cli = Find-HfCli + Write-Ok "HF CLI: $cli" + + # DiT GGUF only (no Q6/Q5/Q4, no full bf16 DiT) + Invoke-HfFile -Cli $cli -Repo 'abenzerps/Qwen-Image-2.1-Uncensored-GGUF' -File 'qwen-image-2.1-Q8_0.gguf' -LocalDir $Diff + + # TE + VAE companions from Comfy-Org (INT8 TE for 16 GB VRAM) + Invoke-HfFile -Cli $cli -Repo 'Comfy-Org/Qwen-Image-2.1' -File 'text_encoders/qwen3vl_8b_int8_convrot.safetensors' -LocalDir $TE + Invoke-HfFile -Cli $cli -Repo 'Comfy-Org/Qwen-Image-2.1' -File 'vae/qwen_image_2.1_vae_bf16.safetensors' -LocalDir $VAE +} + +Write-Step "Verify weights" +Assert-ModelFile (Join-Path $Diff 'qwen-image-2.1-Q8_0.gguf') 7000000000 +Assert-ModelFile (Join-Path $TE 'qwen3vl_8b_int8_convrot.safetensors') 1000000000 +Assert-ModelFile (Join-Path $VAE 'qwen_image_2.1_vae_bf16.safetensors') 100000000 + +if (-not $SkipNode) { + Write-Step "city96 ComfyUI-GGUF" + $nodes = Join-Path $root 'custom_nodes\ComfyUI-GGUF' + if (-not (Test-Path $nodes)) { + git clone https://github.com/city96/ComfyUI-GGUF $nodes + if ($LASTEXITCODE -ne 0) { throw "git clone ComfyUI-GGUF failed" } + Write-Ok "Cloned $nodes" + } else { + Write-Ok "Already present: $nodes" + } + + $py = Join-Path $root '.venv\Scripts\python.exe' + if (-not (Test-Path $py)) { + $py = Join-Path (Split-Path $root) 'standalone-env\python.exe' + } + if (Test-Path $py) { + Write-Step "Ensure gguf package in Comfy python ($py)" + & $py -m pip install -q gguf + if ($LASTEXITCODE -ne 0) { Write-Warn "pip install gguf returned $LASTEXITCODE" } + else { Write-Ok "gguf importable" } + } else { + Write-Warn "Comfy python not found; install gguf into the interpreter that runs main.py" + } +} + +Write-Host "" +Write-Host "Done. Restart Comfy only when /status shows gpu.busy=false." -ForegroundColor Cyan +Write-Host "ComfyRoot=$root" +Write-Host "ModelsRoot=$models"