fix: auto-disable rotary compat when upstream restores apply_rotary_emb
This commit is contained in:
@@ -12,7 +12,18 @@ This document records the error, root cause, modified files, full patched code,
|
||||
| Python | 3.13 (embedded) |
|
||||
| PyTorch | 2.12.0+cu132 |
|
||||
|
||||
**Design constraint:** Do **not** edit `ComfyUI-nunchaku` upstream files. Apply the shim only from **ComfyUI-QwenImageLoraLoader**.
|
||||
**Design constraint:** Do **not** edit `ComfyUI-nunchaku` upstream files. Apply the shim only from **ComfyUI-QwenImageLoraLoader**, and **only when still needed**.
|
||||
|
||||
The compat layer is **conditional and upstream-safe**:
|
||||
|
||||
| Condition | Action |
|
||||
|-----------|--------|
|
||||
| ComfyUI already exports `apply_rotary_emb` (native restore) | **Skip** — never overwrite |
|
||||
| ComfyUI-nunchaku `models/qwenimage.py` no longer imports `apply_rotary_emb` | **Skip** — upstream fixed the import |
|
||||
| Symbol missing **and** nunchaku still imports it | **Apply** — alias `apply_rope1`, tagged `_qwen_lora_loader_rotary_shim` |
|
||||
| `QWENIMAGE_ROTARY_COMPAT=0` (or `false` / `off`) | **Skip** — manual opt-out |
|
||||
|
||||
When upstream fixes land, the shim stops patching automatically; no nunchaku file edits and no permanent module mutation.
|
||||
|
||||
---
|
||||
|
||||
@@ -194,40 +205,15 @@ except Exception:
|
||||
logger.exception("ComfyUI-QwenImageLoraLoader prestartup: apply_rotary_emb compat failed")
|
||||
```
|
||||
|
||||
### 4.2 Added to `patches/nunchaku_patch.py` — module flag and compat function
|
||||
### 4.2 Added to `patches/nunchaku_patch.py` — conditional compat function
|
||||
|
||||
```python
|
||||
_qwen_apply_rotary_emb_compat_applied: bool = False
|
||||
See `patches/nunchaku_patch.py` for the full implementation. Summary:
|
||||
|
||||
|
||||
def apply_qwen_image_apply_rotary_emb_compat() -> bool:
|
||||
"""
|
||||
ComfyUI >= 0.24 removed comfy.ldm.qwen_image.model.apply_rotary_emb.
|
||||
ComfyUI-nunchaku still imports it; alias to comfy.ldm.flux.math.apply_rope1.
|
||||
"""
|
||||
global _qwen_apply_rotary_emb_compat_applied
|
||||
if _qwen_apply_rotary_emb_compat_applied:
|
||||
return True
|
||||
|
||||
try:
|
||||
import comfy.ldm.qwen_image.model as qwen_image_model
|
||||
from comfy.ldm.flux.math import apply_rope1
|
||||
|
||||
if hasattr(qwen_image_model, "apply_rotary_emb"):
|
||||
_qwen_apply_rotary_emb_compat_applied = True
|
||||
return False
|
||||
|
||||
qwen_image_model.apply_rotary_emb = apply_rope1
|
||||
_qwen_apply_rotary_emb_compat_applied = True
|
||||
logger.info(
|
||||
"Patched comfy.ldm.qwen_image.model.apply_rotary_emb -> apply_rope1 "
|
||||
"(ComfyUI-nunchaku Qwen Image compat)"
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error("Failed to apply apply_rotary_emb compat patch: %s", e)
|
||||
return False
|
||||
```
|
||||
1. **Probe** sibling `ComfyUI-nunchaku/models/qwenimage.py` for `from comfy.ldm.qwen_image.model import ... apply_rotary_emb`.
|
||||
2. **Skip** if ComfyUI already exports native `apply_rotary_emb` (not our tagged shim).
|
||||
3. **Skip** if nunchaku no longer imports `apply_rotary_emb`.
|
||||
4. **Apply** only when the symbol is missing and nunchaku still needs it — assign `apply_rope1` with `_qwen_lora_loader_rotary_shim` marker.
|
||||
5. **Opt-out** via env `QWENIMAGE_ROTARY_COMPAT=0`.
|
||||
|
||||
### 4.3 Modified in `patches/nunchaku_patch.py` — `apply_nunchaku_patch()` entry and return
|
||||
|
||||
@@ -310,25 +296,26 @@ sequenceDiagram
|
||||
|
||||
### 5.2 What the shim does (step by step)
|
||||
|
||||
1. Import `comfy.ldm.qwen_image.model` as a module object (not only symbols from `__init__` if any).
|
||||
2. If `apply_rotary_emb` **already exists** (future ComfyUI or another patcher), do nothing and return `False`.
|
||||
3. Otherwise assign `qwen_image_model.apply_rotary_emb = apply_rope1`.
|
||||
4. Python’s `from comfy.ldm.qwen_image.model import apply_rotary_emb` binds the name at import time to whatever attribute exists on the module **at that moment**. Injecting the attribute on the module **before** nunchaku’s import is sufficient.
|
||||
1. Import `comfy.ldm.qwen_image.model` as a module object.
|
||||
2. If our tagged shim is already on the module, return `True` (idempotent).
|
||||
3. If ComfyUI **natively** exports `apply_rotary_emb`, log skip and return `False` (never overwrite).
|
||||
4. Read `ComfyUI-nunchaku/models/qwenimage.py`; if it no longer imports `apply_rotary_emb`, log skip and return `False`.
|
||||
5. Only if the symbol is still missing and nunchaku still imports it: assign `apply_rope1` with shim tag.
|
||||
6. Python’s `from comfy.ldm.qwen_image.model import apply_rotary_emb` binds at import time; prestartup injection remains sufficient when step 5 runs.
|
||||
|
||||
### 5.3 Idempotency and double execution
|
||||
|
||||
- `prestartup_script.py` loads `nunchaku_patch.py` via `importlib` under a **unique module name** (`comfyui_qwenimageloraloader_nunchaku_patch_prestartup`).
|
||||
- Later, `__init__.py` imports `patches.nunchaku_patch` as a normal package submodule (second module object, separate `_qwen_apply_rotary_emb_compat_applied` flag).
|
||||
- That is acceptable: the **real effect** is on `comfy.ldm.qwen_image.model`, which is a singleton module. The second call sees `hasattr(qwen_image_model, "apply_rotary_emb")` as true and skips re-patching.
|
||||
- That is acceptable: the **real effect** is on `comfy.ldm.qwen_image.model`, which is a singleton module. The second call detects the tagged shim (or native export) and does not re-patch.
|
||||
- Startup logs therefore show the INFO lines **once** (from prestartup), not twice.
|
||||
|
||||
### 5.4 Return values of `apply_qwen_image_apply_rotary_emb_compat()`
|
||||
|
||||
| Return | Meaning |
|
||||
|--------|---------|
|
||||
| `True` | Shim was applied in this call (attribute was missing and was added). |
|
||||
| `False` | Shim not needed (`apply_rotary_emb` already present) or apply failed (logged as error). |
|
||||
| `True` (immediate) | Already applied in **this** module instance’s prior call (`_qwen_apply_rotary_emb_compat_applied` flag). |
|
||||
| `True` | Shim applied this call, or already applied in this module instance (flag / tagged shim on singleton). |
|
||||
| `False` | Skipped: native ComfyUI export, nunchaku no longer imports `apply_rotary_emb`, env disabled, or apply failed (error logged). |
|
||||
|
||||
### 5.5 Risk assessment
|
||||
|
||||
@@ -336,7 +323,7 @@ sequenceDiagram
|
||||
|-------|------------|
|
||||
| Correctness vs ComfyUI 0.24 native path | **Aligned** — same function the core model uses (`apply_rope1`). |
|
||||
| Effect on other custom nodes | **Low** — only adds a name on `comfy.ldm.qwen_image.model` if missing; does not replace ComfyUI core files. |
|
||||
| Upstream nunchaku fix | Eventually nunchaku may switch imports to `apply_rope1`; then `hasattr` branch skips shim (harmless). |
|
||||
| Upstream nunchaku fix | Probe of `models/qwenimage.py` detects removed import → shim skipped automatically. ComfyUI native restore → also skipped without overwrite. |
|
||||
| Performance | **None** — alias only, no extra wrapper. |
|
||||
|
||||
### 5.6 Unrelated warning: `nunchaku_versions.json`
|
||||
@@ -385,7 +372,7 @@ Run a minimal Qwen Image + Nunchaku workflow (load `NunchakuQwenImageDiTLoader`,
|
||||
2. Revert changes in `patches/nunchaku_patch.py` (remove `apply_qwen_image_apply_rotary_emb_compat` and its call in `apply_nunchaku_patch`).
|
||||
3. Restart ComfyUI.
|
||||
|
||||
Alternatively, upgrade **ComfyUI-nunchaku** to a release that imports `apply_rope1` directly (when upstream publishes one), then remove this shim.
|
||||
Alternatively, upgrade **ComfyUI-nunchaku** to a release that no longer imports `apply_rotary_emb` (or upgrade ComfyUI if it restores the symbol). The shim should **skip by itself**; you can remove `prestartup_script.py` / compat code later for a cleaner tree, or set `QWENIMAGE_ROTARY_COMPAT=0` to force-disable.
|
||||
|
||||
---
|
||||
|
||||
@@ -395,7 +382,7 @@ Alternatively, upgrade **ComfyUI-nunchaku** to a release that imports `apply_rop
|
||||
|----------|--------|
|
||||
| What broke? | `ImportError: cannot import name 'apply_rotary_emb'` during ComfyUI-nunchaku load. |
|
||||
| Why? | ComfyUI 0.24 removed `apply_rotary_emb`; nunchaku still imports it; LoraLoader `__init__` runs too late. |
|
||||
| Fix? | Early `prestartup_script.py` + `apply_rope1` alias on `comfy.ldm.qwen_image.model`; duplicate guard in `apply_nunchaku_patch()`. |
|
||||
| Fix? | Early `prestartup_script.py` + **conditional** `apply_rope1` alias (only when nunchaku still imports missing symbol); auto-skip when ComfyUI or nunchaku upstream fixes land. |
|
||||
| Files? | `prestartup_script.py` (new), `patches/nunchaku_patch.py` (modified). |
|
||||
| nunchaku touched? | **No.** |
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
from typing import Optional, Tuple
|
||||
from typing import Callable, Optional, Tuple
|
||||
|
||||
import torch
|
||||
|
||||
@@ -11,25 +13,112 @@ logger = logging.getLogger(__name__)
|
||||
_svdq_from_linear_patched: bool = False
|
||||
_qwen_apply_rotary_emb_compat_applied: bool = False
|
||||
|
||||
_ROTARY_SHIM_TAG = "_qwen_lora_loader_rotary_shim"
|
||||
_NUNCHAKU_QWENIMAGE_APPLY_ROTARY_PATTERNS = (
|
||||
re.compile(
|
||||
r"from\s+comfy\.ldm\.qwen_image\.model\s+import\s+\([^)]*\bapply_rotary_emb\b",
|
||||
re.MULTILINE | re.DOTALL,
|
||||
),
|
||||
re.compile(
|
||||
r"from\s+comfy\.ldm\.qwen_image\.model\s+import\s+[^\n#]*\bapply_rotary_emb\b",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _rotary_compat_disabled_by_env() -> bool:
|
||||
value = os.environ.get("QWENIMAGE_ROTARY_COMPAT", "").strip().lower()
|
||||
return value in ("0", "false", "no", "off", "disable", "disabled")
|
||||
|
||||
|
||||
def _mark_rotary_shim(fn: Callable) -> Callable:
|
||||
setattr(fn, _ROTARY_SHIM_TAG, True)
|
||||
return fn
|
||||
|
||||
|
||||
def _rotary_shim_installed_on(qwen_image_model) -> bool:
|
||||
fn = getattr(qwen_image_model, "apply_rotary_emb", None)
|
||||
return fn is not None and getattr(fn, _ROTARY_SHIM_TAG, False)
|
||||
|
||||
|
||||
def _comfyui_has_native_apply_rotary_emb(qwen_image_model) -> bool:
|
||||
return (
|
||||
getattr(qwen_image_model, "apply_rotary_emb", None) is not None
|
||||
and not _rotary_shim_installed_on(qwen_image_model)
|
||||
)
|
||||
|
||||
|
||||
def _find_nunchaku_qwenimage_py() -> Optional[str]:
|
||||
custom_nodes = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
|
||||
)
|
||||
for folder in ("ComfyUI-nunchaku", "ComfyUI_Nunchaku", "comfyui-nunchaku"):
|
||||
path = os.path.join(custom_nodes, folder, "models", "qwenimage.py")
|
||||
if os.path.isfile(path):
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
def _nunchaku_qwenimage_still_imports_apply_rotary_emb() -> Optional[bool]:
|
||||
"""
|
||||
True: nunchaku still imports apply_rotary_emb (compat may be needed).
|
||||
False: nunchaku source no longer imports it (skip shim).
|
||||
None: qwenimage.py not found (apply only if symbol missing).
|
||||
"""
|
||||
path = _find_nunchaku_qwenimage_py()
|
||||
if path is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
with open(path, encoding="utf-8", errors="replace") as handle:
|
||||
text = handle.read()
|
||||
except OSError as exc:
|
||||
logger.debug("Could not read %s for rotary compat probe: %s", path, exc)
|
||||
return None
|
||||
|
||||
for pattern in _NUNCHAKU_QWENIMAGE_APPLY_ROTARY_PATTERNS:
|
||||
if pattern.search(text):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def apply_qwen_image_apply_rotary_emb_compat() -> bool:
|
||||
"""
|
||||
ComfyUI >= 0.24 removed comfy.ldm.qwen_image.model.apply_rotary_emb.
|
||||
ComfyUI-nunchaku still imports it; alias to comfy.ldm.flux.math.apply_rope1.
|
||||
ComfyUI-nunchaku still imports it; alias to comfy.ldm.flux.math.apply_rope1
|
||||
only when the import is still required and ComfyUI has not restored the symbol.
|
||||
"""
|
||||
global _qwen_apply_rotary_emb_compat_applied
|
||||
if _qwen_apply_rotary_emb_compat_applied:
|
||||
return True
|
||||
|
||||
if _rotary_compat_disabled_by_env():
|
||||
logger.info(
|
||||
"apply_rotary_emb compat skipped (QWENIMAGE_ROTARY_COMPAT is disabled)"
|
||||
)
|
||||
return False
|
||||
|
||||
try:
|
||||
import comfy.ldm.qwen_image.model as qwen_image_model
|
||||
from comfy.ldm.flux.math import apply_rope1
|
||||
|
||||
if hasattr(qwen_image_model, "apply_rotary_emb"):
|
||||
if _rotary_shim_installed_on(qwen_image_model):
|
||||
_qwen_apply_rotary_emb_compat_applied = True
|
||||
return True
|
||||
|
||||
if _comfyui_has_native_apply_rotary_emb(qwen_image_model):
|
||||
logger.info(
|
||||
"apply_rotary_emb compat skipped: ComfyUI already exports apply_rotary_emb"
|
||||
)
|
||||
return False
|
||||
|
||||
qwen_image_model.apply_rotary_emb = apply_rope1
|
||||
nunchaku_needs = _nunchaku_qwenimage_still_imports_apply_rotary_emb()
|
||||
if nunchaku_needs is False:
|
||||
logger.info(
|
||||
"apply_rotary_emb compat skipped: ComfyUI-nunchaku no longer imports apply_rotary_emb"
|
||||
)
|
||||
return False
|
||||
|
||||
qwen_image_model.apply_rotary_emb = _mark_rotary_shim(apply_rope1)
|
||||
_qwen_apply_rotary_emb_compat_applied = True
|
||||
logger.info(
|
||||
"Patched comfy.ldm.qwen_image.model.apply_rotary_emb -> apply_rope1 "
|
||||
|
||||
Reference in New Issue
Block a user