fix: SDXL ControlNet (diffusers naming + graph size)#1752
Merged
leejet merged 1 commit intoJul 6, 2026
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes SDXL ControlNet, which has never worked end-to-end in this repo. Loading any diffusers-format SDXL ControlNet (thibaud's
OpenPoseXL2.safetensors, Stability'scontrolnet-*-sdxl-1.0) against an SDXL base failed with 844 back-to-back "not in model metadata" errors during validation, one per tensor. Two independent gaps are involved and both need fixing:name_conversion.cpp:convert_tensor_nameonly routes diffusers→LDM names throughconvert_diffusers_unet_to_original_sdxlwhen the tensor starts with"model.diffusion_model.". ControlNet tensors have no such prefix (they arrive asdown_blocks.…,controlnet_down_blocks.…,controlnet_cond_embedding.…,add_embedding.…,time_embedding.…,controlnet_mid_block.…,conv_in.…), so the converter was never called and the LDM block map declared incontrol.hpp(input_blocks.*,zero_convs.*,input_hint_block.*,middle_block_out.*) had zero overlap with what the file provided.control.hpp:CONTROL_NET_GRAPH_SIZE = 1536was sized for the SD 1.5 ControlNet (transformer_depth={1,1,1}). SDXL ControlNet'stransformer_depth={1,2,10}blows past that on the first compute call withGGML_ASSERT(cgraph->n_nodes < cgraph->size) failed- a second, latent bug that (2) would have exposed as soon as (1) was fixed.SD 1.5 ControlNet is unaffected because those files (
control_v11p_sd15_*) ship in LDM naming directly and stay under the old graph budget.Related Issue / Discussion
Closes long-standing #172 ("Are SDXL Controlnet models supported?") and #661 ("Stable Diffusion XL Control Net support"). Note #661's specific ask about
xinsir/controlnet-union-sdxl-1.0is not covered here - that model is the ControlNet-Union variant (extracontrol_type_projembeds + Union routing), a different architecture, and would be a separate PR.Additional Information
The fix
src/name_conversion.cppconvert_diffusers_controlnet_to_original_sdxl(name)that first calls the existing UNet SDXL converter (which already handlesdown_blocks,mid_block,time_embedding,add_embedding,conv_inand their resnet/attention internals), then applies the ControlNet-only mappings:controlnet_cond_embedding.conv_in.*→input_hint_block.0.*controlnet_cond_embedding.blocks.<k>.*→input_hint_block.<2k+2>.*for k=0..5controlnet_cond_embedding.conv_out.*→input_hint_block.14.*controlnet_down_blocks.<i>.*→zero_convs.<i>.0.*controlnet_mid_block.*→middle_block_out.0.*is_diffusers_controlnet_name(name)recognizes the top-level heads used by diffusers ControlNet checkpoints.// controlnetsection ofconvert_tensor_name, gated onsd_version_is_sdxl(version)so SD 1.5 goes through the existing pre-normalized path unchanged and other architectures aren't touched.src/model/diffusion/control.hppCONTROL_NET_GRAPH_SIZEbumped from the hardcoded 1536 toMAX_GRAPH_SIZE(the same budget the main UNet uses). The graph node array cost is tiny (~2.6 MiB of pointer slots, alloc-time only, not per-step) and matches what the SDXL UNet already reserves.Verified against the file
OpenPoseXL2.safetensorsheader parsed directly: 844 keys, top-level namespaces before the fix are all diffusers with zero overlap with the LDM block map:down_blocksinput_blocks.<3i+j+1>.…mid_blockmiddle_block.<0/1/2>.…controlnet_down_blockszero_convs.<i>.0.…controlnet_cond_embeddinginput_hint_block.<even>.…add_embeddinglabel_emb.0.<0/2>.…(SDXL micro-cond)time_embeddingtime_embed.<0/2>.…controlnet_mid_blockmiddle_block_out.0.…conv_ininput_blocks.0.0.…After the fix, the load prints zero "not in model metadata" errors and generation runs to completion.
End-to-end test
Result: knight in side view, walking, matching the OpenPose skeleton. Generation completes in 175s on a 12 GiB card via CPU streaming (see
docs/performance.mdfor the streaming flags).Not in scope
control-lora-*-rank256.safetensors): different architecture (LoRA-decomposed.down/.updeltas), separate PR.controlnet-union-sdxl-1.0_promax.safetensors): extracontrol_type_projembeds and Union routing logic, separate PR.Checklist