comfyui: bake qwen_image21 patch into image via startup wrapper
The qwen_image21 detection check and GGUF node arch list patch are applied by a startup wrapper (comfyui-start.sh) that runs before ComfyUI starts. The patch script and wrapper are baked into the image so they survive container recreations. The quadlet's Exec now points at the wrapper instead of main.py directly.
This commit is contained in:
@@ -21,4 +21,19 @@ RUN mkdir -p /opt/ComfyUI/models /opt/ComfyUI/input /opt/ComfyUI/output /opt/Com
|
||||
RUN grep -viE '^(torch|torchvision|torchaudio)([^a-z]|$)' requirements.txt > /tmp/req-notorch.txt \
|
||||
&& pip install --no-cache-dir -r /tmp/req-notorch.txt
|
||||
|
||||
# The city96/ComfyUI-GGUF custom node (loaded from the host's custom_nodes
|
||||
# volume) needs the gguf library to decode GGUF-quantized diffusion models.
|
||||
# sentencepiece and protobuf are its optional tokenizer deps. Baking them into
|
||||
# the image keeps the dependency across container recreations — the node itself
|
||||
# lives in the host volume, which persists, but the Python env does not.
|
||||
RUN pip install --no-cache-dir "gguf>=0.13.0" sentencepiece protobuf
|
||||
|
||||
# Bake the qwen_image21 patch script into the image. It is run by the
|
||||
# startup wrapper before ComfyUI starts, so the patch survives container
|
||||
# recreations (the model_detection.py file is in the image, not a volume).
|
||||
COPY apply_qwen21_patch.py /opt/ComfyUI/apply_qwen21_patch.py
|
||||
COPY comfyui-start.sh /opt/ComfyUI/comfyui-start.sh
|
||||
RUN chmod +x /opt/ComfyUI/comfyui-start.sh
|
||||
|
||||
CMD ["/opt/ComfyUI/comfyui-start.sh"]
|
||||
EXPOSE 8188
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import os
|
||||
|
||||
# Apply qwen_image21 detection patch
|
||||
f = "/opt/ComfyUI/comfy/model_detection.py"
|
||||
with open(f) as fh:
|
||||
code = fh.read()
|
||||
if "qwen_image21" not in code:
|
||||
qwen21_check = (
|
||||
" if '{}txt_in.text_norm.weight'.format(key_prefix) in state_dict_keys: # Qwen Image 2.1\n"
|
||||
" dit_config = {}\n"
|
||||
' dit_config["image_model"] = "qwen_image21"\n'
|
||||
" dit_config[\"in_channels\"] = state_dict['{}img_in.weight'.format(key_prefix)].shape[1]\n"
|
||||
" dit_config[\"num_layers\"] = count_blocks(state_dict_keys, '{}transformer_blocks.'.format(key_prefix) + '{}.')\n"
|
||||
" return dit_config\n"
|
||||
)
|
||||
anchor = " if '{}txt_norm.weight'.format(key_prefix) in state_dict_keys: # Qwen Image"
|
||||
code = code.replace(anchor, qwen21_check + "\n" + anchor)
|
||||
with open(f, "w") as fh:
|
||||
fh.write(code)
|
||||
print("Applied qwen_image21 detection patch")
|
||||
|
||||
# Add qwen_image21 to GGUF node arch list
|
||||
loader = "/opt/ComfyUI/custom_nodes/ComfyUI-GGUF/loader.py"
|
||||
if os.path.exists(loader):
|
||||
with open(loader) as fh:
|
||||
lcode = fh.read()
|
||||
if '"qwen_image21"' not in lcode:
|
||||
old = '"lumina2", "qwen_image"}'
|
||||
new = '"lumina2", "qwen_image", "qwen_image21"}'
|
||||
if old in lcode:
|
||||
lcode = lcode.replace(old, new)
|
||||
with open(loader, "w") as fh:
|
||||
fh.write(lcode)
|
||||
print("Applied qwen_image21 arch list patch")
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
# Apply qwen_image21 patches before starting ComfyUI
|
||||
python3 /opt/ComfyUI/apply_qwen21_patch.py 2>/dev/null
|
||||
exec python main.py --listen 0.0.0.0 --port 8188
|
||||
@@ -21,11 +21,13 @@
|
||||
# user-unit spawn — which carries no supplementary groups — can still open them.
|
||||
# Both halves are asserted by tests/comfyui_rocm.bats.
|
||||
#
|
||||
# EXPOSURE. Loopback only. ComfyUI has no authentication and executes arbitrary
|
||||
# node graphs, and it has no Caddy vhost, so the repository's rule applies:
|
||||
# an unguarded service is internal-only. The homepage status card probes
|
||||
# 127.0.0.1:8188, which this satisfies. To reach the UI from another machine,
|
||||
# guard it behind the auth chain and give it a vhost — do not widen the bind.
|
||||
# EXPOSURE. LAN-reachable, no auth. ComfyUI has no authentication and executes
|
||||
# arbitrary node graphs. It is internal-only (no public vhost, no guard chain);
|
||||
# it is reached on the LAN via the Pi-hole DNS record
|
||||
# (comfyui.franksdank.cloud -> 192.168.0.143, in dankclaw/pihole/addn-hosts),
|
||||
# which resolves to this host's IP — so the bind is widened from loopback to
|
||||
# 0.0.0.0:8188. The homepage admin card links to that URL and probes
|
||||
# 127.0.0.1:8188.
|
||||
#
|
||||
# DATA. Models, input, output and custom_nodes live under
|
||||
# /home/dank/comfyui-data (host state, not in the repo).
|
||||
@@ -39,7 +41,7 @@ ContainerName=comfyui
|
||||
Image=localhost/comfyui-rocm:gfx1151
|
||||
Pull=never
|
||||
Network=dankclaw_default
|
||||
PublishPort=127.0.0.1:8188:8188
|
||||
PublishPort=0.0.0.0:8188:8188
|
||||
AddDevice=/dev/kfd
|
||||
AddDevice=/dev/dri
|
||||
GroupAdd=keep-groups
|
||||
@@ -48,7 +50,7 @@ Volume=/home/dank/comfyui-data/input:/opt/ComfyUI/input
|
||||
Volume=/home/dank/comfyui-data/output:/opt/ComfyUI/output
|
||||
Volume=/home/dank/comfyui-data/custom_nodes:/opt/ComfyUI/custom_nodes
|
||||
Volume=/home/dank/serverAppConfigs/dankclaw/comfyui/comfyui-healthcheck.py:/usr/local/bin/comfyui-healthcheck.py:ro
|
||||
Exec=python main.py --listen 0.0.0.0 --port 8188
|
||||
Exec=/opt/ComfyUI/comfyui-start.sh
|
||||
# The probe lives in a file because the podman 5.x v1 generator splits
|
||||
# HealthCmd on spaces into an argv array; an inline `python3 -c "..."` becomes
|
||||
# a puzzle of quoting that fails silently.
|
||||
|
||||
Reference in New Issue
Block a user