Strictly speaking, itâs not a hub bug, but a problem that occurred with the hubâŚ?
This is not your local .ckpt âfailing to read.â It is Diffusers trying to fetch extra configuration from the Hub while loading the checkpoint, and that fetch is failing because stabilityai/stable-diffusion-2-1 is now not publicly reachable (removed, made private, or gated). Your traceback shows Diffusers (via huggingface_hub) requesting:
https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/config.json
and receiving 401 Unauthorized.
That pattern is common with Diffusers âsingle fileâ loaders: a .ckpt/.safetensors contains weights, not a complete, self-describing âDiffusers config,â so Diffusers sometimes looks up a base model repo to reconstruct the architecture. When that repo cannot be accessed, you get a network error even though your weights are local. This exact failure mode is discussed in Diffusers issue threads where from_single_file() tries to download .../resolve/main/config.json from a base SD repo and gets 404/401. (GitHub)
Why Diffusers is contacting stabilityai/stable-diffusion-2-1 at all
1) A checkpoint file is âjust parametersâ
A .ckpt is typically a PyTorch pickle-based checkpoint holding tensors (and sometimes other objects). It generally does not contain everything needed to rebuild the model class (layer counts, channel multipliers, attention dims, etc.). Diffusers therefore needs a config from somewhere. (Hugging Face)
2) For SD2.x ControlNet, the config is meaningfully different
Your repo alfredplpl/ControlNetForSD2 includes a YAML config control_picasso11_openpose.yaml alongside the checkpoint. The YAML shows context_dim: 1024 and an OpenCLIP text embedder, which is characteristic of SD2-style conditioning. (Hugging Face)
So Diffusersâ loader heuristics decide âthis looks like an SD2.x-derived ControlNet,â then it tries to anchor itself to the canonical SD2.1 base repo (in your case stabilityai/stable-diffusion-2-1) to find config. That remote anchor is whatâs breaking.
3) The base repo being âgoneâ is now a real-world issue
Your linked HF forum thread (Nov 14, 2025) reports that stabilityai/stable-diffusion-2, stabilityai/stable-diffusion-2-1-base, and stabilityai/stable-diffusion-2-1 return 404 for users, with discussion that it may be retracted or made private. (Hugging Face Forums)
When a repo is private or gated, clients often see 401 (Unauthorized) unless properly authenticated, and sometimes platforms intentionally blur ânot found vs not allowed.â HF staff have explained 401 commonly means ânot authenticatedâ and the model is private or doesnât exist. (Hugging Face Forums)
Root causes (what can produce this exact 401)
You have a few plausible causes. In your case it is likely a combination of (A) and (B):
A) The base repo is no longer publicly accessible
If stabilityai/stable-diffusion-2-1 was removed or made private, any attempt to fetch config.json from it will fail. Users reported exactly this disappearance in Nov 2025. (Hugging Face Forums)
B) You are not authenticated (or your script isnât using your token)
Even if a repo exists, gated/private repos require an access token. A very common pitfall: you are logged into the website in your browser, but your Python environment is not authenticated, so the hub download gets 401. HF forum guidance explicitly ties 401 to missing auth when the model is private or missing. (Hugging Face Forums)
There are also cases where people believe they are logged in, but environment variables or cache locations make the CLI token invisible to the runtime. (GitHub)
C) from_single_file() is pulling config from the wrong place (known Diffusers behavior)
There are open/closed Diffusers issues where ControlNetModel.from_single_file() succeeds for some files but fails for others because it tries to fetch a base SD repoâs config.json. (GitHub)
This is not âyour fault,â it is how the loader is designed when it cannot fully infer architecture from the checkpoint alone.
Solutions that work (ordered by practicality)
Solution 1 (best): Provide the original YAML config that ships with your checkpoint
Your ControlNet repo already includes control_picasso11_openpose.yaml. (Hugging Face)
If you pass that YAML to Diffusers, it does not need to fetch the base model config from stabilityai/stable-diffusion-2-1.
Diffusers supports an original_config_file argument for single-file loading in multiple contexts (maintainers explicitly recommend it for offline and single-file loads). (GitHub)
Example:
# deps:
# pip install -U diffusers huggingface_hub omegaconf safetensors torch
from diffusers import ControlNetModel
import torch
controlnet = ControlNetModel.from_single_file(
"./models/picasso11OpenPose/control_picasso11_openpose.ckpt",
original_config_file="./models/picasso11OpenPose/control_picasso11_openpose.yaml",
torch_dtype=torch.float16,
local_files_only=True, # prevents surprise hub fetches
)
controlnet.save_pretrained("./controlnet_picasso11_openpose_diffusers")
Why this works:
- The YAML fully describes the original architecture (channels, blocks, context_dim, etc.). (Hugging Face)
local_files_only=True ensures Diffusers wonât quietly reach for Hub files. This aligns with Diffusers maintainer guidance for offline/single-file usage. (GitHub)
If you still see a hub call after adding original_config_file, it usually means the loader is trying to fetch some other component. For ControlNetModel alone, it generally should not need text encoders or tokenizers, so a remaining hub fetch is a hint something is misrouted (wrong API, wrong class, or a bug).
Solution 2 (most robust): Convert using the official conversion script
Diffusers themselves recommend converting .ckpt to Diffusers format using the script, and they emphasize original_config_file as a key argument. (Hugging Face)
Conceptually:
from_single_file() is convenient but can be fragile with custom checkpoints.
- The conversion script is more deterministic and produces a standard Diffusers folder with
config.json, weights, etc.
Typical flow (ControlNet conversion):
# clone diffusers
git clone https://github.com/huggingface/diffusers
cd diffusers
python scripts/convert_original_stable_diffusion_to_diffusers.py ^
--checkpoint_path "C:\path\to\control_picasso11_openpose.ckpt" ^
--original_config_file "C:\path\to\control_picasso11_openpose.yaml" ^
--dump_path "C:\path\to\output\controlnet_diffusers" ^
--controlnet
This is exactly the pattern the docs and forum recommend: pass --checkpoint_path, --original_config_file, and --dump_path, plus --controlnet. (Hugging Face)
After that:
from diffusers import ControlNetModel
controlnet = ControlNetModel.from_pretrained("C:/path/to/output/controlnet_diffusers")
Solution 3: Authenticate, if the base model is merely gated (not actually removed)
If stabilityai/stable-diffusion-2-1 still exists but is gated, you need:
- access approved on the model page
- a token
- your Python environment actually using that token
A quick diagnostic is to try downloading just the config with huggingface_hub. If this returns 401, your runtime is not authorized:
from huggingface_hub import hf_hub_download
hf_hub_download("stabilityai/stable-diffusion-2-1", "config.json")
Background: HF staff have repeatedly explained 401 is what you see when you are not authenticated and the model is private or missing. (Hugging Face Forums)
And there are known cases where people âlogged inâ but their token is invalid/ignored due to environment/caching differences. (GitHub)
That said, your forum link suggests SD2.x repos may be gone from the Hub for many users, so authentication may not fix it. (Hugging Face Forums)
Solution 4: Use a different base SD2.x repo (local or community-hosted) for inference
Important distinction:
- Converting/loading the ControlNet weights is one problem.
- Actually running generation requires a base SD2.x pipeline (UNet, VAE, text encoder).
If the official SD2.1 repos are unavailable, you need an alternative base model source (local copy you already downloaded, or another host). The forum thread even lists unofficial mirrors, but you need to handle licensing and long-term availability yourself. (Hugging Face Forums)
âSame behavior on latest diffusers?â
Very likely yes, unless you change inputs (provide the YAML or convert).
The latest PyPI release as of Dec 8, 2025 is diffusers 0.36.0. (PyPI)
But the key problem is not âan old diffusers bug.â It is the loaderâs dependency on remote config when it cannot fully resolve architecture locally. That behavior is documented and shows up in issues across versions. (Hugging Face)
So: upgrading alone rarely fixes âmissing config.json from a base repo.â Supplying original_config_file or converting does.
Similar cases online (same failure pattern, different repo IDs)
These are worth reading because they match your situation closely:
- Diffusers #9208:
ControlNetModel.from_single_file() fails on some ControlNet weights because it tries to download a base SD repo config.json and gets 404. Same mechanism as your 401. (GitHub)
- Diffusers #7935: explicit issue title about
stabilityai/stable-diffusion-2-1 âdoes not appear to have a file named config.json.â (GitHub)
- HF forum (Nov 2025): SD2.x repos returning 404, with discussion that the models may be retracted or private. (Hugging Face Forums)
- Diffusers #7209: offline
from_single_file() fails because additional config is needed; references original_config_file in code. (GitHub)
- Diffusers discussion #6836: maintainer guidance for offline
from_single_file(), including needing original_config_file. (GitHub)
Good references: papers, guides, and âhow-toâ material (relevant to your case)
Core papers (background)
- ControlNet paper: explains why ControlNet is an adapter that reuses a base diffusion backbone. This is the conceptual reason you need the base model architecture right. (arXiv)
- Latent Diffusion Models (LDM): the backbone concept that Stable Diffusion and many ControlNet checkpoints build on. (arXiv)
Practical guides and official docs (what to do)
- Diffusers: âLoad different Stable Diffusion formatsâ: recommends conversion scripts, emphasizes
.safetensors security, and highlights original_config_file as critical. (Hugging Face)
- HF forum: âHow to convert ckpt to diffusers formatâ: canonical step-by-step and the exact key flags. (Hugging Face Forums)
- Diffusers ControlNet docs: pipeline usage and model structure context. (Hugging Face)
Implementation detail (why config lookups happen)
- Diffusers code contains mappings to locate canonical YAML configs and detect checkpoint types (
CONFIG_URLS, key heuristics). This is part of how âsingle-fileâ loading decides what it is looking at. (Hugging Face)
What I would do in your exact case
-
Stop relying on stabilityai/stable-diffusion-2-1 entirely for conversion. Your ControlNet repo already includes the correct YAML config. (Hugging Face)
-
Load with:
original_config_file=...control_picasso11_openpose.yaml
local_files_only=True
That directly addresses the âhub fetch for config.jsonâ path. (GitHub)
-
Save the converted model via save_pretrained().
-
Separately solve the base model availability problem for inference. If official SD2.1 repos are truly gone, you need a local base model or an alternative host, and you should expect any code that hardcodes stabilityai/stable-diffusion-2-1 to break until updated. (Hugging Face Forums)
Quick summary
- Your 401 happens because
from_single_file() is trying to download config.json from stabilityai/stable-diffusion-2-1, not because your local .ckpt is unreadable. (GitHub)
- SD2.x repos appear to be unavailable on the Hub for many users as of Nov 2025. (Hugging Face Forums)
- Fix it by supplying the local YAML config (
original_config_file=...yaml) or converting with the official script. (Hugging Face)
- Upgrading Diffusers alone usually does not help because the failure is triggered by a missing/inaccessible remote dependency. (PyPI)