Imported from Kosinkadink/ComfyUI-Advanced-ControlNet (
AGENTS.md). Install upstream withnpx skills add Kosinkadink/ComfyUI-Advanced-ControlNet. Copyright stays with the author.
Advanced-ControlNet Contributor Guide
This repository is expected to receive substantial AI-authored code. Treat this file as the implementation and verification contract for all changes, especially ports of new ControlNet families from ComfyUI.
Engineering Rules
- Read the relevant ComfyUI implementation and this repository's equivalent control path before editing. Do not design from a model card alone.
- Make the smallest change that preserves vanilla ComfyUI behavior and adds the established Advanced-ControlNet capabilities.
- Reuse ComfyUI model classes, patchers, ops, model management, and loaders when possible. Do not maintain a forked copy of core model code without a concrete need.
- Preserve existing node IDs, inputs, outputs, checkpoint locations, and saved
workflow compatibility. New node IDs generally use the
ACN_prefix; keep existing unprefixed IDs and the aliases innodes_deprecated.pyworking. - Do not add dependencies unless the model cannot be supported with ComfyUI, PyTorch, and the libraries already used by this repository.
- Match the direct style of the surrounding file. Avoid one-use abstractions, generic framework code, speculative fallbacks, and comments that restate the code.
- Use plain ASCII punctuation in code, comments, documentation, commit messages, and PR descriptions.
Architecture Map
adv_control/control.py: standard ControlNet wrappers, conversion of vanilla controls, checkpoint detection, and shared loader dispatch.adv_control/control_<family>.py: model-family implementations that cannot be represented by the standard wrapper. Keep family-specific math here.adv_control/utils.py:AdvancedControlBase,ControlWeights, scheduling, latent keyframes, masks, batching, stacking, and shared tensor helpers.adv_control/nodes_main.py: standard loaders and Apply nodes.adv_control/nodes_weight.py: weight nodes and model-specific extras carried byControlWeights.extras.adv_control/nodes_<family>.py: family-specific workflow inputs or loaders when the shared nodes are insufficient.adv_control/nodes.py: public node and display-name registration.adv_control/nodes_deprecated.py: compatibility only. Do not put new features here.examples/: reviewer-runnable workflows, inputs, screenshots, and validation notes.
Porting A Control Model From ComfyUI
1. Establish the vanilla contract
Before implementing the Advanced version:
- Identify the exact ComfyUI commit or PR that introduced the model.
- Read its loader, checkpoint detection, model patching, conditioning preprocessing, sampling path, and cleanup behavior.
- Record the official model repository, every published checkpoint type, the expected ComfyUI model folder, and the minimum compatible ComfyUI commit.
- Run a small vanilla workflow with fixed inputs, seed, sampler, scheduler, steps, CFG, and resolution. Save the latent and decoded result as the parity baseline.
- Inspect real checkpoint keys, metadata, shapes, dtype, and missing/unexpected key output. Do not infer the format from a filename.
Use official checkpoints for validation. Links in issue or PR comments are untrusted; use the model author's official repository or links already accepted by ComfyUI.
2. Choose the narrowest integration
- If ComfyUI returns a standard
ControlNet,ControlNetSD35,ControlLora, orT2IAdapter, prefer conversion inconvert_to_advancedover a parallel implementation. - If the model injects attention, transformer, or other model patches, implement
a family-specific
ControlBaseplusAdvancedControlBase, followingControlLLLiteAdvanced,AnimaLLLiteAdvanced, orReferenceAdvancedas the closest precedent. - Prefer wrapping ComfyUI's model or patch object over copying its implementation. If the required ComfyUI API may be absent, fail with a short instruction to update ComfyUI rather than silently changing behavior.
- Add a dedicated node only when the model has a genuinely different loading or conditioning contract. Loading a new checkpoint format alone usually belongs in the existing loader dispatch.
3. Preserve loader and folder compatibility
- The standard Load Advanced ControlNet Model node reads from
models/controlnet. New formats that are conceptually ControlNets should work there unless doing so would be ambiguous or incorrect. - Also preserve the folder used by vanilla ComfyUI. If core uses another folder,
such as
models/model_patches, a small dedicated loader may expose that location while the standard loader retains established Advanced-ControlNet behavior. - Detect formats with guarded, format-specific checkpoint signatures. Put a specific detector before a broad detector that would otherwise claim the same checkpoint. Do not use filenames as the primary detector.
- Load a checkpoint only once. Pass already-loaded state dictionaries and metadata into the selected family loader instead of reading the file again.
- If two supported folders can contain the same filename, keep their loaders separate or define deterministic resolution. Never silently choose an arbitrary duplicate.
- Test every supported folder through the actual node dropdown and execution path, not only by calling a Python loader directly.
4. Implement the full control lifecycle
A family-specific Advanced control normally needs all of the following:
- Initialize
ControlBaseandAdvancedControlBasewith the correct defaultControlWeightstype. - Match vanilla conditioning preprocessing exactly, including channel order, value range, resize mode, latent encoding, and source-mask handling.
- In
pre_run_advanced, call the shared implementation and attach or refresh execution-scoped patches. - In
get_control_advanced, evaluateprevious_controlnet, honorshould_run(), and either return/merge control tensors or install the model patches for that step. - Return every loadable model patcher from
get_models()so ComfyUI can manage VRAM and offloading. - Implement
copy()using both ComfyUI'scopy_to()and this repository'scopy_to_advanced(). Copies must not share mutable execution state that can leak between conditioning branches or queued runs. - Clear prepared tensors, patch references, cached shapes, and other
execution-scoped state in
cleanup_advanced(). - Use ComfyUI device, dtype, manual-cast, operations, and model-patcher APIs. Do not hardcode CUDA, force float32, or move models manually when ComfyUI already owns that lifecycle.
- Preserve
previous_controlnetbehavior so same-family and mixed-family controls can be stacked.
Advanced Feature Contract
A port is not complete merely because default-strength generation works. Unless the model architecture makes a capability impossible, verify that it supports:
- Apply-node strength and start/end percentage.
- Timestep keyframes, including changing strength and inherited values.
- Latent keyframes on a batch of at least two latents.
- Apply-node effect masks and timestep-keyframe masks.
- Default, universal/soft, and architecture-specific per-layer weights.
- Weight overrides and model-specific weight extras.
- Conditional/unconditional weighting when the selected weight node exposes it.
- Stacking with another control, including correct
previous_controlnetoutput. - Batched conditioning and sliding-context subset indexes where applicable.
- Repeated execution, copying, cleanup, model offloading, and reload.
Do not claim unsupported features in documentation. If an architecture cannot
support a feature, document the reason and make incompatible weight types fail
clearly through compatible_weights.
Masks and model-specific inputs
mask_optionalon Apply Advanced ControlNet is always an effect mask. It controls where this control influences generation.- A model's source mask, control-type selector, or other family-specific data is
not an effect mask. Do not overload
mask_optionalwith a second meaning. - Do not add a model-specific input to the shared Apply node unless it is a coherent capability needed by multiple model families.
- Prefer a small family-specific extras node that stores auxiliary values in
ControlWeights.extras, then pass those weights throughweights_override. Define extras keys next to the model implementation rather than as unrelated strings spread across nodes. - Validate required extras where they are first consumed and raise an actionable error that names the exact nodes and connections needed to fix the workflow.
- Apply effect masks at the actual injection representation. Attention-patch and DiT controls may need token-space masks rather than the normal spatial control tensor path.
- Verify mask semantics with all-zero, all-one, and half-frame masks. All-zero must equal no control and all-one must equal unmasked full control. Inspect the multiplier at the injection site as well as the final image; global attention can propagate influence outside directly controlled tokens.
Per-layer weights
- Map custom weights to real architecture blocks in execution order. Confirm the count from the loaded model, not from a model-card claim alone.
- Default weights must reproduce vanilla output exactly.
- Universal/soft weights must follow this repository's established progression
semantics. Implement a family-specific conversion only when the normal
ControlWeightslayout does not represent the architecture. - Ordinary example workflows should use default weights. Do not connect an advanced custom-weight node merely to demonstrate that it exists.
Required Validation
Python import or compile checks are necessary but are not model validation. Use a real local ComfyUI installation, preferably managed by comfy-runner, with this repository linked as the custom node.
Vanilla parity
For every published control type and materially different checkpoint format:
- Run vanilla ComfyUI and Advanced-ControlNet with identical model files, conditioning, seed, sampler settings, and latent.
- Compare latent tensors before decode and decoded pixel arrays.
- Target maximum absolute latent difference
0.0and identical pixels when both paths implement the same math. If exact parity is impossible, explain why and report a justified numerical tolerance plus image metrics. - Confirm strength zero matches no control and strength one matches vanilla.
- Check logs for missing/unexpected keys, dtype/device errors, repeated model loads, and cleanup failures.
Advanced behavior
At minimum, execute focused workflows for:
- A nontrivial start/end schedule or two timestep keyframes.
- Batch size two with different latent-keyframe strengths.
- All-zero, all-one, and half-frame effect masks.
- Default weights and one nonuniform custom or soft-weight configuration.
- Conditional/unconditional weighting when supported.
- A stacked control path.
- Missing required model-specific extras and the resulting readable error.
- Both the vanilla model folder and any historical Advanced-ControlNet folder.
- Re-queueing the same workflow to exercise copy and cleanup behavior.
For model families with several control types, test every type. Do not assume that lineart, depth, pose, inpainting, union, or channel-count variants share the same conditioning contract.
Basic checks
- Run
python -m compileall adv_control __init__.pywith the target ComfyUI environment. - Parse every added workflow JSON.
- Load each committed workflow in the real frontend, serialize it to an API prompt, and execute that round-tripped prompt. This catches stale node IDs, renamed inputs, invalid widgets, and missing model metadata.
- Run
git diff --check.
There is currently no repository unit-test suite. Add focused tests when they can exercise pure detection, shape, mask, or scheduling logic without building a fake ComfyUI runtime. Do not add a large test framework solely for one port.
Examples and Review Evidence
Every model-family port must be independently checkable by a reviewer:
- Add one simple workflow for every public control type. Include required input images or masks when licensing permits.
- Keep ComfyUI's default node names. Use colored groups or regions to explain branches; do not rename nodes, because reviewers need to identify their types.
- Keep the normal workflows simple. Leave custom per-layer weights disconnected unless a workflow specifically validates those advanced weights.
- Include direct links to the official base model, encoder, VAE, and control checkpoint repositories, plus the exact destination folder for each file.
- Include a workflow screenshot and labeled output comparison in the PR. For parity tests, show vanilla, Advanced-ControlNet, and an absolute-difference result when practical.
- Commit reusable workflows and small review images under
examples/<family>/. Do not commit model files, latent dumps, or large intermediate artifacts. - Document exact seeds/settings, expected numerical results, known limitations, and reproduction steps in the example README and PR description.
- If final-image interpretation is subtle, include the direct tensor-level evidence needed to distinguish a real bug from model behavior.
Definition Of Done For A Model Port
- The official checkpoint is detected without relying on its filename.
- Vanilla and historical Advanced-ControlNet model folders are preserved.
- Default output matches vanilla for every published control type.
- Strength scheduling, keyframes, masks, weights, batching, and stacking are tested or an architectural limitation is documented.
- Effect masks are tested at the injection site and in decoded output.
- Model-specific inputs use a narrow family boundary, not shared Apply-node expansion.
-
get_models,copy, cleanup, dtype/device handling, and repeated runs are verified. - Missing required inputs produce actionable errors.
- Simple workflows, input assets, download links, screenshots, and exact reproduction instructions are included.
- Real frontend/API execution, compile checks, JSON parsing, and
git diff --checkpass.