mirror of
https://github.com/AUTOMATIC1111/stable-diffusion-webui.git
synced 2026-03-22 22:30:45 -07:00
ci(M01): replace manual LDM stubs with dynamic stub module
Made-with: Cursor
This commit is contained in:
parent
9a83c70e6c
commit
bdda999f81
4 changed files with 143 additions and 105 deletions
60
docs/milestones/M01/M01_CI_report.md
Normal file
60
docs/milestones/M01/M01_CI_report.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# M01 CI Report — 2026-03-08
|
||||
|
||||
**Branch:** m01-ci-truthfulness
|
||||
**Latest commit:** 9a83c70e
|
||||
**Report generated:** 2026-03-08
|
||||
|
||||
---
|
||||
|
||||
## 1. CI Status
|
||||
|
||||
| Workflow | Run ID | Status |
|
||||
|----------|--------|--------|
|
||||
| Linter | 22812569761 | ✓ PASS |
|
||||
| Tests | 22812569762 | ✗ FAIL |
|
||||
|
||||
---
|
||||
|
||||
## 2. Test Failure
|
||||
|
||||
**Root cause:** Server startup fails before binding to port 7860.
|
||||
|
||||
**Error (from output.txt):**
|
||||
```
|
||||
ModuleNotFoundError: No module named 'ldm.models.diffusion.plms'
|
||||
File "modules/sd_hijack.py", line 15, in <module>
|
||||
import ldm.models.diffusion.plms
|
||||
```
|
||||
|
||||
**Effect:** `wait-for-it` times out (20s); pytest never runs.
|
||||
|
||||
---
|
||||
|
||||
## 3. Stub Progression
|
||||
|
||||
| Step | Blocker | Fix applied |
|
||||
|------|---------|-------------|
|
||||
| 1 | paths.py assert | ddpm.py |
|
||||
| 2 | LatentDiffusion | ddpm classes |
|
||||
| 3 | ldm.util | default() |
|
||||
| 4 | ldm.modules.midas | midas/ |
|
||||
| 5 | ldm.modules.distributions | DiagonalGaussianDistribution |
|
||||
| 6 | ldm.modules.diffusionmodules.openaimodel | openaimodel.py |
|
||||
| 7 | sgm.* | sgm stubs |
|
||||
| 8 | k_diffusion.* | external, utils, sampling |
|
||||
| 9 | ldm.models.diffusion.ddim | ddim.py |
|
||||
| 10 | **ldm.models.diffusion.plms** | **Next fix** |
|
||||
|
||||
---
|
||||
|
||||
## 4. Fix Applied
|
||||
|
||||
**Dynamic stub module** (commit in progress): MetaPathFinder + _StubModule for ldm and sgm. Resolves any nested import without individual files.
|
||||
|
||||
---
|
||||
|
||||
## 5. Links
|
||||
|
||||
- **PR:** (create when ready to merge)
|
||||
- **Linter run:** https://github.com/m-cahill/serena/actions/runs/22812569761
|
||||
- **Tests run:** https://github.com/m-cahill/serena/actions/runs/22812569762
|
||||
|
|
@ -10,8 +10,8 @@
|
|||
|
||||
| Workflow | Latest Run | Status |
|
||||
|----------|------------|--------|
|
||||
| Linter | 22812483655 | ✓ success |
|
||||
| Tests | 22812483662 | ✗ failure (iterating) |
|
||||
| Linter | 22812569761 | ✓ success |
|
||||
| Tests | 22812569762 | ✗ failure |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -69,6 +69,13 @@ repositories/
|
|||
|
||||
---
|
||||
|
||||
## 5. Status
|
||||
## 5. Dynamic Stub Approach (Commit 9a83c70e+)
|
||||
|
||||
Iterative stub addition continues. Each CI run reveals the next missing import. Latest commit: f013e553 (explicit ldm.modules.diffusionmodules import).
|
||||
Replaced manual file-by-file stubs with **dynamic stub modules**:
|
||||
|
||||
- `_StubFinder` (MetaPathFinder): catches any `ldm.*` or `sgm.*` import the default finder misses
|
||||
- `_StubModule`: resolves attributes as submodules or stub classes
|
||||
- Keeps `ddpm.py` for paths.py assertion and LatentDiffusion
|
||||
- Keeps k_diffusion file-based (needs real get_sigmas_*, torch, etc.)
|
||||
|
||||
Eliminates whack-a-mole import chain.
|
||||
|
|
|
|||
|
|
@ -37,11 +37,15 @@ With `--skip-prepare-environment`, no repos are cloned. The app expects `reposit
|
|||
- paths.py assertion (ddpm.py)
|
||||
- LatentDiffusion, LatentDepth2ImageDiffusion
|
||||
- ldm.util.default
|
||||
- ldm.modules.attention, diffusionmodules.model, midas
|
||||
- ldm.modules.attention, diffusionmodules (model, openaimodel), midas, distributions
|
||||
- ldm.models.diffusion.ddim
|
||||
- sgm.modules.encoders, attention, diffusionmodules
|
||||
- sgm.models.diffusion (DiffusionEngine)
|
||||
- sgm.modules.diffusionmodules.denoiser_scaling, discretizer
|
||||
- sgm.modules.GeneralConditioner, openaimodel
|
||||
- k_diffusion (utils, external, sampling)
|
||||
|
||||
**Fix applied:** Dynamic stub module (MetaPathFinder) for ldm and sgm.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
Create minimal stub repositories for CI.
|
||||
Satisfies paths.py assertion and import chain without cloning external repos.
|
||||
Deterministic, no network required.
|
||||
|
||||
Uses dynamic stub modules for ldm and sgm to avoid whack-a-mole import chain.
|
||||
"""
|
||||
import os
|
||||
|
||||
|
|
@ -17,114 +19,79 @@ def touch(path: str, content: str = "") -> None:
|
|||
f.write(content)
|
||||
|
||||
|
||||
DYNAMIC_STUB = '''"""Dynamic stub module for CI - satisfies any nested import."""
|
||||
import types
|
||||
import sys
|
||||
import importlib.abc
|
||||
import importlib.machinery
|
||||
|
||||
class _StubModule(types.ModuleType):
|
||||
"""Resolves any attribute as submodule or stub class."""
|
||||
|
||||
def __getattr__(self, name):
|
||||
module_name = f"{self.__name__}.{name}"
|
||||
if module_name not in sys.modules:
|
||||
if name and name[0].isupper():
|
||||
sys.modules[module_name] = type(name, (), {})
|
||||
else:
|
||||
m = _StubModule(module_name)
|
||||
m.__call__ = lambda *a, **k: None
|
||||
sys.modules[module_name] = m
|
||||
return sys.modules[module_name]
|
||||
|
||||
class _StubFinder(importlib.abc.MetaPathFinder):
|
||||
def __init__(self, prefix):
|
||||
self.prefix = prefix
|
||||
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
if fullname.startswith(self.prefix + "."):
|
||||
return importlib.machinery.ModuleSpec(fullname, _StubLoader())
|
||||
return None
|
||||
|
||||
class _StubLoader(importlib.abc.Loader):
|
||||
def create_module(self, spec):
|
||||
return None
|
||||
|
||||
def exec_module(self, module):
|
||||
pass
|
||||
|
||||
# Append finder so default finders run first; we catch modules they miss
|
||||
def _install_finder():
|
||||
for prefix in ("ldm", "sgm"):
|
||||
sys.meta_path.append(_StubFinder(prefix))
|
||||
|
||||
_install_finder()
|
||||
|
||||
original = sys.modules[__name__]
|
||||
stub = _StubModule(__name__)
|
||||
if "__file__" in original.__dict__:
|
||||
stub.__file__ = original.__file__
|
||||
if "__path__" in original.__dict__:
|
||||
stub.__path__ = original.__path__
|
||||
sys.modules[__name__] = stub
|
||||
'''
|
||||
|
||||
|
||||
def main() -> None:
|
||||
sd = "stable-diffusion-stability-ai"
|
||||
# paths.py asserts ldm/models/diffusion/ddpm.py; sd_models_types imports LatentDiffusion
|
||||
|
||||
# paths.py asserts ldm/models/diffusion/ddpm.py exists
|
||||
ddpm_content = (
|
||||
"# stub for CI\n"
|
||||
"# stub for CI - paths.py assertion + LatentDiffusion import\n"
|
||||
"class LatentDiffusion:\n pass\n"
|
||||
"class LatentDepth2ImageDiffusion(LatentDiffusion):\n pass\n"
|
||||
)
|
||||
touch(os.path.join(REPOS, sd, "ldm", "models", "diffusion", "ddpm.py"), ddpm_content)
|
||||
touch(os.path.join(REPOS, sd, "ldm", "models", "diffusion", "ddim.py"), "# stub\n")
|
||||
# ldm.util: default, instantiate_from_config, ismap, etc. (sd_hijack_optimizations, etc.)
|
||||
touch(os.path.join(REPOS, sd, "ldm", "util.py"), "def default(a, b): return b if a is None else a\n")
|
||||
touch(os.path.join(REPOS, sd, "ldm", "__init__.py"))
|
||||
touch(
|
||||
os.path.join(REPOS, sd, "ldm", "modules", "__init__.py"),
|
||||
"from . import distributions, diffusionmodules\n",
|
||||
)
|
||||
touch(os.path.join(REPOS, sd, "ldm", "modules", "encoders", "__init__.py"))
|
||||
# ldm.modules.encoders.modules: FrozenCLIPEmbedder, FrozenOpenCLIPEmbedder, CLIPTextModel
|
||||
ldm_modules = (
|
||||
"class FrozenCLIPEmbedder:\n pass\n"
|
||||
"class FrozenOpenCLIPEmbedder:\n pass\n"
|
||||
"class CLIPTextModel:\n pass\n"
|
||||
)
|
||||
touch(os.path.join(REPOS, sd, "ldm", "modules", "encoders", "modules.py"), ldm_modules)
|
||||
# ldm.modules.attention, diffusionmodules.model (sd_hijack_optimizations)
|
||||
touch(
|
||||
os.path.join(REPOS, sd, "ldm", "modules", "attention", "__init__.py"),
|
||||
"class CrossAttention:\n def forward(self, *a, **k): pass\n",
|
||||
)
|
||||
touch(
|
||||
os.path.join(REPOS, sd, "ldm", "modules", "diffusionmodules", "__init__.py"),
|
||||
"from . import model, openaimodel\n",
|
||||
)
|
||||
touch(
|
||||
os.path.join(REPOS, sd, "ldm", "modules", "diffusionmodules", "model.py"),
|
||||
"class AttnBlock:\n def forward(self, *a, **k): pass\n",
|
||||
)
|
||||
touch(
|
||||
os.path.join(REPOS, sd, "ldm", "modules", "diffusionmodules", "openaimodel.py"),
|
||||
"# stub\n",
|
||||
)
|
||||
# ldm.modules.midas (sd_models)
|
||||
touch(os.path.join(REPOS, sd, "ldm", "modules", "midas", "__init__.py"))
|
||||
# ldm.modules.distributions.distributions (textual_inversion.dataset)
|
||||
touch(os.path.join(REPOS, sd, "ldm", "modules", "distributions", "__init__.py"))
|
||||
touch(
|
||||
os.path.join(REPOS, sd, "ldm", "modules", "distributions", "distributions.py"),
|
||||
"class DiagonalGaussianDistribution:\n pass\n",
|
||||
)
|
||||
# Dynamic stubs at each package level so Python loads them (not namespace pkgs)
|
||||
touch(os.path.join(REPOS, sd, "ldm", "__init__.py"), DYNAMIC_STUB)
|
||||
touch(os.path.join(REPOS, sd, "ldm", "models", "__init__.py"), DYNAMIC_STUB)
|
||||
touch(os.path.join(REPOS, sd, "ldm", "models", "diffusion", "__init__.py"), DYNAMIC_STUB)
|
||||
|
||||
# generative-models: sgm.modules.encoders.modules
|
||||
# generative-models: paths.py checks sgm exists
|
||||
gm = "generative-models"
|
||||
touch(os.path.join(REPOS, gm, "sgm", "__init__.py"))
|
||||
touch(os.path.join(REPOS, gm, "sgm", "modules", "__init__.py"))
|
||||
touch(os.path.join(REPOS, gm, "sgm", "modules", "encoders", "__init__.py"))
|
||||
sgm_modules = (
|
||||
"class FrozenCLIPEmbedder:\n pass\n"
|
||||
"class FrozenOpenCLIPEmbedder2:\n pass\n"
|
||||
"class ConcatTimestepEmbedderND:\n pass\n"
|
||||
)
|
||||
touch(os.path.join(REPOS, gm, "sgm", "modules", "encoders", "modules.py"), sgm_modules)
|
||||
# sgm.modules.attention, diffusionmodules.model (sd_hijack_optimizations)
|
||||
touch(
|
||||
os.path.join(REPOS, gm, "sgm", "modules", "attention", "__init__.py"),
|
||||
"class CrossAttention:\n def forward(self, *a, **k): pass\n"
|
||||
"\nSDP_IS_AVAILABLE = True\nXFORMERS_IS_AVAILABLE = False\n",
|
||||
)
|
||||
touch(
|
||||
os.path.join(REPOS, gm, "sgm", "modules", "diffusionmodules", "__init__.py"),
|
||||
"from . import model, openaimodel\n",
|
||||
)
|
||||
touch(
|
||||
os.path.join(REPOS, gm, "sgm", "modules", "diffusionmodules", "model.py"),
|
||||
"class AttnBlock:\n def forward(self, *a, **k): pass\n",
|
||||
)
|
||||
# sgm.models.diffusion (sd_models_xl)
|
||||
touch(os.path.join(REPOS, gm, "sgm", "models", "__init__.py"))
|
||||
touch(
|
||||
os.path.join(REPOS, gm, "sgm", "models", "diffusion", "__init__.py"),
|
||||
"class DiffusionEngine:\n pass\n",
|
||||
)
|
||||
# sgm.modules.diffusionmodules.denoiser_scaling, discretizer (sd_models_xl)
|
||||
touch(
|
||||
os.path.join(REPOS, gm, "sgm", "modules", "diffusionmodules", "denoiser_scaling.py"),
|
||||
"class VScaling:\n pass\n",
|
||||
)
|
||||
touch(
|
||||
os.path.join(REPOS, gm, "sgm", "modules", "diffusionmodules", "discretizer.py"),
|
||||
"class LegacyDDPMDiscretization:\n alphas_cumprod = [1.0]\n",
|
||||
)
|
||||
# sgm.modules.GeneralConditioner (sd_models_xl)
|
||||
touch(os.path.join(REPOS, gm, "sgm", "modules", "__init__.py"))
|
||||
touch(
|
||||
os.path.join(REPOS, gm, "sgm", "modules", "conditioner.py"),
|
||||
"class GeneralConditioner:\n pass\n",
|
||||
)
|
||||
touch(
|
||||
os.path.join(REPOS, gm, "sgm", "modules", "__init__.py"),
|
||||
"from .conditioner import GeneralConditioner\n"
|
||||
"from . import attention, diffusionmodules, encoders\n",
|
||||
)
|
||||
touch(
|
||||
os.path.join(REPOS, gm, "sgm", "modules", "diffusionmodules", "openaimodel.py"),
|
||||
"# stub\n",
|
||||
)
|
||||
touch(os.path.join(REPOS, gm, "sgm", "__init__.py"), DYNAMIC_STUB)
|
||||
|
||||
# k-diffusion: k_diffusion.sampling, utils (sd_schedulers, sd_samplers_lcm)
|
||||
# k-diffusion: paths.py checks k_diffusion/sampling.py; needs real attrs
|
||||
kd = "k-diffusion"
|
||||
touch(
|
||||
os.path.join(REPOS, kd, "k_diffusion", "__init__.py"),
|
||||
|
|
@ -151,10 +118,10 @@ def main() -> None:
|
|||
)
|
||||
touch(os.path.join(REPOS, kd, "k_diffusion", "sampling.py"), kd_sampling)
|
||||
|
||||
# BLIP: models/blip.py
|
||||
# BLIP: paths.py checks models/blip.py
|
||||
touch(os.path.join(REPOS, "BLIP", "models", "blip.py"), "# stub\n")
|
||||
|
||||
# stable-diffusion-webui-assets (optional, paths may warn)
|
||||
# stable-diffusion-webui-assets (optional)
|
||||
touch(os.path.join(REPOS, "stable-diffusion-webui-assets", ".gitkeep"))
|
||||
|
||||
print("Stub repositories created.")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue