14 KiB
Detailed Explanation of Z-Image-Turbo ControlNet Support Fix in ComfyUI-QwenImageLoraLoader
Reason for Additional Fix
The unofficial Z-Image-Turbo loader (ComfyUI-nunchaku-unofficial-z-image-turbo-loader) monkey-patched NunchakuZImageTransformer2DModel.forward to accept transformer_options and control parameters.
However, the ComfyZImageTurboWrapper class in ComfyUI-QwenImageLoraLoader was not correctly passing these parameters to the underlying NunchakuZImageTransformer2DModel.forward.
ComfyZImageTurboWrapper is a wrapper that separates LoRA composition and forward pass, passing arguments received in the forward method to NunchakuZImageTransformer2DModel.forward via the _execute_model method. We needed to add transformer_options and control to this transmission path.
Modified File
ComfyUI-QwenImageLoraLoader/wrappers/zimageturbo.py
Fix Details
Fix 1: forward Method Signature (Lines 76-85)
Before
def forward(
self,
x,
timestep,
context=None,
y=None,
guidance=None,
**kwargs,
):
After
def forward(
self,
x,
timestep,
context=None,
y=None,
guidance=None,
control=None,
transformer_options={},
**kwargs,
):
Explanation
Added control and transformer_options to the forward method signature to receive them from ComfyUI's ModelPatcher.
Fix 2: transformer_options Merge Processing in forward Method (Lines 93-102)
Fix Content
# Remove guidance, transformer_options, and attention_mask from kwargs
if "guidance" in kwargs:
kwargs.pop("guidance")
if "transformer_options" in kwargs:
if isinstance(transformer_options, dict) and isinstance(kwargs["transformer_options"], dict):
transformer_options = {**transformer_options, **kwargs.pop("transformer_options")}
else:
kwargs.pop("transformer_options")
if "attention_mask" in kwargs:
kwargs.pop("attention_mask")
Explanation
ComfyUI's ModelPatcher can pass parameters either as explicit arguments or via **kwargs. To handle both cases correctly:
- If
transformer_optionsis passed both as an explicit parameter and inkwargs, merge them with the explicit parameter taking precedence (dictionary spread operator{**transformer_options, **kwargs["transformer_options"]}ensures explicit parameter values override kwargs values) - Remove
guidanceandattention_maskfromkwargsto prevent duplicate parameter errors, as these are handled separately - This merging strategy ensures compatibility with different ComfyUI versions that may pass parameters in different ways
Fix 3: Argument Passing to _execute_model Method (Lines 219, 221)
Fix Content
Modified calls to _execute_model within the forward method to pass control and transformer_options:
with cache_context(self._cache_context):
out = self._execute_model(x, timestep, context, y, guidance, control, transformer_options, **kwargs)
else:
out = self._execute_model(x, timestep, context, y, guidance, control, transformer_options, **kwargs)
Explanation
By passing control and transformer_options to _execute_model, they can be transmitted to the underlying NunchakuZImageTransformer2DModel.forward.
Fix 4: _execute_model Method Signature and Parameter Cleanup (Line 239-247)
Before
def _execute_model(self, x, timestep, context, y, guidance, **kwargs):
After
def _execute_model(self, x, timestep, context, y, guidance, control, transformer_options, **kwargs):
"""Helper function to run the Z-Image-Turbo model's forward pass."""
# Get ref_latents from kwargs before removing it
ref_latents_value = kwargs.pop("ref_latents", None)
# Remove guidance, transformer_options, and attention_mask from kwargs
kwargs.pop("guidance", None)
kwargs.pop("transformer_options", None)
kwargs.pop("attention_mask", None)
...
Explanation
- Added
controlandtransformer_optionsas explicit parameters to the_execute_modelsignature to match theforwardmethod's parameter list - Extract
ref_latentsfromkwargsearly (line 242) before cleaningkwargs, asref_latentsis needed later in bothcustomized_forwardand direct forward paths - Remove
guidance,transformer_options, andattention_maskfromkwargsto prevent duplicate argument errors when passing**kwargsto downstream methods - This cleanup ensures that these parameters are passed explicitly where needed, rather than through
**kwargs, providing better control and preventing conflicts
Fix 5: Transmission When Using customized_forward (Lines 282-300)
Fix Content
Processing when customized_forward is set (when using the custom forward from the unofficial ZIT loader):
if self.customized_forward:
with torch.inference_mode():
forward_kwargs_cleaned = {k: v for k, v in self.forward_kwargs.items() if k not in ("guidance", "ref_latents", "transformer_options", "attention_mask")}
transformer_options_cleaned = dict(transformer_options) if transformer_options else {}
transformer_options_cleaned.pop("guidance", None)
transformer_options_cleaned.pop("ref_latents", None)
return self.customized_forward(
self.model,
hidden_states=x,
encoder_hidden_states=context,
timestep=timestep,
guidance=guidance if self.config.get("guidance_embed", False) else None,
ref_latents=ref_latents_value,
control=control,
transformer_options=transformer_options_cleaned,
**forward_kwargs_cleaned,
**kwargs,
)
Explanation
Pass control and transformer_options when calling customized_forward. Remove guidance and ref_latents from transformer_options (to avoid duplication).
Additional Details:
-
forward_kwargs cleaning (line 134): The
self.forward_kwargsdictionary may contain parameters that conflict with explicit arguments passed tocustomized_forward. We filter outguidance,ref_latents,transformer_options, andattention_maskto prevent duplicate argument errors. -
transformer_options cleaning (lines 135-137): Create a copy of
transformer_optionsto avoid modifying the original dictionary, then removeguidanceandref_latentsif they exist. This prevents duplicate parameter passing, asguidanceis passed explicitly based onself.config.get("guidance_embed", False), andref_latentsis extracted fromkwargsearlier and passed asref_latents_value. -
ref_latents handling: The
ref_latents_valuewas extracted fromkwargsin_execute_model(line 242) before cleaningkwargs. It is passed explicitly tocustomized_forwardrather than throughtransformer_options, providing clearer separation of concerns. -
guidance conditional passing:
guidanceis only passed tocustomized_forwardifself.config.get("guidance_embed", False)isTrue, allowing for conditional guidance embedding support.
Fix 6: Transmission When Directly Calling forward (Lines 389-409)
Fix Content
Processing when customized_forward is not set (when directly calling NunchakuZImageTransformer2DModel.forward):
# Call Z-Image-Turbo forward with correct signature
# Pass control and transformer_options to allow Model Patcher (double_block patches) to work
# Check if the model's forward method accepts control parameter
import inspect
forward_sig = inspect.signature(self.model.forward)
forward_params = set(forward_sig.parameters.keys())
zimage_kwargs_clean = {k: v for k, v in zimage_kwargs.items() if k not in ('control', 'transformer_options')}
# Build kwargs based on what the forward method accepts
forward_kwargs = zimage_kwargs_clean.copy()
if 'control' in forward_params:
forward_kwargs['control'] = control
if 'transformer_options' in forward_params:
forward_kwargs['transformer_options'] = transformer_options
model_output = self.model(
x_list,
t_zimage,
cap_feats=cap_feats,
**forward_kwargs
)
Explanation
- Check the signature of
self.model.forwardusinginspect.signature - Add
controlandtransformer_optionstoforward_kwargsonly if they are accepted - This allows the
_patched_forward(monkey-patched forward method) from the unofficial ZIT loader to correctly receivetransformer_optionsandcontrol
Additional Details:
-
zimage_kwargs preparation (lines 365-371, not shown in snippet): Before the dynamic parameter check,
zimage_kwargsis prepared with Z-Image-Turbo specific parameters likepatch_size,f_patch_size, andreturn_dict=False. These are extracted fromkwargsand prepared separately. -
zimage_kwargs_clean (line 171): Remove
controlandtransformer_optionsfromzimage_kwargsif they were accidentally included, ensuring they are only passed through the dynamic parameter checking mechanism. -
Dynamic parameter inspection (lines 391-393):
inspect.signature(self.model.forward)retrieves the signature of the model's forward methodforward_params = set(forward_sig.parameters.keys())creates a set of parameter names for fast lookup- This checks the actual signature at runtime, not the static type definition
-
Conditional parameter addition (lines 399-402): Only add
controlandtransformer_optionstoforward_kwargsif they are accepted by the forward method. This is crucial because:- The original
NunchakuZImageTransformer2DModel.forwarddoes not accept these parameters - The unofficial ZIT loader monkey-patches the forward method to add these parameters
- This check ensures compatibility whether the monkey-patch is applied or not
- The original
-
Model call structure (lines 404-409): The model is called with positional arguments (
x_list,t_zimage,cap_feats) followed by**forward_kwargs, which includescontrolandtransformer_optionsif accepted. This matches the expected signature pattern for Z-Image-Turbo models. -
Why inspect.signature is necessary: Without dynamic parameter checking, passing
controlortransformer_optionsto a forward method that doesn't accept them would raise aTypeError: forward() got an unexpected keyword argument. Usinginspect.signatureallows the code to adapt to the actual method signature at runtime, supporting both patched and unpatched versions of the model.
Key Design Decisions
Dynamic Parameter Checking with inspect.signature
Using inspect.signature to dynamically check whether the forward method accepts control and transformer_options. This ensures:
- Backward compatibility is maintained (no errors in older versions that don't accept these parameters)
- Works appropriately regardless of whether the monkey patch from the unofficial ZIT loader is applied
- Easy to adapt to future extensions
Integration with Unofficial ZIT Loader
This fix enables the following integration flow:
- ComfyUI's ModelPatcher passes
transformer_optionsandcontroltoComfyZImageTurboWrapper.forward ComfyZImageTurboWrapper.forwardpasses these to_execute_model_execute_modelchecks withinspect.signaturebefore passing toNunchakuZImageTransformer2DModel.forward- The
_patched_forward(monkey-patched forward) from the unofficial ZIT loader receivestransformer_optionsandcontroland applies thedouble_blockpatch
Summary
Modified the ComfyZImageTurboWrapper class in ComfyUI-QwenImageLoraLoader to correctly pass control and transformer_options parameters.
Main changes:
- Added
controlandtransformer_optionsto theforwardmethod signature - Added
controlandtransformer_optionsto the_execute_modelmethod signature - Implemented using
inspect.signatureto pass these parameters only if the underlyingforwardmethod accepts them
This enables the ControlNet support monkey patch added in the unofficial ZIT loader to work correctly, achieving compatibility between ControlNet and LoRA.
Technical Flow Diagram
The complete parameter transmission flow:
ComfyUI ModelPatcher
↓ (passes control, transformer_options)
ComfyZImageTurboWrapper.forward()
↓ (merges transformer_options if in kwargs, removes from kwargs)
↓ (passes control, transformer_options as explicit parameters)
ComfyZImageTurboWrapper._execute_model()
↓ (extracts ref_latents from kwargs, cleans kwargs)
↓
├─→ customized_forward path (if self.customized_forward is set)
│ ↓ (cleans forward_kwargs and transformer_options)
│ ↓ (passes control, transformer_options explicitly)
│ Custom Forward Function (from unofficial ZIT loader)
│ ↓ (receives control, transformer_options)
│ NunchakuZImageTransformer2DModel.forward() (patched)
│ ↓ (applies double_block patches using transformer_options and control)
│
└─→ Direct forward path (if customized_forward is None)
↓ (prepares zimage_kwargs, uses inspect.signature)
↓ (conditionally adds control, transformer_options to forward_kwargs)
NunchakuZImageTransformer2DModel.forward() (patched or unpatched)
↓ (if patched: receives control, transformer_options, applies double_block patches)
↓ (if unpatched: ignores extra parameters, works normally)
Parameter Cleanup Strategy
The fix implements a comprehensive parameter cleanup strategy to prevent duplicate argument errors:
- forward method level (lines 93-102): Merge
transformer_optionsfrom kwargs with explicit parameter, remove conflicting parameters from kwargs - _execute_model level (lines 241-247): Extract
ref_latentsearly, removeguidance,transformer_options, andattention_maskfrom kwargs - customized_forward path (lines 284-287): Clean
forward_kwargsandtransformer_optionsto remove duplicates before passing to customized forward - Direct forward path (lines 395-402): Use dynamic parameter checking to only pass
controlandtransformer_optionsif accepted
This multi-level cleanup ensures that parameters are passed explicitly where needed and not duplicated through **kwargs, preventing runtime errors and ensuring correct parameter precedence.