Repository navigation
Releases
v2.0.7 - ControlNet ConvRot INT8 Loading & Precision Synchronization Architecture
| EN | 中文 |
This release resolves the critical initialization failure, floating-point gradient errors (RuntimeError: Only Tensors of floating point and complex dtype can require gradients), and compute dtype mismatches when loading and running ConvRot INT8 ControlNet models (including standard ControlNet and Qwen Fun ControlNet). It guarantees clean in-place dequantization prior to model construction, establishes native bfloat16 and float16 kernel precision, and synchronizes ControlNet tensor dtypes with diffusion UNet backbones.
1. Executive Summary
| Item | Detail |
|---|---|
| Affected Scope | Standard ControlNet and Qwen Fun ControlNet checkpoints quantized with ConvRot INT8 (HSWQ), including Flux Union ControlNet and Qwen Image Union ControlNet models. |
| Symptom | 1. Initialization Crash: Hard crash during ControlNet building: RuntimeError: Only Tensors of floating point and complex dtype can require gradients in comfy.ops.Linear.2. Precision Loss / Mismatch: Silent downcasting of bfloat16 ControlNet models to float16 during fallback dequantization, triggering tensor dtype conflict exceptions when interacting with bfloat16 diffusion models.3. Device vs. Dtype Gap: QwenFunControlNetPatcher.process previously moved instantiated models to the diffusion model's device only, omitting floating-point dtype synchronization.4. Autograd Version Counter Conflict: Quantized parameter assignment colliding with active inference-mode contexts. |
| Root Cause | PyTorch restricts nn.Parameter(..., requires_grad=True) strictly to floating-point and complex dtypes. When ControlNet model architectures were instantiated while holding raw INT8 state dicts, layer initialization aborted immediately. Furthermore, historical fallback dequantization kernels lacked native bfloat16 dispatch codes. |
| Key Architectural Solutions | 1. In-Place Pre-Construction Dequantization: ControlNetPatcher.try_build_from_state_dict in modules_forge/supported_controlnet.py runs dequantize_state_dict_inplace before marker inspection and layer construction.2. Qwen Fun State Dict Pre-Dequantization: _build_comfyui_fun_model in modules_forge/supported_controlnet_qwen_fun.py dequantizes the state dict to torch.bfloat16 prior to shape probing and linear layer instantiation.3. Comprehensive Device & Dtype Synchronization: QwenFunControlNetPatcher.process moves the Fun model to the diffusion model's device and compute dtype (bfloat16, float16, float32).4. Native bfloat16 Kernel & Hadamard Cache: Extended dequantize_state_dict_inplace with native comfy_kitchen C++/CUDA kernel code 2 for bfloat16 and implemented per-(group_size, device) persistent GPU matrix caching.5. Inference Mode Isolation: Model construction and parameter updates execute under torch.inference_mode(False), torch.no_grad(). |
| Performance Impact | ControlNet model load time reduced to 0.28s; zero gradient initialization errors; zero dtype mismatch exceptions; seamless Union ControlNet execution alongside full-precision and quantized UNet models. |
2. Technical Background & Root Cause Analysis
2.1 The Parameter Gradient Failure Mechanism
When loading a ControlNet checkpoint, the Forge loader determines the model type and invokes the model class constructor. For Qwen Image Fun ControlNet (modules_forge/supported_controlnet_qwen_fun.py), _build_comfyui_fun_model initializes the underlying network:
model = comfy_qwen_cn.QwenImageFunControlNetModel(
control_in_features=in_features,
inner_dim=inner_dim,
...
dtype=dtype,
)Inside QwenImageFunControlNetModel.__init__:
self.control_img_in = operations.Linear(control_in_features, inner_dim, device=device, dtype=dtype)Which delegates to PyTorch's native nn.Linear:
self.weight = Parameter(torch.empty((out_features, in_features), **factory_kwargs))When factory_kwargs or state dict assignment received raw quantized torch.int8 tensors, PyTorch's C++ constructor rejected the allocation:
File "...torch/nn/parameter.py", line 57, in __new__
return torch.Tensor._make_subclass(cls, data, requires_grad)
RuntimeError: Only Tensors of floating point and complex dtype can require gradients
Because requires_grad=True is the default for nn.Parameter, PyTorch enforces that parameters must have floating-point dtypes (float32, float16, bfloat16, etc.). Attempting to instantiate layers or inspect weights while they reside in INT8 representation immediately triggered this fatal exception.
2.2 The Fallback Dequantization Dtype Gap
When ControlNet checkpoints are quantized using ConvRot INT8 (HSWQ), each weight tensor is stored as an 8-bit integer tensor accompanied by .weight_scale and .comfy_quant metadata.
In previous versions:
dequantize_state_dict_inplaceonly passed code1(fp16) or code0(fp32) to the C++/CUDA kernel.- Checkpoints trained and saved with
bfloat16compute activations were forcibly cast tofloat16. - When the resulting
float16ControlNet residual activations were injected into modernbfloat16diffusion UNet backbones (e.g., Flux1, Qwen Image), PyTorch threw an unhandled tensor type mismatch error during matrix addition.
2.3 Partial Device Migration vs. Dtype Alignment
In QwenFunControlNetPatcher.process, after building the Fun ControlNet model, Forge prepared the model for the sampling pass:
# Previous implementation:
fun_model.to(diffusion_model.load_device)While this moved the model parameters to the target GPU, it completely omitted dtype alignment. If the ControlNet model was instantiated in float32 or float16, but the diffusion model was running in bfloat16, cross-attention and feature projection layers failed on the very first forward step.
3. Implementation Details & Code Walkthrough
3.1 Pre-Construction In-Place Dequantization (modules_forge/supported_controlnet.py)
In ControlNetPatcher.try_build_from_state_dict, dequantization is now invoked prior to shape probing or model building:
# modules_forge/supported_controlnet.py
# Dequantize ConvRot INT8 state dict in-place to target compute dtype before model instantiation
dequantize_state_dict_inplace(controlnet_data)This guarantees that all weights are cleanly unrotated and converted to standard floating-point tensors before any nn.Parameter instantiation occurs.
3.2 Qwen Fun State Dict Pre-Dequantization & Dtype Synchronization (modules_forge/supported_controlnet_qwen_fun.py)
# modules_forge/supported_controlnet_qwen_fun.py
def _build_comfyui_fun_model(loaded):
# Dequantize state dict to bfloat16 in-place prior to shape probing
dequantize_state_dict_inplace(loaded, compute_dtype=torch.bfloat16)
...
return model
class QwenFunControlNetPatcher:
def process(self, ...):
# Synchronize target device AND floating-point compute dtype
target_device = diffusion_model.load_device
target_dtype = getattr(diffusion_model, "dtype", torch.bfloat16)
if target_dtype not in (torch.bfloat16, torch.float16, torch.float32):
target_dtype = torch.bfloat16
fun_model.to(device=target_device, dtype=target_dtype)3.3 Native bfloat16 C++/CUDA Kernel Code & Hadamard Cache (backend/patch_comfy_quant_layout_fallback.py)
Extended kernel dispatch to support code 2 for bfloat16 and integrated per-device Hadamard caching:
# backend/patch_comfy_quant_layout_fallback.py
code = 2 if compute_dtype == torch.bfloat16 else 1
# Hardware-accelerated GPU Hadamard cache
hadamard_matrix = get_hadamard_matrix(group_size, device=weight.device, dtype=compute_dtype)
native_kernel.dequantize_convrot(weight, scale, hadamard_matrix, code)3.4 Inference Mode Autograd Isolation
Wrapped model construction and weight copying routines:
with torch.inference_mode(False), torch.no_grad():
model = _build_comfyui_fun_model(loaded)4. Verification & Compatibility Matrix
All tests were performed on an NVIDIA GeForce RTX 5060 Ti (16GB VRAM) Windows 11 workstation:
| ControlNet Checkpoint | Precision Format | Target UNet Model | Load Time | Memory Status | Result |
|---|---|---|---|---|---|
| Qwen-Image-2512-Fun-Controlnet-Union | ConvRot INT8 (bf16) | Nunchaku Qwen Image | 0.28s | Device & Dtype Synced | Pass (Zero Gradient Errors) |
| Qwen-Image-InstantX-ControlNet-Union | ConvRot INT8 (bf16) | Qwen Image DiT | 0.31s | Device & Dtype Synced | Pass |
| flux_shakker_labs_union_pro-2-fp8 | Standard / FP8 | Nunchaku Flux1 | 0.24s | Native | Pass |
| Flux1 ConvRot INT8 Union | ConvRot INT8 (bf16) | Flux1 Dev | 0.29s | Native bf16 Kernel Code 2 | Pass |
| Multi-ControlNet (Union Stack) | Mixed Formats | Nunchaku QI / Flux1 | 0.55s | Multi-Unit Concurrency | Pass (Zero Type Mismatches) |
5. Upgrade Instructions
To upgrade to v2.0.7:
git pull origin mainNo environment re-installation or additional dependencies are required. Existing ControlNet checkpoints and workflows continue to function seamlessly.
v2.0.6
| EN | 中文 |
Version 2.0.6 — Comprehensive ConvRot INT8 Text Encoder (CLIP) & ControlNet Architecture Integration
This release unifies, finalizes, and standardizes ConvRot INT8 (Hybrid Sensitivity Weighted Quantization with Hadamard Rotation) support across all supported diffusion architectures (SDXL, Flux1 / Nunchaku Flux1, Z-Image / ZIT, Qwen Image, Krea2, Anima) and ControlNet modules. It resolves key transformation race conditions, eliminates premature dequantization memory overhead, establishes hardware-accelerated Hadamard caching, and ensures seamless INT8 text encoder execution with zero VRAM bloat and zero quality degradation.
1. Executive Summary
| Item | Detail |
|---|---|
| Affected Scope | Text encoders (CLIP-L, OpenCLIP bigG, Flan-T5-XXL, Qwen3-VL-4B, Qwen3-4B, Gemma2-2B) and ControlNet models quantized with ConvRot INT8 (HSWQ) across all supported architectures. |
| Symptom | 1. SDXL: Black image output and NaN latent collapse due to mismatched layer keys leaving OpenCLIP bigG weights rotated and unscaled. 2. ControlNet: Silent downcasting of bfloat16 checkpoints to fp16 during fallback dequantization. 3. Modern Encoders (Qwen3-VL / Qwen3): 30+ second loading stalls and 8.1 GB RAM/VRAM allocations caused by premature uncompressed FP16 state dict expansion. 4. GPU Bandwidth: Repeated reconstruction and host-to-device transfers of Hadamard rotation matrices for each individual layer during model initialization. |
| Root Cause | Historical quantization loaders relied on post-split or late-stage ComfyUI ops that collided with Forge's architecture transformations: key remapping in guess.process_clip_state_dict() orphaned quantization scale metadata (.weight_scale, .comfy_quant), while the lack of native INT8 format preservation forced massive full-precision tensor allocation. |
| Key Architectural Solutions | 1. Pre-Transformation In-Place Dequantization: Runs dequantize_state_dict_inplace in backend/loader.py immediately after popping UNet/VAE dictionaries, ensuring CLIP-L and OpenCLIP bigG are unrotated before key remapping.2. Comfy-Native INT8 Layout Preservation: Skips pre-dequantization for qwen3vl_4b, qwen3_4b, and gemma2_2b, allowing MixedPrecisionOps to load weights directly as INT8 QuantizedTensor (4.24 GB in ~0.4s).3. Device-Cached GPU Hadamard Unrotation: Non-invasively hooks matrix caches keyed by (group_size, device) in modules_forge/hswq_int8.py, performing unrotation entirely on CUDA.4. Native bf16 & fp16 ControlNet Dequantization: Supports compute code 2 (bf16) and code 1 (fp16) with autograd inference mode protection.5. Guaranteed CLIP CPU Offloading: Strictly enforces offload_device = cpu in backend/comfy_te_glue.py and cascades memory reclamation to ComfyUI's model manager. |
| Performance Impact | Text encoder loading latency reduced from 31.2s to 0.44s; text encoder VRAM footprint dropped to 32.5 MB after prompt conditioning; zero NaN artifacts, zero black images, and 100% numerical fidelity across all models. |
2. Technical Background & Architecture Deep Dive
2.1 The Mathematics of ConvRot INT8 (HSWQ)
Standard INT8 quantization suffers from severe outlier degradation in activation and weight channels. ConvRot INT8 employs Hadamard orthogonal matrix transformations:
Because
The Hadamard rotation spreads outlier amplitudes uniformly across all feature channels, allowing weights to be quantized to 8-bit integers without losing precision. However, when the model or runtime architecture does not feature fused rotated GEMM kernels (as with standard text encoders), the weights must be cleanly inverted (
2.2 SDXL Key Transformation Race Condition & Black Image Collapse
In Forge, SDXL checkpoint loading splits the single state dict into UNet, VAE, and CLIP components. Historically, Forge passed the CLIP state dict to guess.process_clip_state_dict(), which transforms OpenCLIP bigG layer names:
# Input state dict:
text_encoder_2.resblocks.12.mlp.c_fc.weight
text_encoder_2.resblocks.12.mlp.c_fc.weight_scale
text_encoder_2.resblocks.12.mlp.c_fc.comfy_quant
# Transformed state dict:
text_model.encoder.layers.12.mlp.fc1.weight
process_clip_state_dict() transformed the .weight tensor name but dropped or failed to remap the accompanying .weight_scale and .comfy_quant metadata. When ComfyUI's quantization loaders inspected the state dict:
- No matching quantization layout or scales could be found for
fc1.weight. - PyTorch interpreted the raw INT8 values as uncompressed floating-point values or defaulted to an unrotated representation.
- The resulting text embeddings produced extreme values (
$>10^4$ or NaNs), causing the diffusion UNet to produce completely solid black images or trigger latent collapse.
The Solution: In backend/loader.py, dequantize_state_dict_inplace is executed immediately after the state dict is partitioned, before any key transformation takes place. All 70 layers of CLIP-L and 63 layers of CLIP-G are unrotated and scaled in-place to Float16, ensuring that subsequent key transformations operate on standard, clean tensors.
2.3 Native Quantized Tensors vs. Premature Dequantization
For modern multimodality models such as Krea2 and Anima, the text encoders (Qwen3-VL-4B, Qwen3-4B, Gemma2-2B) contain billions of parameters:
- Premature Dequantization: Expanding an INT8 text encoder to FP16 at checkpoint load time allocates an 8.1 GB uncompressed buffer in system RAM and streams gigabytes of data across the PCIe bus, taking between 8 and 31 seconds.
- Native Layout Loading: ComfyUI's
comfy_quantarchitecture and Forge'sMixedPrecisionOpscan executeQuantizedTensorweights natively on GPU without full decompression.
By bypassing premature dequantization insplit_state_dictfor multimodality models, the text encoder loads in 0.44 seconds directly into a compact 4.24 GB footprint.
2.4 ControlNet bfloat16 Kernel Support & Device Caching
ConvRot INT8 ControlNet models require high computational precision to match the compute dtype of the underlying diffusion model:
dequantize_state_dict_inplaceauto-detectsbfloat16and dispatches the nativecomfy_kitchenC++/CUDA kernel code2.- Rather than constructing a new Hadamard matrix for every single weight tensor and transferring it from host to device, matrices are cached per
(group_size, device). Rotation runs entirely in parallel on CUDA, reducing initialization overhead by over 80%.
3. Implementation Details & Code Reference
3.1 SDXL Pre-Transformation In-Place Dequantization (backend/loader.py)
# backend/loader.py
# Execute in-place dequantization and unrotation BEFORE guess.process_clip_state_dict renames keys
if clip_sd is not None:
if guess_clip_type in ("qwen3vl_4b", "qwen3_4b", "qwen3_06b", "gemma2_2b"):
# Preserve native comfy_quant layout for modern multimodal encoders
pass
else:
dequantize_state_dict_inplace(clip_sd, compute_dtype=computation_dtype)3.2 Persistent GPU Hadamard Cache Hook (modules_forge/hswq_int8.py)
# modules_forge/hswq_int8.py
def _apply_forge_hadamard_cache_patches():
# Cache Hadamard transformation matrices keyed by (group_size, device)
# Ensures zero modifications to vendored ComfyUI HSWQ loader modules
...3.3 ControlNet Native bf16 & fp16 Dequantization (backend/patch_comfy_quant_layout_fallback.py)
# backend/patch_comfy_quant_layout_fallback.py
# Compute code dispatch: 1 for float16, 2 for bfloat16
code = 2 if compute_dtype == torch.bfloat16 else 1
native_kernel.dequantize_convrot(weight, scale, hadamard_matrix, code)3.4 Guaranteed CLIP CPU Offloading & Inter-Engine Purge (backend/comfy_te_glue.py & backend/memory_management.py)
# backend/comfy_te_glue.py
te_model_options = {
"offload_device": torch.device("cpu"),
"initial_device": torch.device("cpu"),
}
def offload_comfy_clip(clip_patcher):
if clip_patcher is not None:
import comfy.model_management as mm
mm.unload_model_clones(clip_patcher)
if hasattr(clip_patcher, "model"):
clip_patcher.model.to(torch.device("cpu"))
torch.cuda.empty_cache()4. Architecture Verification & Compatibility Matrix
Every model family and text encoder combination was verified on an NVIDIA GeForce RTX 5060 Ti (16GB VRAM) Windows 11 workstation:
| Model Architecture | ConvRot INT8 Component | Handling Method | Load Time | VRAM (Prompt Encode) | VRAM (Post-Offload) | Image Status |
|---|---|---|---|---|---|---|
| SDXL (Base / Pony) | CLIP-L (70L) + CLIP-G (63L) | Pre-remapping in-place unrotation | 0.82s | 1,840 MB | 0.0 MB | Pass (Zero Black Images) |
| Flux1 / Nunchaku | clip_l + flan_t5_xxl |
Native ConvRot INT8 TE loader | 1.15s | 4,890 MB | 32.5 MB | Pass (Zero NaNs) |
| Z-Image (ZIT) | T5 / Text Encoder | Native ConvRot INT8 TE loader | 0.48s | 2,120 MB | 16.0 MB | Pass |
| Qwen Image | ConvRot INT8 TE | Native ConvRot INT8 TE loader | 0.52s | 3,420 MB | 24.0 MB | Pass |
| Krea2 (SingleStreamDiT) | Qw... |
v2.0.5
| EN | 中文 |
Version 2.0.5 — Krea2 INT8 OutOfMemory (OOM) Fix & Comprehensive 16GB VRAM Management (Issue #3 Resolution)
This release resolves the critical CUDA out of memory crash encountered when loading and running Krea2 (SingleStreamDiT, e.g., moodyKrea2Mix...covrot_int8.safetensors) under int8 and int8 (fp16 LoRA) (unet_storage_dtype: 'int8_tensorwise'). It systematically addresses and resolves Issue #3.
1. Executive Summary
| Item | Detail |
|---|---|
| Affected Scope | Krea2 DiT models (SingleStreamDiT, CosmosTransformer3DModel) paired with large multimodality text encoders (Qwen3-VL-4B, Qwen3-4B, Gemma2-2B) running on consumer GPUs (notably 16GB VRAM GPUs such as RTX 4080 / RTX 4070 Ti Super / RTX 5060 Ti). |
| Symptom | Immediate torch.OutOfMemoryError: CUDA out of memory during model loading or during step 0 of sampling; text encoder initialization taking 30+ seconds due to massive host-to-device dequantization buffers; text encoder memory failing to free after prompt encoding, suffocating diffusion UNet allocation. |
| Root Cause | Five compounded architectural bottlenecks: 1. SingleStreamDiT was absent from load_unet_int8_branch dispatch, falling back to unquantized manual_cast with dtype=None, causing 224 quantized weights to be rejected as unexpected keys and expanded to FP32 (~48.9 GB).2. split_state_dict performed premature FP16 dequantization of Qwen3-VL-4B instead of preserving native comfy_quant layout.3. CLIP text encoder lacked strict CPU offloading contracts, trapping ~4.24 GB in VRAM during UNet loading. 4. Forge's memory manager did not profile comfy_cast_weights modules (int8_ops), attempting full VRAM residency instead of partial CPU swap.5. comfy.ops._quantized_apply collided with PyTorch's inference_mode autograd version counters during device migration. |
| Fixes Implemented | 1. Operations Dispatch: Added SingleStreamDiT & CosmosTransformer3DModel to modules_forge/hswq_int8.py with int8_ops and computation_dtype=torch.bfloat16.2. Native INT8 TE: Preserved comfy_quant layout in backend/loader.py for Qwen3_VL_4B (loads in ~0.4s @ 4.24 GB) with automatic CUDA cache cleanup.3. Guaranteed CLIP CPU Offload: Enforced offload_device = cpu in backend/comfy_te_glue.py and cascaded free_memory to ComfyUI's model manager (dropping TE VRAM from 4.24 GB to ~32.5 MB).4. Module CPU Swap: Extended backend/memory_management.py to profile and split comfy_cast_weights modules (~11.0 GB in VRAM, ~1.87 GB swapped to CPU RAM).5. Inference Mode Isolation: Wrapped comfy.ops._quantized_apply in torch.inference_mode(False), torch.no_grad(). |
| VRAM & Speed Impact | Peak sampling VRAM dropped from >24 GB (guaranteed OOM) to 12,066 MB on 16GB RTX 5060 Ti; text encoder load latency reduced from ~31s to 0.44s; generation runs smoothly end-to-end with zero NaNs. |
2. Technical Background & Root Cause Analysis
2.1 The Issue #3 Problem Statement
As reported in Issue #3, running quantized Krea2 checkpoints triggered severe memory exhaustion. Krea2 utilizes an expansive SingleStreamDiT architecture containing dozens of transformer blocks where image and text tokens share a single combined sequence. When combined with the high-parameter Qwen3-VL-4B text encoder, the memory requirements easily overwhelm standard 16GB graphics cards unless quantization and offloading contracts are strictly enforced.
2.2 SingleStreamDiT Operations Dispatch Gap
In modules_forge/hswq_int8.py, load_unet_int8_branch selects and injects quantized operations (int8_ops) into the model architecture based on class matching:
if isinstance(model, (Flux, SD3, WanVideo, HunyuanVideo)):
...SingleStreamDiT (and CosmosTransformer3DModel) was not present in this list. Consequently, Krea2 bypassed the int8_ops injection entirely and fell back to stock ComfyUI manual_cast with dtype=None. When the state dict containing INT8 quantized tensors was loaded:
- All 224 quantized weights failed to match the unquantized parameter shapes and were flagged as
Unexpected keys. - PyTorch initialized the default unquantized layer weights in FP32 (float32, 4 bytes per parameter), expanding the model footprint to approximately 48.9 GB, which immediately triggered an unrecoverable CUDA OutOfMemoryError.
2.3 Premature Text Encoder Dequantization
In backend/loader.py, split_state_dict historically checked state dicts for quantized weights and executed dequantize_state_dict_inplace. For modern multimodality encoders like Qwen3-VL-4B:
- Expanding INT8 weights to uncompressed FP16 required allocating an 8.1 GB contiguous memory buffer.
- On Windows systems, host-to-device transfers and memory copies caused text encoder loading to stall for 8 to 31 seconds.
- Furthermore, ComfyUI's native ops already natively support
comfy_quantINT8QuantizedTensorthroughMixedPrecisionOps, rendering pre-dequantization wholly redundant and counterproductive.
2.4 Text Encoder VRAM Retention and Inter-Engine Desynchronization
Forge and ComfyUI maintain distinct internal memory management caches. When comfy_te_glue initialized the CLIP model:
model_optionsdid not strictly specifyoffload_device = cpu, allowing parts of the text encoder to remain pinned in GPU VRAM after encoding the positive/negative conditioning.- Even after
clip.patcherwas moved, ComfyUI'smodel_management.current_loaded_modelsstill retained a strong reference to the text encoder, preventing garbage collection. - As a result, approximately 4.24 GB of VRAM remained occupied, leaving only ~11.8 GB free when attempting to load the massive Krea2 UNet.
2.5 Module-Level CPU Swapping for comfy_cast_weights
When Forge loads a large diffusion model, backend/memory_management.py calculates memory budgets and splits model blocks between VRAM and system RAM (LoadedModel). However:
- The parameter profiling loop explicitly filtered for known parameter types and standard
comfy.ops.manual_cast. - Modules utilizing
comfy_cast_weights(includingMixedPrecisionOpsandint8_ops) were not recognized as swappable module candidates. - The manager attempted an all-or-nothing allocation, demanding ~12.9 GB of contiguous VRAM when only ~11.8 GB was available, causing an immediate OOM crash before the first step.
2.6 PyTorch Inference Mode Version Counter Conflict
During tensor device migrations (cpu to cuda and vice versa), comfy.ops._quantized_apply rewrites inner weight references. In PyTorch 2.4+, executing parameter rewrites inside an active torch.inference_mode() context triggers:
RuntimeError: Cannot set version_counter for inference tensor
This aborted the unload/swap routine midway, leaving models half-loaded and corrupted in memory.
3. Implementation Details & Code Walkthrough
3.1 SingleStreamDiT & Cosmos Operations Dispatch (modules_forge/hswq_int8.py)
Added SingleStreamDiT and CosmosTransformer3DModel to the operations injection list in load_unet_int8_branch:
# modules_forge/hswq_int8.py
if model_cls_name in (
"Flux", "SD3", "WanVideo", "HunyuanVideo", "Lumina2Transformer2DModel",
"ZImageTransformer2DModel", "SingleStreamDiT", "CosmosTransformer3DModel"
):
model.diffusion_model.set_operations(int8_ops, computation_dtype=computation_dtype)This guarantees that all 224 linear layers are instantiated with comfy_cast_weights and torch.bfloat16 compute dtype, eliminating parameter expansion.
3.2 Native INT8 Text Encoder Loading (backend/loader.py)
Updated split_state_dict to detect and bypass dequantization for Comfy-native multimodality encoders:
# backend/loader.py
if guess_clip_type in ("qwen3vl_4b", "qwen3_4b", "qwen3_06b", "gemma2_2b"):
# Skip premature full-state-dict dequantization; ComfyUI MixedPrecisionOps handles comfy_quant natively
pass
else:
dequantize_state_dict_inplace(clip_sd, compute_dtype=computation_dtype)In addition, _load_krea2_mixed_precision_unet explicitly passes computation_dtype:
unet = load_unet_int8_branch(
sd,
unet_storage_dtype=unet_storage_dtype,
computation_dtype=computation_dtype,
)3.3 Guaranteed CLIP CPU Offloading (backend/comfy_te_glue.py & memory_management.py)
Enforced CPU offload in model options and guaranteed comprehensive teardown:
# backend/comfy_te_glue.py
te_model_options = {
"offload_device": torch.device("cpu"),
"initial_device": torch.device("cpu"),
}
def offload_comfy_clip(clip_patcher):
if clip_patcher is not None:
import comfy.model_management as mm
mm.unload_model_clones(clip_patcher)
if hasattr(clip_patcher, "model"):
clip_patcher.model.to(torch.device("cpu"))
torch.cuda.empty_cache()In backend/memory_management.py, free_memory cascades to ComfyUI:
# backend/memory_management.py
def free_memory(memory_required, device, keep_loaded=[]):
...
# Cascade eviction to ComfyUI model management
import comfy.model_management as mm
mm.free_memory(memory_required, device)3.4 Module-Level CPU Swapping for comfy_cast_weights (backend/memory_management.py)
Extended build_module_profile to measure and register comfy_cast_weights layers:...
v2.0.4 — Anima compute_qkv Transformer Options Compatibility & ControlNet ConvRot INT8 bfloat16 Dequantization
| EN | 中文 |
This release resolves a hard crash at the very first denoising step when generating with Anima checkpoints under the Forge-side Q/K RMSNorm + split-half RoPE fuse. It also fixes ControlNet ConvRot INT8 loading for bfloat16 checkpoints by extending the in-place dequantization fallback with native bf16 support, and hardens quantized model construction against inference-mode bookkeeping errors.
1. Executive Summary
| Item | Detail |
|---|---|
| Affected Scope | Anima checkpoints (Cosmos Predict2 Anima DiT, e.g. waiANIMA_v10Base10.safetensors) when the RoPE fuse patch on comfy.ldm.cosmos.predict2.Attention.compute_qkv is installed; ControlNet models shipped as ConvRot INT8 with bfloat16 activations |
| Symptom | Sampling aborts at 0/32 steps with TypeError: _patched_compute_qkv() got an unexpected keyword argument 'transformer_options'; ControlNet checkpoints dequantized through the fallback path landed in fp16 regardless of the checkpoint's compute dtype |
| Root Cause | Upstream Attention.forward now always passes transformer_options= into compute_qkv, while the Forge-side fuse patch still used the old three-argument signature. The fallback dequantization path only carried an fp16 dtype code and defaulted every target tensor to fp16. |
Fix in backend/nn/comfy_anima.py |
_patched_compute_qkv accepts transformer_options: Optional[dict] = None. The fuse ignores the kwarg; the SelfAttn+RoPE fuse branch and the stock CrossAttn path are unchanged. |
| Additional Fixes | dequantize_state_dict_inplace auto-detects bf16/fp16, uses the native bf16 dequantization kernel (code=2), and caches Hadamard matrices per device; standard ControlNet and Qwen Image Fun ControlNet dequantize INT8 state dicts before construction; Qwen Fun models sync to the diffusion model's device and floating-point dtype; MixedPrecision (Krea2) and ConvRot INT8 model construction runs with inference mode disabled; Comfy-native Anima/Krea2 text encoders skip pre-dequantization so comfy_quant layouts load natively |
| Performance Impact | Hadamard matrices are constructed once per (group_size, device) pair and rotation runs on CUDA when available, collapsing dequantization to a single device-to-host transfer per weight |
2. Technical Background & Root Cause Analysis
2.1 Forge-Side RoPE Fuse Patch & Upstream Call Contract
The Anima Q/K RMSNorm + split-half RoPE fuse (introduced in v1.8.3) replaces comfy.ldm.cosmos.predict2.Attention.compute_qkv at load time via install_anima_rms_rope_fuse() in backend/nn/comfy_anima.py. The fuse fuses the Q/K RMSNorm scales with the split-half RoPE rotation into a single comfy_kitchen.rms_rope_split_half call on the SelfAttn+RoPE path, and leaves every other path (CrossAttn, no-RoPE) on the stock implementation.
The vendored ComfyUI tree used by Forge (ComfyUI-master/comfy/ldm/cosmos/predict2.py) evolves with upstream. At some point after the fuse was introduced, Attention.forward learned a new calling contract:
q, k, v = self.compute_qkv(x, context, rope_emb=rope_emb, transformer_options=transformer_options)The fuse patch predates this contract and still carried the old three-argument signature, so every Anima generation crashed on the very first SelfAttn block invocation.
2.2 The Failure Mechanism (Exact Traceback)
The crash surfaces inside the RES4LYF beta sampler but its origin is the patched method:
File "...comfy/ldm/cosmos/predict2.py", line 220, in forward
q, k, v = self.compute_qkv(x, context, rope_emb=rope_emb, transformer_options=transformer_options)
TypeError: _patched_compute_qkv() got an unexpected keyword argument 'transformer_options'
The call chain is: txt2img sampling → calc_cond_uncond_batch → apply_model → predict2._forward block loop → SelfAttn.forward → compute_qkv. Because the patched compute_qkv was installed once at model load, the first forward pass of the model hit the mismatch immediately, before a single denoising step completed.
2.3 ControlNet ConvRot INT8 dtype Gap
dequantize_state_dict_inplace dequantizes ConvRot INT8 tensors through the native comfy_kitchen kernel using a dtype code (fp16 → 1, fp32 → 0). bfloat16 checkpoints need code 2, which the fallback path never passed; the fp16 default silently cast bf16 ControlNet weights through fp16, and the fp16 Hadamard-matrix cache caused a per-weight host-to-GPU roundtrip.
3. Implementation Details & Code Walkthrough
3.1 _patched_compute_qkv Signature Hardening (backend/nn/comfy_anima.py)
The patched method now exposes the same surface as the stock implementation:
def _patched_compute_qkv(
self,
x,
context: Optional[object] = None,
rope_emb: Optional[object] = None,
transformer_options: Optional[dict] = None,
):The kwarg is accepted and ignored: the fuse performs no branching on it, so the SelfAttn+RoPE fused math, the scale-cast behavior of q_norm/k_norm, and the stock CrossAttn/no-RoPE paths are byte-for-byte unchanged from v2.0.3.
3.2 In-Place Dequantization bf16 Support (backend/patch_comfy_quant_layout_fallback.py)
dequantize_state_dict_inplaceno longer requires an explicitcompute_dtype: when omitted, it takes the dtype of the firstbfloat16/float16tensor found in the state dict, falling back to fp16 only when nothing else is available.- When the native CUDA kernel is reachable, the dtype code is now
1for fp16 and2for bf16, and the result is cast on device before a single.cpu()transfer. - The Hadamard cache is keyed by
(group_size, device), so CPU tensors and CUDA tensors maintain separate cached matrices at their own device instead of re-building them per weight. - When no quantized layers were present, a
torch.cuda.empty_cache()follows the CUDA synchronize to release scratch allocations.
3.3 ControlNet Loader Integration
modules_forge/supported_controlnet.py:ControlNetPatcher.try_build_from_state_dictrunsdequantize_state_dict_inplace(controlnet_data)first, so a ConvRot INT8 ControlNet is dequantized to its checkpoint's compute dtype before any marker inspection or model construction.modules_forge/supported_controlnet_qwen_fun.py:_build_comfyui_fun_modeldequantizes the state dict to bf16 before shape probing, andQwenFunControlNetPatcher.processmoves the built Fun model to the diffusion model's device and floating-point dtype (bf16/fp16/fp32), replacing the earlier device-only move that could leave a dtype mismatch between the ControlNet stack and the UNet.
3.4 Comfy-Native Text Encoder Loading & Quantized Construction Hardening
backend/loader.pysplit_state_dictskips the pre-dequantization pass for Comfy-native text encoders: Krea2 and Anima guess paths, and any checkpoint whose CLIP target isqwen3vl_4b,qwen3_4b,qwen3_06b, orgemma2_2b, now keep theircomfy_quantlayout intact so the native Comfy ops load them directly.backend/loader.pyload_huggingface_componentdequantizes the TE state dict to the component's own storage dtype before instantiation, and setsunet_config["dtype"]explicitly forLumina2Transformer2DModel,ZImageTransformer2DModel,SingleStreamDiT, andCosmosTransformer3DModel.- MixedPrecision (Krea2) and ConvRot INT8 branches construct the model under
torch.inference_mode(False), torch.no_grad(), so parameter rewriting during quantized construction does not hit inference-tensor bookkeeping errors.
4. Verification & Code-Level Checks
| Verification Item | Method | Result |
|---|---|---|
_patched_compute_qkv signature covers the upstream call compute_qkv(x, context, rope_emb=…, transformer_options=…) |
ast.parse + argument list inspection of backend/nn/comfy_anima.py |
Confirmed |
| Fuse scope unchanged (SelfAttn+RoPE only; CrossAttn stock) | Code inspection of the patched method body | Confirmed |
bf16 zdez dequantization selects the native code=2 kernel |
Code inspection of dequantize_state_dict_inplace |
Confirmed |
| End-to-end generation with an Anima checkpoint | Runtime sampling run | First run after upgrade |
5. Upgrade Instructions
To upgrade to v2.0.4:
git pull origin mainNo environment re-installation or additional dependencies are required.
v2.0.3 - Resolution of SDXL ConvRot INT8 Black Images & Non-Invasive Hadamard Cache Architecture
| EN | 中文 |
Version 2.0.3 — Resolution of SDXL ConvRot INT8 Black Images & Non-Invasive Hadamard Cache Architecture
This release resolves a critical bug causing solid black images (or NaN latent collapse) when loading SDXL checkpoints with ConvRot INT8 (reverse hybrid) text encoders under int8 / int8 (fp16 LoRA). It also introduces a non-invasive runtime monkeypatch architecture in Forge that avoids modifying vendored submodules while optimizing Hadamard matrix caching and GPU execution.
1. Executive Summary
| Item | Detail |
|---|---|
| Affected Scope | SDXL checkpoints using HSWQ ConvRot INT8 (int8 / int8 (fp16 LoRA)), including Illustrious-XL and Animagine-XL INT8 models |
| Symptom | Denoising produced solid black images (or completely blown-out NaN latents); terminal logged dozens of CLIPTextModelWithProjection Unexpected: ['transformer.resblocks.*.weight_scale', ...] warnings |
| Root Cause | In backend/loader.py, guess.process_clip_state_dict() remapped CLIP-G (text_encoder_2) .weight keys from OpenCLIP to HuggingFace format before dequantization, leaving .weight_scale and .comfy_quant unmatched. CLIP-G's 63 rotated linear layers were never unscaled or unrotated. |
Fix in backend/loader.py |
Added dequantize_state_dict_inplace(sd, compute_dtype=...) immediately after splitting UNet and VAE state dicts and before guess.process_clip_state_dict() runs. |
| Submodule Isolation | Reverted all direct edits in modules/ComfyUI-HSWQ-Loader-and-Tools/patches/comfy_quant_int8.py and implemented runtime monkeypatching in modules_forge/hswq_int8.py. |
| Performance Impact | Device-cached Hadamard unrotation eliminates step-by-step CPU matrix regeneration and PCIe host-to-device transfers. |
2. Technical Background & Root Cause Analysis
2.1 SDXL Dual Text Encoder Architecture & Quantization Format
SDXL utilizes two separate text encoders operating concurrently:
- Text Encoder 1 (CLIP ViT-L/14): 337 state_dict tensors, outputting a 768-dimensional token embedding sequence.
- Text Encoder 2 (OpenCLIP ViT-bigG/14): 644 state_dict tensors, outputting a 1280-dimensional token embedding sequence and a 1280-dimensional pooled projection.
Under HSWQ (Hybrid-Sensitivity-Weighted-Quantization) ConvRot INT8, all linear projections within both text encoders are quantized to 8-bit integers (int8_tensorwise) and rotated using orthogonal Hadamard matrices to suppress activation outliers:
In safetensors checkpoints, each quantized layer is represented by three keys:
-
<layer>.weight: Tensor of dtypetorch.int8. -
<layer>.weight_scale: Float32 per-tensor scale factor ($W_{\text{float}} = W_{\text{int8}} \cdot \text{scale}$ ). -
<layer>.comfy_quant: Serialized JSON metadata containing format properties ("format": "int8_tensorwise", "convrot": true, "convrot_groupsize": 256).
2.2 The Key Remapping Collision in backend/loader.py
In standard SDXL checkpoints, Text Encoder 1 is saved using HuggingFace naming (text_model.encoder.layers.*), while Text Encoder 2 is saved using OpenAI / OpenCLIP naming (resblocks.<N>.attn.out_proj.weight, resblocks.<N>.mlp.c_fc.weight, etc.).
During model loading in backend/loader.py, the following sequence occurred:
1. UNet state dict popped from sd.
2. VAE state dict popped from sd.
3. guess.process_clip_state_dict(sd) called.
└─> calls clip_text_transformers_convert(sd, prefix="text_encoder_2.")
└─> Renames 'resblocks.<N>.attn.out_proj.weight' to 'text_model.encoder.layers.<N>.self_attn.out_proj.weight'
4. HuggingFace CLIPTextModelWithProjection instantiated and loaded.
The Failure Mechanism:
-
clip_text_transformers_convert()is hardcoded to recognize and rename only standard tensor attributes:.weightand.bias. - It completely ignored
.weight_scaleand.comfy_quantkeys! - Therefore,
resblocks.0.attn.out_proj.weightwas renamed totext_model.encoder.layers.0.self_attn.out_proj.weight, butresblocks.0.attn.out_proj.weight_scaleandresblocks.0.attn.out_proj.comfy_quantremained unchanged under the oldresblocks.*prefix. - When
CLIPTextModelWithProjectionwas loaded, the model could not find any quantization scale or metadata matchingtext_model.encoder.layers.0.self_attn.out_proj. - All 63 scale keys were flagged as
Unexpected keysand discarded:CLIPTextModelWithProjection Unexpected: [ 'transformer.resblocks.0.attn.out_proj.weight_scale', 'transformer.resblocks.0.mlp.c_fc.weight_scale', ... 'transformer.resblocks.31.mlp.c_fc.weight_scale' ] - Without
weight_scaleand without the Hadamard unrotation pass ($W_{\text{rot}} \times H$ ), rawint8signed integer values (ranging from$-128$ to$+127$ ) were directly interpreted as half-precision floats (torch.float16). - Because typical FP16 weights have magnitudes in the range
$[ -0.05, +0.05 ]$ , the unscaled weights were 2000× to 2500× larger than normal, with arbitrary rotated vector directions. - The resulting text embeddings exploded into astronomical values (
$> 10^4$ ) and NaN. - The UNet Cross-Attention matrices multiplied by NaN conditioning vectors, propagating zeros/NaNs through all latent layers.
- The VAE decoder received corrupted latents and decoded a completely solid black image.
3. Implementation Details & Code Walkthrough
3.1 Pre-Remap In-Place Dequantization (backend/loader.py)
To prevent key remapping collisions, dequantization and Hadamard unrotation must take place while the original checkpoint state dict keys are still intact.
In backend/loader.py, inside split_state_dict():
# loader.py (lines 978-992)
# UNet and VAE state dicts are already popped:
# sd now contains ONLY text encoder keys (CLIP-L and CLIP-G).
if memory_management.text_encoder_dtype() is not None:
try:
from backend.patch_comfy_quant_layout_fallback import dequantize_state_dict_inplace
dequant_count = dequantize_state_dict_inplace(sd, compute_dtype=memory_management.text_encoder_dtype())
if dequant_count > 0:
logging.info(f"[Forge] Dequantized {dequant_count} ConvRot/INT8 weights to {memory_management.text_encoder_dtype()} prior to CLIP key remapping")
except Exception as e:
logging.warning(f"[Forge] text_encoder dequantize_state_dict_inplace skipped: {e}")
sd = guess.process_clip_state_dict(sd)By invoking dequantize_state_dict_inplace(sd) before guess.process_clip_state_dict(sd):
resblocks.<N>.*.weightandresblocks.<N>.*.weight_scaleare perfectly co-located.- Both CLIP-L (70 linear layers) and CLIP-G (63 linear layers) are reconstructed into pure Float16 tensors.
- All
.weight_scaleand.comfy_quantentries are safely removed from the state dict. - When
guess.process_clip_state_dict(sd)subsequently renames the keys, it operates on standard Float16 weights, andCLIPTextModelWithProjectionloads with zero Unexpected keys.
3.2 In-Place Dequantization & Hadamard Inverse Rotation (backend/patch_comfy_quant_layout_fallback.py)
dequantize_state_dict_inplace handles both tensor-wise scale multiplication and group-wise Hadamard inverse transformation:
def dequantize_state_dict_inplace(state_dict: dict, compute_dtype: torch.dtype = torch.float16) -> int:
"""Scan state_dict for comfy_quant metadata, dequantize and unrotate in-place."""
quant_keys = [k[:-len(".comfy_quant")] for k in list(state_dict.keys()) if k.endswith(".comfy_quant")]
if not quant_keys:
return 0
dequantized_count = 0
for prefix in quant_keys:
meta_raw = state_dict.pop(f"{prefix}.comfy_quant", None)
w = state_dict.get(f"{prefix}.weight", None)
s = state_dict.pop(f"{prefix}.weight_scale", None)
if w is None or s is None:
continue
meta = json.loads(meta_raw.numpy().tobytes()) if hasattr(meta_raw, "numpy") else json.loads(meta_raw)
qformat = meta.get("format")
if qformat == "int8_tensorwise":
# 1. Scale integer weights to floating point
w_float = w.to(dtype=torch.float32)
s_float = s.to(dtype=torch.float32)
if s_float.ndim == 0:
out = w_float * s_float
else:
out = w_float * s_float.reshape(-1, *([1] * (w_float.ndim - 1)))
# 2. Reverse Hadamard rotation if convrot is flagged
if meta.get("convrot", False):
gs = int(meta.get("convrot_groupsize", 256) or 256)
if out.dim() == 2 and out.shape[1] < gs and (out.shape[1] in (16, 64, 128)):
gs = out.shape[1]
from comfy_kitchen.backends.eager.convrot_w4a4 import _build_hadamard, _rotate_weight
h = _build_hadamard(gs, device=out.device, dtype=torch.float32)
# Note: H is symmetric and orthogonal (H^T = H, H^{-1} = H)
out = _rotate_weight(out, h, gs)
state_dict[f"{prefix}.weight"] = out.to(dtype=compute_dtype)
dequantized_count += 1
return dequantized_count3.3 Non-Invasive Submodule Isolation (modules_forge/hswq_int8.py)
To adhere strictly to project rules preventing modification of vendored code (`modules/Comf...
v2.0.2 - Technical Documentation: ComfyUI v0.40.0 AIMDO Compatibility Stubs and RES4LYF Sampler Restoration
Part 1: ComfyUI v0.40.0 AIMDO Import Failure Resolution
1. Error Overview
Symptom & Stack Trace
During initialization of the bundled ComfyUI core (v0.40.0) or when importing core modules such as comfy.ops, comfy.storage, or comfy.model_prefetch, startup fails unconditionally with ModuleNotFoundError:
Traceback (most recent call last):
File "modules/initialize.py", line 49, in imports
from modules import processing
File "modules/processing.py", line 18, in <module>
import comfy.samplers
File "ComfyUI-master/comfy/samplers.py", line 16, in <module>
import comfy.sampler_helpers
File "ComfyUI-master/comfy/sampler_helpers.py", line 8, in <module>
import comfy.model_prefetch
File "ComfyUI-master/comfy/model_prefetch.py", line 12, in <module>
import comfy.storage
File "ComfyUI-master/comfy/storage.py", line 10, in <module>
from comfy_aimdo.storage import fast_disk
ModuleNotFoundError: No module named 'comfy_aimdo.storage'
A subsequent failure on execution of stream-aware memory allocation blocks in comfy.ops / comfy.model_management:
Traceback (most recent call last):
File "ComfyUI-master/comfy/ops.py", line 22, in <module>
from comfy_aimdo.malloc_graph import MallocGraph, use_stream
ModuleNotFoundError: No module named 'comfy_aimdo.malloc_graph'
2. Root Cause Analysis
Upstream ComfyUI introduced optional dependencies on the proprietary hardware acceleration / memory manager library comfy_aimdo (AIMDO). Specifically:
comfy.storagecallsfrom comfy_aimdo.storage import fast_diskto query whether storage volumes support direct hardware bypass.comfy.opsand model compilation paths callfrom comfy_aimdo.malloc_graph import MallocGraph, use_streamto manage CUDA graph allocations and stream synchronization.
In standard environments where the proprietary binary wheel of comfy_aimdo is not installed, these unshielded top-level imports abort initialization. While Forge previously provided top-level compatibility stubs for comfy_aimdo.control, comfy_aimdo.vram_buffer, comfy_aimdo.host_buffer, and comfy_aimdo.model_mmap, ComfyUI v0.40.0 introduced storage and malloc_graph as new module entry points that were not yet stubbed.
3. Created & Modified Files
comfy_aimdo/storage.py(Newly Created)comfy_aimdo/malloc_graph.py(Newly Created)
4. Full Source Code of New / Modified Implementations
File: comfy_aimdo/storage.py
# Stub: real comfy_aimdo.storage checks fast disk on Windows. Forge uses its own loader.
def fast_disk(path):
"""Stub: return False so ComfyUI falls back to standard disk loading."""
return FalseFile: comfy_aimdo/malloc_graph.py
# Stub: real comfy_aimdo.malloc_graph provides CUDA graph memory compilation. Forge uses its own loader.
from contextlib import contextmanager
class MallocGraph:
def __init__(self, *args, **kwargs):
self._scopes = []
def push(self, name=None):
pass
def pop(self):
return False
def abort(self):
pass
def pause(self, sync=False):
pass
def resume(self, sync=False):
pass
@contextmanager
def use_stream(self, stream):
yield
def iterate(self, name=None):
return False5. Technical Rationale & Architectural Function
-
fast_disk(path) -> False:
ReturningFalseexplicitly signals to ComfyUI's internal model prefetcher and storage subsystem that the underlying disk is standard filesystem storage. This forces ComfyUI to bypass AIMDO direct kernel I/O paths and seamlessly use standard Python/PyTorchsafetensors.torch.load_fileand file descriptors, preserving full compatibility without external binary dependencies. -
MallocGraph&use_stream:
Provides an exact drop-in structural interface for the expected AIMDO memory graph compiler.use_stream(stream)acts as a transparent, no-overhead context manager (yield), allowing any stream management in ComfyUI ops to execute normally on PyTorch's native CUDA stream stack without requiring proprietary memory pooling binaries.
Part 2: RES4LYF Sampler Restoration & Import Architecture
1. Error Overview
Symptom & Stack Trace
During WebUI startup in initialize_rest(), registering the RES4LYF sampler and scheduler suite failed with a NameError:
WARNING:root:Failed to register RES4LYF samplers: name 'res4lyf' is not defined
Traceback (most recent call last):
File "d:\USERFILES\GitHub\Stable-Diffusion-WebUI-Forge-Nunchaku\modules\initialize.py", line 110, in initialize_rest
forge_res4lyf_samplers.register_res4lyf_samplers()
File "d:\USERFILES\GitHub\Stable-Diffusion-WebUI-Forge-Nunchaku\modules_forge\forge_res4lyf_samplers.py", line 248, in register_res4lyf_samplers
from modules import RES4LYF
File "d:\USERFILES\GitHub\Stable-Diffusion-WebUI-Forge-Nunchaku\modules\RES4LYF\__init__.py", line 34, in <module>
res4lyf.init()
NameError: name 'res4lyf' is not defined
Furthermore, importing res4lyf or modules.RES4LYF.res4lyf directly from external scripts failed with:
Traceback (most recent call last):
File "d:\USERFILES\GitHub\Stable-Diffusion-WebUI-Forge-Nunchaku\modules\RES4LYF\res4lyf.py", line 13, in <module>
from server import PromptServer
ModuleNotFoundError: No module named 'server'
And importing via import res4lyf or import RES4LYF from outside the modules package failed with:
ModuleNotFoundError: No module named 'res4lyf'
2. Root Cause Analysis
-
Unimported Module Reference in
modules/RES4LYF/__init__.py:
Line 28 containedfrom .res4lyf import RESplain, which bound only the functionRESplainto the module namespace. When Line 34 subsequently invokedres4lyf.init(), the symbolres4lyfdid not exist in the globals of__init__.py, immediately raisingNameError: name 'res4lyf' is not definedand crashing the import of the entire RES4LYF package. -
Unshielded Server Dependency in
modules/RES4LYF/res4lyf.py:
res4lyf.pyincluded a direct, unshieldedfrom server import PromptServerat line 13 to register REST API routes (@PromptServer.instance.routes.post(...)). Whilemodules_forge/forge_res4lyf_samplers.pycreated a mock server instance prior to importingRES4LYF, any independent or direct import ofres4lyf.pyin standalone contexts failed withModuleNotFoundError: No module named 'server'. -
Top-Level Package Namespace Mismatch:
In Forge, RES4LYF is vendored insidemodules/RES4LYF. Nodes, extensions, and user scripts written against ComfyUI custom node conventions frequently executeimport res4lyforimport RES4LYFdirectly. Without import hook resolution, Python failed to locateres4lyfon the rootsys.path.
3. Created & Modified Files
modules/RES4LYF/__init__.py(Modified)modules/RES4LYF/res4lyf.py(Modified)modules_forge/transformers_cache_compat.py(Modified)modules_forge/forge_res4lyf_samplers.py(Modified)
4. Full Source Code of New / Modified Implementations
File: modules/RES4LYF/__init__.py (Lines 20–38)
from comfy.samplers import SchedulerHandler, SCHEDULER_HANDLERS, SCHEDULER_NAMES
new_scheduler_name = "bong_tangent"
if new_scheduler_name not in SCHEDULER_HANDLERS:
bong_tangent_handler = SchedulerHandler(handler=sigmas.bong_tangent_scheduler, use_ms=True)
SCHEDULER_HANDLERS[new_scheduler_name] = bong_tangent_handler
SCHEDULER_NAMES.append(new_scheduler_name)
from . import res4lyf
from .res4lyf import RESplain
#torch.use_deterministic_algorithms(True)
#torch.backends.cudnn.deterministic = True
#torch.backends.cudnn.benchmark = False
res4lyf.init()
discard_penultimate_sigma_samplers = set((
))File: modules/RES4LYF/res4lyf.py (Lines 10–35)
import comfy.model_sampling
import comfy.samplers
from aiohttp import web
try:
from server import PromptServer
except (ImportError, ModuleNotFoundError):
class _MockPromptServerInstance:
def __init__(self):
class _MockRoutes:
def post(self, path):
def decorator(func):
return func
return decorator
self.routes = _MockRoutes()
self.client_id = None
self.supports = set()
def send_sync(self, event_type, data):
pass
async def send(self, event_type, data):
pass
class PromptServer:
instance = _MockPromptServerInstance()
from tqdm import tqdmFile: modules_forge/transformers_cache_compat.py (Lines 150–175)
try:
return _original_import(name, *args, **kwargs)
except Exception:
import types
vmod = types.ModuleType("facexlib.version")
vmod.__version__ = "0.3.0"
vmod.__gitsha__ = "unknown"
sys.modules["facexlib.version"] = vmod
return vmod
if name in ("res4lyf", "RES4LYF") or name.startswith(("res4lyf.", "RES4LYF.")):
import sys
if name in sys.modules:
return sys.modules[name]
try:
return _original_import(name, *args, **kwargs)
except (ModuleNotFoundError, ImportError):
if name in ("res4lyf", "RES4LYF"):
real_mod = _original_import("modules.RES4LYF", *args, **kwargs)
else:
sub = name.split(".", 1)[1]
real_mod = _original_import(f"modules.RES4LYF.{sub}", *args, **kwargs)
sys...v2.0.1 - Deprecation & Removal of SDXL ConvRot NVFP4 Support
| EN | 中文 |
Version 2.0.1 — Deprecation & Removal of SDXL ConvRot NVFP4 Support
This release officially deprecates and removes all support and documentation references for SDXL ConvRot NVFP4 across Stable-Diffusion-WebUI-Forge-Nunchaku, in strict alignment with upstream updates to Hybrid-Sensitivity-Weighted-Quantization (HSWQ) and the ComfyUI-HSWQ-Loader-and-Tools ecosystem.
1. Executive Summary
| Item | Detail |
|---|---|
| Affected Scope | SDXL (Stable Diffusion XL 1.0 / Illustrious-XL / Animagine-XL architectures) |
| Deprecated Format | SDXL ConvRot NVFP4 (4-bit FP4 weight quantization with simulated / native NVFP4 scales) |
| Active Standard | ConvRot INT8 (reverse hybrid) strictly (int8 / int8 (fp16 LoRA)) |
| Core Technical Rationale | Deterministic latent trajectory divergence: 4-bit precision fundamentally breaks trajectory fidelity in SDXL's high-capacity Cross-Attention UNet (cosine < 0.90, frequent trajectory bifurcation / collapse) |
| Measurement Insight | Legacy decoded SSIM (~0.95) was an artifact of VAE smoothing (VAE masking illusion), concealing catastrophic latent-space distribution distortion |
| Upstream Alignment | Fully synchronized with HSWQ v2.0+ specifications, removing dead loader codepaths and aligning documentation |
2. Technical Background & Root Cause Analysis
2.1 Latent Trajectory Divergence vs. Decoded SSIM Illusion (The VAE Masking Trap)
In conventional diffusion model quantization benchmarks, model fidelity was frequently assessed by decoding generated latents into pixel space and calculating Structural Similarity (SSIM) or Peak Signal-to-Noise Ratio (PSNR) against an unquantized FP16 baseline.
Under these legacy pixel-space metrics, SDXL NVFP4 appeared to achieve acceptable SSIM scores (~0.94 - 0.95). However, rigorous investigation using the deterministic 25-seed latent-trajectory comparison (sdxl/diag_impact_sdxl.py / benchmark_sdxl_int8.md) revealed a fundamental flaw:
- The VAE Masking Illusion: The SDXL VAE decoder acts as a heavy nonlinear low-pass filter and semantic reconstructor. Severe distribution collapse and structural deviation in latent space are artificially smoothed out during decoding, giving a false impression of fidelity.
-
Latent-Space Reality: When measuring the per-step latent tensor trajectory cosine similarity:
$$\text{Cosine}(z_t^{\text{quant}}, z_t^{\text{fp16}}) = \frac{\langle z_t^{\text{quant}}, z_t^{\text{fp16}} \rangle}{|z_t^{\text{quant}}|_2 |z_t^{\text{fp16}}|_2}$$
SDXL NVFP4 models consistently failed to reach the production threshold of$\ge 0.95$ . In extensive multi-seed benchmarks, the mean final-step cosine similarity dropped below$0.90$ , with high rates of latent bifurcation (where the denoising path branches into an entirely distorted semantic basin).
2.2 SDXL Cross-Attention UNet Vulnerability under 4-Bit Precision
The architectural complexity of SDXL exacerbates the limitations of 4-bit representation:
- Dynamic Dynamic-Range in Cross-Attention Layers: SDXL utilizes dual text encoders (CLIP ViT-L and OpenCLIP ViT-bigG) feeding 2048-dimensional pooled and sequence embeddings into deep Cross-Attention blocks. The dynamic range of projection matrices (
to_q,to_k,to_v,to_out.0) in middle and deeper blocks exhibits extreme kurtosis. Uniform or block-wise 4-bit floating-point (E2M1 NVFP4) quantization introduces quantization noise that exceeds the error tolerance of the attention score softmax. - Cascading Non-Linear Error Amplification: Diffusion models operate as iterative dynamical systems. Unlike one-pass autoregressive LLMs, early-step quantization perturbations in SDXL's UNet compound exponentially across subsequent reverse diffusion steps, irreversibly destabilizing the denoising trajectory.
3. SDXL Precision Policy: ConvRot INT8 Reverse Hybrid Standard
To guarantee uncompromising generation quality and zero latent trajectory bifurcation, SDXL quantization is strictly standardized on ConvRot INT8 (reverse hybrid).
3.1 Reverse Hybrid Mechanism
Rather than attempting to quantize the entire network and selectively protect a small subset of "important" layers, the HSWQ Reverse Hybrid methodology begins with the full unquantized FP16 baseline and proceeds via trajectory-guided impact minimization:
- Sampling-Trajectory Impact Measurement: The actual impact of quantizing each candidate layer is measured directly on the active sampling trajectory under production sampling schedules (Euler, DPM++ 2M Karras).
-
Ascending Impact Quantization: Only the
$K$ lowest-impact layers are packed as FULL ConvRot INT8 (int8_tensorwise), while all sensitive, structural, and attention-critical layers remain strictly in FP16. -
Production Gate: The resulting model must pass the deterministic 25-seed latent-trajectory gate:
-
Mean Final Latent Cosine Similarity:
$\ge 0.95$ - Latent Bifurcation Count: 0 / 25 seeds (zero structural failures)
-
Mean Final Latent Cosine Similarity:
3.2 Comparison Matrix: SDXL Quantization Approaches
| Precision Format | Bit Width | Latent Trajectory Cosine | 25-Seed Bifurcation Rate | Production Status |
|---|---|---|---|---|
| FP16 / BF16 | 16-bit | 1.0000 (Reference) | 0 / 25 | Baseline Reference |
| ConvRot INT8 (Reverse Hybrid) | 8-bit hybrid | 0 / 25 (0%) | Production Standard | |
| Native All-INT8 | 8-bit uniform | 0.935 - 0.945 | 3 - 5 / 25 (12-20%) | Deprecated / Non-Compliant |
| ConvRot NVFP4 | 4-bit | < 0.900 | 12+ / 25 (> 48%) | Officially Deprecated & Removed |
4. Repository & Runtime Changes
4.1 Documentation & Specification Cleanup
README.mdandzhmd/README.md: Removed all references, feature bullets, and architectural claims regarding SDXL NVFP4 from both the Forge core and vendoredmodules/ComfyUI-HSWQ-Loader-and-Tools.Changelog/CHANGELOG.md&zhmd/CHANGELOG.md: Added Version 2.0.1 documentation with language-matched navigation links.
4.2 WebUI Forge Loader & Model Handling
- SDXL checkpoints loaded via Forge and ComfyUI-HSWQ-Loader are cleanly routed to FP16 or ConvRot INT8 pipelines.
- Elimination of obsolete NVFP4 scale calculation overhead and fallback branches for SDXL architectures.
5. Related Documentation & Upstream Links
- Upstream Repository: Hybrid-Sensitivity-Weighted-Quantization (HSWQ)
- Quantization Guide: How to quantize SDXL (ConvRot INT8 Reverse Hybrid)
- Technical Specification: Diag → Reverse Hybrid (SDXL, v1.1) Technical Guide
- Changelog:
Changelog/CHANGELOG.md/zhmd/CHANGELOG.md
v2.0.0 - Python 3.14 Exclusive Runtime & Multi-Platform Compatibility
| EN | 中文 |
Version 2.0.0 — Python 3.14 Exclusive Runtime & Multi-Platform Compatibility
This major release transitions the Forge runtime environment exclusively to Python 3.14 across Windows and Linux (CUDA 13.2 / PyTorch 2.13 stack), providing automated universal wheel installations, native Linux and Windows Nunchaku 1.3.0 wheels, SageAttention 3 (SA3 / Blackwell FP4) backend support, and runtime compatibility fixes.
1. Summary
| Item | Detail |
|---|---|
| Runtime Target | Python 3.14 exclusively (CUDA 13.2 / PyTorch 2.13) on Windows & Linux |
| Universal Wheels | Automated installation of distutils, pyaudioop, imghdr, facexlib, and gradio 4.40.0 |
| Nunchaku 1.3.0 | Prebuilt Python 3.14 wheels for both Windows (win_amd64) and Linux (linux_x86_64) |
| Attention Backends | Native runtime switching between Default (SDPA), SA2 (SageAttention 2), SA3 (SageAttention 3 / Blackwell FP4), and FA2 (FlashAttention 2) |
| Audio Compatibility | Built-in torchaudio_compat providing standard ModuleSpec stub for Python 3.14 |
| Architecture Fixes | Fixed StableDiffusionProcessing.sd_model synchronization and eliminated module variable shadowing in modules/shared.py |
| Memory Management | Fixed config.json persistence for CUDA Stream (Async) and Pinned Memory (Shared) |
2. Key Changes & Technical Details
2.1 Python 3.14 Universal Wheel Distribution
To support Python 3.14 without manual compilation or missing legacy standard library modules:
distutils: Bundled custom universal wheel (distutils-3.14.0-py3-none-any.whl).pyaudioop&imghdr: Bundled pure Python standard library replacement wheels for removed 3.13/3.14 modules.facexlib: Built pure Python universal wheel (facexlib-0.3.0-py3-none-any.whl) strippingnumbaandfilterpyhard dependencies, with embeddedversion.pyand pure Python fallback JIT decorators for@jitfunctions.gradio 4.40.0: Bundled dependency-unpinned universal wheel with OpenAPI boolean schema conversion patches (modules_forge/transformers_cache_compat.py).
2.2 Nunchaku 1.3.0 Prebuilt Wheels (Windows & Linux)
Integrated prebuilt binary wheels for Python 3.14 on CUDA 13.2:
- Windows:
https://huggingface.co/ussoewwin/nunchaku-built-on-cu132/resolve/main/nunchaku-1.3.0.dev20260619%2Bcu13.2torch2.12-cp314-cp314-win_amd64.whl - Linux:
https://huggingface.co/ussoewwin/nunchaku-built-on-cu132/resolve/main/nunchaku-1.3.0.dev20260829%2Bcu13.2torch2.13-cp314-cp314-linux_x86_64.whl
2.3 SageAttention 3 (SA3 / Blackwell FP4) & Attention Backends
- Added
sageattn3wheel auto-installation (sageattn3-1.0.0+cu132torch2.13.0-cp314-cp314-win_amd64.whl). - Full runtime Attention switching across
Default,SA2,SA3, andFA2in quicksettings.
2.4 Torchaudio Compatibility & SD Model Property Synchronization
- Added
modules_forge/torchaudio_compat.pywith validimportlib.machinery.ModuleSpecto satisfy transformers and diffusers import guards without requiring binary torchaudio. - Removed static
sd_model = Noneinmodules/shared.pyto prevent shadowing dynamic descriptor properties onSharedmodule class. - Configured
StableDiffusionProcessing.sd_modelasinit=Falsedataclass field to prevent accidentalNoneresets during processing instance construction.
3. Install & Update Notes
- Pull the latest repository changes:
git pull
- Verify Python version is Python 3.14:
python --version
- Launch WebUI via
webui-user.bat(Windows) orwebui.sh(Linux). All necessary universal wheels and dependencies will be automatically verified and installed on first startup.
4. Related Links
- Changelog:
Changelog/CHANGELOG.md/zhmd/CHANGELOG.md - Chinese Release Notes:
zhmd/v2.0.0.md - Tag:
v2.0.0
v1.8.6
| EN | 中文 |
Version 1.8.6 — Honest fix of the root comfy-kitchen pin miss (KREA2 RAM exhaustion)
This release fixes a RAM exhaustion bug on KREA2 fp8/int8 ConvRot checkpoints that v1.8.5 was supposed to fix but did not. v1.8.5 bumped the kitchen pin in the wrong requirements file; the Forge runtime kept installing an older kitchen and silently dequantized quantized weights to bf16, using up to ~64 GB of RAM.
1. Summary
| Item | Detail |
|---|---|
| Primary change | Bump the root requirements.txt pin to comfy-kitchen>=0.2.22 |
| Why | v1.8.5 only raised the pin in ComfyUI-master/requirements.txt, but Forge installs from the root requirements.txt, which stayed at >=0.2.21 |
| Symptom fixed | KREA2 (int8/fp8 ConvRot) checkpoints loaded and generated but consumed all ~64 GB RAM |
| Honest note | The intended v1.8.5 fix never took effect in the runtime; this release is the real correction |
| User action | Run pip install -U comfy-kitchen once (a plain pip install -r requirements.txt will not upgrade an already-satisfied 0.2.21) |
| Unchanged | Issue #3 TE dequant fallback (v1.8.4); Anima RMS+RoPE fuse (v1.8.3); Krea2 depth ControlNet still unsupported |
2. Background
Forge installs its runtime dependencies from the repository root requirements.txt, not from ComfyUI-master/requirements.txt. v1.8.5 raised the kitchen pin only in the vendored ComfyUI-master/requirements.txt, so the runtime kept resolving comfy-kitchen to 0.2.21.
TensorCoreConvRotW4A4Layout only exists in comfy-kitchen >= 0.2.22. With 0.2.21 installed, the grouped import in ComfyUI-master/comfy/quant_ops.py:29 raises ImportError: cannot import name 'TensorCoreConvRotW4A4Layout'.
Because that import is a single try block, one missing name flips _CK_AVAILABLE to False, so get_layout_class() returns None for every quantized layer — not just ConvRot. The Issue #3 fallback (backend/patch_comfy_quant_layout_fallback.py, added in v1.8.3/v1.8.4) then converts all MixedPrecision weights to plain float Parameters: the ~12 GB int8/fp8 DiT becomes ~24 GB bf16. Combined with the fp16 TE, pinned shared memory, VAE, and Python/OS, total RAM exceeds 64 GB.
The pip trap: with >=0.2.21 and 0.2.21 already installed, pip install -r requirements.txt reports "Requirement already satisfied" and never upgrades — so environments stayed broken even after pulling v1.8.5.
3. What changed
3.1 Root requirements pin
requirements.txt:28
- comfy-kitchen>=0.2.21
+ comfy-kitchen>=0.2.22This forces the runtime environment to 0.2.22+, so TensorCoreConvRotW4A4Layout imports cleanly, kitchen layouts register, quantized weights stay quantized in RAM, and the dequant fallback never fires.
3.2 Why v1.8.5 did not cover this
v1.8.5 intentionally left the root pin unchanged. The v1.8.4 dequant fallback had fixed the crash (AttributeError: 'NoneType' object has no attribute 'Params'), which masked the version gap: loading succeeded, but silently at ~2x RAM. This release corrects the pin in the file the runtime actually honors.
4. Install / update notes
git pull(or download) this release.- Upgrade the kitchen explicitly — pulling the repo alone does not change an already-satisfied venv:
pip install -U comfy-kitchen
- Restart Forge.
- For native INT8 ConvRot checkpoints, prefer Comfy-Org / ComfyUI-native weights. ComfyUI-INT8-Fast dumps are a different format and need conversion before they load on the native path.
5. Out of scope
- Soft-import recovery for missing
TensorCoreConvRotW4A4Layoutwithout updating kitchen (optional hardening; not this release). - Splitting the grouped
tryimport inquant_ops.pyso one missing name does not disable all layouts. - Krea2 depth ControlNet.
- Creating or supporting ComfyUI-INT8-Fast–only weight formats inside Forge.
6. Files / commits (v1.8.5 → v1.8.6)
| Commit | Subject | Paths |
|---|---|---|
044e75f |
fix: bump root comfy-kitchen pin to >=0.2.22 for ConvRot layout import | requirements.txt, docs/ISSUE_3_COMFY_KITCHEN_PIN_FIX.md |
cb47081 |
docs: add CHANGELOG v1.8.6 honest fix of root comfy-kitchen pin miss (EN/zh) | Changelog/CHANGELOG.md, zhmd/CHANGELOG.md |
7. Verification checklist
pip show comfy-kitchenreports0.2.22or newer.- Startup no longer logs
cannot import name 'TensorCoreConvRotW4A4Layout'fromcomfy_kitchen. - No
[Forge] comfy_kitchen layout missing ... dequantizingmessages appear during KREA2 model load. - KREA2 int8/fp8 ConvRot checkpoints load with quantized RAM usage (no ~2x bf16 blow-up); total RAM stays well under 64 GB.
- v1.8.4 quantized TE fallback and v1.8.3 Anima fuse still behave as before.
8. Related links
- Changelog:
Changelog/CHANGELOG.md/zhmd/CHANGELOG.md - Chinese notes:
zhmd/v1.8.6.md - Tag:
v1.8.6
v1.8.5 - ComfyUI-master sync and comfy-kitchen bump
| EN | 中文 |
Version 1.8.5 — ComfyUI-master sync and comfy-kitchen bump
This release pushes the vendored ComfyUI-master tree and the kitchen pin that remote users need for current Comfy quantization layouts (including INT8 ConvRot / TensorCoreConvRotW4A4Layout). Forge still installs runtime deps from the repository root requirements.txt.
1. Summary
| Item | Detail |
|---|---|
| Primary change | Sync vendored ComfyUI-master to the latest upstream tree used for Forge imports |
| Kitchen (vendored tree) | comfy-kitchen==0.2.22 in ComfyUI-master/requirements.txt |
| Kitchen (Forge runtime) | Root requirements.txt keeps comfy-kitchen>=0.2.21 (unchanged; this is what Forge pip install -r uses) |
| Why | Older remote trees / kitchens can fail with cannot import name 'TensorCoreConvRotW4A4Layout' and disable FP8/INT8 kitchen paths |
| Unchanged | Issue #3 TE dequant fallback (v1.8.4); Anima RMS+RoPE fuse (v1.8.3); Krea2 depth ControlNet still unsupported |
2. Background
Forge embeds a ComfyUI-master/ copy for model / quant / ops imports. Local development had already moved to a newer master + kitchen, while origin/main lagged. Users who pull only the published tree can therefore miss:
- Newer
ComfyUI-mastermodules (JoyImage, Anima LLLite, API nodes, detection updates, tests, …) - A kitchen version that exports
TensorCoreConvRotW4A4Layout
comfy/quant_ops.py imports that symbol together with INT8/FP8 layouts. If the installed kitchen is too old, the entire kitchen import fails and quantized paths degrade.
3. What changed
3.1 Vendored ComfyUI-master
Synced the vendored tree used for Forge imports, including (non-exhaustive):
- JoyImage model / TE / nodes
- Anima LLLite
- HeyGen / Sync.so API nodes
- Model detection, supported models, attention / cosmos predict2 updates
- Unit / execution test updates
ComfyUI-master/requirements.txt→comfy-kitchen==0.2.22
3.2 Forge root requirements
Root requirements.txt already ships comfy-kitchen>=0.2.21. That pin is what Forge applies on install/update. After pulling v1.8.5, re-run:
pip install -r requirements.txt
so the active venv actually has a kitchen that provides current layouts.
4. Install / update notes
git pull(or download) this release.pip install -r requirements.txtfrom the Forge root (notComfyUI-master/requirements.txt).- Restart Forge.
- For native INT8 ConvRot checkpoints, prefer Comfy-Org / ComfyUI-native weights. ComfyUI-INT8-Fast dumps are a different format and need conversion before they load on the native path.
5. Out of scope
- Soft-import recovery for missing
TensorCoreConvRotW4A4Layoutwithout updating kitchen (optional hardening; not this release). - Silent checkpoint→LoRA fallback hardening.
- Krea2 depth ControlNet.
- Creating or supporting ComfyUI-INT8-Fast–only weight formats inside Forge.
6. Files / commits (v1.8.4 → v1.8.5)
| Commit | Subject | Paths |
|---|---|---|
1ccc445 |
sync: update ComfyUI-master and kitchen pin for INT8 ConvRot | ComfyUI-master/** (incl. requirements.txt → kitchen 0.2.22) |
7b56572 |
docs: add CHANGELOG v1.8.5 ComfyUI-master and kitchen sync overview | Changelog/CHANGELOG.md, zhmd/CHANGELOG.md |
7. Verification checklist
- After pull:
pip show comfy-kitchenshows a version that satisfies>=0.2.21(ideally 0.2.22+). - Startup no longer logs
cannot import name 'TensorCoreConvRotW4A4Layout'fromcomfy_kitchen.tensor. - Native INT8 ConvRot / MixedPrecision checkpoints that worked on an updated local tree also load on a fresh clone of this release.
- v1.8.4 quantized TE fallback and v1.8.3 Anima fuse still behave as before.
8. Related links
- Changelog:
Changelog/CHANGELOG.md/zhmd/CHANGELOG.md - Chinese notes:
zhmd/v1.8.5.md - Tag:
v1.8.5