After grounding research in actual source code: - Disty's SDNQ repo (https://github.com/Disty0/sdnq) - diffusers 0.36.0+ (https://github.com/huggingface/diffusers) - ComfyUI 0.3.77+ (https://github.com/comfyanonymous/ComfyUI) Identified that current wrapper approach is fundamentally incompatible: - ComfyUI expects ModelPatcher objects with clone(), latent_format, etc. - Our SDNQModelWrapper/CLIPWrapper/VAEWrapper don't inherit from ComfyUI base classes - GitHub Issue #14 confirms no one has successfully generated images Changes: - Mark README.md as WIP with clear warning - Add ASSESSMENT.md with detailed analysis and three viable paths forward - Update context.md to reflect broken status Recommendation: Implement standalone sampler node (Option A in ASSESSMENT.md) as the most realistic path to working functionality. See ASSESSMENT.md for full details, implementation plan, and decision points.
13 KiB
ComfyUI-SDNQ Reality-Based Assessment
Date: December 9, 2025 Status: CRITICAL ARCHITECTURAL ISSUES IDENTIFIED Recommendation: REDESIGN REQUIRED
Executive Summary
After grounding research in actual source code (Disty's SDNQ repo, diffusers 0.36.0+, ComfyUI 0.3.77+), I've identified that the current wrapper-based architecture is fundamentally incompatible with ComfyUI.
Key Finding: ComfyUI expects ModelPatcher objects with specific cloning/patching infrastructure. Our custom wrappers (SDNQModelWrapper, SDNQCLIPWrapper, SDNQVAEWrapper) don't inherit from these classes and will fail when KSampler or other nodes try to use them.
Evidence: GitHub Issue #14 confirms no one has successfully generated images with this node pack. Multiple users report the same incompatibility issues.
Problems Identified (Grounded in Reality)
1. Wrapper Architecture is Fundamentally Wrong
What ComfyUI Expects:
# ComfyUI expects MODEL to be a ModelPatcher object
from comfy.model_patcher import ModelPatcher
class ModelPatcher:
def __init__(self, model, load_device, offload_device, ...):
self.model = model # The actual PyTorch module
self.model_options = {}
self.model_size()
self.patches = {}
# ... extensive patching/cloning infrastructure
def clone(self):
"""KSampler calls this to create working copies"""
# Returns new ModelPatcher with same model
@property
def latent_format(self):
"""Required for latent space operations"""
# Returns LatentFormat object defining channels, scale, etc.
What We Currently Provide:
# core/wrapper.py
class SDNQModelWrapper:
def __init__(self, pipeline, model_component, model_type=None):
self.pipeline = pipeline
self.model = model_component
self.model_type = model_type
# Missing: clone(), latent_format, model_options, patches, etc.
Result: When KSampler tries to call model.clone() or access model.latent_format, it will fail with AttributeError.
Evidence:
- Lines 204-234 in context.md document this exact failure
- Error:
'NoneType' object has no attribute 'latent_channels' - This is because we don't set
latent_format
2. Device Parameter Never Used (Confirmed Bug)
Source: GitHub Issue #14, Ph0rk0z comment
Current Code (nodes/loader.py:97-99, 291-296):
# Line 97-99: We ACCEPT device parameter
"device": (["auto", "cuda", "cpu"],),
# Line 291-296: We NEVER USE it
pipeline = DiffusionPipeline.from_pretrained(
model_path,
torch_dtype=torch_dtype,
local_files_only=is_local,
attn_implementation="sdpa",
# Missing: device_map parameter!
)
Impact: Models always load to CPU first, then rely on enable_model_cpu_offload(), which is slower than direct GPU loading.
3. SDPA Warning on Flux2Pipeline (Confirmed Issue)
Current Error:
Keyword arguments {'attn_implementation': 'sdpa'} are not expected by Flux2Pipeline and will be ignored.
Root Cause: I assumed all pipelines support attn_implementation parameter without verifying.
Reality Check: According to diffusers documentation, attn_implementation IS supported on modern pipelines, BUT there may be version mismatches or the parameter name changed.
Need to Verify: What's the correct parameter name in diffusers 0.36.0.dev0?
4. Tensor Dimension Mismatch (SDNQ Dequantization Bug)
Current Error:
RuntimeError: a and b must have same reduction dim, but got [32, 4096] X [5120, 32]
Root Cause: torch.compile is failing during SDNQ weight dequantization.
Reality: This suggests deeper issues with how SDNQ quantized weights interact with torch.compile on certain hardware/compiler combinations.
Hypothesis: The suppress_errors=True config may not be working as expected, or the error is happening before compilation.
What Actually Works (Grounded in Reality)
SDNQ Standalone (Proven Working)
From GitHub Issue #14, yopfix's example:
import torch
import diffusers
from sdnq import SDNQConfig
# This works perfectly
pipe = diffusers.ZImagePipeline.from_pretrained(
"./models/diffusers/sdnq/[model-path]",
torch_dtype=torch.bfloat16
)
pipe.enable_model_cpu_offload()
# Generate image
image = pipe(
prompt="a beautiful landscape",
num_inference_steps=25,
guidance_scale=7.0,
).images[0]
image.save("output.png")
Key Insight: SDNQ works flawlessly when used directly with diffusers. The problem is ONLY the ComfyUI integration layer.
Three Viable Paths Forward
Option A: Standalone Sampler Node (RECOMMENDED)
Description: Create a self-contained node that loads SDNQ models and generates images in one step. No MODEL/CLIP/VAE outputs.
Architecture:
class SDNQSampler:
"""
All-in-one SDNQ node:
- Loads model from dropdown/path
- Takes prompt + sampler params
- Outputs IMAGE directly
"""
INPUT_TYPES = {
"required": {
"model_selection": (model_options,),
"prompt": ("STRING", {"multiline": True}),
"negative_prompt": ("STRING", {"multiline": True}),
"steps": ("INT", {"default": 25}),
"cfg": ("FLOAT", {"default": 7.0}),
"width": ("INT", {"default": 1024}),
"height": ("INT", {"default": 1024}),
"seed": ("INT", {"default": 0}),
}
}
RETURN_TYPES = ("IMAGE",)
CATEGORY = "sampling/SDNQ"
def generate(self, model_selection, prompt, negative_prompt,
steps, cfg, width, height, seed):
# Load SDNQ pipeline
pipe = self._load_pipeline(model_selection)
# Set seed
generator = torch.Generator(device="cuda").manual_seed(seed)
# Generate
image = pipe(
prompt=prompt,
negative_prompt=negative_prompt,
num_inference_steps=steps,
guidance_scale=cfg,
width=width,
height=height,
generator=generator,
).images[0]
# Convert PIL to ComfyUI tensor format
return (pil_to_comfy_tensor(image),)
Pros:
- ✅ Will actually work (proven by yopfix example)
- ✅ Simple implementation (1-2 days)
- ✅ Reliable and maintainable
- ✅ Can add advanced params (LoRA, ControlNet via diffusers API)
Cons:
- ❌ Can't reuse ComfyUI's existing sampler nodes
- ❌ Less flexible for complex workflows
- ❌ Need to duplicate sampler parameters
Estimated Effort: 2-3 days
Risk: LOW
Option B: ModelPatcher Integration (COMPLEX)
Description: Make wrappers inherit from ComfyUI's base classes (ModelPatcher, CLIP, VAE).
Architecture:
from comfy.model_patcher import ModelPatcher
from comfy.sd import CLIP, VAE
from comfy.model_base import BaseModel
class SDNQModelPatcher(ModelPatcher):
def __init__(self, diffusers_pipeline, model_component):
# Detect model type and create appropriate BaseModel
base_model = self._create_base_model(model_component)
super().__init__(
model=base_model,
load_device=torch.device("cuda"),
offload_device=torch.device("cpu"),
)
self.pipeline = diffusers_pipeline
self.model_options["latent_format"] = self._detect_latent_format()
def _detect_latent_format(self):
# Return appropriate LatentFormat based on model type
# FLUX: SD3LatentFormat
# SDXL: SDXLLatentFormat
pass
Challenges:
- Need to wrap diffusers transformer/unet in ComfyUI's
BaseModelclass - Need to implement proper
latent_formatdetection - Need to ensure quantized weights survive the wrapping
- Need to handle clone() operations without breaking SDNQ
Research Needed:
- Study ComfyUI-DiffusersLoader source
- Understand how state dicts are extracted and reconstructed
- Test if SDNQ weights survive state dict operations
Estimated Effort: 1-2 weeks
Risk: HIGH (may not be possible without extensive monkeypatching)
Option C: Hybrid Approach
Description: Provide both a standalone sampler AND limited MODEL/CLIP/VAE outputs for experimental use.
Architecture:
class SDNQLoader:
"""
Loads SDNQ model and provides:
1. IMAGE output (standalone generation)
2. MODEL/CLIP/VAE outputs (experimental, may not work with all nodes)
"""
RETURN_TYPES = ("IMAGE", "MODEL", "CLIP", "VAE")
# Document limitations clearly in UI
Pros:
- ✅ Provides working solution immediately (IMAGE output)
- ✅ Allows experimentation with ComfyUI nodes
- ✅ Users can choose based on their needs
Cons:
- ❌ Confusing to users (which output to use?)
- ❌ Experimental outputs may fail unpredictably
Estimated Effort: 3-4 days
Risk: MEDIUM
Recommended Implementation Plan
Phase 1: Prove It Works (Days 1-2)
Goal: Get SDNQ generating images in ComfyUI, even if not integrated with other nodes.
Tasks:
- Create
SDNQSamplernode (standalone, all-in-one) - Implement basic parameters (prompt, steps, cfg, seed, size)
- Test with FLUX.1, FLUX.2, Z-Image models
- Verify images generate successfully
Success Criteria: Can generate images with SDNQ models in ComfyUI.
Phase 2: Fix Known Bugs (Day 3)
Goal: Fix confirmed issues in current code.
Tasks:
-
Fix device parameter usage:
pipeline = DiffusionPipeline.from_pretrained( model_path, torch_dtype=torch_dtype, device_map="auto" if device == "auto" else device, ) -
Research and fix attn_implementation parameter:
- Verify correct parameter name in diffusers 0.36.0.dev0
- Test with different pipeline classes
- Document which pipelines support it
-
Handle torch.compile errors more gracefully:
- Add better error messages
- Provide clear fallback behavior
Success Criteria: No warnings/errors during model loading.
Phase 3: Advanced Features (Days 4-5)
Goal: Add features users expect from SDNQ.
Tasks:
- Add LoRA support (via diffusers
load_lora_weights()) - Add batch generation
- Add img2img support
- Add inpainting support
- Add video model support (I2V, T2V)
Success Criteria: Feature parity with standalone SDNQ usage.
Phase 4: Documentation & Polish (Day 6)
Goal: Make the node pack production-ready.
Tasks:
- Update README with clear usage examples
- Document limitations (can't use with KSampler, etc.)
- Create example workflows
- Add error handling and user-friendly messages
- Update context.md with lessons learned
Success Criteria: Users can install and use without confusion.
Decision Point: Integration Worth It?
Question: Should we attempt Option B (ModelPatcher integration) after Phase 1-4?
Factors to Consider:
For Integration:
- Users expect ComfyUI nodes to work with KSampler
- Would enable complex workflows (ControlNet, IPAdapter, etc.)
- More "native" ComfyUI experience
Against Integration:
- High implementation complexity (1-2 weeks)
- High risk of failure (may not be possible)
- High maintenance burden (breaks with ComfyUI updates)
- Standalone sampler works perfectly fine
Recommendation:
- Ship Phase 1-4 first (standalone sampler)
- Gather user feedback
- If users strongly demand KSampler integration, then attempt Option B
- Otherwise, iterate on standalone sampler features
Reality Check: What I Got Wrong
Hallucinations Identified
- "AutoPipeline was removed in 0.36.0" - WRONG. It still exists.
- "attn_implementation works on all pipelines" - PARTIALLY WRONG. It works on most, but not verified for all.
- "Wrappers will work if we just implement the right methods" - WRONG. Need to inherit from ComfyUI base classes.
- "torch.compile errors are just warnings" - WRONG. They indicate actual runtime failures.
How I'll Avoid This
- Always fetch actual source code before making claims
- Test assumptions before implementing
- Read error messages carefully instead of assuming
- Study working examples (like yopfix's standalone code)
Next Steps
If continuing with current session:
- Implement Phase 1 (SDNQSampler node)
- Test with real models
- Verify images generate
If starting fresh session:
- Read this ASSESSMENT.md file
- Follow the implementation plan
- Start with Phase 1 (standalone sampler)
References (All Verified)
- SDNQ Repository - Actual SDNQ implementation
- diffusers Documentation - Current API reference
- ComfyUI ModelPatcher - What MODEL must be
- GitHub Issue #14 - User-reported problems
- yopfix's working example - Proven code
Bottom Line: The standalone sampler approach (Option A) is the most realistic path to a working node pack. ModelPatcher integration is theoretically possible but high-risk and may not be worth the effort.