Skip to content

Do not mutate the source transformer config in SD3ControlNetModel.from_transformer - #14672

Open
Devam0311 wants to merge 1 commit into
huggingface:mainfrom
Devam0311:sd3-controlnet-from-transformer-config
Open

Do not mutate the source transformer config in SD3ControlNetModel.from_transformer#14672
Devam0311 wants to merge 1 commit into
huggingface:mainfrom
Devam0311:sd3-controlnet-from-transformer-config

Conversation

@Devam0311

@Devam0311 Devam0311 commented Sep 1, 2026

Copy link
Copy Markdown

Issue link: this addresses Issue 4 of #13611.
I have deliberately not used a Fixes keyword: #13611 tracks 6 separate findings, so a closing keyword would close it as soon as this one merges while the others are still open. Happy for the no-issue-needed label to be applied, or I can add a closing keyword if you would rather the review issue be closed and reopened.

What does this PR do?

Fixes Issue 4 of #13611 (stable_diffusion_3 model/pipeline review).

SD3ControlNetModel.from_transformer bound config = transformer.config, which aliases the transformer's live config, and then wrote ControlNet-specific values into it. Building a ControlNet therefore mutated the transformer it was built from.

Reproduced on main:

before: num_layers = 3 | has extra_conditioning_channels = False
after : num_layers = 1 | extra_conditioning_channels = 2

The source transformer's num_layers is overwritten and an unrelated extra_conditioning_channels key is injected, which can corrupt later serialization, logging, or any pipeline construction that reuses that transformer.

Solution

Copy the config before mutating it — which is what the sibling ControlNets already do:

Model
controlnet_flux.py:135 config = dict(transformer.config)
controlnet_qwenimage.py:113 config = dict(transformer.config)
controlnet_sd3.py:257 config = transformer.config ← fixed here
config = dict(transformer.config)
config["num_layers"] = num_layers or transformer.config.num_layers

The num_layers fallback now reads from transformer.config rather than the copied dict, since a plain dict has no attribute access.

I also checked controlnet_hunyuan.py:177, which binds the config the same way but only ever reads attributes from it and never writes, so it is not affected and is left alone.

Testing

New tests/models/controlnets/test_models_controlnet_sd3.py:

  • test_from_transformer_does_not_mutate_source_config — the regression itself
  • test_from_transformer_applies_controlnet_config — the overrides still reach the ControlNet
  • test_from_transformer_num_layers_falls_back_to_transformer — covers the num_layers=None path, which the rewritten fallback touches

Verified the regression test actually catches the bug: with the fix reverted it fails, with it applied it passes.

pytest tests/models/controlnets/test_models_controlnet_sd3.py   -> 3 passed
pytest tests/pipelines/controlnet_sd3/                          -> all passed
ruff check / ruff format --check                                -> clean
git diff --check                                                -> clean

Note: tests/models/controlnets/test_models_controlnet_cosmos.py has 7 failures, but they reproduce identically on a clean checkout of main here, so they are unrelated to this change.

Before submitting

Self-review notes

Reviewed against .ai/references/review-rules.md, models.md and testing.md. No blocking issues.

For the reviewer:

  1. tests/models/controlnets/ previously held only the Cosmos file, and there were no from_transformer tests anywhere in the suite. I added a small focused file rather than wiring up the full BaseModelTesterConfig mixin suite, which felt like scope creep for a one-line fix — happy to expand it if you'd rather it be a full model tester.
  2. Scope kept to Issue 4. stable_diffusion_3 model/pipeline review #13611 lists six issues; Issue 1 is in Apply the VAE shift factor when decoding in SD3 inpaint #14671, and the rest are untouched.
  3. This is behaviour-preserving for the returned ControlNet — only the side effect on the source transformer changes.

Who can review?

@yiyixuxu @DN6 @hlky

…m_transformer

`from_transformer` bound `config = transformer.config`, aliasing the
transformer's live config, then wrote `num_layers` and
`extra_conditioning_channels` into it. Building a ControlNet therefore
silently changed the source transformer, which can corrupt later
serialization, logging, or pipeline construction that reuses it.

Copy the config first, as FluxControlNetModel and QwenImageControlNetModel
already do.

Add regression tests covering the config staying untouched, the ControlNet
receiving the overrides, and `num_layers=None` falling back to the
transformer's value.

Ref huggingface#13611 (Issue 4)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XBYeq5vB4DNZDEaqUsroKR
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Hi @Devam0311, thanks for the PR! It does not appear to link an issue it fixes. If this PR addresses an existing issue, please add a closing keyword (e.g. Fixes #1234) to the PR description so the issue is linked. See the contribution guide for more details. If this PR intentionally does not fix a tracked issue, a maintainer can add the no-issue-needed label to silence this reminder.

Please note that PRs without a linked issue are likely to be automatically closed 10 days after this notice.

Once the PR links an issue (or gets the no-issue-needed label), you can ignore this message — it stays here as a comment, but it no longer applies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

models size/M PR with diff < 200 LOC tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant