SKILL.md
SKILL.mdBrowse 2 files
9,285 tokens
39,749 bytes
Token encoding: o200k_base
Snapshot a9fb1c3
1---2name: sglang-diffusion-add-model3description: Use when adding a new diffusion model or Diffusers pipeline to SGLang.4---5 6# Add a Diffusion Model to SGLang7 8Use this skill when adding a new diffusion model or pipeline variant to `sglang.multimodal_gen`.9 10## Three Pipeline Styles11 12### Style A: Hybrid Monolithic Pipeline (Recommended)13 14The recommended default for most new models. Uses a three-stage structure:15 16```17BeforeDenoisingStage (model-specific) --> DenoisingStage (standard) --> DecodingStage (standard)18```19 20- **BeforeDenoisingStage**: A single, model-specific stage that consolidates all pre-processing logic: input validation, text encoding, image encoding, latent preparation, timestep setup. This stage is unique per model.21- **DenoisingStage**: Framework-standard stage for the denoising loop (DiT/UNet forward passes). Shared across models.22- **DecodingStage**: Framework-standard stage for VAE decoding. Shared across models.23 24**Why recommended?** Modern diffusion models have highly heterogeneous pre-processing requirements (different text encoders, different latent formats, different conditioning mechanisms). The Hybrid approach keeps pre-processing isolated per model, avoids fragile shared stages with excessive conditional logic, and lets developers port Diffusers reference code quickly.25 26### Style B: Modular Composition Style27 28Uses the framework's fine-grained standard stages (`TextEncodingStage`, `LatentPreparationStage`, `TimestepPreparationStage`, etc.) to build the pipeline by composition.29 30This style is appropriate when:31- **The new model's pre-processing can largely reuse existing stages** — e.g., a model that uses standard CLIP/T5 text encoding + standard latent preparation with minimal customization. In this case, `add_standard_t2i_stages()` or `add_standard_ti2i_stages()` may be all you need.32- **A model-specific optimization needs to be extracted as a standalone stage** — e.g., a specialized encoding or conditioning step that benefits from being a separate stage for profiling, parallelism control, or reuse across multiple pipeline variants.33 34See existing Modular examples: `QwenImagePipeline` (uses `add_standard_t2i_stages`), `FluxPipeline`, `WanPipeline`, `SanaPipeline`, `StableDiffusion3Pipeline`, and `ZImagePipeline`.35 36### Style C: Native Task-Contract Pipeline37 38Use this only when one checkpoint exposes multiple tightly coupled modalities39or request profiles that cannot be represented safely by generic image/video40sampling fields. MiniMax-H3 is the reference: it selects FL2VA or Ref2VA41weights from one root model ID, validates canonical `task` / `conditions` /42`target` requests before queueing, packs text/video/audio tokens into one43denoise sequence, and returns synchronized video plus audio.44 45This style still uses `ComposedPipelineBase`, but owns a model-specific chain46under `stages/model_specific_stages/<model>/`. Keep request validation, media47materialization, packed-sequence construction, per-modality encode/decode, and48presentation as explicit stages. Do not force coupled state into the standard49`DenoisingStage` / `DecodingStage` contract just to resemble a simpler model.50 51Choose this style only with source evidence that the public API, scheduler, or52joint latent state needs it. Preserve one canonical request object from API53admission through offline generation and server execution so the two entry54points cannot silently diverge.55 56### How to Choose57 58| Situation | Recommended Style |59|-----------|-------------------|60| Model has unique/complex pre-processing (VLM captioning, AR token generation, custom latent packing, etc.) | **Hybrid** — consolidate into a BeforeDenoisingStage |61| Model jointly denoises multiple modalities or exposes partitioned task contracts from one root checkpoint | **Native task contract** — use MiniMax-H3 as the reference and keep model-specific stages explicit |62| Model fits neatly into standard text-to-image or text+image-to-image pattern | **Modular** — use `add_standard_t2i_stages()` / `add_standard_ti2i_stages()` |63| Porting a Diffusers pipeline with many custom steps | **Hybrid** — copy the `__call__` logic into a single stage |64| Adding a variant of an existing model that shares most logic | **Modular** — reuse existing stages, customize via PipelineConfig callbacks |65| A specific pre-processing step needs special parallelism or profiling isolation | **Modular** — extract that step as a dedicated stage |66 67**Key principle (standard-denoise styles)**: For Hybrid and Modular pipelines,68the stage(s) before `DenoisingStage` must produce a `Req` batch object with all69the standard tensor fields that `DenoisingStage` expects (latents, timesteps,70prompt embeds, and model-specific conditioning). Native task-contract pipelines71may own a different denoise/decode contract; keep that divergence explicit and72covered by request-contract tests.73 74---75 76## Key Files and Directories77 78| Purpose | Path |79|---------|------|80| Pipeline classes | `python/sglang/multimodal_gen/runtime/pipelines/` |81| Model-specific stages | `python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/` |82| PipelineStage base class | `python/sglang/multimodal_gen/runtime/pipelines_core/stages/base.py` |83| Pipeline base class | `python/sglang/multimodal_gen/runtime/pipelines_core/composed_pipeline_base.py` |84| Standard stages (Denoising, Decoding) | `python/sglang/multimodal_gen/runtime/pipelines_core/stages/` |85| Pipeline configs | `python/sglang/multimodal_gen/configs/pipeline_configs/` |86| Sampling params | `python/sglang/multimodal_gen/configs/sample/` |87| DiT model implementations | `python/sglang/multimodal_gen/runtime/models/dits/` |88| VAE implementations | `python/sglang/multimodal_gen/runtime/models/vaes/` |89| Encoder implementations | `python/sglang/multimodal_gen/runtime/models/encoders/` |90| Scheduler implementations | `python/sglang/multimodal_gen/runtime/models/schedulers/` |91| Model/VAE/DiT configs | `python/sglang/multimodal_gen/configs/models/dits/`, `vaes/`, `encoders/` |92| Central registry | `python/sglang/multimodal_gen/registry.py` |93| Model component registry | `python/sglang/multimodal_gen/runtime/models/registry.py` |94| Current support list | `docs/docs/sglang-diffusion/compatibility_matrix.mdx` |95 96---97 98## Step-by-Step Implementation99 100### Step 1: Obtain and Study the Reference Implementation101 102**Before writing any code, obtain the model's reference implementation or Diffusers pipeline code.** You need the actual source code to work from — do not guess or assume the model's architecture. If the user already gave a HuggingFace model ID or repo, inspect that yourself first. Ask the user only when the reference implementation is private, ambiguous, or otherwise unavailable. Typical sources are:103- The model's Diffusers pipeline source (e.g., the `pipeline_*.py` file from the `diffusers` library or HuggingFace repo)104- Or the model's official reference implementation (e.g., from the model author's GitHub repo)105- Or the HuggingFace model ID so you can look up `model_index.json` and the associated pipeline class106 107Once you have the reference code, study it thoroughly:108 1091. Find the model's `model_index.json` to identify required modules (text_encoder, vae, transformer, scheduler, etc.)1102. Read the Diffusers pipeline's `__call__` method end-to-end. Identify:111 - How text prompts are encoded112 - How latents are prepared (shape, dtype, scaling)113 - How timesteps/sigmas are computed114 - What conditioning kwargs the DiT/UNet expects115 - How the denoising loop works (classifier-free guidance, etc.)116 - How VAE decoding is done (scaling factors, tiling, etc.)117 118### Step 2: Evaluate Reuse of Existing Pipelines and Stages119 120**Before creating any new files, check whether an existing pipeline or stage can be reused or extended.** Only create new pipelines/stages when the existing ones would require extensive modifications or when no similar implementation exists.121 122Specifically:1231. **Compare the new model's architecture against existing pipelines** before creating files. Current native families include MiniMax-H3, Krea-2, LTX-2/2.3/2.5, HunyuanVideo/FastHunyuan, Wan/FastWan/TurboWan/LingBot World/LingBot Video MoE, MOVA, FLUX/FLUX.2/Klein, LongCat-Image, Z-Image, Qwen-Image/edit/layered, GLM-Image, SD3, Hunyuan3D, Helios, Cosmos3 Nano/Super/Edge/distilled, SANA/SANA-Video/SANA-WM, FireRed, ERNIE-Image, JoyAI, and Ideogram4. If the new model shares most of its structure with an existing one (e.g., same text encoders, similar latent format, compatible denoising loop), prefer:124 - Adding a new config variant to the existing pipeline rather than creating a new pipeline class125 - Reusing the existing `BeforeDenoisingStage` with minor parameter differences126 - Using `add_standard_t2i_stages()` / `add_standard_ti2i_stages()` / `add_standard_ti2v_stages()` if the model fits standard patterns1272. **Check existing stages** in `runtime/pipelines_core/stages/` and `stages/model_specific_stages/`. If an existing stage handles 80%+ of what the new model needs, extend it rather than duplicating it.1283. **Check existing model components** — many models share VAEs (e.g., `AutoencoderKL`), text encoders (CLIP, T5), and schedulers. Reuse these directly instead of re-implementing.129 130**Rule of thumb**: Only create a new file when the existing implementation would need substantial structural changes to accommodate the new model, or when no architecturally similar implementation exists.131 132### Step 3: Implement Model Components133 134Adapt or implement the model's core components in the appropriate directories.135 136**DiT/Transformer** (`runtime/models/dits/{model_name}.py`):137 138```python139# python/sglang/multimodal_gen/runtime/models/dits/my_model.py140 141import torch142import torch.nn as nn143 144from sglang.multimodal_gen.runtime.layers.layernorm import (145 LayerNormScaleShift,146 RMSNormScaleShift,147)148from sglang.multimodal_gen.runtime.layers.attention.selector import (149 get_attn_backend,150)151 152 153class MyModelTransformer2DModel(nn.Module):154 """DiT model for MyModel.155 156 Adapt from the Diffusers/reference implementation. Key points:157 - Use SGLang's fused LayerNorm/RMSNorm ops (see `existing-fast-paths.md` under the benchmark/profile skill)158 - Use SGLang's attention backend selector159 - Keep the same parameter naming as Diffusers for weight loading compatibility160 """161 162 def __init__(self, config):163 super().__init__()164 # ... model layers ...165 166 def forward(167 self,168 hidden_states: torch.Tensor,169 encoder_hidden_states: torch.Tensor,170 timestep: torch.Tensor,171 # ... model-specific kwargs ...172 ) -> torch.Tensor:173 # ... forward pass ...174 return output175```176 177**Tensor Parallel (TP) and Sequence Parallel (SP)**: For multi-GPU deployment, it is recommended to add TP/SP support to the DiT model. This can be done incrementally after the single-GPU implementation is verified. Reference existing implementations and adapt to your model's architecture:178 179- **Wan model** (`runtime/models/dits/wanvideo.py`) — Full TP + SP reference:180 - TP: Uses `ColumnParallelLinear` for Q/K/V projections, `RowParallelLinear` for output projections, attention heads divided by `tp_size`181 - SP: Sequence dimension sharding via `get_sp_world_size()`, padding for alignment, `sequence_model_parallel_all_gather` for aggregation182 - Cross-attention skips SP (`skip_sequence_parallel=is_cross_attention`)183- **Qwen-Image model** (`runtime/models/dits/qwen_image.py`) — SP + USPAttention reference:184 - SP: Uses `USPAttention` (Ulysses + Ring Attention), configured via `--ulysses-degree` / `--ring-degree`185 - TP: Uses `MergedColumnParallelLinear` for QKV (with Nunchaku quantization), `ReplicatedLinear` otherwise186 187**Important**: These are references only — each model has its own architecture and parallelism requirements. Consider:188- How attention heads can be divided across TP ranks189- Whether the model's sequence dimension is naturally shardable for SP190- Which linear layers benefit from column/row parallel sharding vs. replication191- Whether cross-attention or other special modules need SP exclusion192 193Key imports for distributed support:194```python195from sglang.multimodal_gen.runtime.distributed import (196 divide,197 get_sp_group,198 get_sp_world_size,199 get_tp_world_size,200 sequence_model_parallel_all_gather,201)202from sglang.multimodal_gen.runtime.layers.linear import (203 ColumnParallelLinear,204 RowParallelLinear,205 ReplicatedLinear,206)207```208 209**VAE** (`runtime/models/vaes/{model_name}.py`): Implement if the model uses a non-standard VAE. Many models reuse existing VAEs.210 211**Encoders** (`runtime/models/encoders/{model_name}.py`): Implement if the model uses custom text/image encoders.212 213**Schedulers** (`runtime/models/schedulers/{scheduler_name}.py`): Implement if the model requires a custom scheduler not available in Diffusers.214 215### Step 4: Create Model Configs216 217**DiT Config** (`configs/models/dits/{model_name}.py`):218 219```python220# python/sglang/multimodal_gen/configs/models/dits/mymodel.py221 222from dataclasses import dataclass, field223 224from sglang.multimodal_gen.configs.models.dits.base import DiTConfig225 226 227@dataclass228class MyModelDitConfig(DiTConfig):229 arch_config: dict = field(default_factory=lambda: {230 "in_channels": 16,231 "num_layers": 24,232 "patch_size": 2,233 # ... model-specific architecture params ...234 })235```236 237**VAE Config** (`configs/models/vaes/{model_name}.py`):238 239```python240from dataclasses import dataclass, field241 242from sglang.multimodal_gen.configs.models.vaes.base import VAEConfig243 244 245@dataclass246class MyModelVAEConfig(VAEConfig):247 vae_scale_factor: int = 8248 # ... VAE-specific params ...249```250 251**Sampling Params** (`configs/sample/{model_name}.py`):252 253```python254from dataclasses import dataclass255 256from sglang.multimodal_gen.configs.sample.base import SamplingParams257 258 259@dataclass260class MyModelSamplingParams(SamplingParams):261 num_inference_steps: int = 50262 guidance_scale: float = 7.5263 height: int = 1024264 width: int = 1024265 # ... model-specific defaults ...266```267 268### Step 5: Create PipelineConfig269 270The `PipelineConfig` holds static model configuration and defines callback methods used by the standard `DenoisingStage` and `DecodingStage`.271 272```python273# python/sglang/multimodal_gen/configs/pipeline_configs/my_model.py274 275from dataclasses import dataclass, field276 277import torch278 279from sglang.multimodal_gen.configs.models import DiTConfig, VAEConfig280from sglang.multimodal_gen.configs.pipeline_configs.base import (281 ImagePipelineConfig,282 ModelTaskType,283 # PipelineConfig, # common base for many video pipelines284 # SpatialImagePipelineConfig, # alternative base for spatial image models285)286from sglang.multimodal_gen.configs.models.dits.mymodel import MyModelDitConfig287from sglang.multimodal_gen.configs.models.vaes.mymodel import MyModelVAEConfig288 289 290@dataclass291class MyModelPipelineConfig(ImagePipelineConfig):292 """Pipeline config for MyModel.293 294 This config provides callbacks that the standard DenoisingStage and295 DecodingStage use during execution. The BeforeDenoisingStage handles296 all model-specific pre-processing independently.297 """298 299 task_type: ModelTaskType = ModelTaskType.T2I300 vae_precision: str = "bf16"301 should_use_guidance: bool = True302 vae_tiling: bool = False303 enable_autocast: bool = False304 305 dit_config: DiTConfig = field(default_factory=MyModelDitConfig)306 vae_config: VAEConfig = field(default_factory=MyModelVAEConfig)307 308 # --- Callbacks used by DenoisingStage ---309 310 def get_freqs_cis(self, batch, device, rotary_emb, dtype):311 """Prepare rotary position embeddings for the DiT."""312 # Model-specific RoPE computation313 ...314 return freqs_cis315 316 def prepare_pos_cond_kwargs(self, batch, latent_model_input, t, **kwargs):317 """Build positive conditioning kwargs for each denoising step."""318 return {319 "hidden_states": latent_model_input,320 "encoder_hidden_states": batch.prompt_embeds[0],321 "timestep": t,322 # ... model-specific kwargs ...323 }324 325 def prepare_neg_cond_kwargs(self, batch, latent_model_input, t, **kwargs):326 """Build negative conditioning kwargs for CFG."""327 return {328 "hidden_states": latent_model_input,329 "encoder_hidden_states": batch.negative_prompt_embeds[0],330 "timestep": t,331 # ... model-specific kwargs ...332 }333 334 # --- Callbacks used by DecodingStage ---335 336 def get_decode_scale_and_shift(self):337 """Return (scale, shift) for latent denormalization before VAE decode."""338 return self.vae_config.latents_std, self.vae_config.latents_mean339 340 def post_denoising_loop(self, latents, batch):341 """Optional post-processing after the denoising loop finishes."""342 return latents.to(torch.bfloat16)343 344 def post_decoding(self, frames, server_args):345 """Optional post-processing after VAE decoding."""346 return frames347```348 349There is no separate `VideoPipelineConfig` base class. For video models, choose350`ModelTaskType.T2V`, `ModelTaskType.I2V`, or `ModelTaskType.TI2V`, and follow351existing video configs such as Wan, LTX, Hunyuan, Helios, or MOVA when deciding352whether to subclass `PipelineConfig` directly or use a model-specific base.353 354**Important**: The `prepare_pos_cond_kwargs` / `prepare_neg_cond_kwargs` methods define what the DiT receives at each denoising step. These must match the DiT's `forward()` signature.355 356### Step 6: Implement the BeforeDenoisingStage (Core Step)357 358This is the heart of the Hybrid pattern. Create a single stage that handles ALL pre-processing.359 360```python361# python/sglang/multimodal_gen/runtime/pipelines_core/stages/model_specific_stages/my_model.py362 363import torch364from typing import List, Optional, Union365 366from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req367from sglang.multimodal_gen.runtime.pipelines_core.stages.base import PipelineStage368from sglang.multimodal_gen.runtime.server_args import ServerArgs369from sglang.multimodal_gen.runtime.distributed import get_local_torch_device370from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger371 372logger = init_logger(__name__)373 374 375class MyModelBeforeDenoisingStage(PipelineStage):376 """Monolithic pre-processing stage for MyModel.377 378 Consolidates all logic before the denoising loop:379 - Input validation380 - Text/image encoding381 - Latent preparation382 - Timestep/sigma computation383 384 This stage produces a Req batch with all fields required by385 the standard DenoisingStage.386 """387 388 def __init__(self, vae, text_encoder, tokenizer, transformer, scheduler):389 super().__init__()390 self.vae = vae391 self.text_encoder = text_encoder392 self.tokenizer = tokenizer393 self.transformer = transformer394 self.scheduler = scheduler395 # ... other initialization (image processors, scale factors, etc.) ...396 397 # --- Internal helper methods ---398 # Copy/adapt directly from the Diffusers reference pipeline.399 # These are private to this stage; no need to make them reusable.400 401 def _encode_prompt(self, prompt, device, dtype):402 """Encode text prompt into embeddings."""403 # ... model-specific text encoding logic ...404 return prompt_embeds, negative_prompt_embeds405 406 def _prepare_latents(self, batch_size, height, width, dtype, device, generator):407 """Create initial noisy latents."""408 # ... model-specific latent preparation ...409 return latents410 411 def _prepare_timesteps(self, num_inference_steps, device):412 """Compute the timestep/sigma schedule."""413 # ... model-specific timestep computation ...414 return timesteps, sigmas415 416 # --- Main forward method ---417 418 @torch.no_grad()419 def forward(self, batch: Req, server_args: ServerArgs) -> Req:420 """Execute all pre-processing and populate batch for DenoisingStage.421 422 This method mirrors the first half of a Diffusers pipeline __call__,423 up to (but not including) the denoising loop.424 """425 device = get_local_torch_device()426 dtype = torch.bfloat16427 generator = torch.Generator(device=device).manual_seed(batch.seed)428 429 # 1. Encode prompt430 prompt_embeds, negative_prompt_embeds = self._encode_prompt(431 batch.prompt, device, dtype432 )433 434 # 2. Prepare latents435 latents = self._prepare_latents(436 batch_size=1,437 height=batch.height,438 width=batch.width,439 dtype=dtype,440 device=device,441 generator=generator,442 )443 444 # 3. Prepare timesteps445 timesteps, sigmas = self._prepare_timesteps(446 batch.num_inference_steps, device447 )448 449 # 4. Populate batch with everything DenoisingStage needs450 batch.prompt_embeds = [prompt_embeds]451 batch.negative_prompt_embeds = [negative_prompt_embeds]452 batch.latents = latents453 batch.timesteps = timesteps454 batch.num_inference_steps = len(timesteps)455 batch.sigmas = sigmas456 batch.generator = generator457 batch.raw_latent_shape = latents.shape458 batch.height = batch.height459 batch.width = batch.width460 461 return batch462```463 464**Key fields that `DenoisingStage` expects on the batch** (set these in your `forward`):465 466| Field | Type | Description |467|-------|------|-------------|468| `batch.latents` | `torch.Tensor` | Initial noisy latent tensor |469| `batch.timesteps` | `torch.Tensor` | Timestep schedule |470| `batch.num_inference_steps` | `int` | Number of denoising steps |471| `batch.sigmas` | `list[float]` | Sigma schedule (as a list, not numpy) |472| `batch.prompt_embeds` | `list[torch.Tensor]` | Positive prompt embeddings (wrapped in list) |473| `batch.negative_prompt_embeds` | `list[torch.Tensor]` | Negative prompt embeddings (wrapped in list) |474| `batch.generator` | `torch.Generator` | RNG generator for reproducibility |475| `batch.raw_latent_shape` | `tuple` | Original latent shape before any packing |476| `batch.height` / `batch.width` | `int` | Output dimensions |477 478### Step 7: Define the Pipeline Class479 480The pipeline class is minimal -- it just wires the stages together.481 482```python483# python/sglang/multimodal_gen/runtime/pipelines/my_model.py484 485from sglang.multimodal_gen.runtime.pipelines_core import LoRAPipeline486from sglang.multimodal_gen.runtime.pipelines_core.composed_pipeline_base import (487 ComposedPipelineBase,488)489from sglang.multimodal_gen.runtime.pipelines_core.stages import DenoisingStage490from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.my_model import (491 MyModelBeforeDenoisingStage,492)493from sglang.multimodal_gen.runtime.server_args import ServerArgs494 495 496class MyModelPipeline(LoRAPipeline, ComposedPipelineBase):497 pipeline_name = "MyModelPipeline" # Must match model_index.json _class_name498 499 _required_config_modules = [500 "text_encoder",501 "tokenizer",502 "vae",503 "transformer",504 "scheduler",505 # ... list all modules from model_index.json ...506 ]507 508 def create_pipeline_stages(self, server_args: ServerArgs):509 # 1. Monolithic pre-processing (model-specific)510 self.add_stage(511 MyModelBeforeDenoisingStage(512 vae=self.get_module("vae"),513 text_encoder=self.get_module("text_encoder"),514 tokenizer=self.get_module("tokenizer"),515 transformer=self.get_module("transformer"),516 scheduler=self.get_module("scheduler"),517 ),518 )519 520 # 2. Standard denoising loop (framework-provided)521 self.add_stage(522 DenoisingStage(523 transformer=self.get_module("transformer"),524 scheduler=self.get_module("scheduler"),525 ),526 )527 528 # 3. Standard VAE decoding (framework-provided)529 self.add_standard_decoding_stage()530 531 532# REQUIRED: This is how the registry discovers the pipeline533EntryClass = [MyModelPipeline]534```535 536### Step 8: Register the Model537 538In `python/sglang/multimodal_gen/registry.py`, register your configs:539 540```python541register_configs(542 sampling_param_cls=MyModelSamplingParams,543 pipeline_config_cls=MyModelPipelineConfig,544 hf_model_paths=[545 "org/my-model-name", # HuggingFace model ID(s)546 ],547 model_detectors=[548 lambda path: "my-model" in path.lower(),549 ],550)551```552 553`register_configs()` does not take a `model_family` argument. It registers the554sampling and pipeline config classes, then resolves models by exact555`hf_model_paths` or optional detector predicates. Prefer exact `hf_model_paths`556for public checkpoints used in docs or tests; use detector predicates only for557families where local mirrors, renamed repos, or generated paths are common.558 559The `EntryClass` in your pipeline file is automatically discovered by the registry's `_discover_and_register_pipelines()` function -- no additional registration needed for the pipeline class itself.560 561### Step 9: Verify Output Quality562 563After implementation, **you must verify that the generated output is not noise**. A noisy or garbled output image/video is the most common sign of an incorrect implementation. Common causes include:564 565- Incorrect latent scale/shift factors (`get_decode_scale_and_shift` returning wrong values)566- Wrong timestep/sigma schedule (order, dtype, or value range)567- Mismatched conditioning kwargs (fields not matching the DiT's `forward()` signature)568- Incorrect VAE decoder configuration (wrong `vae_scale_factor`, missing denormalization)569- Rotary embedding style mismatch (`is_neox_style` set incorrectly)570- Wrong prompt embedding format (missing list wrapping, wrong encoder output selection)571 572**If the output is noise, the implementation is incorrect — do not ship it.** Debug by:5731. Comparing intermediate tensor values (latents, prompt_embeds, timesteps) against the Diffusers reference pipeline5742. Running the Diffusers pipeline and SGLang pipeline side-by-side with the same seed5753. Checking each stage's output shape and value range independently576 577### Step 10: Decide the ComfyUI Route (Optional)578 579A model is reachable from ComfyUI two ways. Pick one deliberately — the wrong580choice costs several hundred lines of weight-mapping code that buys nothing.581 582**Server route.** ComfyUI sends an HTTP request and SGLang runs the whole583pipeline. Choose this when the model needs conditioning ComfyUI cannot supply584(audio, reference materials, task routing), produces more than one modality,585or has its own request contract.586 587Cost: nothing, if the request fits the existing `generate_image` /588`generate_video` fields. If the model has extra request fields, pass them589through `extra_fields` — the request schemas accept unknown keys, so the590client in `apps/ComfyUI_SGLDiffusion/core/server_api.py` does **not** need a591per-model change. Add a node in `nodes.py` only when the inputs are worth592surfacing as ComfyUI widgets. `SGLDiffusionGenerateH3` is the worked example.593 594**Executor route.** ComfyUI's KSampler drives the denoise loop and SGLang595replaces the DiT forward, using ComfyUI's own text encoders and VAE. Choose596this only when the model denoises a single latent tensor that ComfyUI already597knows how to build and decode.598 599Cost, per model: a `runtime/pipelines/comfyui_<model>_pipeline.py` that maps600ComfyUI's single-file checkpoint layout onto the native module tree (350-690601lines in the existing three), an executor in602`apps/ComfyUI_SGLDiffusion/executors/` that adapts latent layout and603conditioning to `Req`, and entries in both dicts in `core/generator.py`.604 605The deciding question is not model size or modality — it is whether ComfyUI's606sampler can drive the model's loop unchanged. If reproducing the conditioning607inside ComfyUI would duplicate stages the server already runs, take the server608route.609 610### Step 11: Opt In to BCG and Quality Fast Paths Only After Eager Parity611 612Do not put a new model behind Breakable CUDA Graph (BCG) merely because one613forward captures. Diffusion BCG support has three independent admission paths:614 6151. register the exact model IDs and safe basename aliases in616 `BREAKABLE_CUDA_GRAPH_SUPPORTED_MODEL_IDS`6172. register the resolved pipeline config class in618 `BREAKABLE_CUDA_GRAPH_SUPPORTED_PIPELINE_CONFIGS`6193. implement or select the correct prompt padder under620 `runtime/breakable_cuda_graph/model_padders/`621 622The third item is model semantics, not a generic shape utility. Reuse623`pad_masked_prompt_kwargs` only when the model already consumes a real mask and624zero-padding every coupled text tensor leaves attention and RoPE unchanged.625Existing special cases show the common contracts:626 627- Qwen pads embeddings, masks, text RoPE caches, and sequence-length metadata628 together; it synthesizes a mask when the eager path did not need one.629- Ideogram pads the combined text-image sequence and carries replay-local630 `DynamicVarlenMaskMeta`; stale capture-time varlen indices are incorrect.631- Z-Image preserves native prompt length because extra tokens change its632 semantics even when the padding looks conventional.633- MiniMax-H3 buckets only within compatible packed-sequence alignment groups.634- LongCat-Image and default SANA-Video already produce fixed 512- and635 300-token contracts, respectively, so their padders are pass-through.636 637Keep mask construction active for batch size one. A shortcut such as638`if batch > 1` can make eager B=1 appear valid while BCG B=1 attends padded639tokens. Any object whose values depend on live lengths must be rebuilt from640static replay buffers once per replay; do not bake Python lists, varlen641indices, or weakly referenced tensors from warmup into the graph.642 643BCG validation must prove all of the following:644 645- warmup logs `[Diffusion BCG] captured`646- serving logs no support disable, capture failure, or647 `serving signature MISSED`648- lossless Eager and BCG artifacts are byte-identical for the same prompt,649 seed, shape, steps, guidance, dtype, and topology650- short/long prompts exercise every intended bucket and an over-limit prompt651 falls back deliberately652- video frame count and conditioning shapes match the captured signature;653 `--warmup-resolutions` specifies only width and height654- padder and support-gate unit tests cover aliases, pipeline config, fixed655 lengths, masks, RoPE/position tensors, and replay-local metadata656 657For a non-bit-exact optimization, integrate through the request-scoped site658framework under `sglang.kernels.ops.diffusion.sites`. Mark sites during model659construction and let `QualityGatedFusion` mount them for both660`quality="extra-high"` and `quality="high"`; `quality="lossless"` must keep661the original code path. A high-only sparse, caching, or other approximate662path must remain outside this fusion gate.663Eligibility must be all-or-nothing for coupled sites and fail closed on dtype,664shape, layout, backend, BCG, or compile incompatibility. Add clean site-level665guard/parity tests and a model wiring test instead of embedding request-policy666branches throughout the DiT.667 668Finally, use the benchmark/profile skill's `--quality-bcg-matrix` to run669same-GPU ABBA pairs for Eager/BCG at lossless/extra-high/high. Report denoise and saved670request e2e separately, require at least 1.5% repeated mean e2e improvement for671an optimization PR, attach profile and generated-media A/B evidence, then672delete the task-owned checkpoint cache and verify zero residual weight files673in the cleanup ledger.674 675## Reference Implementations676 677### Hybrid Style (recommended for most new models)678 679| Model | Pipeline | BeforeDenoisingStage | PipelineConfig |680|-------|----------|---------------------|----------------|681| GLM-Image | `runtime/pipelines/glm_image.py` | `stages/model_specific_stages/glm_image.py` | `configs/pipeline_configs/glm_image.py` |682| Qwen-Image-Layered | `runtime/pipelines/qwen_image.py` (`QwenImageLayeredPipeline`) | `stages/model_specific_stages/qwen_image_layered.py` | `configs/pipeline_configs/qwen_image.py` (`QwenImageLayeredPipelineConfig`) |683| Cosmos3 | `runtime/pipelines/cosmos3_pipeline.py` | `stages/model_specific_stages/cosmos3.py` | `configs/pipeline_configs/cosmos3.py` |684| LongCat-Image | `runtime/pipelines/longcat_image.py` | `stages/model_specific_stages/longcat_image.py` | `configs/pipeline_configs/longcat_image.py` |685| ErnieImage | `runtime/pipelines/ernie_image.py` | `stages/model_specific_stages/ernie_image_pe.py` | `configs/pipeline_configs/ernie_image.py` |686| Hunyuan3D | `runtime/pipelines/hunyuan3d_pipeline.py` | `stages/model_specific_stages/hunyuan3d/` | `configs/pipeline_configs/hunyuan3d.py` |687| SANA-WM | `runtime/pipelines/sana_wm_pipeline.py`, `sana_wm_realtime_pipeline.py` | `stages/model_specific_stages/sana_wm/` | `configs/pipeline_configs/sana_wm.py` |688| LingBot World realtime | `runtime/pipelines/lingbot_world_causal_dmd_pipeline.py` | `stages/model_specific_stages/lingbot_world/` | `configs/pipeline_configs/lingbot_world.py` |689| Krea-2 | `runtime/pipelines/krea2.py` | `stages/model_specific_stages/krea2.py` | `configs/pipeline_configs/krea2.py` |690 691### Modular Style (when standard stages fit well)692 693| Model | Pipeline | Notes |694|-------|----------|-------|695| Qwen-Image (T2I) | `runtime/pipelines/qwen_image.py` | Uses `add_standard_t2i_stages()` — standard text encoding + latent prep fits this model |696| Qwen-Image-Edit | `runtime/pipelines/qwen_image.py` | Uses `add_standard_ti2i_stages()` — standard image-to-image flow |697| Flux | `runtime/pipelines/flux.py` | Uses `add_standard_t2i_stages()` with custom `prepare_mu` |698| FLUX.2 / FLUX.2 Klein | `runtime/pipelines/flux_2.py`, `flux_2_klein.py` | Reuses FLUX.2 stages; Klein differences live in config and sampling params |699| Z-Image | `runtime/pipelines/zimage_pipeline.py` | Uses standard image pipeline stages plus Z-Image-specific config/model code |700| Ideogram4 | `runtime/pipelines/ideogram.py` | Uses dedicated text encoding and denoising stages while keeping standard latent prep |701| SANA | `runtime/pipelines/sana.py` | Spatial image pipeline; reuse the spatial image config pattern |702| SANA-Video | `runtime/pipelines/sana_video.py` | Native 3D transformer with model-specific text encoding and otherwise standard T2V stages |703| Stable Diffusion 3/3.5 | `runtime/pipelines/stable_diffusion_3.py` | Spatial image pipeline; compare scheduler, VAE scale, and conditioning layout |704| LTX-2 / LTX-2.3 / LTX-2.5 | `runtime/pipelines/ltx_2_pipeline.py` | Video pipeline family with one-stage, two-stage, HQ, joint audio/video, and optional LTX-2.5 diffusion-decoder variants; prefer config/loader specialization over a new pipeline |705| Helios | `runtime/pipelines/helios_pipeline.py` | Video pipeline family with custom denoising and decoding stages |706| FireRed/JoyAI image edit | `runtime/pipelines/qwen_image.py`, `runtime/pipelines/joy_image.py` | FireRed reuses Qwen edit-plus config; JoyAI has its own edit pipeline |707| Wan | `runtime/pipelines/wan_pipeline.py` | Uses `add_standard_ti2v_stages()` |708| LingBot Video MoE 30B | `runtime/pipelines/lingbot_video_moe.py` | Uses a model-specific structured-JSON text-encoding stage, then standard latent/timestep preparation, denoising, and decoding |709 710### Native Task-Contract Style (coupled multimodal requests)711 712| Model | Pipeline | Request / stage references |713|-------|----------|----------------------------|714| MiniMax-H3 | `runtime/pipelines/minimax_h3_pipeline.py` | `configs/sample/minimax_h3.py` owns the canonical request fields; `stages/model_specific_stages/minimax_h3/` owns admission, material I/O, packed video/audio/text denoising, separate video/audio VAE work, and synchronized presentation |715 716---717 718## Checklist719 720Before submitting, verify:721 722**Common (all styles):**723- [ ] **Pipeline file** exists at `runtime/pipelines/{model_name}.py` with `EntryClass`724- [ ] **PipelineConfig** at `configs/pipeline_configs/{model_name}.py`725- [ ] **SamplingParams** at `configs/sample/{model_name}.py`726- [ ] **DiT model** at `runtime/models/dits/{model_name}.py`727- [ ] **DiT config** at `configs/models/dits/{model_name}.py`728- [ ] **VAE** — reuse existing (e.g., `AutoencoderKL`) or create new at `runtime/models/vaes/`729- [ ] **VAE config** — reuse existing or create new at `configs/models/vaes/{model_name}.py`730- [ ] **Registry entry** in `registry.py` via `register_configs()`731- [ ] `pipeline_name` matches Diffusers `model_index.json` `_class_name`732- [ ] `_required_config_modules` lists all modules from `model_index.json`733- [ ] `PipelineConfig` callbacks (`prepare_pos_cond_kwargs`, `get_freqs_cis`, etc.) match DiT's `forward()` signature734- [ ] Latent scale/shift factors are correctly configured735- [ ] Use fused kernels where possible (see `existing-fast-paths.md` under the benchmark/profile skill)736- [ ] Weight names match Diffusers for automatic loading737- [ ] **TP/SP support** considered for DiT model (recommended; reference `wanvideo.py` for TP+SP, `qwen_image.py` for USPAttention)738- [ ] **Output quality verified** — generated images/videos are not noise; compared against Diffusers reference output739- [ ] **BCG admission is complete or intentionally absent** — model ID,740 pipeline config, and model-specific padding contract agree741- [ ] **BCG replay is proven when enabled** — capture marker present, no742 signature miss/fallback, and lossless artifact hash is exact743- [ ] **Quality fast paths are request-scoped** — lossless remains untouched;744 high-quality sites fail closed and have guard/parity tests745- [ ] **Performance evidence is controlled** — same-GPU repeated e2e, profile,746 generated-media comparison, and task-owned weight cleanup ledger747 748**Hybrid style only:**749- [ ] **BeforeDenoisingStage** at `stages/model_specific_stages/{model_name}.py`750- [ ] `BeforeDenoisingStage.forward()` populates all fields needed by `DenoisingStage`751 752**Native task-contract style only:**753 754- [ ] Root checkpoint plus variant selection maps to the intended partition;755 do not require users to discover internal subdirectories756- [ ] Offline `generate` and HTTP serving lower through the same validated757 request contract758- [ ] Task, condition role/order, target canvas/time, and output container are759 rejected early when invalid760- [ ] Joint-modality correctness covers every output stream; a valid video is761 insufficient when the model also generates audio or action data762 763## Common Pitfalls764 7651. **`batch.sigmas` must be a Python list**, not a numpy array. Use `.tolist()` to convert.7662. **`batch.prompt_embeds` is a list of tensors** (one per encoder), not a single tensor. Wrap with `[tensor]`.7673. **Don't forget `batch.raw_latent_shape`** -- `DecodingStage` uses it to unpack latents.7684. **Rotary embedding style matters**: `is_neox_style=True` = split-half rotation, `is_neox_style=False` = interleaved. Check the reference model carefully.7695. **VAE precision**: Many VAEs need fp32 or bf16 for numerical stability. Set `vae_precision` in the PipelineConfig accordingly.7706. **Avoid forcing model-specific logic into shared stages**: If your model's pre-processing doesn't naturally fit the existing standard stages, prefer the Hybrid pattern with a dedicated BeforeDenoisingStage rather than adding conditional branches to shared stages.771 772## After Implementation: Tests and Performance Data773 774After the model produces non-noise output, read775[references/testing-and-accuracy.md](references/testing-and-accuracy.md) before776adding GPU cases, component-accuracy skips/hooks, suite entries, or benchmark777claims. That reference tracks the current `gpu_cases.py`,778`DiffusionTestCase.run_component_accuracy_check`,779`single_test_file/component_accuracy/`, and `run_suite.py` split.780 Discovery context
Discovered by repository scan. No exact path reference found in the snapshot’s root docs/AGENTS.md.