feat(pipelines): wire native_spec for Qwen-Image and custom-class arches

Qwen-Image: diffusers registers a no-op identity lambda for
QwenImageTransformer2DModel; from_single_file silently accepted any
key naming and loaded with mismatches. QWEN_SPEC sets converter=None
to short-circuit the no-op.

Custom transformer classes (Lens, Step1X-Edit, VIBE Sana, f-lite):
runtime-registered onto diffusers, never in SINGLE_FILE_LOADABLE_CLASSES.
Specs land in each arch's existing pipelines/<arch>/__init__.py.

NextStep skipped: loader returns None up front.
This commit is contained in:
CalamitousFelicitousness
2026-05-25 05:47:56 +01:00
parent e302518d73
commit 3368ce95f7
10 changed files with 52 additions and 5 deletions
+5 -1
View File
@@ -1,5 +1,9 @@
from pipelines.native_transformer import TransformerSpec
from .pipeline import FLitePipeline, FLitePipelineOutput, APGConfig
from .model import DiT
__all__ = ["APGConfig", "DiT", "FLitePipeline", "FLitePipelineOutput"]
FLITE_SPEC = TransformerSpec(cls=DiT, subfolder='dit_model')
__all__ = ["APGConfig", "DiT", "FLITE_SPEC", "FLitePipeline", "FLitePipelineOutput"]
+5
View File
@@ -2,6 +2,7 @@
import diffusers as _diffusers
import transformers as _transformers
from pipelines.native_transformer import TransformerSpec
from .pipeline import LensPipeline, LensPipelineOutput
from .pipeline_image import LensImg2ImgPipeline, LensInpaintPipeline
from .reasoner import PromptReasoner
@@ -9,6 +10,9 @@ from .resolution import RESOLUTION_BUCKETS, resolve_resolution
from .text_encoder import LensGptOssEncoder
from .transformer import LensTransformer2DModel
LENS_SPEC = TransformerSpec(cls=LensTransformer2DModel)
# ---------------------------------------------------------------------------
# Make our custom subclasses discoverable by ``diffusers.DiffusionPipeline``.
#
@@ -38,6 +42,7 @@ if not hasattr(_diffusers, "LensInpaintPipeline"):
del _diffusers, _transformers
__all__ = [
"LENS_SPEC",
"LensPipeline",
"LensPipelineOutput",
"LensImg2ImgPipeline",
+1 -1
View File
@@ -19,7 +19,7 @@ def load_flite(checkpoint_info, diffusers_load_config=None):
diffusers.FLitePipeline = f_lite.FLitePipeline
sys.modules['f_lite'] = f_lite
dit_model = generic.load_transformer(repo_id, cls_name=f_lite.DiT, load_config=diffusers_load_config, subfolder="dit_model")
dit_model = generic.load_transformer(repo_id, cls_name=f_lite.DiT, load_config=diffusers_load_config, subfolder="dit_model", native_spec=f_lite.FLITE_SPEC)
text_encoder = generic.load_text_encoder(repo_id, cls_name=transformers.T5EncoderModel, load_config=diffusers_load_config, subfolder="text_encoder")
pipe = f_lite.FLitePipeline.from_pretrained(
+1 -1
View File
@@ -15,7 +15,7 @@ def load_lens(checkpoint_info, diffusers_load_config=None):
load_args, _quant_args = model_quant.get_dit_args(diffusers_load_config, allow_quant=False)
log.debug(f'Load model: type=Lens repo="{repo_id}" config={diffusers_load_config} offload={shared.opts.diffusers_offload_mode} dtype={devices.dtype} reasoner={shared.opts.model_lens_enable_pe} args={load_args}')
transformer = generic.load_transformer(repo_id, cls_name=lens.LensTransformer2DModel, load_config=diffusers_load_config)
transformer = generic.load_transformer(repo_id, cls_name=lens.LensTransformer2DModel, load_config=diffusers_load_config, native_spec=lens.LENS_SPEC)
text_encoder = generic.load_text_encoder(repo_id, cls_name=lens.LensGptOssEncoder, load_config=diffusers_load_config, allow_quant=False)
pipe = lens.LensPipeline.from_pretrained(
+2
View File
@@ -57,12 +57,14 @@ def load_qwen(checkpoint_info, diffusers_load_config=None):
transformer_subfolder = "transformer"
if transformer is None:
from pipelines.qwen import QWEN_SPEC
transformer = generic.load_transformer(
repo_transformer,
subfolder=transformer_subfolder,
cls_name=diffusers.QwenImageTransformer2DModel,
load_config=diffusers_load_config,
modules_to_not_convert=["transformer_blocks.0.img_mod.1.weight"],
native_spec=QWEN_SPEC,
)
repo_te = 'Qwen/Qwen-Image'
+2 -1
View File
@@ -22,7 +22,8 @@ def load_step1x_edit(checkpoint_info, diffusers_load_config=None):
text_encoder = generic.load_text_encoder(repo_id, cls_name=transformers.Qwen2_5_VLForConditionalGeneration, load_config=diffusers_load_config)
processor = transformers.Qwen2_5_VLProcessor.from_pretrained(repo_id, cache_dir=shared.opts.hfcache_dir, subfolder='processor')
transformer = generic.load_transformer(repo_id, cls_name=Step1XEditTransformer2DModel, load_config=diffusers_load_config)
from pipelines.step1x import STEP1X_SPEC
transformer = generic.load_transformer(repo_id, cls_name=Step1XEditTransformer2DModel, load_config=diffusers_load_config, native_spec=STEP1X_SPEC)
pipe = Step1XEditPipeline.from_pretrained(
repo_id,
+2
View File
@@ -21,11 +21,13 @@ def load_vibe(checkpoint_info, diffusers_load_config=None):
sys.modules['vibe.transformer.vibe_sana_editing'] = diffusers # monkey patch since hf model_index.json points to custom class path
from pipelines.vibe import VIBE_SPEC
transformer = generic.load_transformer(
repo_id,
cls_name=VIBESanaEditingModel,
load_config=diffusers_load_config,
allow_quant=False,
native_spec=VIBE_SPEC,
)
text_encoder = generic.load_text_encoder(
repo_id,
+22
View File
@@ -1,2 +1,24 @@
"""Qwen-Image pipeline package.
Exports :data:`QWEN_SPEC` for use by :mod:`pipelines.model_qwen` together
with :mod:`pipelines.native_transformer`. Qwen-Image is the lone Mode C
arch: diffusers registers a no-op identity lambda as its
``SINGLE_FILE_LOADABLE_CLASSES`` converter, so ``from_single_file`` silently
accepts whatever key naming the community file uses and loads with mangled
weights instead of raising.
The spec explicitly sets ``converter=None`` to short-circuit the no-op
pickup; if a real Qwen-Image converter is needed for some trainer dump
in the wild, it can be plugged in here. Until then, validation surfaces a
clear error listing unexpected/missing keys instead of letting a malformed
file load silently.
"""
import diffusers
from pipelines.native_transformer import TransformerSpec
from pipelines.qwen.qwen_nunchaku import load_qwen_nunchaku
from pipelines.qwen.qwen_pruning import check_qwen_pruning
QWEN_SPEC = TransformerSpec(cls=diffusers.QwenImageTransformer2DModel, converter=None)
+6
View File
@@ -1,8 +1,14 @@
from pipelines.native_transformer import TransformerSpec
from pipelines.step1x.pipeline_output import Step1XEditPipelineOutput
from pipelines.step1x.pipeline_step1x_edit import Step1XEditPipeline
from pipelines.step1x.transformer_step1x_edit import Step1XEditTransformer2DModel
STEP1X_SPEC = TransformerSpec(cls=Step1XEditTransformer2DModel)
__all__ = [
"STEP1X_SPEC",
"Step1XEditPipeline",
"Step1XEditPipelineOutput",
"Step1XEditTransformer2DModel",
+6 -1
View File
@@ -1,6 +1,11 @@
"""VIBE pipeline components for SD.Next."""
from pipelines.native_transformer import TransformerSpec
from .vibe_sana_editing import VIBESanaEditingModel
from .vibe_sana_pipeline import VIBESanaEditingPipeline, VIBESanaImagePipeline
__all__ = ["VIBESanaEditingModel", "VIBESanaEditingPipeline", "VIBESanaImagePipeline"]
VIBE_SPEC = TransformerSpec(cls=VIBESanaEditingModel)
__all__ = ["VIBE_SPEC", "VIBESanaEditingModel", "VIBESanaEditingPipeline", "VIBESanaImagePipeline"]