Skip to content

feat: hot-reload ControlNet - swap without rebuilding the context#1768

Merged
leejet merged 3 commits into
leejet:masterfrom
fszontagh:feat/hot-reload-controlnet
Jul 10, 2026
Merged

feat: hot-reload ControlNet - swap without rebuilding the context#1768
leejet merged 3 commits into
leejet:masterfrom
fszontagh:feat/hot-reload-controlnet

Conversation

@fszontagh

@fszontagh fszontagh commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What

Adds three C API entry points so a long-lived process can swap the ControlNet weights on an existing sd_ctx_t without tearing it down:

SD_API bool sd_ctx_load_control_net(sd_ctx_t*, const char* path);
SD_API bool sd_ctx_unload_control_net(sd_ctx_t*);
SD_API bool sd_ctx_has_control_net(const sd_ctx_t*);

sd_ctx_load_control_net acts as both first-load and replace. Reuses the same construction path as --control-net, so hot-loaded ControlNets are indistinguishable at runtime.

Why

Loading the diffusion model and text encoders takes tens of seconds. Switching ControlNet (canny to depth, etc.) shouldn't require rebuilding the ctx.

How

  • StableDiffusionGGML gains load_control_net_from_file(path) and unload_control_net() sharing the existing construction code.
  • ModelManager gains unregister_param_tensors(desc) so re-registering the same tensor names does not collide.
  • diffusion_conv_direct and control_net_params_mem_size promoted from locals to members so the reload path honors the ctx's original settings.

The helpers are shaped so the same pattern generalizes to other module families (VAE, LoRA, upscaler) later; only ControlNet is exposed here.

Caveats

  • Not thread-safe against generation in flight. The caller serializes, same as the rest of sd_ctx_*.
  • The ControlNet must be compatible with the base model. Version detection is delegated to the existing ControlNet ctor.

Verification

  • sd-cli --control-net ... init path unchanged.
  • Standalone smoke test cycles load / swap / unload / reload / free cleanly.

Adds three new C API entry points on stable-diffusion.h so a long-lived process
(server / GUI / integration) can swap the ControlNet weights without paying the
cost of tearing down the ctx and reloading the base diffusion / VAE / text
encoders (multi-GB, tens of seconds):

    SD_API bool sd_ctx_load_control_net(sd_ctx_t*, const char* path);
    SD_API bool sd_ctx_unload_control_net(sd_ctx_t*);
    SD_API bool sd_ctx_has_control_net(const sd_ctx_t*);

Implementation:

- StableDiffusionGGML gains load_control_net_from_file(path) and
  unload_control_net() helpers. They reuse the existing ControlNet construction
  path (backend pair, conv2d-direct setting, register_runner_params), so hot-
  loaded ControlNets are indistinguishable at runtime from init-time ones.
- ModelManager gains unregister_param_tensors(desc) which releases both the
  compute and params backend buffers for a runner's tensors and removes them
  from tensor_states_by_name_ / tensor_states_, so a subsequent register with
  the same tensor names does not collide.
- diffusion_conv_direct + control_net_params_mem_size are promoted from
  local-scope in the initial-load path to StableDiffusionGGML members so that
  the reload path can honor the ctx's original conv-direct setting and keep the
  memory bookkeeping consistent.
- The three C API functions are thin wrappers over those helpers, matching the
  existing sd_ctx_* pattern in stable-diffusion.h.

Doc caveats in the header note that this API is intended for use when no
generation is in flight — thread safety is the caller's responsibility, same as
the rest of the sd_ctx API surface.

Smoke test (create ctx w/o ControlNet, load canny, swap to depth, unload,
reload canny, free) exits cleanly with correct has_control_net transitions.
Existing --control-net init-time path is unchanged (regression-tested with
sd-cli).

Motivation is to unblock two follow-ups:
1. Servers can offer a ControlNet dropdown without a full model reload.
2. The same pattern generalizes to other module families (LoRA, VAE, upscaler);
   the private helpers were factored with that extension in mind.
@fszontagh fszontagh changed the title feat: hot-reload ControlNet — swap without rebuilding the context feat: hot-reload ControlNet - swap without rebuilding the context Jul 10, 2026
@leejet leejet merged commit 12b6fbf into leejet:master Jul 10, 2026
11 checks passed
@leejet

leejet commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Thank you for your contribution.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants