nvidia/flashdreams-integrations
Navigate the FlashDreams package layout and integration architecture: core vs infra vs recipes vs workspace integrations, Transformer/Encoder/StreamingDecoder/Pipeline config contracts, AR caches, CP, CFG, KV cache, CUDA graph wrapping, runner registration, and test placement. Use when adding or editing built-in recipes under flashdreams/flashdreams/recipes/, plugin packages under integrations/, model configs, pipeline wiring, or when the user asks where code belongs. The template recipe is the reference design.
npx skills add https://github.com/NVIDIA/flashdreams --skill flashdreams-integrations
A map of how flashdreams/ is organized and how a single rollout flows through the framework. Read once before adding a built-in recipe under flashdreams/flashdreams/recipes/, adding a workspace integration under integrations/, or restructuring an existing one. Keep docstrings consistent with the python-docstring-style skill.
> For the step-by-step procedure of doing an integration end-to-end (scope → scaffold → recipe → checkpoint remap → conditioners → runner → verify → perf), see the integrate-a-model skill. This skill is the *map*; that one is the *route*.
> The fastest way to learn this codebase is to clone the structure of flashdreams/flashdreams/recipes/template/. It is the reference integration — every contract this skill describes is wired up there in its minimal form. Skim it side-by-side with this document.
core -> infra -> recipes/integrations. infra and core never import from integrations. Recipes and integrations may reuse sibling recipe code when the dependency stays explicit and model-owned.Pipeline that owns a DiffusionModel + optional Encoder / StreamingDecoder. The DiffusionModel owns a Transformer + a Scheduler. You author the integration-specific subclasses of these and ship one module-level literal StreamInferencePipelineConfig per variant in config.py. No build_*(...) factories — variants derive from a base via derive_config(BASE, ...).name: str (a stable slug). The per-integration <NAME>_CONFIGS: dict[str, StreamInferencePipelineConfig] dict is keyed by name. There is no central pipeline-config registry — pipelines are reachable via direct per-integration imports. Integrations that ship a Runner (see §5) self-register their slugs into flashdreams.configs.registry._SUPPORTED_RUNNERS (read it via supported_runners()), which is the only registry the flashdreams-run CLI dispatches over.*Cache dataclasses that mirror the same containment tree.pipeline.initialize_cache(...) once, then a loop of pipeline.generate(ar_idx, ...) + pipeline.finalize(ar_idx, ...).transformer.patchify_and_maybe_split_cp: pre-patchify [B, C, T, H, W] outside, post-patchify [B, L/cp, C] inside.flashdreams/
├── core/ reusable numerical primitives (no integration-specific code, no infra deps)
├── infra/ framework contracts + orchestration (ABCs, base configs, pipeline glue)
└── integrations/ concrete model bindings that satisfy the infra contracts
| Layer | Owns | Imports from |
|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| core/ | attention/ (NativeAttention, RingAttention, BlockKVCache, RotaryPositionEmbedding3D, apply_rope_freqs), checkpoint/load.py, distributed/ (split_inputs_cp, cat_outputs_cp, *_object_list), io/ | nothing in flashdreams |
| infra/ | config (InstantiateConfig, derive_config), pipeline (StreamInferencePipeline*), diffusion.{model, scheduler, transformer} (ABCs + base impls), encoder (Encoder + StreamingEncoder + StreamingVideoEncoder + NullEncoder), decoder (StreamingDecoder + StreamingVideoDecoder), compile, cuda_graph, profiler | core |
| integrations/<name>/ | concrete model: transformer/, optional encoder.py / decoder.py / pipeline.py, config.py builders | core, infra |
| Question | Layer |
|---------------------------------------------------------|------------------------------------|
| New attention kernel or shared CUDA utility | core/ |
| Reusable text/CLIP encoder any integration could use | infra/encoder/<kind>/ |
| New ABC or generic orchestrator | infra/ |
| Model-specific DiT, control encoder, or VAE | integrations/<name>/ |
| CLI runner config + run() body | integrations/<name>/runner.py |
If you're tempted to add an integration-specific branch in infra/ or core/ — expose a config slot or override hook instead.
The whole framework is built around three nested objects: pipeline, diffusion model, transformer. Each layer (a) holds the next layer down and (b) holds a per-rollout cache that mirrors the same shape.
flowchart TB
classDef infra fill:#d6f5d6,stroke:#2e7d32,color:#1b5e20
classDef integration fill:#fddede,stroke:#c62828,color:#b71c1c
subgraph PIPE["StreamInferencePipeline (infra)"]
ENC["encoder<br/>(per-AR-step, optional)"]:::integration
DM["DiffusionModel (infra)"]:::infra
DEC["decoder<br/>(optional)"]:::integration
end
subgraph DM_INNER[" "]
TF["YourTransformer (integration)<br/>+ context_encoder slot<br/>+ rope_adapter (built per rollout)"]:::integration
SCH["Scheduler (infra)<br/>FlowMatch / UniPC"]:::infra
end
subgraph TF_INNER[" "]
NET["YourDiT network (integration)<br/>blocks + KV caches"]:::integration
end
PIPE --> DM
DM --> TF
DM --> SCH
TF --> NET
Containment, top-down:
StreamInferencePipeline (use as-is in most cases)encoder: StreamingEncoder | None (optional; per-AR-step control like HDMap, camera, first-frame VAE)diffusion_model: DiffusionModeltransformer: YourTransformer ← you write thisnetwork: YourDiT ← you write thiscontext_encoder: Encoder (one-shot encoder slot — text / CLIP-image / NullEncoder)rope_adapter: RotaryPositionEmbedding3D (built per rollout, lives on the cache)scheduler: FlowMatchScheduler | UniPCScheduler (pick from infra.diffusion.scheduler)decoder: StreamingDecoder | None (optional; latent → pixels). Use StreamingVideoDecoder when the decoder is a pixel-video VAE.The per-rollout cache mirrors that tree (StreamInferencePipelineCache → transformer_cache → network_cache). Each level forwards before_update / after_update to the level below.
There are two encoder slots, and they take different base classes. Confusing them is the most common pitfall.
| Slot | Runs | Base class | Input | Disable |
|-----------------------------------------------|-------------------------------------|-----------------------------|------------------------------------|---------------------|
| transformer.context_encoder (one-shot) | once, in initialize_autoregressive_cache | Encoder (stateless) | text prompts, reference image | NullEncoderConfig() |
| pipeline.encoder (per-AR-step) | every AR step, in pipeline.generate | StreamingEncoder (stateful, has cache) | per-step control (HDMap, camera, hand-crafted control latent) | encoder=None |
Text encoders (subclass Encoder) go on context_encoder. Per-AR-step controls (subclass StreamingEncoder) go on pipeline.encoder. Putting a text encoder on the per-AR-step slot reruns it every step; putting a streaming encoder on the one-shot slot drops its cache.
The decoder slot (pipeline.decoder) takes a StreamingDecoder (stateful, forward(input, ar_idx, cache)). Use StreamingVideoDecoder for pixel-video VAEs (WAN VAE, TAEHV) — it adds the spatial / temporal compression contracts the pipeline needs to size pixel I/O. Stateless decoders just return an empty StreamingDecoderCache from initialize_autoregressive_cache and ignore autoregressive_index / cache in forward (see template/decoder.py).
Where the per-AR-step control tensor flows. This is the path a new control input (HDMap, camera trajectory, ...) takes through the framework. Defining a new control = author one StreamingEncoder subclass under integrations/<name>/encoder.py and consume the control arg inside your network's forward.
user passes raw control as `pipeline.generate(ar_idx, cache, input=hdmap)`
│ [B, C_ctrl, T, H, W]
▼
pipeline.encoder.forward(input, ar_idx, cache.encoder_cache) ← integrations/<name>/encoder.py
│ [B, C_latent, T, H, W] (still pre-patchify; same T/H/W as the noisy latent)
▼
diffusion_model.generate(ar_idx, transformer_cache, input=encoded)
│
├── transformer.patchify_and_maybe_split_cp(encoded)
│ [B, L/cp, C]
│
└── scheduler loop:
transformer.predict_flow(noisy, t, cache, input=patchified_control)
└── network.forward(noisy, ..., control=patchified_control)
└── x = input_proj(noisy) + input_proj(control) # additive bias
Two corollaries:
patchify_and_maybe_split_cp call works on both, and so the network can fuse them as an additive bias on the per-token channel dim.encoder=None round-trips input=None end-to-end. Your network's forward should treat control=None as "skip the control bias" — flashdreams/flashdreams/recipes/template/transformer/network.py is the reference. This lets the same integration support both controlled and uncontrolled rollouts without a separate config.A minimum viable built-in recipe (what flashdreams/flashdreams/recipes/template/ ships) is 3 files and 4 classes:
flashdreams/flashdreams/recipes/<name>/
├── transformer/
│ ├── __init__.py YourTransformerConfig + YourTransformerCache + YourTransformer
│ └── network.py YourDiTConfig + YourDiTCache + YourDiT
└── config.py module-level literal StreamInferencePipelineConfig per variant
<NAME>_CONFIGS: dict[str, StreamInferencePipelineConfig]
Add files only when you actually need them:
| File | When to add |
|---------------------|---------------------------------------------------------------------|
| encoder.py | integration needs a per-AR-step control input |
| decoder.py | integration owns the latent → pixel stage |
| pipeline.py | rare — only when pipeline.initialize_cache(...) needs a custom signature (e.g. derive per-rollout (height, width) from an input image, accept text strings instead of pre-encoded embeddings) |
| transformer/impl/ | network is large enough to split (modules.py, network.py, ...) |
| config/ | many shipped variants — split config.py into a package |
| transformer/constants.py | transformer-scoped constants (e.g. CFG negative prompt). Integration-wide URIs go in <integration>/constants.py; subpackage-specific constants live with the consumer. |
The contracts are all under flashdreams.infra. Subclass and override.
Transformer[YourCache] (infra.diffusion.transformer)__init__(config) — single argument. Don't take a device kwarg; the caller does model.to(device) (or pipeline.setup().to(device)). Keep __init__ cheap: build sub-modules, derive _cuda_graph_capture_ar_idx, leave _output_height = _output_width = None until cache build time.latent_shape (property) — per-rank post-patchify shape (already CP-divided). Asserts _output_height / _output_width are set; reading before initialize_autoregressive_cache must fail loudly.patchify_and_maybe_split_cp(x) / unpatchify_and_maybe_gather_cp(x) — the only place the pre/post-patchify boundary crosses.predict_flow(noisy_latent, timestep, cache, input=None) — one flow-match forward, with CFG merge when cache.network_cache_uncond is populated.initialize_autoregressive_cache(*, height, width, **transformer_context) — receives the per-rollout spatial layout, stashes it as self._output_height / self._output_width, runs context encoders, allocates KV buffers, builds the RotaryPositionEmbedding3D adapter, lazy-builds CUDAGraphWrappers, and returns YourCache. Do all divisibility checks here (H % patch_spatial == 0, L % cp_size == 0, ...).postprocess_clean_latent (e.g. I2V first-frame pin), finalize_kv_cache (default runs one extra predict_flow to advance the cache).YourTransformerCache(TransformerAutoregressiveCache) — an @dataclass(kw_only=True) carrying network_cache, network_cache_uncond | None, rope_adapter, rope_freqs | None, autoregressive_index. Its start(ar_idx) and finalize(ar_idx) hoist KV before_update / after_update and the RoPE shift out of the (potentially graph-captured) network forward. See flashdreams/flashdreams/recipes/template/transformer/__init__.py.YourTransformerConfig(InstantiateConfig) with _target = field(default_factory=lambda: YourTransformer) — exposes the standard knobs (see §5).Encoder / StreamingEncoder / StreamingDecoder (only if you ship them — pick the right base class for the slot):Encoder (stateless, slim forward(self, input)) — transformer.context_encoder only. Text encoders (UMT5, Cosmos-Reason1), CLIP image encoders, identity (NullEncoder).StreamingEncoder[YourCache] (forward(self, input, autoregressive_index, cache) + initialize_autoregressive_cache(**encoder_context)) — pipeline.encoder only. Per-AR-step controls (HDMap, camera, I2V first-frame VAE).StreamingVideoEncoder[YourCache] (subclass of StreamingEncoder) — pixel-video encoders. Adds the spatial_compression_ratio / temporal_compression_ratio properties plus the AR-step-aware get_output_temporal_size(ar_idx, input_T) / get_input_temporal_size(ar_idx, output_T) mappers. Subclass this whenever the pipeline needs to size pixel I/O without knowing the encoder's causal-padding topology — e.g. WAN VAE encoder, PixelShuffle pseudo-VAE, the I2V wrappers around them.StreamingDecoder[YourCache] (forward(self, input, autoregressive_index, cache) + initialize_autoregressive_cache(**decoder_context)) — pipeline.decoder. Stateful decoders (e.g. WAN VAE) thread a per-rollout cache across AR steps; stateless decoders (e.g. template/decoder.py's 1×1 Conv3d) just return an empty StreamingDecoderCache and ignore the cache argument.StreamingVideoDecoder[YourCache] (subclass of StreamingDecoder) — pixel-video decoders. Adds the spatial_compression_ratio / temporal_compression_ratio properties plus the AR-step-aware get_output_temporal_size(ar_idx, input_T) / get_input_temporal_size(ar_idx, output_T) mappers. Subclass this (instead of plain StreamingDecoder) whenever the pipeline needs to size pixel I/O without knowing the decoder's causal-padding / sliding-window topology — e.g. WAN VAE, TAEHV.StreamInferencePipelineConfig directly and plug encoders into the slots above.A "rollout" = build a cache once, then loop AR steps. Bidirectional models are N=1; streaming AR is N≥2.
pipeline.initialize_cache(*, image=None, height=None, width=None, ...)
├── derive (height, width) from image.shape[-2:] OR from explicit kwargs
├── pack into transformer_context = {"height": H, "width": W, ...}
└── transformer.initialize_autoregressive_cache(**transformer_context)
├── self._output_height, self._output_width = height, width
├── assert H % patch_spatial == 0, (T*H*W) % cp_size == 0, ...
├── context_encoder(context) → context_embeddings
├── if guidance_scale > 1.0: context_encoder(negative_context)
├── allocate KV slots (cond + optional uncond)
├── build RotaryPositionEmbedding3D for this (height, width, head_dim)
└── if use_cuda_graph: build two CUDAGraphWrapper(network)
for ar_idx in range(N):
pipeline.generate(ar_idx, cache, input)
├── encoder.forward(input, ar_idx, ...) # optional, per-AR-step control
├── diffusion_model.generate(ar_idx, ...)
│ ├── transformer.patchify_and_maybe_split_cp(input)
│ ├── cache.start(ar_idx) # rope_freqs = shift_t; KV before_update
│ ├── noisy = randn(transformer.latent_shape)
│ ├── for _ in range(num_inference_steps):
│ │ scheduler.step(noisy, t, predict_flow)
│ │ └── transformer.predict_flow(...) # CFG merge inside
│ ├── transformer.postprocess_clean_latent(...) # e.g. I2V pin
│ └── transformer.unpatchify_and_maybe_gather_cp(clean)
└── decoder.forward(clean, ar_idx, ...) # optional, latent → pixels
pipeline.finalize(ar_idx, cache)
└── diffusion_model.finalize(...)
├── if context_noise > 0: scheduler.add_noise(clean, context_noise)
├── transformer.finalize_kv_cache(noisy, ...) # one extra predict to advance KV
└── cache.finalize(ar_idx) # KV after_update
There are exactly two shape regimes, separated by patchify:
[B, C, T, H, W] for video, [B, N_ctx, D] for context.[B, L/cp, C] with L = T*H*W.patchify_and_maybe_split_cp is the only place that boundary crosses. Never CP-split or gather at a call site.
Compressed reference. The first time you touch one of these, also read the matching code in flashdreams/flashdreams/recipes/template/.
@dataclass(kw_only=True) extending InstantiateConfig (or one of the category bases like EncoderConfig / DecoderConfig / TransformerConfig), with _target: type["Target"] = field(default_factory=lambda: Target) (always parameterize type[...] with the concrete class — use a forward-ref string when the class isn't yet in scope). The base's setup() -> Any flows through, so callers narrow with isinstance or by knowing the literal config they passed in. Never use a bare instance as a default — always field(default_factory=...).__post_init__. It's a smell:network.in_dim = base + control_channels) belong in the literal — set the final integer the network sees on the literal itself. Conditional channel math lives at the literal definition site, not on the config class._cuda_graph_capture_ar_idx) belong on the transformer instance, computed in __init__. The config should be pure data.initialize_autoregressive_cache, not on the config — (height, width) aren't config fields.__post_init__-free makes them trivially serializable and derive_config-friendly.<VARIANT_SLUG>: StreamInferencePipelineConfig = StreamInferencePipelineConfig(name="...", ...) per shipped variant in config.py. Mirrors nerfstudio's configs/method_configs.py exactly — every shipped config is a fully-populated module-level literal.derive_config. Define the canonical chassis as one literal, then express each variant as cast(YourPipelineConfig, derive_config(BASE, name="...", ...)). Each variant must set its own name. Use dict(...) for nested patches and pass a fresh sub-config object when replacing a whole slot (e.g. decoder=PixelShuffleVAEEncoderConfig()).<NAME>_CONFIGS: dict[str, StreamInferencePipelineConfig]. Build the dict from a tuple of literals using a name-keyed comprehension so a typo in either side is impossible: <NAME>_CONFIGS: dict[str, StreamInferencePipelineConfig] = {
cfg.name: cfg for cfg in (VARIANT_A, VARIANT_B, ...)
}
from flashdreams.recipes.<name>.config import <NAME>_CONFIGS). The only central registry is flashdreams.configs.registry._SUPPORTED_RUNNERS (read it via supported_runners(); see "Runner layer + flashdreams-run CLI" below) — integrations that haven't been wrapped into a runner just don't appear in flashdreams-run.name slug convention. Lowercase, hyphen-separated, prefixed by the integration family (omnidreams-..., causal-wan21-..., lingbot-world-...). It's a stable user-facing key — treat it like an HTTP route, not a Python identifier. The matching runner (if any) reuses the same slug as its runner_name.build_*(...) helpers. If you find yourself writing one, use derive_config from a shared base instead. Tiny private factories that just shorten a repeated nested literal (e.g. one for each branch of a Wan 2.2 MoE) are fine — they take no "knobs" and just inline a fixed sub-config.DEFAULT_VIDEO_HEIGHT, DEFAULT_VIDEO_WIDTH, <NAME>_VAE_SPATIAL_COMPRESSION) as module-level constants without leading underscore in config.py. Examples and integrations import these to compute latent dimensions; keeping them private forces every caller to hard-code the same numbers.flashdreams-run CLIA StreamInferencePipeline is intentionally narrow: it owns the encode → diffuse → decode loop given *already-prepared* inputs (text embeddings, padded first-frame, control latents). A Runner is the layer above that turns user-facing CLI arguments (--prompt, --image_path, --output_dir) into those pipeline inputs, drives the AR loop, and persists outputs. Two responsibilities, two classes — same shape as nerfstudio's Trainer ⊃ Pipeline split.
Runner ABC + RunnerConfig base live in flashdreams/infra/runner.py. Built-in per-variant subclasses live in flashdreams/flashdreams/recipes/<name>/runner.py. The base __init__ eagerly does pipeline = config.pipeline.setup().to(config.device).eval(); the subclass's only job is run() — load runner-config inputs, build the cache, loop generate + finalize, persist outputs.RunnerConfig literal per shipped pipeline variant (mirrors the literal style of <NAME>_CONFIGS). When two variants share the same I/O (e.g. the three template configs), they can share one _target Runner class but each variant still gets its own RunnerConfig literal pinning the right pipeline=. When two variants need different I/O (e.g. Wan T2V vs I2V), each gets its own Runner subclass too. Reference templates: flashdreams/flashdreams/recipes/template/runner.py (one Runner, three configs) and flashdreams/flashdreams/recipes/wan/runner.py (two Runners, two configs).<NAME>_RUNNERS: dict[str, RunnerConfig] in flashdreams/flashdreams/recipes/<name>/config.py is keyed by runner_name (built from a tuple comprehension, same shape as <NAME>_CONFIGS). Each config module registers its runners with register_runner(name, cfg, source="builtin") (from flashdreams.configs.registry) so slugs land in _SUPPORTED_RUNNERS. flashdreams/configs/runner_configs.py side-effect-imports each recipe config module, so adding a new built-in runner means adding one import flashdreams.recipes.<name>.config line.runner_name mirrors pipeline.name by convention. A smoke test (tests/test_recipe_configs.py::test_runner_name_mirrors_pipeline_name) enforces it for every in-tree runner so flashdreams-run <name> "just works". Per-runner literals are free to opt out (an integration with two runners over the same pipeline would have to), but the in-tree set holds the line._SUPPORTED_RUNNERS in flashdreams/configs/registry.py is the only central registry; treat it as immutable after integration imports complete and read it via supported_runners(). Each runner config carries its own one-line CLI description on cfg.description (annotated with tyro.conf.Suppress so it's hidden from per-runner --help). The smoke test tests/test_recipe_configs.py::test_supported_runners_have_descriptions enforces non-empty descriptions for every in-tree runner.all_runners() returns the sorted, layered view: built-ins first, then plugin discoveries on top via register_runner(..., source="plugin") (a plugin can never silently shadow a shipped slug; collisions are logged and skipped).RunnerConfig (with description= set) and register it via the flashdreams.runner_configs entry-point group: [project.entry-points."flashdreams.runner_configs"]
my-integration-fast = "my_pkg.runners:MY_RUNNER_CONFIG"
The entry-point name is informational — the registry keys the runner by cfg.runner_name.
NERFSTUDIO_METHOD_CONFIGS): FLASHDREAMS_RUNNER_CONFIGS=slug=module:attr,slug2=other.module:factory_callable. Useful for in-development runners that aren't installed yet; the attribute can be a RunnerConfig or a zero-arg factory returning one. Built-ins always win; bad entries are logged-and-skipped, never crash the CLI.flashdreams-run (flashdreams/scripts/cli.py) is the unified CLI — one hyphenated console script (nerfstudio's ns-train shape) fronting a tyro subcommand union over the runner registry. flashdreams-run --help lists every runner; flashdreams-run <runner> --help shows every overridable field (including everything inside the wrapped pipeline); flashdreams-run wan21-i2v-14b-480p --prompt "..." --image_path frame.png resolves the literal + the overrides, builds the pipeline + runner, and dispatches into runner.run(). Use --no-instantiate to skip GPU work and just dump the resolved config.torchrun --nproc_per_node=N --no-python flashdreams-run <slug> ... (--no-python tells torchrun to execvp the console script on PATH directly, instead of wrapping it in python <script>). The Runner ABC bridges the launcher to torch.distributed *before* pipeline.setup(), so the integration transformer's auto-CP picks up WORLD at construction time and shards T*H*W tokens across ranks. Runner.__init__ also pins cuda:LOCAL_RANK and exposes self.local_rank / self.world_size / self.global_rank / self.is_rank_zero. New runners must gate their persistence step (mp4, stats JSON, .pt dump, user-facing logs) on self.is_rank_zero; compute (generate / finalize) runs on every rank. There is no cp_size knob on any config — the launcher is the single source of truth.supported_runners() and is *not* a flashdreams-run subcommand — the pipeline config is still reachable via the per-integration import. Migrate one integration at a time.runners extras. Image decoding (cv2) and MP4 muxing (mediapy) are lazy-imported inside Runner.run() and gated by the runners extras (pip install 'flashdreams[runners]'). This keeps serving deployments that only need the bare pipeline (e.g. integrations/lingbot) free of ffmpeg / opencv. The error message points users at the right extras when missing.Keep these names stable across integrations — tests and tooling look for them:
network, context_encoder (defaults to NullEncoderConfig()), dtype, checkpoint_path (None → random init), len_t, window_size_t, sink_size_t, guidance_scale, compile_network, use_cuda_graph, cuda_graph_warmup_iters, h_extrapolation_ratio, w_extrapolation_ratio. Plus a requires_negative_context_embeddings property → guidance_scale > 1.0.
Not config fields: height, width, cp_size, device. These are per-rollout (height/width → initialize_autoregressive_cache), launch-time (cp_size → auto-detect from torch.distributed), or call-site (device → model.to(device)).
height, width)(height, width) are pre-patchify pixel-latent dimensions for the rollout. They belong on initialize_autoregressive_cache, not the config:
transformer_context. For I2V the pipeline reads them off image.shape[-2:]; for T2V the pipeline accepts explicit height/width kwargs (see integrations/wan/pipeline.py for the I2V-or-explicit-fallback pattern).self._output_height / self._output_width — raw pre-patchify dims, not divided by patch_spatial. Compute pH = _output_height // network.patch_spatial inline at the use site (latent_shape, unpatchify_and_maybe_gather_cp, _build_network_cache). Storing the pre-patchify value keeps the variable's meaning unambiguous and matches what the user passed in.config.py, conditioning_wrapper.py) never set network.height/width on the transformer config — they're not there. They configure the *static* fields of network (additional_concat_ch, enable_cross_view_attn, in_dim, ...) and let initialize_autoregressive_cache thread the per-rollout shape.(L = T*H*W) % cp_size == 0, H % patch_spatial == 0) live in initialize_autoregressive_cache, not __post_init__.cp_size at transformer construction from torch.distributed.get_world_size(); fall back to 1 when not initialized. The launcher (torchrun --nproc_per_node=N) is the single source of truth — don't hard-code cp_size on the integration config.flashdreams.core.distributed.{split_inputs_cp, cat_outputs_cp}; cp_group=None is the single-GPU no-op. Use the _object_list variants for per-view strings.flashdreams.core.attention.RingAttention over manual all-gather + SDPA — it fuses the cross-rank KV gather with the SDPA call via an LSE merge.L % cp_size == 0 etc.) at cache build time (inside initialize_autoregressive_cache) with a readable message — (height, width) aren't known at config-construction time.guidance_scale == 1.0 and cache.network_cache_uncond is None. Short-circuit predict_flow to the cond branch in that case; otherwise return flow_uncond + s * (flow_cond - flow_uncond).requires_negative_context_embeddings drives the assertion: CFG on requires negative_context at cache build time. Only encode it inside that if branch — CFG-off rollouts shouldn't pay for it.CUDAGraphWrapper, allocate two independent wrappers (cond + uncond). The residual streams diverge at the first context-bias addition and must not share static buffers.torch.compile + CUDA graphsThe interaction here is subtle — only opt in once eager works.
BlockKVCache has two code paths: *filling* (append + slice) and *steady-state* (roll-left + overwrite). Each is a separate Dynamo subgraph and autotunes separately the first time it runs.compile_module(network) (pins mode="max-autotune-no-cudagraphs" so torch.compile doesn't manage its own CUDA graphs).CUDAGraphWrapper(network, warmup_iters=cfg.cuda_graph_warmup_iters). warmup_iters >= 2 drains Inductor autotune on the eager path before capture.initialize_autoregressive_cache, not __init__. The graph captures against the current KV-cache pointers; a fresh rollout (new H/W, new cache) needs a fresh wrapper. CFG → two wrappers.__init__ (it depends only on config):self._cuda_graph_capture_ar_idx = (cfg.sink_size_t + cfg.window_size_t) // cfg.len_tar_idx < threshold → wrapper.drain (eager — drains autotune AND exercises the cache's filling path).ar_idx >= threshold → wrapper.__call__ (warmup → capture → replay).__init__ (not __post_init__) keeps the config trivially serializable and lets derive_config round-trip cleanly.cudaErrorStreamCaptureUnsupported, autotune is firing inside capture — re-check the threshold and that .drain is used throughout filling.compile_network=False and use_cuda_graph=False for ease of debugging. Production integrations (Wan, Lingbot, Omnidreams) flip compile_network=True directly in their literal configs, and ship a separate *_COMPILED literal (or, like Omnidreams, a *_PERF literal) that additionally turns on use_cuda_graph for the encoders / decoder. Mirror whichever default matches the integration's intended deployment.flashdreams.core.attention.RotaryPositionEmbedding3D is the shared 3D RoPE for every (T, H, W)-patchified DiT. Use it instead of hand-rolling.
__init__. head_dim and the per-rollout len_h/len_w are only known once (height, width) are passed to initialize_autoregressive_cache. Right after building, call rope_adapter.set_context_parallel_group(self._cp_group) so frequency buffers get split along the seq dim.cache.start(ar_idx) computes cache.rope_freqs = rope_adapter.shift_t(ar_idx) once per AR step, hoisting it out of the network forward. Reuse the same rope_freqs for cond and uncond branches.kv_cache.update(k, v) — cached K's must already carry positional info, otherwise steady-state attention reads unrotated K's against rotated Q's.interleaved=True for Wan-style models; default False matches the half-split layout.h_extrapolation_ratio / w_extrapolation_ratio (and optionally t_extrapolation_ratio) raise the base θ for higher resolution / longer context.Pick from infra.diffusion.scheduler: FlowMatchSchedulerConfig (self-forcing, 1–4 step) or a UniPC variant (full 35–50 step bidirectional). The scheduler config is a field on DiffusionModelConfig, not on the integration or pipeline config.
if config.checkpoint_path is not None:
state_dict = load_checkpoint(config.checkpoint_path)
self.network.load_state_dict(state_dict)
checkpoint_path=None keeps the random init — the right default for unit tests. Pass a state_dict_transform on your transformer config when upstream training adds a prefix (net., generator_ema.model., etc.).
flashdreams/tests/test_<integration>.py — top-level tests/, not inside the integration.pytest + @pytest.mark.parametrize. Default to checkpoint_path=None, compile_network=False, use_cuda_graph=False.compile_network=False explicitly in unit tests, even if you think it's the default. Production integrations flip the default to True; if a test introspects transformer.network (e.g. isinstance(transformer.network, _DummyNetwork)) it will silently break when the production default sneaks in via OptimizedModule-wrapping.latent_shape-not-set asserts), the trigger is initialize_autoregressive_cache(height=..., width=...), not config construction. Update fakes accordingly: SimpleNamespace mocks shouldn't carry _pH/_pW/_pT; set network.patch_temporal / patch_spatial and pass height / width through the cache-init call..setup().to("cuda").eval(), run ≥ 2 AR steps (covers filling + the first steady step when window_size_t == 2 * len_t), assert output shape / device / finiteness.derive_config patches on the base builder, not separate builders. Compare against the eager baseline in an equivalence test.<tmpdir>/<integration>/cp_reference.pt; a torchrun --nproc_per_node=N run reads it back and asserts equality. Run both with the same tmpdir so they share the reference file.Adding a new integration foo:
integrations/foo/transformer/network.py — FooDiT + FooDiTCache + FooDiTConfig. Use RingAttention for CP-aware self-attention. Apply RoPE to q/k *before* kv_cache.update. Network config carries in_dim, additional_concat_ch, patch_temporal, patch_spatial — never height/width.integrations/foo/transformer/__init__.py — FooTransformerConfig (standard knobs above, no height/width/device/__post_init__), FooTransformerCache (carries rope_adapter + rope_freqs; start() hoists shift_t and KV before_update), FooTransformer (single-arg __init__(config); auto-detects CP size; sets _cuda_graph_capture_ar_idx and _output_height = _output_width = None in __init__; initialize_autoregressive_cache(*, height, width, ...) stashes the spatial layout and builds the rope adapter and any wrappers).integrations/foo/encoder.py, integrations/foo/decoder.py. Pick the right base class for the slot:transformer.context_encoder → Encoder (slim forward(self, input), no cache).pipeline.encoder (per-AR-step control) → StreamingEncoder[YourCache] (full forward(self, input, ar_idx, cache) + initialize_autoregressive_cache), or StreamingVideoEncoder[YourCache] if it's a pixel-video encoder (adds spatial_compression_ratio / temporal_compression_ratio + get_{input,output}_temporal_size).pipeline.decoder → StreamingDecoder[YourCache] (stateless decoders just return StreamingDecoderCache()), or StreamingVideoDecoder[YourCache] for pixel-video decoders that need to publish spatial_compression_ratio / temporal_compression_ratio + get_{input,output}_temporal_size.integrations/foo/pipeline.py only if the base pipeline's initialize_cache signature doesn't fit — most commonly to derive (height, width) from an input image (I2V) or accept them as explicit kwargs (T2V).integrations/foo/config.py — one module-level literal StreamInferencePipelineConfig per shipped variant (no build_*(...) factories). Each literal sets a unique name slug. Express variants as derive_config(BASE, name="foo-variant", ...). Register every variant in FOO_CONFIGS: dict[str, StreamInferencePipelineConfig] via {cfg.name: cfg for cfg in (...)}. Ship a separate *_COMPILED literal if you want a torch.compile + CUDA-graph fast path. Export DEFAULT_VIDEO_HEIGHT, DEFAULT_VIDEO_WIDTH, <NAME>_VAE_SPATIAL_COMPRESSION as public module-level constants. Literals fully resolve network.in_dim / network.additional_concat_ch / etc. so the config has no __post_init__.flashdreams-run) integrations/foo/runner.py — one RunnerConfig literal per shipped variant (per-variant Runner subclass when the I/O signature differs; one shared Runner when it doesn't). Each literal pins pipeline= to one of the <NAME>_CONFIGS literals and sets a non-empty description= (the CLI shows it next to the subcommand). Convention is runner_name == pipeline.name. Build FOO_RUNNERS: dict[str, RunnerConfig] via the {cfg.runner_name: cfg for cfg in (...)} comprehension, then loop register_runner(name, cfg, source="builtin") (from flashdreams.configs.registry) over its items so the slugs land in _SUPPORTED_RUNNERS. Finally, add one import flashdreams.recipes.foo.runner line to flashdreams/configs/runner_configs.py so the side effects fire at CLI startup. The smoke tests in tests/test_integration_configs.py enforce both the registry merge and a non-empty cfg.description per slug.flashdreams/tests/test_foo.py — bidirectional smoke + streaming smoke + CFG on/off + no-control branch + compile/CUDA-graph equivalence + CP equivalence. Always set compile_network=False explicitly in tests that introspect transformer.network (use derive_config(FOO_BASE, diffusion_model=dict(transformer=dict(compile_network=False)))).Layer / structure:
infra/ or core/. Breaks the dependency direction. Add a config slot or override hook instead.@dataclass default. Mutations leak between rollouts. Use field(default_factory=...).cp_size on the integration config. Auto-detect from torch.distributed.get_world_size().pipeline.encoder. That slot runs every AR step and expects a StreamingEncoder. Stateless one-shot encoders (text / CLIP / NullEncoder) subclass Encoder and go on transformer.context_encoder.Encoder for a per-AR-step control input. The pipeline calls per-AR-step encoders with (input, ar_idx, cache) — Encoder is the slim stateless base. Use StreamingEncoder[YourCache] instead.StreamingVideoDecoder / StreamingVideoEncoder for pixel-video VAEs. A plain StreamingDecoder works, but the pipeline can no longer query get_{input,output}_temporal_size to size pixel I/O — you'll end up duplicating that arithmetic in every integration pipeline.device kwarg on Transformer.__init__. Use model.to(device) (or pipeline.setup().to(device)) at the call site instead. Keeping __init__ device-free lets configs round-trip without carrying a torch.device.Configs:
__post_init__. Set network.in_dim = base + control_channels directly on the literal, where the conditional logic is colocated with the option that triggers it. The config should hold the final integer the network sees.build_* helper instead of a literal + derive_config. The 1:1 nerfstudio mapping bans builder factories — variants are derive_config(BASE, ...). Tiny private factories that just shorten a fixed sub-config (no kwargs) are fine.name on a new literal. It's a required, kw-only field. The smoke test in tests/test_integration_configs.py asserts every supported_runners()[k].pipeline.name == k for runner-equipped integrations.name across variants. Aggregators in runner_configs.py raise on duplicates; pick a fresh slug.description=. flashdreams-run --help shows an empty help line and the smoke test (test_supported_runners_have_descriptions) fails. Set description= on every RunnerConfig literal.runner_name != pipeline.name. The CLI contract is "flashdreams-run <name> runs that integration"; a smoke test enforces parity on the in-tree set. Per-runner literals can opt out (e.g. two runners over one pipeline), but the default is to mirror._SUPPORTED_RUNNERS from an integration package. Out-of-tree code should ship a RunnerConfig (with description= set) and register it via the flashdreams.runner_configs entry point (or call register_runner(..., source="plugin", target=...) against a local dict); writing source="builtin" into the global registry from outside flashdreams/ violates the dependency direction.cv2, mediapy, ...) at module load time. Lazy-import them inside Runner.run() and gate on the runners extras with a clear ImportError so a serving deployment that only uses the bare pipeline doesn't pay the install cost.config.height, config.width). They aren't config — they vary every rollout. Pass them through initialize_autoregressive_cache(height=..., width=...) and stash them on the transformer instance.__post_init__ cross-config validation that depends on (height, width). Move it into initialize_autoregressive_cache; that's where the spatial layout actually exists._DEFAULT_VIDEO_HEIGHT, _WAN_VAE_SPATIAL_COMPRESSION). These are imported from runner modules and integrations to compute pixel ↔ latent dimensions; export them publicly.Latent shape:
latent_shape returns the global (pre-CP) shape. It must be per-rank — DiffusionModel.generate draws noise at this shape on each rank.latent_shape before initialize_autoregressive_cache. Per-rollout (B, H, W) is populated lazily; reading earlier must assert._pH / _pW / _pT (post-patchify) on the transformer. Store the raw _output_height / _output_width (pre-patchify) and divide by network.patch_spatial / patch_temporal inline at the use site. The variable name then matches the dimension the user passed in.ndim and .shape to the assertion in patchify_and_maybe_split_cp.CFG / CUDA graphs:
CUDAGraphWrapper across cond and uncond. Capture fails or silently reuses stale activations. Allocate two.CUDAGraphWrapper in __init__. The graph binds to the first cache's KV pointers; the second rollout reads stale storage. Build it inside initialize_autoregressive_cache._cuda_graph_capture_ar_idx on the config. It's a derived runtime quantity, not config data. Compute it once in Transformer.__init__ and store on the instance._cuda_graph_capture_ar_idx = chunks_total // len_t - 1. Off-by-one — that's the last filling step, not the first steady step.compile_network=True with mode="max-autotune". torch.compile then owns its own CUDA graphs and conflicts with CUDAGraphWrapper. Always go through compile_module.negative_context encoding. Only encode inside if cfg.requires_negative_context_embeddings: so CFG-off rollouts don't pay for it.Tests:
isinstance(transformer.network, MyDummy) without setting compile_network=False. Production integration configs default compile_network=True; the assertion will fail against an OptimizedModule wrapper. Always pin the flag explicitly in tests that introspect the network.(height, width), those checks moved to initialize_autoregressive_cache. Wrap the *cache build* call in pytest.raises, not the config call.RoPE:
RotaryPositionEmbedding3D in __init__. Per-rollout (height, width) aren't known yet, and the buffers wouldn't get CP-split for that rollout.shift_t(ar_idx) inside network.forward. Re-runs cat / repeat for every cond/uncond pass and ties the index into the captured graph as a Python int. Hoist into cache.start.kv_cache.update(k, v). Cached K's lose positional info; steady-state attention reads unrotated K's against rotated Q's.Take nvidia/flashdreams-integrations from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference pip.
Without those the skill loads but fails at the first command.