diff --git a/.github/workflows/reuse-lint.yml b/.github/workflows/reuse-lint.yml index c286ca314..ba0e44acb 100644 --- a/.github/workflows/reuse-lint.yml +++ b/.github/workflows/reuse-lint.yml @@ -73,14 +73,14 @@ jobs: echo "OK: $f" done - - name: CONTRIBUTING.md references the DCO / sign-off + - name: Contribution guide retains DCO policy run: | set -euo pipefail if ! grep -qE "Developer.{1,40}Certificate.{1,10}of.{1,10}Origin|Signed-off-by|sign-off" CONTRIBUTING.md; then echo "::error::CONTRIBUTING.md does not reference DCO / sign-off" exit 1 fi - echo "OK: CONTRIBUTING.md references DCO / sign-off" + echo "OK: CONTRIBUTING.md retains DCO policy" # Inline SPDX-License-Identifier on first-party source files. # REUSE.toml provides aggregate coverage so 'reuse lint' is green even diff --git a/AGENTS.md b/AGENTS.md index 4c37e354e..7e8220186 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -87,7 +87,7 @@ Before performance work, collect an nsys report with `uv run flashdreams-profile ## Testing Guidance -Every pytest test must carry exactly one of `ci_cpu`, `ci_gpu`, or `manual`; `CONTRIBUTING.md` has the exact rules, including how pytest discovers files and functions (`test_*.py`, `test_*`). Use module-level `pytestmark = pytest.mark.ci_cpu` for pure Python/metadata tests. Keep GPU, `libGL`/`cv2`, large-checkpoint, credential, and download-heavy checks out of `ci_cpu`. +Every pytest test must carry exactly one of `ci_cpu`, `ci_gpu`, or `manual`; [Testing](https://nvidia.github.io/flashdreams/main/documentation/tools/testing/) has the exact discovery and marker rules. Use module-level `pytestmark = pytest.mark.ci_cpu` for pure Python/metadata tests. Keep GPU, `libGL`/`cv2`, large-checkpoint, credential, and download-heavy checks out of `ci_cpu`. **v2 test ownership** — put a new test next to the thing it validates: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cafffe48d..6e3ffc397 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,6 +11,12 @@ We have intentionally kept the process light. If something below is unclear or feels heavier than it should be, that's a bug; please file an issue and we'll fix it. +Setup and build instructions: + +- [install and run FlashDreams](https://nvidia.github.io/flashdreams/main/quickstart/); +- [work on one integration package](https://nvidia.github.io/flashdreams/main/quickstart/#install-one-integration); +- [build and preview the website](https://nvidia.github.io/flashdreams/main/documentation/tools/build_website/). + ## Table of contents 1. [Ways to contribute](#ways-to-contribute) @@ -19,13 +25,14 @@ issue and we'll fix it. 4. [Submitting a pull request](#submitting-a-pull-request) 5. [Code review and merge](#code-review-and-merge) 6. [File Tree Of FlashDreams](#file-tree-of-flashdreams) -7. [Coding conventions](#coding-conventions) -8. [Testing](#testing) -9. [Dependency version bounds](#dependency-version-bounds) -10. [Working with a single integration package](#working-with-a-single-integration-package) -11. [Licensing of contributions](#licensing-of-contributions) -12. [Reporting issues](#reporting-issues) -13. [Code of Conduct](#code-of-conduct) +7. [Adding documentation](#adding-documentation) +8. [Coding conventions](#coding-conventions) +9. [Testing](#testing) +10. [Dependency version bounds](#dependency-version-bounds) +11. [Working with a single integration package](#working-with-a-single-integration-package) +12. [Licensing of contributions](#licensing-of-contributions) +13. [Reporting issues](#reporting-issues) +14. [Code of Conduct](#code-of-conduct) ## Ways to contribute @@ -35,9 +42,9 @@ overhead" to "high overhead": - **Try FlashDreams and tell us what broke.** A clear bug report — what you ran, what you expected, what you saw — is one of the most valuable contributions a project of this kind can receive. -- **Improve documentation.** README clarifications, integration walkthroughs, - performance notes, and FAQ entries all land easily and benefit every - future user. +- **Improve documentation.** Website articles, integration walkthroughs, + performance notes, and troubleshooting entries all land easily and benefit + every future user. - **Fix bugs.** Issues labelled `good first issue` are a friendly starting point. Larger fixes are welcome too — please leave a comment on the relevant issue first so we can avoid duplicate work. @@ -242,11 +249,13 @@ tracking issue where available. ## File Tree Of FlashDreams -Where a new app or model goes. Each documents its own layout: +Where a new app or model goes: ```text -apps/ # reusable apps; layout in apps/README.md -integrations_v2/ # model packages; layout in integrations_v2/README.md +apps/ # reusable, model-independent applications +integrations_v2/ # model packages and application adapters +docs/source/demos/ # canonical demo articles +docs/source/models/ # canonical model articles ``` The framework package, and the test and doc trees: @@ -274,6 +283,46 @@ tests/ # repo-wide test-runner scripts + meta checks, n docs/source/ # Zensical Markdown sources ``` +## Adding documentation + +Write documentation for users and contributors as a website article under +`docs/source/`, not as a package `README.md`. Update an existing article when +it already owns the topic. Otherwise, add a Markdown file in the section that +owns it: + +| Article | Website source | Example | +| --- | --- | --- | +| Install or first run | `docs/source/quickstart/` | `docs/source/quickstart/index.md` | +| Model requirements, setup, commands, or limitations | `docs/source/models/` | `docs/source/models/my_model.md` | +| Demo controls, options, or behavior | `docs/source/demos/` | `docs/source/demos/my_demo.md` | +| API concept or development guide | `docs/source/documentation//guides/` | `docs/source/documentation/demo_api/guides/my_topic.md` | +| API reference | `docs/source/documentation//api_reference/` | `docs/source/documentation/inferencing_api/api_reference/my_api.md` | +| Project or community process | `docs/source/community/` | `docs/source/community/my_process.md` | +| Common user failure | `docs/source/troubleshooting.md` | Add a focused section to the existing page | + +For example, a new demo article starts at `docs/source/demos/my_demo.md`: + +```markdown +--- +title: 'My Demo' +--- + + + + +Explain what the demo does, how to launch it, its controls, and its options. +``` + +Register a new page in the `.nav.yml` beside it; for this example, add +`My Demo: my_demo.md` to `docs/source/demos/.nav.yml`. Use a lowercase +snake-case filename that matches the model or demo package when applicable. + +Files outside `docs/source/` should link to the published website article +instead of copying its instructions. `README.md` outside website, should try to point to the canonical website as much as possible. + +Build or preview the site with the commands in +[Build Website](https://nvidia.github.io/flashdreams/main/documentation/tools/build_website/#build-the-website). + ## Coding conventions - Python 3.10+. Type-annotate new code; the project type-checks with @@ -303,65 +352,8 @@ docs/source/ # Zensical Markdown sources ## Testing -Pytest collection is configured in the root `pyproject.toml`. There is -no `testpaths` setting, so `pytest` from the repo root walks the tree: - -- Files named `test_*.py`. Pytest's default also collects `*_test.py`; - don't use that name here. -- Classes named `Test*` (pytest default). Some modules use classes, - some only module-level functions. -- Functions and methods named `test_*` (pytest default), including - `async def test_*`. - -Same test filename in different folders is fine (several -`test_application.py` files exist). Root `pyproject.toml` already sets -`--import-mode=importlib` so pytest keeps them separate. Do not remove -that flag. - -A `test_*.py` next to the code it validates is collected with no CI -change. The root `pyproject.toml` skips `parity_check`, -`parity_check_v2`, `baseline_fastvideo`, and `baseline_lightx2v` via -`norecursedirs`. - -Every test function must be marked with exactly one **CI tier marker**. -A pytest plugin (`flashdreams._pytest_plugins.marker_enforcement`) -enforces this at collection time -- tests without a marker are -rejected, and tests with both `ci_cpu` and `ci_gpu` are rejected. - -| Marker | When to use | CI runner | -|--------|-------------|-----------| -| `@pytest.mark.ci_cpu` | Pure CPU logic, no GPU or `libGL` needed | CPU runner | -| `@pytest.mark.ci_gpu` | Needs CUDA, `libGL`, or transitive `cv2` | GPU runner (RTX Pro 6000) | -| `@pytest.mark.manual` | Heavy (OOM risk), flaky, needs credentials or large downloads | Not run in CI | - -Use a module-level `pytestmark` when every test in a file shares the -same marker: - -```python -import pytest - -pytestmark = pytest.mark.ci_cpu -``` - -Use per-function markers when tests in the same file have different -tiers: - -```python -@pytest.mark.ci_cpu -def test_basic_math(): ... - -@pytest.mark.ci_gpu -def test_cudagraph_path(): ... -``` - -Running tests locally: - -```bash -uv run pytest -m ci_cpu # CPU-safe tests only -uv run pytest -m ci_gpu # GPU tests only (needs CUDA) -uv run pytest -m "not manual" # everything that runs in CI -uv run pytest # all tests including manual -``` +See [Testing](https://nvidia.github.io/flashdreams/main/documentation/tools/testing/) for test discovery, +CI tier markers, and commands. ## Dependency version bounds @@ -382,40 +374,7 @@ the declared minimums. This means: ## Working with a single integration package -The workspace contains many integration packages under `integrations_v2/`. -A full `uv sync` installs dependencies for *all* of them. If you only -need one (e.g. you're working on `omnidreams`), use the distribution -package name with `--package` to sync only that package's dependencies: - -```bash -# Only install omnidreams + its deps (skips unrelated heavy packages) -uv sync --package flashdreams-omnidreams --extra dev - -# Run a script/test from that integration only -uv run --package flashdreams-omnidreams pytest integrations_v2/omnidreams/tests/ -m ci_gpu -``` - -This avoids pulling in (and compiling) dependencies that other -integrations require but yours does not, further reducing setup time. - -Available integration packages: - -| Path | `uv --package` name | -|------|---------------------| -| `integrations_v2/causal_forcing` | `flashdreams-causal-forcing` | -| `integrations_v2/cosmos_predict2` | `flashdreams-cosmos-predict2` | -| `integrations_v2/fastvideo_causal_wan22` | `flashdreams-fastvideo-causal-wan22` | -| `integrations_v2/flashvsr` | `flashdreams-flashvsr` | -| `integrations_v2/lingbot` | `flashdreams-lingbot` | -| `integrations_v2/omnidreams` | `flashdreams-omnidreams` | -| `integrations_v2/self_forcing` | `flashdreams-self-forcing` | -| `integrations_v2/swiftvr` | `flashdreams-swiftvr` | -| `integrations_v2/wan21` | `flashdreams-wan21` | -| `integrations_v2/wan22` | `flashdreams-wan22` | - -The nested `integrations_v2/omnidreams/impl/ludus-renderer` workspace package is -named `ludus-renderer` and is installed as part of Omnidreams workflows -that need it. +See our [model gallery](https://nvidia.github.io/flashdreams/main/models/) page. ## Licensing of contributions diff --git a/README.md b/README.md index 66566d773..529a53cdb 100644 --- a/README.md +++ b/README.md @@ -22,129 +22,31 @@ and has grown into a general platform for real-time world-model applications across gaming, autonomous vehicles, robotics, simulated or virtual environments, and more. -[omnidreams-blog]: https://research.nvidia.com/labs/sil/projects/omnidreams-blog/ - -https://github.com/user-attachments/assets/2b000ce9-effe-4cc9-a227-5b4619413e4d - -## System Requirements - -- NVIDIA GPU with **80 GB VRAM or more** (e.g. H100 80GB), see notes below. -- NVIDIA driver from the **R580 series or newer** (default compatible with CUDA 13.x) -- **CUDA 13.x by default** (PyTorch `2.11.0+cu130` and the `nvidia-*-cu13` libraries are - resolved by `uv sync`. A system CUDA toolkit is needed only for the - developer extras and is included in `nvidia/cuda:13.2.1-cudnn-devel-ubuntu24.04`) -- **Python >= 3.10** -- **PyTorch >= 2.11.0+cu130** (`>= 2.9` for bare PyPI library install) -- Linux x86-64 or arm64 -- **100 GB+ free storage space** recommended for environment and model checkpoints. -- Docker with the - [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) - (optional, only for the container workflow) - -> Development and testing were performed on GPUs with **80 GB of VRAM or more**. -> Inference can fail (out-of-memory) on consumer and even enthusiast GPUs. -> Per-model GPU and VRAM requirements are listed on each model page in -> [the model gallery](https://nvidia.github.io/flashdreams/main/models/index.html). ## Quickstart -The complete setup is in -[the installation guide](https://nvidia.github.io/flashdreams/main/quickstart/index.html#install). -Assuming `uv` is [installed](https://docs.astral.sh/uv/getting-started/installation), the shortest viable path is: - -```bash -git clone https://github.com/NVIDIA/flashdreams.git -cd flashdreams -uv sync --extra runners -export HF_TOKEN= -uv run flashdreams-run --help -``` +The complete setup is in our +[quick-start guide](https://nvidia.github.io/flashdreams/main/quickstart/index.html#install). -Note for developers/maintainers you would want to run `uv sync --extra dev --extra runners` instead. +## Checkout Our Demos! 🎉 -### Select CUDA Version +Quick-start to our interactive driving demo is [here](https://nvidia.github.io/flashdreams/main/quickstart/index.html#run-your-first-model). -FlashDreams defaults to the CUDA 13 PyTorch stack (the `cuda13` dependency -group is activated automatically via `default-groups`): +![](docs/source/_static/demo_clips/interactive-drive.avif) -- **Linux:** `uv sync` pulls `torch>=2.9` from PyPI, whose Linux wheels are - the CUDA 13 build (the `+cu130` local-version tag is just stripped on - PyPI). -- **Windows:** `uv sync` pulls torch from NVIDIA's `cu130` index - (`https://download.pytorch.org/whl/cu130`) — PyPI's Windows torch wheel - is CPU-only. +Checkout our game-like crazy-robotaxi demo [here](https://nvidia.github.io/flashdreams/main/demos/crazy_robotaxi/). -CUDA 12.8 is available as an opt-in side profile (Linux only) via the -`cuda12` dependency group: - -```bash -uv sync --group cuda12 --extra runners -``` - -The two groups are declared mutually exclusive in `pyproject.toml`, so uv -deactivates the default `cuda13` group automatically when `--group cuda12` -is passed. - -Then launch your first model by following [the Get Started guide](https://nvidia.github.io/flashdreams/main/quickstart/index.html#run-your-first-model). -For example, the Self-Forcing T2V v2 application is: - -```bash -uv run --project integrations_v2/self_forcing \ - flashdreams-run-v2 t2v-self-forcing-wan2.1-t2v-1.3b \ - --output-path artifacts/t2v-self-forcing-wan2.1-t2v-1.3b.mp4 --timeout unbound -- \ - --prompt "A cat surfing." --total-blocks 7 -``` - -You can also install FlashDreams as a library from PyPI: - -```bash -pip install flashdreams -``` - -### Try the interactive driving demo - -Drive a world model in real time with the unified OmniDreams `local-window` or -`webrtc` launch mode. See the -**[interactive demo guide](https://nvidia.github.io/flashdreams/main/models/omnidreams.html#launch-the-interactive-demo)**. - -## Supported models - -FlashDreams ships first-party integrations under -[`integrations_v2/`](integrations_v2/). Each model has a dedicated docs page with -runner slugs, multi-GPU commands, and (where available) profiling benchmarks. - -| Model | Family | -| --- | --- | -| [Self-Forcing](https://nvidia.github.io/flashdreams/main/models/self_forcing.html) | Streaming Wan2.1 T2V | -| [OmniDreams](https://nvidia.github.io/flashdreams/main/models/omnidreams.html) | HDMap-conditioned driving world model | -| [LingBot-World](https://nvidia.github.io/flashdreams/main/models/lingbot_world.html) | Camera-controllable I2V world model | -| [Waypoint 1.5](https://nvidia.github.io/flashdreams/main/models/waypoint.html) | Interactive image-established keyboard/mouse-controlled world model | -| [Wan2.1](https://nvidia.github.io/flashdreams/main/models/wan21.html) | Bidirectional T2V / I2V | -| [Causal-Forcing](https://nvidia.github.io/flashdreams/main/models/causal_forcing.html) | Streaming Wan2.1 T2V / I2V | -| [Causal Wan2.2](https://nvidia.github.io/flashdreams/main/models/causal_wan22.html) | FastVideo Causal Wan 2.2 14B MoE T2V | -| [FlashVSR](https://nvidia.github.io/flashdreams/main/models/flashvsr.html) | Streaming video super-resolution | -| [Cosmos-Predict2.5](https://nvidia.github.io/flashdreams/main/models/cosmos_predict2.html) | Bidirectional T2V / I2V | - -See [the model gallery](https://nvidia.github.io/flashdreams/main/models/index.html) and -[the new method guide](https://nvidia.github.io/flashdreams/main/developer_guides/new_integration.html) -to add your own. +![](docs/source/_static/demo_clips/crazy-robotaxi.avif) ## Developer guides -- [Architecture](ARCHITECTURE.md) -- [Inference pipeline overview](https://nvidia.github.io/flashdreams/main/developer_guides/inference_pipeline_overview.html) -- [Config system](https://nvidia.github.io/flashdreams/main/developer_guides/config_system.html) -- [Add a new method](https://nvidia.github.io/flashdreams/main/developer_guides/new_integration.html) +### For Demo Developers -For day-to-day development: +Make demos using world-models with our [Demo API](https://nvidia.github.io/flashdreams/main/documentation/demo_api/). -```bash -uv sync --extra dev --extra runners -uv run --group lint pre-commit run -a -uv run pytest -m "not manual" -``` +### For Model Developers -See [`DEV.md`](DEV.md) for repository-specific workflow notes. +Implement world-models with our [Inferencing API](https://nvidia.github.io/flashdreams/main/documentation/inferencing_api/). ## Contributing @@ -197,3 +99,5 @@ If FlashDreams is useful in your research or product, please cite the project: url = {https://arxiv.org/abs/2606.03159}, } ``` + +[omnidreams-blog]: https://research.nvidia.com/labs/sil/projects/omnidreams-blog/ diff --git a/apps/README.md b/apps/README.md index eb7abe002..8c72baa92 100644 --- a/apps/README.md +++ b/apps/README.md @@ -15,7 +15,8 @@ apps// / # the app implementation tests/ # validate the app against a stub, not a real model pyproject.toml - README.md # purpose, controls, example command line, options + +docs/source/demos/.md # canonical website article ``` Tests live in `apps//tests/`, beside the package rather than inside @@ -24,6 +25,7 @@ model or its adapter belongs in `integrations_v2//tests/`. ## What is here +- `minimal_t2v` — Minimal T2V app from the Create a demo tutorial. - `t2v` — text-to-video: prompt in, frames out. The reference app to copy. - `cam2v` — interactive camera-to-video. - `action2v` — world models driven by direct action input. @@ -37,5 +39,8 @@ model or its adapter belongs in `integrations_v2//tests/`. `omnidreams_game_engine` is not the OmniDreams model. That is `integrations_v2/omnidreams`. The names are close; the packages are unrelated. -Each app's own README covers how to launch it and what the controls are. To -bind a model to one of these, read `integrations_v2/README.md`. +Put launch instructions, controls, and options in +`docs/source/demos/.md`. Follow the website's +[Create a demo](../docs/source/documentation/demo_api/guides/create_demo.md) +and [Integrate model adapter for a demo](../docs/source/documentation/demo_api/guides/integrate_model_adapter_for_a_demo.md) +guides. diff --git a/apps/minimal_t2v/minimal_t2v/__init__.py b/apps/minimal_t2v/minimal_t2v/__init__.py new file mode 100644 index 000000000..4e7852900 --- /dev/null +++ b/apps/minimal_t2v/minimal_t2v/__init__.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Reference application for the Create a demo tutorial.""" + +from .application import MinimalT2VApplication + +__all__ = ["MinimalT2VApplication"] diff --git a/apps/minimal_t2v/minimal_t2v/application.py b/apps/minimal_t2v/minimal_t2v/application.py new file mode 100644 index 000000000..2235eb68b --- /dev/null +++ b/apps/minimal_t2v/minimal_t2v/application.py @@ -0,0 +1,74 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Minimal text-to-video application with lazy model initialization.""" + +import argparse +from collections.abc import Sequence +from typing import Any + +from flashdreams.api_v2.application import IApplication +from flashdreams.runtime_v2.session_desc import SessionDesc +from flashdreams.runtime_v2.video_tensor import VideoTensorLayout + +from .session import MinimalT2VSession + + +class MinimalT2VApplication(IApplication): + """Tutorial app configured by a model adapter. + + The pipeline must accept text and latent dimensions in ``initialize_cache`` + and return ``tchw`` video from ``generate``, as Wan T2V pipelines do. + """ + + def __init__( + self, pipeline_config: Any, desc: SessionDesc, total_blocks: int = 8 + ) -> None: + """Keep model configuration without loading weights.""" + if desc.output_layout is not VideoTensorLayout.tchw: + raise ValueError("Minimal T2V requires tchw output.") + if total_blocks <= 0: + raise ValueError("total_blocks must be positive.") + self.pipeline_config = pipeline_config + self.desc = desc + self.total_blocks = total_blocks + self.pipeline: Any = None + self.prompt = "" + + def init(self, commandline_args: Sequence[str]) -> None: + """Parse the initial prompt and load the shared pipeline.""" + parser = argparse.ArgumentParser(description="Generate video from text.") + parser.add_argument("--default-prompt", default="") + parser.add_argument("--device", default="cuda") + args = parser.parse_args(commandline_args) + self.prompt = args.default_prompt.strip() + self.pipeline = self.pipeline_config.setup().to(args.device).eval() + + def session_desc(self) -> SessionDesc: + """Return the model adapter's output description.""" + return self.desc + + def create_session(self, session_desc: SessionDesc) -> MinimalT2VSession: + """Create a session at the model's supported size and layout.""" + if self.pipeline is None: + raise RuntimeError("Call init before create_session.") + if ( + session_desc.video_width != self.desc.video_width + or session_desc.video_height != self.desc.video_height + or session_desc.output_layout != self.desc.output_layout + or session_desc.frames_per_second_for_step + != self.desc.frames_per_second_for_step + ): + raise ValueError("The session must match the model's output description.") + ratio = self.pipeline.decoder.spatial_compression_ratio + if session_desc.video_width % ratio or session_desc.video_height % ratio: + raise ValueError( + "Video dimensions must be divisible by decoder compression." + ) + return MinimalT2VSession( + self.pipeline, session_desc, self.prompt, self.total_blocks + ) + + def close(self) -> None: + """Release the shared pipeline reference.""" + self.pipeline = None diff --git a/apps/minimal_t2v/minimal_t2v/model_loop.py b/apps/minimal_t2v/minimal_t2v/model_loop.py new file mode 100644 index 000000000..1d4dc0b00 --- /dev/null +++ b/apps/minimal_t2v/minimal_t2v/model_loop.py @@ -0,0 +1,77 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Prompt and autoregressive cache owned by the tutorial model loop.""" + +from dataclasses import dataclass +from typing import Any + +from flashdreams.api_v2.loop import IModelLoop +from flashdreams.runtime_v2.session_desc import SessionDesc +from flashdreams.runtime_v2.step_result import StepResult +from flashdreams.runtime_v2.user_input_events import UserInputEvents + + +@dataclass +class TextToVideoState: + """Mutable rollout state, accessed on the model thread.""" + + pipeline: Any + """Text-conditioned pipeline shared across sessions.""" + desc: SessionDesc + """Pixel dimensions and frame layout requested by the runtime.""" + total_blocks: int + """Maximum blocks the selected model supports per prompt.""" + prompt: str = "" + """Current prompt; empty text pauses generation.""" + cache: Any = None + """Per-prompt cache, created lazily on the model thread.""" + index: int = 0 + """Next autoregressive block, independent of the runtime step index.""" + + def set_prompt(self, prompt: str) -> None: + """Start a fresh rollout or pause generation for empty text.""" + self.prompt = prompt.strip() + self.cache = None + self.index = 0 + + +class TextToVideoLoop(IModelLoop[TextToVideoState]): + """Generate bounded rollouts while waiting for further UI prompts.""" + + def step(self, step_index: int, events: UserInputEvents) -> list[StepResult]: + """Generate and finalize one block, or wait for another prompt.""" + state = self.state + if not state.prompt or state.index >= state.total_blocks: + return [] + if state.cache is None: + ratio = state.pipeline.decoder.spatial_compression_ratio + state.cache = state.pipeline.initialize_cache( + text=[state.prompt], + height=state.desc.video_height // ratio, + width=state.desc.video_width // ratio, + ) + frames = state.pipeline.generate( + autoregressive_index=state.index, cache=state.cache + ) + metrics = state.pipeline.finalize( + autoregressive_index=state.index, cache=state.cache + ) + state.index += 1 + return [ + StepResult( + step_index=step_index, + output=frames.detach(), + frame_count=int(frames.shape[0]), + output_layout=state.desc.output_layout, + metrics=metrics, + ) + ] + + def reset(self) -> None: + """Restart the current prompt after a runtime reset.""" + self.state.set_prompt(self.state.prompt) + + def close(self) -> None: + """Release the per-prompt cache.""" + self.state.cache = None diff --git a/apps/minimal_t2v/minimal_t2v/session.py b/apps/minimal_t2v/minimal_t2v/session.py new file mode 100644 index 000000000..1e5c83387 --- /dev/null +++ b/apps/minimal_t2v/minimal_t2v/session.py @@ -0,0 +1,45 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Session wiring for the Minimal T2V model and UI loops.""" + +from typing import Any + +from flashdreams.api_v2.session import ISession +from flashdreams.runtime_v2.session_desc import SessionDesc + +from .model_loop import TextToVideoLoop, TextToVideoState +from .ui_loop import MinimalT2VUILoop, MinimalT2VUIState + + +class MinimalT2VSession(ISession): + """One interactive tutorial session sharing the application's pipeline.""" + + def __init__( + self, pipeline: Any, desc: SessionDesc, prompt: str, total_blocks: int + ) -> None: + """Keep session inputs until the runtime initializes the loops.""" + self.pipeline = pipeline + self.desc = desc + self.prompt = prompt + self.total_blocks = total_blocks + + @property + def session_desc(self) -> SessionDesc: + """Return the description used to configure the window.""" + return self.desc + + def init(self) -> None: + """Register independent model and UI state.""" + model_loop = self.register_model_loop( + TextToVideoLoop, + state=TextToVideoState( + self.pipeline, self.desc, self.total_blocks, self.prompt + ), + ) + self.register_ui_loop( + MinimalT2VUILoop, + state=MinimalT2VUIState(model_loop, self.prompt), + width=self.desc.video_width, + height=self.desc.video_height, + ) diff --git a/apps/minimal_t2v/minimal_t2v/ui_loop.py b/apps/minimal_t2v/minimal_t2v/ui_loop.py new file mode 100644 index 000000000..704487da7 --- /dev/null +++ b/apps/minimal_t2v/minimal_t2v/ui_loop.py @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""ImGui prompt controls sending messages to the tutorial model loop.""" + +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +from torch import Tensor + +from flashdreams.api_v2.loop import IModelLoop, invoke_async +from flashdreams.runtime_v2.imgui_ui_loop import ImGuiUILoop +from flashdreams.runtime_v2.user_input_events import UserInputEvents + +if TYPE_CHECKING: + from .model_loop import TextToVideoState + + +@dataclass +class MinimalT2VUIState: + """Editable prompt owned by the UI thread.""" + + model_loop: IModelLoop["TextToVideoState"] + """Destination of prompt update messages.""" + text: str = "" + """Text currently displayed in the prompt field.""" + + +class MinimalT2VUILoop(ImGuiUILoop[MinimalT2VUIState]): + """Send prompt updates without directly mutating model state.""" + + def step_ui( + self, imgui: Any, step_index: int, events: UserInputEvents + ) -> Tensor | None: + """Draw prompt controls over the latest generated frame.""" + imgui.begin("Minimal T2V") + try: + _, self.state.text = imgui.input_text("Prompt", self.state.text) + if imgui.button("Send"): + invoke_async( + self.state.model_loop, + lambda state, text=self.state.text: state.set_prompt(text), + ) + finally: + imgui.end() + return self.presented_model_frame() diff --git a/apps/minimal_t2v/pyproject.toml b/apps/minimal_t2v/pyproject.toml new file mode 100644 index 000000000..e4b94edbd --- /dev/null +++ b/apps/minimal_t2v/pyproject.toml @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +[build-system] +requires = ["setuptools>=69", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "flashdreams-minimal-t2v" +version = "0.1.0" +description = "Reference implementation for the Create a demo tutorial" +requires-python = ">=3.10" +dependencies = ["flashdreams[local-window]"] + +[tool.uv.sources] +flashdreams = { workspace = true } + +[tool.setuptools.packages.find] +include = ["minimal_t2v*"] diff --git a/apps/minimal_t2v/tests/test_application.py b/apps/minimal_t2v/tests/test_application.py new file mode 100644 index 000000000..8146a29b4 --- /dev/null +++ b/apps/minimal_t2v/tests/test_application.py @@ -0,0 +1,136 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""CPU checks for the tutorial's startup, prompt messages, and cache lifecycle.""" + +from dataclasses import replace +from unittest.mock import Mock + +import pytest +import torch +from minimal_t2v import MinimalT2VApplication +from minimal_t2v.ui_loop import MinimalT2VUILoop + +from flashdreams.runtime_v2.session_desc import SessionDesc +from flashdreams.runtime_v2.user_input_events import UserInputEvents +from flashdreams.runtime_v2.video_tensor import VideoTensorLayout + +pytestmark = pytest.mark.ci_cpu + + +def _application() -> tuple[MinimalT2VApplication, Mock, Mock]: + pipeline = Mock() + pipeline.to.return_value = pipeline + pipeline.eval.return_value = pipeline + pipeline.decoder.spatial_compression_ratio = 8 + pipeline.generate.return_value = torch.zeros(2, 3, 16, 32) + pipeline.finalize.return_value = {"total_ms": 1.0} + config = Mock() + config.setup.return_value = pipeline + app = MinimalT2VApplication( + config, SessionDesc(video_width=32, video_height=16), total_blocks=2 + ) + return app, pipeline, config + + +def test_help_and_description_do_not_load_the_model() -> None: + app, _, config = _application() + assert app.session_desc().video_width == 32 + with pytest.raises(SystemExit) as exit_info: + app.init(["--help"]) + assert exit_info.value.code == 0 + config.setup.assert_not_called() + with pytest.raises(RuntimeError, match="init"): + app.create_session(app.session_desc()) + + +def test_prompt_generation_reset_and_close() -> None: + app, pipeline, _ = _application() + app.init(["--default-prompt", " First prompt ", "--device", "cpu"]) + pipeline.to.assert_called_once_with("cpu") + session = app.create_session(app.session_desc()) + session.init() + loop = session.model_loop + events = UserInputEvents([]) + pipeline.initialize_cache.assert_not_called() + + first = loop.step(7, events)[0] + cache = pipeline.initialize_cache.return_value + pipeline.initialize_cache.assert_called_once_with( + text=["First prompt"], height=2, width=4 + ) + pipeline.generate.assert_called_once_with(autoregressive_index=0, cache=cache) + pipeline.finalize.assert_called_once_with(autoregressive_index=0, cache=cache) + assert first.step_index == 7 + assert first.frame_count == 2 + assert first.output_layout is VideoTensorLayout.tchw + assert first.read_output().shape == (2, 3, 16, 32) + assert first.metrics == {"total_ms": 1.0} + loop.step(8, events) + assert loop.step(9, events) == [] + assert pipeline.generate.call_count == 2 + assert not loop.is_finished() # The UI can still send another prompt. + + loop.reset() + loop.step(10, events) + assert pipeline.initialize_cache.call_count == 2 + pipeline.generate.assert_called_with(autoregressive_index=0, cache=cache) + loop.close() + assert loop.state.cache is None + app.close() + assert app.pipeline is None + + +def test_ui_captures_prompt_and_queues_model_thread_update() -> None: + app, pipeline, _ = _application() + app.init(["--device", "cpu"]) + session = app.create_session(app.session_desc()) + session.init() + loop, ui = session.model_loop, session.ui_loop + assert isinstance(ui, MinimalT2VUILoop) + events = UserInputEvents([]) + assert loop.step(0, events) == [] + imgui = Mock() + imgui.input_text.return_value = (True, "Second prompt") + imgui.button.return_value = True + + ui.step_ui(imgui, 0, events) + imgui.end.assert_called_once() + ui.state.text = "Edited after clicking Send" + assert loop.state.prompt == "" + loop._run_message_batch() + assert loop.state.prompt == "Second prompt" + loop.step(4, events) + pipeline.initialize_cache.assert_called_once_with( + text=["Second prompt"], height=2, width=4 + ) + + imgui.input_text.return_value = (True, " ") + ui.step_ui(imgui, 1, events) + loop._run_message_batch() + assert loop.step(5, events) == [] + assert loop.state.cache is None + + +@pytest.mark.parametrize( + "changes", + [ + {"video_width": 64}, + {"video_height": 32}, + {"output_layout": VideoTensorLayout.btchw}, + {"frames_per_second_for_step": 16}, + ], +) +def test_session_must_match_model_output(changes: dict) -> None: + app, _, _ = _application() + app.init(["--device", "cpu"]) + with pytest.raises(ValueError, match="output description"): + app.create_session(replace(app.session_desc(), **changes)) + + +def test_rejects_dimensions_that_truncate_latents() -> None: + app, _, _ = _application() + app.desc = replace(app.desc, video_width=31) + app.init(["--device", "cpu"]) + with pytest.raises(ValueError, match="divisible"): + app.create_session(app.session_desc()) diff --git a/apps/t2v/tests/test_structure.py b/apps/t2v/tests/test_structure.py index 12a85b779..69b52a922 100644 --- a/apps/t2v/tests/test_structure.py +++ b/apps/t2v/tests/test_structure.py @@ -44,60 +44,6 @@ """Model packages and their user-facing application slugs.""" -@pytest.mark.parametrize(("model", "factories"), _MODEL_APPLICATIONS) -def test_model_owns_v2_t2v_adapter(model: str, factories: dict[str, str]) -> None: - """Keep model code and its T2V adapter in one v2 integration package.""" - project = _REPO_ROOT / "integrations_v2" / model - application_project = project / "apps" / "t2v" - implementation = project / "impl" - config = project / "config.py" - manifest = tomllib.loads((project / "pyproject.toml").read_text()) - - assert not (_REPO_ROOT / "integrations" / model).exists() - assert {path.name for path in project.glob("*.py")} == {"__init__.py", "config.py"} - assert not (project / "runner.py").exists() - assert {path.name for path in application_project.glob("*.py")} == { - "__init__.py", - "adapter.py", - } - assert not (application_project / "t2v").exists() - assert config.is_file() - assert (implementation / "__init__.py").is_file() - assert not (implementation / "config.py").exists() - for source_path in implementation.rglob("*.py"): - source = source_path.read_text() - assert "from t2v" not in source - assert "import t2v" not in source - assert "flashdreams.runtime_v2" not in source - assert "flashdreams.serving" not in source - assert not list(project.glob("apps/*/config.py")) - application_readme = (application_project / "README.md").read_text() - model_readme = (project / "README.md").read_text() - for slug in factories: - assert slug in application_readme - assert slug in model_readme - assert "../../../../apps/t2v/README.md" in application_readme - assert not (application_project / "tests").exists() - assert not (application_project / "impl").exists() - assert (project / "tests").is_dir() - assert "flashdreams-t2v" in manifest["project"]["dependencies"] - assert f"{model}.impl" in manifest["tool"]["setuptools"]["packages"] - for slug, factory in factories.items(): - assert factory == "create_app" or ( - factory.startswith("create_app_") - and slug.endswith( - "-" + factory.removeprefix("create_app_").replace("_", "-") - ) - ) - assert manifest["tool"]["setuptools"]["package-dir"] == {model: "."} - assert manifest["project"]["entry-points"]["flashdreams.applications_v2"] == { - slug: f"{model}.apps.t2v.adapter:{factory}" - for slug, factory in factories.items() - } - assert "flashdreams.runner_configs" not in manifest["project"]["entry-points"] - assert "RUNNER_" not in config.read_text() - - def test_shared_t2v_code_is_owned_by_app_package() -> None: """Keep reusable demo infrastructure outside the framework and models.""" project = _REPO_ROOT / "apps" / "t2v" diff --git a/docs/source/.nav.yml b/docs/source/.nav.yml index d922344e7..4ccf41a32 100644 --- a/docs/source/.nav.yml +++ b/docs/source/.nav.yml @@ -10,11 +10,8 @@ nav: - Demo API Overview: documentation/demo_api/index.md - Guides: - Create a demo: documentation/demo_api/guides/create_demo.md - - Integrate a model with a demo: documentation/demo_api/guides/integrate_model.md - - Demo configuration: documentation/demo_api/guides/configuration.md - - Application slugs and dispatch: documentation/demo_api/guides/application_slugs.md + - Integrate model adapter for a demo: documentation/demo_api/guides/integrate_model_adapter_for_a_demo.md - Interactive serving: documentation/demo_api/guides/interactive_serving.md - - Local benchmarks: documentation/demo_api/guides/local_benchmarks.md - Profiling a demo: documentation/demo_api/guides/profiling.md - API Reference: - Application API: documentation/demo_api/api_reference/application.md @@ -23,16 +20,17 @@ nav: - Guides: - Create a model: documentation/inferencing_api/guides/create_model.md - Stream inference pipeline: documentation/inferencing_api/guides/stream_inference_pipeline.md - - Latency tuning: documentation/inferencing_api/guides/latency_tuning.md - Profiling a pipeline: documentation/inferencing_api/guides/profiling.md - Accelerated building blocks: documentation/inferencing_api/guides/accelerated.md - API Reference: - - Inference Runtime API: documentation/inferencing_api/api_reference/runtime.md - Pipeline API: documentation/inferencing_api/api_reference/pipeline.md - Core API: documentation/inferencing_api/api_reference/core.md + - Accelerated API: documentation/inferencing_api/api_reference/accelerated.md - Tooling: - Tooling Overview: documentation/tools/index.md - Offline Program Packager: documentation/tools/offline_program_packager.md - Build Website: documentation/tools/build_website.md + - Testing: documentation/tools/testing.md + - Benchmarking: documentation/tools/benchmarking.md - Troubleshooting: troubleshooting.md - Community: community diff --git a/docs/source/community/.nav.yml b/docs/source/community/.nav.yml new file mode 100644 index 000000000..fbe5d798b --- /dev/null +++ b/docs/source/community/.nav.yml @@ -0,0 +1,2 @@ +nav: + - Overview: index.md diff --git a/docs/source/community/index.md b/docs/source/community/index.md index 57493e5b5..ccfada5b6 100644 --- a/docs/source/community/index.md +++ b/docs/source/community/index.md @@ -6,9 +6,6 @@ title: 'Community' -Use the issue tracker for work that needs a record. Use Discord for informal -questions and conversation. Do not report security problems in public. -
ContributeDevelopment workflow, testing requirements, and contribution policy. Bugs and feature requestsSearch existing reports or open an issue with a reproducible example. @@ -47,17 +44,10 @@ uv sync --package flashdreams-wan21 --extra dev ### Which Python API should I use? -Use `flashdreams.api_v2`; the -[Demo API reference](../documentation/demo_api/index.md) maps its -application, session, and loop contracts. - -### How do I add a model? - -Start with -[Create a model](../documentation/inferencing_api/guides/create_model.md), then -[integrate it with a demo](../documentation/demo_api/guides/integrate_model.md). +Make demos using world-models with our [Demo API](../documentation/demo_api/index.md). +Implement world-models with our [Inferencing API](../documentation/inferencing_api/index.md). -### Can I use FlashDreams commercially? +### How do I implement a model with flashdreams? -The Apache 2.0 license permits commercial use. Model weights and datasets may -have separate licenses. +1. [Implement a model in FlashDreams](../documentation/inferencing_api/guides/create_model.md), then +2. [Integrate your new model with a demo](../documentation/demo_api/guides/integrate_model_adapter_for_a_demo.md). diff --git a/docs/source/demos/.nav.yml b/docs/source/demos/.nav.yml index 7a1aa40a1..8664b90b8 100644 --- a/docs/source/demos/.nav.yml +++ b/docs/source/demos/.nav.yml @@ -6,5 +6,6 @@ nav: - Model Interfaces: - Action2V: action2v.md - Cam2V: cam2v.md + - Minimal T2V: minimal_t2v.md - T2V: t2v.md - V2V: v2v.md diff --git a/docs/source/demos/index.md b/docs/source/demos/index.md index 025f07577..f5b50600a 100644 --- a/docs/source/demos/index.md +++ b/docs/source/demos/index.md @@ -31,4 +31,5 @@ For how to run a particular **demo/model pair**, refer to the format listed here +
diff --git a/docs/source/demos/minimal_t2v.md b/docs/source/demos/minimal_t2v.md new file mode 100644 index 000000000..646569c8f --- /dev/null +++ b/docs/source/demos/minimal_t2v.md @@ -0,0 +1,25 @@ +--- +title: 'Minimal T2V' +--- + + + + +Minimal T2V is the tutorial application built in [Create a demo](../documentation/demo_api/guides/create_demo.md). +It owns an ImGui prompt field, a model loop, and a per-prompt generation cache. + +## Quick Start: + +
+ [Wan 2.1](../models/wan21.md#minimal-t2v){ .fd-button } +
+ +## Demo Arguments + +Edit the prompt and click **Send** to start a new clip. Empty text pauses +generation. The window remains open between clips; close it to stop. + +| App argument | Default | Purpose | +| --- | --- | --- | +| `--default-prompt` | Empty | Initial prompt; omit it to wait for input. | +| `--device` | `cuda` | Device on which to load the model. | diff --git a/docs/source/documentation/cli.md b/docs/source/documentation/cli.md index 00515332c..af47d1066 100644 --- a/docs/source/documentation/cli.md +++ b/docs/source/documentation/cli.md @@ -53,20 +53,8 @@ These options override the default demo session values requested by a particular | `--pixel-height N` | Override generated-frame height. | | `--fps N` | Override the generated-frame playback rate. | | `--layout {tchw,btchw,bcthw,bvtchw}` | Override generated tensor layout. | -| `--backpressure-mode {block,drop_oldest}` | Choose how the model thread handles a full presentation queue. | -| `--presentation-mode {on_demand,continuous}` | Choose when the presentation loop renders. | - -#### Backpressure Mode - -- `block` waits for queue capacity so generated chunks are retained. - -- `drop_oldest` discards the oldest queued chunk to favor recent output. - -#### Presentation Mode - -- `on_demand` presents each selected model frame when it arrives. - -- `continuous` keeps the UI responsive between model frames by reusing the newest frame. +| `--backpressure-mode {block,drop_oldest}` | Choose full-queue behavior: `block` waits for capacity to retain generated chunks; `drop_oldest` discards the oldest queued chunk to favor recent output. | +| `--presentation-mode {on_demand,continuous}` | Choose rendering behavior: `on_demand` presents each selected model frame when it arrives; `continuous` keeps the UI responsive between model frames by reusing the newest frame. | ### Examples @@ -110,7 +98,5 @@ uv run flashdreams-run-v2 DEMO_MODEL_SLUG --mode native-window --window-title Fl ## See also - [Quickstart](../quickstart/index.md) -- [Demo configuration](demo_api/guides/configuration.md) -- [Application slugs](demo_api/guides/application_slugs.md) - [Demo API](demo_api/index.md) - [Offline Program Packager](tools/offline_program_packager.md) diff --git a/docs/source/documentation/demo_api/api_reference/application.md b/docs/source/documentation/demo_api/api_reference/application.md index 3ba277494..83fa0e31e 100644 --- a/docs/source/documentation/demo_api/api_reference/application.md +++ b/docs/source/documentation/demo_api/api_reference/application.md @@ -8,424 +8,26 @@ title: 'Demo Application API Reference' This page documents the public application protocols in `flashdreams.api_v2` and the runtime-owned value types those protocols exchange. For implementation workflows, start with [Create a demo](../guides/create_demo.md) or -[Integrate a model with a demo](../guides/integrate_model.md). +[Integrate model adapter for a demo](../guides/integrate_model_adapter_for_a_demo.md). ## Application and session -### `flashdreams.api_v2.application.IApplication` +::: flashdreams.api_v2.application.IApplication -One application, for as long as the process runs. - -Parses its own arguments and holds whatever its sessions share, such as a -checkpoint or a compiled pipeline. It outlives every session it creates, so -that shared state is loaded once here and released in `close`. - -An application module implements this and `ISession`. The runtime -creates everything else and passes it in. - -[View source](https://github.com/NVIDIA/flashdreams/blob/main/flashdreams/flashdreams/api_v2/application.py#L14) - -#### `init` - -```python -def init(self, commandline_args: Sequence[str]) -> None -``` - -Parse application arguments and validate startup state. - -#### `session_desc` - -```python -def session_desc(self) -> SessionDesc | None -``` - -Return the description of a session this application would generate. - -A caller has to describe a session before there is one to describe, and -only the application knows what its model was trained for. Asked before -`init`, so describing a session costs nothing. - -**Returns** - -The session to create when nobody asks for another, or `None`, -the default, from an application that generates whatever it is -asked for. Its caller describes the session instead. - -#### `create_session` - -```python -def create_session(self, session_desc: SessionDesc) -> ISession -``` - -Create one isolated, uninitialized session for `session_desc`. - -**Parameters** - -- `session_desc`: Session the runtime is asking for. - -**Returns** - -A session for `session_desc`, resolved to what this application can -actually produce. - -**Raises** - -- `ValueError`: The application cannot honour `session_desc`. - -#### `close` - -```python -def close(self) -> None -``` - -Release whatever the application holds. - -Not abstract, and does nothing by default, so an application with nothing -to release does not implement it. - -### `flashdreams.api_v2.session.ISession` - -One application run with a model loop and a UI loop. - -Register the model loop in `init`. If no UI loop is registered, -the runtime uses `BlitModelOutputToScreenLoop`. - -[View source](https://github.com/NVIDIA/flashdreams/blob/main/flashdreams/flashdreams/api_v2/session.py#L21) - -- `_registered_ui_loop: IUILoop[Any] | None` - -- `_registered_model_loop: IModelLoop[Any] | None` - -#### `parallel_context` - -```python -def parallel_context(self) -> ParallelContext | None -``` - -Return the model mesh before initialization, or `None` for a local session. - -The runtime synchronizes model steps and inputs across this world. -Only rank zero owns a client window and output sinks. - -#### `init` - -```python -def init(self) -> None -``` - -Initialize state and register this session's loops. - -A model loop is required. Registering a UI loop is optional; without one -the runtime uses `BlitModelOutputToScreenLoop`. - -**Raises** - -- `RuntimeError`: No model loop was registered by the time the runtime - asks for the loops. - -#### `session_desc` - -```python -def session_desc(self) -> SessionDesc -``` - -Return the description used to configure the runtime. - -#### `register_ui_loop` - -```python -def register_ui_loop(self, loop_type: type[IUILoop[Any]], *, state: Any=None, **kwargs: Any) -> IUILoop[Any] -``` - -Create and register the UI loop, and return it. - -Omit this call to use the default UI. The loop is paced at the session's -`frames_per_second_for_ui`. - -**Parameters** - -- `loop_type`: UI loop class to instantiate. -- `state`: State the loop owns. Unlike a model loop, a UI loop is - allowed to hold none. -- `**kwargs`: Passed to `loop_type`. - -**Raises** - -- `RuntimeError`: The runtime already took the loops, or a UI loop was - registered already. -- `TypeError`: `loop_type` does not derive from `IUILoop`. - -#### `register_model_loop` - -```python -def register_model_loop(self, loop_type: type[IModelLoop[Any]], *, state: Any, **kwargs: Any) -> IModelLoop[Any] -``` - -Create and register the model loop, and return it. - -The loop is paced at the session's `frames_per_second_for_step`. - -**Parameters** - -- `loop_type`: Model loop class to instantiate. -- `state`: State the loop owns. Required, since a model loop with no - state has nothing to generate from. -- `**kwargs`: Passed to `loop_type`. - -**Raises** - -- `RuntimeError`: The runtime already took the loops, or a model loop - was registered already. -- `TypeError`: `loop_type` does not derive from `IModelLoop`. - -#### `ui_loop` - -```python -def ui_loop(self) -> IUILoop[Any] -``` - -Return the registered UI loop. - -#### `model_loop` - -```python -def model_loop(self) -> IModelLoop[Any] -``` - -Return the registered model-generation loop. - -#### `close` - -```python -def close(self) -> None -``` - -Release resources owned by the session. +::: flashdreams.api_v2.session.ISession ## Model and UI loops -### `flashdreams.api_v2.loop.IModelLoop` - -Loop that generates model results on the model thread. - -`ILoop.step` must return `list[StepResult]` here, one entry per -channel, with every channel reporting the same `frame_count`. An empty -list means the step produced no presentable output. - -Every admitted rank executes the same model step and ordered collectives. -The integration owns TP/CP partitioning and must gather all CP shards needed -for publication before the presenting rank returns non-empty results. -Workers return `[]` only after all required collectives, including gathers. -Gather before decoding when it needs the complete latent/view sequence; -decoding may run only on the presenting rank when the model permits it. -TP reductions that already replicate a complete value need no extra gather. - -A run whose steps all present nothing still ends when the model loop does. -Returning a bare `StepResult` or `None` raises `TypeError`. - -[View source](https://github.com/NVIDIA/flashdreams/blob/main/flashdreams/flashdreams/api_v2/loop.py#L289) +::: flashdreams.api_v2.loop.IModelLoop -#### `step` +::: flashdreams.api_v2.loop.IUILoop -```python -def step(self, step_index: int, events: UserInputEvents) -> list[StepResult] -``` - -We expect IModelLoop's to return a list of StepResults, one result per channel. - -#### `inference_state` - -```python -def inference_state(self) -> ModelInferenceState -``` - -Return whether this model loop has started, is running, or finished. - -### `flashdreams.api_v2.loop.IUILoop` - -Loop whose output is sent to the client window. - -`ILoop.step` must return `list[StepResult]` here: one frame to -present, or an empty list to present nothing this step. A list longer than -one raises `TypeError`. Model frames to draw come from -`presented_model_frame` and `presented_model_frames` rather -than from the model loop directly. -`has_pending_model_frames` and `presented_model_frame_count` -report whether more model frames are waiting and how many have been -selected. - -[View source](https://github.com/NVIDIA/flashdreams/blob/main/flashdreams/flashdreams/api_v2/loop.py#L424) - -#### `step` - -```python -def step(self, step_index: int, events: UserInputEvents) -> list[StepResult] -``` - -Return zero or one `StepResult` to present this tick. - -#### `register_session_ui_loop_objects` - -```python -def register_session_ui_loop_objects(self, *, session_desc: SessionDesc, presentation_manager: PresentationManager) -> None -``` - -Store UI objects supplied when this loop is registered with a session. - -**Parameters** - -- `session_desc`: Description of the session this UI presents. -- `presentation_manager`: Buffer containing model frames. - -#### `model_inference_state` - -```python -def model_inference_state(self) -> ModelInferenceState -``` - -Return whether model inference has started, is running, or finished. - -#### `request_new_session` - -```python -def request_new_session(self, session_desc: SessionDesc) -> None -``` - -Ask the runtime to replace this session after the current UI step. - -**Parameters** - -- `session_desc`: Fully resolved description for the replacement session. - -#### `request_hide_cursor` - -```python -def request_hide_cursor(self, hide_cursor: bool) -> None -``` - -Request that the client window show or hide its cursor. - -#### `request_lock_cursor_to_window` - -```python -def request_lock_cursor_to_window(self, lock_cursor_to_window: bool) -> None -``` - -Request that the client window release or capture pointer motion. - -#### `request_new_window_size` - -```python -def request_new_window_size(self, new_window_size: tuple[int, int]) -> None -``` - -Request a positive downstream client-window width and height. - -**Parameters** - -- `new_window_size`: Requested `(width, height)` in pixels. - -**Raises** - -- `TypeError`: The size is not a pair of integers. -- `ValueError`: Either dimension is not positive. - -#### `get_or_create_ui_loop_requests` - -```python -def get_or_create_ui_loop_requests(self) -> UILoopRequests -``` - -#### `flush_ui_loop_requests` - -```python -def flush_ui_loop_requests(self) -> UILoopRequests | None -``` - -return UI loop requests and clear the internal state of requests. - -#### `presented_model_frame` - -```python -def presented_model_frame(self, channel_index: int=0) -> Tensor | None -``` - -Return the current frame from one model-result channel. - -**Parameters** - -- `channel_index`: Channel to read, indexed as the model loop returned - them. - -**Returns** - -A `[C, H, W]` frame with one, three or four channels, or `None` -before the first model result has been presented. - -**Raises** - -- `IndexError`: The presented result has no such channel. -- `ValueError`: The presentation stream is on a different CUDA device - from the presented result. - -#### `presented_model_frames` - -```python -def presented_model_frames(self) -> tuple[Tensor, ...] -``` - -Return the current frame from every model-result channel. - -**Returns** - -One `[C, H, W]` frame per channel, bottom channel first, or an -empty tuple before the first model result has been presented. - -**Raises** - -- `ValueError`: The presentation stream is on a different CUDA device - from a presented result. - -#### `presented_model_frame_count` - -```python -def presented_model_frame_count(self) -> int -``` - -Return how many model frames have been selected in this generation. - -#### `has_pending_model_frames` - -```python -def has_pending_model_frames(self) -> bool -``` - -Return whether another model frame is ready to present. +::: flashdreams.runtime_v2.imgui_ui_loop.ImGuiUILoop ## Session description -### `flashdreams.runtime_v2.session_desc.SessionDesc` - -Description of a session, passed to create one and to open a window on it. - -The runtime fills this in to ask an application for a session, and the -session reports back what it resolved to. The same description then -configures the client window through `OutputSink.open`. - -[View source](https://github.com/NVIDIA/flashdreams/blob/main/flashdreams/flashdreams/runtime_v2/session_desc.py#L35) - -- `output_layout: VideoTensorLayout` - -- `backpressure_mode: BackpressureMode` - -- `presentation_mode: PresentationMode` - -- `frames_per_second_for_ui: int` - -- `frames_per_second_for_step: int` - -- `video_width: int` +::: flashdreams.runtime_v2.session_desc.SessionDesc -- `video_height: int` +::: flashdreams.runtime_v2.session_desc.BackpressureMode -- `metadata: dict[str, Any]` +::: flashdreams.runtime_v2.session_desc.PresentationMode diff --git a/docs/source/documentation/demo_api/guides/application_slugs.md b/docs/source/documentation/demo_api/guides/application_slugs.md deleted file mode 100644 index 4c00df5b3..000000000 --- a/docs/source/documentation/demo_api/guides/application_slugs.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: 'Application slugs and model adapters' ---- - - - - -An application slug identifies one installed, zero-argument -`flashdreams.api_v2.IApplication` factory. It selects both an interaction and a -model binding; it is not a pipeline-config registry key. - -## Register an application - -Register factories in the model integration's `pyproject.toml`: - -```toml -[project.entry-points."flashdreams.applications_v2"] -"t2v-self-forcing-wan2.1-t2v-1.3b" = "self_forcing.apps.t2v.adapter:create_app" -"t2v-self-forcing-wan2.1-t2v-1.3b-taehv" = "self_forcing.apps.t2v.adapter:create_app_taehv" -``` - -Each target must be callable without arguments and return an uninitialized -`IApplication`. Importing the module or calling the factory must not load a -checkpoint, initialize CUDA, or open a window; `IApplication.init` owns that -work. - -Use a stable, lowercase, hyphenated slug. Existing integrations generally use -`-` and append a suffix for a supported variant. The -entry-point table is the source of truth; slug parsing is not an API. - -## Discover and inspect slugs - -```bash -uv run --no-sync flashdreams-run-v2 --help -uv run --no-sync flashdreams-run-v2 \ - t2v-self-forcing-wan2.1-t2v-1.3b -- --help -``` - -The first command lists registered entry points. The second constructs the -application and asks its argument parser for help. Arguments before `--` belong -to `flashdreams-run-v2`; arguments after it belong to the application. - -For small development-only examples, the registry can also import a module -whose name is the slug with hyphens changed to underscores and call its -`create_app`. This fallback is not listed by `--help`, so real integrations -should register an entry point. - -## Keep ownership one-way - -| Concern | Owner | -| --- | --- | -| Interaction, session, controls, UI, presentation policy | `apps//` | -| Model implementation, checkpoint mapping, pipeline configs | `integrations_v2//` | -| Binding and application factory | `integrations_v2//apps//adapter.py` | -| File, browser, or native-window transport | `flashdreams.runtime_v2` | - -Do not copy a reusable app into the model package or add `runner.py`, -`launch.py`, or `model_session.py` merely to expose a v2 slug. Follow -[Integrate a model with a demo](integrate_model.md) for the adapter workflow. diff --git a/docs/source/documentation/demo_api/guides/configuration.md b/docs/source/documentation/demo_api/guides/configuration.md deleted file mode 100644 index 1b7c2b236..000000000 --- a/docs/source/documentation/demo_api/guides/configuration.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -title: 'Demo configuration' ---- - - - - -FlashDreams keeps model configuration separate from application and runtime -configuration. This separation lets one model serve several demos and lets one -demo run several models without either layer importing the other. - -| Configuration | Owner | Examples | -| --- | --- | --- | -| Pipeline architecture | `integrations_v2//config.py` | checkpoint, scheduler, cache, encoder, decoder | -| Application defaults and arguments | app plus model adapter | prompt, block count, native size, device | -| Runtime and presentation | `flashdreams-run-v2` | output mode, host, port, output path, step limit | - -## Define stable pipeline configs - -Export one named, module-level config for each supported model variant. Derive -small variants from a baseline instead of mutating it: - -```python -from typing import cast - -from flashdreams.infra.config import derive_config -from flashdreams.recipes.wan import WanInferencePipelineConfig - -PIPELINE_MY_MODEL = WanInferencePipelineConfig( - name="my-model", - encoder=..., - diffusion_model=..., - decoder=..., -) - -PIPELINE_MY_MODEL_FAST = cast( - WanInferencePipelineConfig, - derive_config( - PIPELINE_MY_MODEL, - name="my-model-fast", - diffusion_model={ - "scheduler": {"num_inference_steps": 4}, - "transformer": {"compile_network": True}, - }, - ), -) - -MY_MODEL_CONFIGS = { - config.name: config - for config in (PIPELINE_MY_MODEL, PIPELINE_MY_MODEL_FAST) -} -``` - -`derive_config` deep-copies the baseline, applies nested patches, and raises -`KeyError` for an unknown field. Always give a derived public variant its own -`name`; never mutate a shared literal in place. - -## Put components in the correct slots - -A `StreamInferencePipelineConfig` composes three model-side stages: - -- `encoder`: optional `StreamingEncoder` for per-step control, such as a camera - trajectory, HD map, or input video chunk; -- `diffusion_model`: transformer plus scheduler; -- `decoder`: optional `StreamingDecoder` from clean latent to output. - -A one-shot text or image context encoder belongs on the transformer's -`context_encoder`, not in the pipeline's per-step `encoder` slot. See the -[stream inference pipeline guide](../../inferencing_api/guides/stream_inference_pipeline.md) -for the lifecycle and cache boundaries. - -## Apply application overrides to a copy - -An application exposes only overrides it can validate. Parse them in -`IApplication.init`, then derive a private pipeline config before calling -`setup()`: - -```python -pipeline_config = derive_config( - PIPELINE_MY_MODEL, - diffusion_model={"seed": args.seed}, -) -pipeline = pipeline_config.setup().to(args.device).eval() -``` - -Keep output paths, browser settings, and presentation modes out of the model -config. They are runtime concerns. Keep prompts and UI policy out of shared -recipes. They are application concerns. - -## Review and test the boundary - -Before loading weights, verify on CPU that: - -- public config names are unique and stable; -- derived variants leave the baseline unchanged; -- component types and model-defining fields are correct; -- unknown override paths fail; -- checkpoint key transforms handle representative real keys; -- each adapter factory returns an uninitialized application. - -Use `ci_gpu` or `manual` checks for checkpoint downloads, real generation, -parity, and performance. Continue with [Create a model](../../inferencing_api/guides/create_model.md) -for model-package responsibilities or [Integrate a model with a demo](integrate_model.md) -for the application binding. diff --git a/docs/source/documentation/demo_api/guides/create_demo.md b/docs/source/documentation/demo_api/guides/create_demo.md index da7634292..1b8cc1b0c 100644 --- a/docs/source/documentation/demo_api/guides/create_demo.md +++ b/docs/source/documentation/demo_api/guides/create_demo.md @@ -5,94 +5,99 @@ title: 'Create a demo' -A demo is a reusable, model-independent `flashdreams.api_v2` application under -`apps//`. It owns interaction, session lifecycle, input handling, UI, and -presentation policy. It must not import a model integration or own a -checkpoint. +A demo implements `IApplication` and `ISession` from `flashdreams.api_v2`. +Reuse an app from the [demo gallery](../../../demos/index.md) when it already +supports your interaction; then [add a model adapter](integrate_model_adapter_for_a_demo.md). -First check the [demo gallery](../../../demos/index.md). If T2V, Cam2V, -Action2V, V2V, Interactive Drive, or Crazy Robotaxi already owns the required -interaction, add a [model adapter](integrate_model.md) instead of another app. +This tutorial builds the [Minimal T2V app](https://github.com/NVIDIA/flashdreams/tree/main/apps/minimal_t2v) +(`minimal_t2v`), which sends ImGui prompts to a model loop. All source below is included from the repository. -## Package layout +## New application files ```text -apps// - / +apps/minimal_t2v/ + pyproject.toml + minimal_t2v/ __init__.py application.py - defaults.py # optional adapter-supplied defaults session.py - ui.py # only for custom composition or controls - tests/ - pyproject.toml - README.md + model_loop.py + ui_loop.py + tests/test_application.py + +docs/source/demos/minimal_t2v.md # Add the app page with controls, arguments, and a link to its model's quick start. +docs/source/demos/.nav.yml # Add `Minimal T2V: minimal_t2v.md` to the sidebar. +docs/source/demos/index.md # Add a Minimal T2V gallery card linking to `minimal_t2v/`. ``` -Depend on `flashdreams`, never on `integrations_v2`. Add the `serving` or -`local-window` extra only when the app directly imports that optional stack. -The root workspace already discovers `apps/*` packages. +## Package + +Define a new demo package and export its application slug. -## Implement the lifecycle +`apps/minimal_t2v/pyproject.toml`: +```toml +--8<-- "apps/minimal_t2v/pyproject.toml" +``` -The runtime calls the API in this order: +`apps/minimal_t2v/minimal_t2v/__init__.py`: +```python +--8<-- "apps/minimal_t2v/minimal_t2v/__init__.py" +``` -1. `IApplication.session_desc()` describes native output without loading the - model. -2. `IApplication.init(args)` parses arguments after `--` and initializes state - shared by sessions. -3. `IApplication.create_session(desc)` validates the requested description and - returns an uninitialized `ISession`. -4. `ISession.init()` creates per-run state and registers exactly one - `IModelLoop`; a custom `IUILoop` is optional. -5. `IModelLoop.step(index, events)` returns `list[StepResult]`. -6. `is_finished`, `reset`, and `close` manage the run lifecycle. +## IApplication -A minimal session registration is: +Implement an [`IApplication`](../api_reference/application.md#flashdreams.api_v2.application.IApplication) to be implemented by *every model* that desires to integrate with this demo: +- `__init__` is your constructor/contract to any implementor of this demo. + - `pipeline_config` is the model's pipeline configuration. + - `desc` is our session description - more on this later. + - `total_blocks` is the model's total number of blocks to generate for the entire session. +- `init()` parses arguments & is where we load/download our model. +- `session_desc()` is how our [`ISession`](../api_reference/application.md#flashdreams.api_v2.session.ISession) collects a session description from our IApplication. + - [`SessionDesc`](../api_reference/application.md#flashdreams.runtime_v2.session_desc.SessionDesc) +- `create_session()` is called every time a session is made, passing `IApplication` data to `ISession` +- `close()` is called when the application is shutting down. ```python -from flashdreams.api_v2.session import ISession +--8<-- "apps/minimal_t2v/minimal_t2v/application.py" +``` -class DemoSession(ISession): - def init(self) -> None: - self.register_model_loop(DemoModelLoop, state=DemoModelState(...)) +## Session - @property - def session_desc(self) -> SessionDesc: - return self._session_desc +[`ISession`](../api_reference/application.md#flashdreams.api_v2.session.ISession) owns the two main loops driving our application *session* (running in separate threads): + +- [`IModelLoop`](../api_reference/application.md#flashdreams.api_v2.loop.IModelLoop): Drives model inference/logic. +- [`IUILoop`](../api_reference/application.md#flashdreams.api_v2.loop.IUILoop): Drives UI/user-interaction. + +These are set with `register_*_loop()` methods part of `ISession`. These must be called during `ISession.init()`. + +```python +--8<-- "apps/minimal_t2v/minimal_t2v/session.py" ``` -If the session does not register a UI loop, the runtime presents model output -with its default blit loop. Model and UI loops run on separate threads and own -their state; use `flashdreams.api_v2.loop.invoke_async` for cross-loop changes. +## Model loop -## Expose a narrow adapter seam +`IModelLoop` governs all model logic and will be where we run our model inference: -Put model-independent facts in the app and accept model-owned facts through a -small immutable defaults object or constructor arguments. Typical adapter -inputs are: +- `MinimalT2VModelState` is the type of `ILoop.state` for our `IModelLoop`. +- `step()` is called for each loop of our `IModelLoop`. + - `pipeline.initialize_cache()` is called to initialize our model's cache. + - `pipeline.generate()` is called to generate a chunk of frames given our current model's state (`pipeline`). + - `pipeline.finalize()` is called to finalize the generated chunk & produce metrics. +- `reset()` is called when the session is reset. +- `close()` is called when the session is closed. -- pipeline config or model factory; -- native width, height, frame rate, and tensor layout; -- default rollout length and device; -- model-specific input resolver callbacks. +```python +--8<-- "apps/minimal_t2v/minimal_t2v/model_loop.py" +``` -Keep expensive shared objects, such as the loaded pipeline, on the application. -Keep caches, counters, and resettable rollout state on the session's model-loop -state. +## UI loop -## Test without a real model +`IUILoop` governs all UI logic and will be where we handle user-interaction: -Tests live in `apps//tests/` and must not import `integrations_v2`. Use a -stand-in pipeline to cover argument validation, session-description checks, -loop registration, step output, reset, finish, close, and scripted input. Mark -pure Python tests `ci_cpu`; put real-model behavior in the integration's tests. +- `MinimalT2VUIState` is the type of `ILoop.state` for our `IUILoop`. +- `invoke_async(self.state.model_loop, lambda...)` is called when the user submits a new prompt (via imgui button) to submit a `lambda` to run on our `IModelLoop` thread before the next new `IModelLoop.step()`. +- `step_ui(...)` is called for each loop of our `IUILoop`. It contains `imgui` state when `IUILoop` inherits from [`ImGuiUILoop`](../api_reference/application.md#flashdreams.runtime_v2.imgui_ui_loop.ImGuiUILoop). -```bash -uv sync --package flashdreams- --extra dev --no-default-groups --inexact -uv run --no-sync pytest apps//tests -m ci_cpu +```python +--8<-- "apps/minimal_t2v/minimal_t2v/ui_loop.py" ``` - -Add `docs/source/demos/.md`, update `demos/.nav.yml`, and keep the package -README focused on development. The demo becomes publicly runnable when a model -adapter registers an application slug. diff --git a/docs/source/documentation/demo_api/guides/integrate_model.md b/docs/source/documentation/demo_api/guides/integrate_model.md deleted file mode 100644 index 1c4dc89d4..000000000 --- a/docs/source/documentation/demo_api/guides/integrate_model.md +++ /dev/null @@ -1,100 +0,0 @@ ---- -title: 'Integrate a model with a demo' ---- - - - - -A demo integration is a thin adapter between a model config under -`integrations_v2//` and a reusable application under `apps//`. -The adapter supplies model-specific defaults and registers a zero-argument -`IApplication` factory. It does not duplicate either side. - -## 1. Choose the existing interaction - -Pick the app whose inputs and presentation match the model: T2V, Cam2V, -Action2V, V2V, Interactive Drive, or Crazy Robotaxi. Create a new app only when -none of them owns the required interaction or UI. - -Add that app package to the integration's dependencies. The app remains -model-independent; only the adapter imports both the app and model config. - -## 2. Add the adapter - -```text -integrations_v2//apps// - __init__.py - adapter.py - README.md # installation and launch details only -``` - -A T2V adapter can be this small: - -```python -from flashdreams.api_v2.application import IApplication -from t2v import T2VApplication, T2VApplicationDefaults - -from my_model.config import PIPELINE_MY_MODEL - -MY_MODEL_T2V_DEFAULTS = T2VApplicationDefaults( - pipeline_config=PIPELINE_MY_MODEL, - total_blocks=8, - pixel_width=832, - pixel_height=480, - fps=16, -) - - -def create_app() -> IApplication: - return T2VApplication(defaults=MY_MODEL_T2V_DEFAULTS) -``` - -Subclass the reusable application only when it exposes a hook the model really -needs, such as a different cache initializer or compile override. The factory -must not load weights or initialize CUDA. - -## 3. Register a stable slug - -```toml -[project.entry-points."flashdreams.applications_v2"] -"t2v-my-model" = "my_model.apps.t2v.adapter:create_app" -``` - -Use a readable `-` slug. Register each supported variant -explicitly with its own factory. Do not add `flashdreams.runner_configs`, a -`runner.py`, or a model-specific copy of the app solely for v2 discovery. - -## 4. Test the seam on CPU - -Put adapter tests in `integrations_v2//tests/`, not under the reusable -app. Inject a stand-in config and verify: - -- factory construction performs no model load; -- defaults report the correct size, rate, layout, and rollout length; -- initialize, generate, and finalize run in order; -- argument validation, reset, finish, and close work; -- every entry-point slug resolves to `IApplication`. - -Use the app's shared testing helpers when available. For example, `t2v.testing` -provides a stand-in pipeline and end-to-end adapter checks. - -## 5. Install and inspect - -```bash -uv sync --package flashdreams- --extra dev --inexact -uv run --no-sync pytest integrations_v2//tests -m ci_cpu -uv run --no-sync flashdreams-run-v2 --help -uv run --no-sync flashdreams-run-v2 - -- --help -``` - -A real launch uses the same slug with `--mode mp4`, `webrtc`, or -`native-window`. Runtime options precede `--`; application options follow it. - -## 6. Document once - -Put installation, supported bindings, and canonical commands on the model page. -Link to the demo page for shared controls. Keep the adapter README limited to -package-specific installation and launch details. - -See [Application slugs](application_slugs.md) for discovery details and -[Demo configuration](configuration.md) for ownership of overrides. diff --git a/docs/source/documentation/demo_api/guides/integrate_model_adapter_for_a_demo.md b/docs/source/documentation/demo_api/guides/integrate_model_adapter_for_a_demo.md new file mode 100644 index 000000000..f225bf65f --- /dev/null +++ b/docs/source/documentation/demo_api/guides/integrate_model_adapter_for_a_demo.md @@ -0,0 +1,85 @@ +--- +title: 'Integrate model adapter for a demo' +--- + + + + +An adapter is a package that contains the logic to connect a demo to a model. + +In this example we connect the [Minimal T2V application](create_demo.md) to Wan 2.1. + + + + + +## Files to add or update + +After creating `apps/minimal_t2v/`, add the adapter package and update the website: + +```text +integrations_v2/wan21/ + config.py # add a new pipeline config if desired + apps/minimal_t2v/ + __init__.py # add the package initializer + adapter.py # add the application factory + pyproject.toml # update dependencies, packages, and entry points + +docs/source/ + models/ + wan21.md # update: add Minimal T2V quick start, preset, and app-page link + .nav.yml # update when adding a new model page + index.md # add a card when adding a new model page + demos/ + minimal_t2v.md # update: link to the model's quick start +``` + +## Create the adapter entry point + +Our adapter entry point is a factory function that creates an instance of our application based on the `IApplication` we defined. + +This factory has the contract of: + +```python +def some_create_app() -> IApplication: + ... +``` + +In our case we then make in `integrations_v2/wan21/apps/minimal_t2v/adapter.py`: + +```python +--8<-- "integrations_v2/wan21/apps/minimal_t2v/adapter.py" +``` + +## Register the application + +In `integrations_v2/wan21/pyproject.toml`, add our application's package name to `tool.uv.sources` + +Then add our `slug` to run our new application entry point to `[project.entry-points."flashdreams.applications_v2"]` + +This is seen in `integrations_v2/wan21/pyproject.toml`: +```toml +--8<-- "integrations_v2/wan21/pyproject.toml" +``` + +## Install and run entry point + +```bash +uv sync --package flashdreams-wan21 --inexact +uv run --no-sync flashdreams-run-v2 minimal-t2v-wan21 --mode native-window -- --default-prompt "A cat surfing" +``` +## Adding a new pipeline configuration to pick from + +Modify `integrations_v2/wan21/config.py` to add a new pipeline config to `wan21`: + +```python +PIPELINE_WAN21_T2V_SEED123 = derive_config( + PIPELINE_WAN21_T2V_1PT3B_480P, + name="wan21-t2v-1.3b-480p-seed123", + diffusion_model={"seed": 123}, +) +``` + +This defines a new pipeline config that is a copy of `PIPELINE_WAN21_T2V_1PT3B_480P` with the addition of a `seed` parameter to the diffusion model, setting the pipeline to always run diffusion with the same seed of `123`. + +To learn more about demo serving, see [Interactive serving](interactive_serving.md). diff --git a/docs/source/documentation/demo_api/guides/interactive_serving.md b/docs/source/documentation/demo_api/guides/interactive_serving.md index dc45006b0..5ca648ff7 100644 --- a/docs/source/documentation/demo_api/guides/interactive_serving.md +++ b/docs/source/documentation/demo_api/guides/interactive_serving.md @@ -5,31 +5,29 @@ title: 'Interactive serving' -`flashdreams-run-v2` can present a compatible application in a browser or a -local native window. Applications generate frames and consume input events; -`flashdreams.runtime_v2` owns transport, pacing, and window lifecycle. +Run a demo in a browser or a local window with `flashdreams-run-v2`. +Use a registered name from `--help` or the [model pages](../../../models/index.md). -## Launch a client - -Serve a browser on the local machine: +## Browser ```bash uv run --no-sync flashdreams-run-v2 APPLICATION_SLUG \ --mode webrtc --host 127.0.0.1 --port 0 ``` -Port `0` selects an available port and prints the URL. Use `0.0.0.0` only when -remote clients must connect; it exposes the server on available network -interfaces, so apply the host's normal firewall and access controls. +Open the URL printed by the server. Port `0` picks an available port. +To accept connections from another machine, use `--host 0.0.0.0` and a fixed +port, then connect to the server's address. -Open a GPU-backed local window instead: +## Local window ```bash -uv run --no-sync flashdreams-run-v2 APPLICATION_SLUG \ - --mode native-window --window-title FlashDreams +uv run --no-sync flashdreams-run-v2 APPLICATION_SLUG --mode native-window ``` -Runtime options precede `--`. Application-specific options follow it: +## Demo arguments + +Runtime options go before `--`; demo options go after it: ```bash uv run --no-sync flashdreams-run-v2 cam2v-lingbot \ @@ -37,43 +35,31 @@ uv run --no-sync flashdreams-run-v2 cam2v-lingbot \ --example-data --total-blocks 21 ``` -Inspect both sides before launching: +List runtime options with `flashdreams-run-v2 --help`. For this demo's options, +use `flashdreams-run-v2 cam2v-lingbot -- --help`. -```bash -uv run --no-sync flashdreams-run-v2 --help -uv run --no-sync flashdreams-run-v2 cam2v-lingbot -- --help -``` - -## Understand the boundary +## Input and display -- `IApplication` owns shared model state and application arguments. -- `ISession` owns one run and registers model and optional UI loops. -- The model loop receives `UserInputEvents` and publishes `StepResult` channels. -- The UI loop composites frames and controls; without one, the runtime uses its - default blit loop. -- The selected client-window mode owns browser, file, or native presentation. +The runtime sends user input to the app's loops and displays their output. +The app handles controls and model inputs. A custom UI loop can draw controls +over the generated frames; otherwise, the runtime displays the frames directly. -The model and UI loops run on separate threads. They must not mutate each -other's state directly; use `flashdreams.api_v2.loop.invoke_async`. Keep camera, -action, and conditioning semantics in the app or adapter rather than teaching -the WebRTC transport about a model. +Model and UI loops run on separate threads. Use `invoke_async` to send changes +between them. [Create a demo](create_demo.md) shows an ImGui text field sending +a prompt to the model loop. -## Distributed applications +## Multiple GPUs -Launch all model ranks through the same command. Rank zero owns the client -window and output sinks; the session's parallel context synchronizes admitted -steps and input across ranks. +For a model adapter that supports distributed execution, launch all ranks: ```bash uv run torchrun --nproc_per_node=2 --no-python flashdreams-run-v2 \ - cam2v-lingbot --mode webrtc -- \ - --example-data --total-blocks 21 + cam2v-lingbot --mode webrtc -- --example-data --total-blocks 21 ``` -The integration remains responsible for its tensor/context parallelism and for -gathering any output required by the presenting rank. +Rank zero handles the window and output. The session's parallel context +synchronizes steps and inputs; the model integration handles parallel inference +and gathers the output for rank zero. -Use [Create a demo](create_demo.md) when the interaction needs new session or UI -behavior. Use [Integrate a model with a demo](integrate_model.md) when the -interaction already exists. The [CLI reference](../../cli.md) documents common -runtime and presentation options. +See the [CLI reference](../../cli.md) for runtime options, or +[Integrate model adapter for a demo](integrate_model_adapter_for_a_demo.md) to connect another model. diff --git a/docs/source/documentation/demo_api/guides/local_benchmarks.md b/docs/source/documentation/demo_api/guides/local_benchmarks.md deleted file mode 100644 index 99df93c52..000000000 --- a/docs/source/documentation/demo_api/guides/local_benchmarks.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: 'Local benchmarks' ---- - - - - -`flashdreams-benchmark` runs registered commands, captures their environment, -logs, metrics, and media, and writes an HTML report. It is a manual local -workflow; real-model scenarios require their normal GPU, checkpoint, asset, -and FFmpeg dependencies. - -## Run the maintained v2 suite - -```bash -uv sync --package flashdreams --group cuda13 --extra runners --inexact -uv run --no-sync flashdreams-benchmark --list-scenarios \ - --scenario-file configs/v2_model_benchmarks.json -uv run --no-sync flashdreams-benchmark \ - --scenario-file configs/v2_model_benchmarks.json \ - --scenario t2v-self-forcing-quality-10s \ - --output-dir artifacts/benchmarks/self-forcing -``` - -Use `--dry-run` first when reviewing a new or expensive scenario. Each run -directory contains the rendered command, manifest, environment metadata, -normalized metrics, logs, media, and `report.html`. Keep the complete directory -when comparing a candidate with a baseline. - -## Define a scenario - -Commands are argument arrays, not shell strings. Use `{output_dir}` for files -that belong to the benchmark artifact: - -```json -{ - "schema_version": 1, - "scenarios": [ - { - "id": "my-app-smoke", - "name": "My application smoke", - "command": [ - "flashdreams-run-v2", - "t2v-self-forcing-wan2.1-t2v-1.3b", - "--mode", "mp4", - "--output-path", "{output_dir}/clip.mp4", - "--stats-path", "{output_dir}/stats.json", - "--timeout", "unbound", - "--", - "--no-ui", - "--prompt", "A cat surfing.", - "--total-blocks", "8" - ], - "warmup_steps": 1, - "requires_runtime_stats": true - } - ] -} -``` - -Load it with `--scenario-file` and select it with `--scenario`. Do not put -credentials in scenario files; pass them through the environment expected by -the model. - -## Make comparisons defensible - -- Keep input, seed, resolution, block count, checkpoint, precision, and runtime - flags fixed. -- Record the commit, GPU, driver, CUDA, PyTorch, and compiler-cache state. -- Separate startup/compile time from steady-state measurements. -- Exclude declared warmup steps and report a distribution, not one best sample. -- Compare output quality when a cache, decoder, attention backend, precision, - or compile change can affect numerics. - -Use `--quality-baseline-dir` for the harness's non-gating MP4 comparison. Treat -long moving autoregressive clips as smoke tests rather than strict parity: small -numerical changes can alter later content. The -[latency tuning guide](../../inferencing_api/guides/latency_tuning.md) explains -how to interpret pipeline-stage timings. diff --git a/docs/source/documentation/demo_api/index.md b/docs/source/documentation/demo_api/index.md index 411e0f3d3..fd4fea51e 100644 --- a/docs/source/documentation/demo_api/index.md +++ b/docs/source/documentation/demo_api/index.md @@ -9,19 +9,12 @@ Use the Demo API to build user-facing applications, manage sessions, expose controls, route inputs and outputs, configure a model-backed demo, and launch it through `flashdreams-run-v2`. -The current application protocol is `flashdreams.api_v2`. The separate -`flashdreams.runtime.demo` API is experimental and provides higher-level -scenario, presentation, replay, warmup, and benchmark utilities. - ## Guides diff --git a/docs/source/documentation/inferencing_api/api_reference/accelerated.md b/docs/source/documentation/inferencing_api/api_reference/accelerated.md new file mode 100644 index 000000000..952797752 --- /dev/null +++ b/docs/source/documentation/inferencing_api/api_reference/accelerated.md @@ -0,0 +1,81 @@ +--- +title: 'Accelerated API Reference' +--- + +The `flashdreams.accelerated` package provides optimized attention, quantization, +and shared inference components. See [Accelerated building blocks](../guides/accelerated.md) +for usage and integration guidance. + +## Common components + +::: flashdreams.accelerated.common.NonPersistentLinear + +::: flashdreams.accelerated.common.rms_norm + +## Quantization + +::: flashdreams.accelerated.quantization.quantizer.Granularity + +::: flashdreams.accelerated.quantization.quantizer.quantize + +::: flashdreams.accelerated.quantization.quantizer.dequantize + +::: flashdreams.accelerated.quantization.linear.WeightGranularity + +::: flashdreams.accelerated.quantization.linear.QuantizedNonPersistentLinear + +## Attention contracts + +::: flashdreams.accelerated.multi_head_attention.AttentionType + +::: flashdreams.accelerated.multi_head_attention.AttentionConfig + +::: flashdreams.accelerated.multi_head_attention.AttentionMask + +::: flashdreams.accelerated.multi_head_attention.QKNormScope + +::: flashdreams.accelerated.multi_head_attention.RoPEStyle + +::: flashdreams.accelerated.multi_head_attention.RoPEScope + +::: flashdreams.accelerated.multi_head_attention.RoPEConfig + +::: flashdreams.accelerated.multi_head_attention.MultiHeadAttention + +## Attention implementations + +::: flashdreams.accelerated.multi_head_attention.torch.TorchMultiHeadAttention + +::: flashdreams.accelerated.multi_head_attention.optimized.SDPABackend + +::: flashdreams.accelerated.multi_head_attention.optimized.QKVFusionOption + +::: flashdreams.accelerated.multi_head_attention.optimized.QuantizationOption + +::: flashdreams.accelerated.multi_head_attention.optimized.OptimizedImplConfig + +::: flashdreams.accelerated.multi_head_attention.optimized.OptimizedMultiHeadAttention + +::: flashdreams.accelerated.multi_head_attention.flex.FlexAttentionOptions + +::: flashdreams.accelerated.multi_head_attention.flex.compiled_flex_attention + +::: flashdreams.accelerated.multi_head_attention.MoTRoute + +::: flashdreams.accelerated.multi_head_attention.mot.MoTMultiHeadAttention + +## Masked attention functions + +::: flashdreams.accelerated.multi_head_attention.functional.masked_attention + +::: flashdreams.accelerated.multi_head_attention.reference.reference_masked_attention + +## KV arena + +::: flashdreams.accelerated.multi_head_attention.kv_arena.KVSegmentLayout + +::: flashdreams.accelerated.multi_head_attention.kv_arena.allocate_kv_arena + +::: flashdreams.accelerated.multi_head_attention.kv_arena.validate_kv_arena_segments + +::: flashdreams.accelerated.multi_head_attention.kv_arena.stage_current_kv_ diff --git a/docs/source/documentation/inferencing_api/api_reference/core.md b/docs/source/documentation/inferencing_api/api_reference/core.md index d88643b93..6f4f5461d 100644 --- a/docs/source/documentation/inferencing_api/api_reference/core.md +++ b/docs/source/documentation/inferencing_api/api_reference/core.md @@ -3,7 +3,6 @@ title: 'Core API Reference' --- - The `flashdreams.core` package collects the low-level attention primitives, @@ -14,368 +13,18 @@ KV-cache implementations, and process-group utilities that integrations share. The attention package provides the kernels used by the transformer and the block-structured KV cache that backs streaming inference. - -### `NativeAttention` - -```text -class NativeAttention ( * args : Any , ** kwargs : Any ) -``` - -Bases: `Module` - -Native attention module with configurable QKV layout and SDPA backend. - - -#### `set_context_parallel_group` - -```text -set_context_parallel_group ( cp_group : ProcessGroup | None ) → None -``` - -Enable or disable context parallelism for ring attention. - -##### `` - -```text -Parameters : -``` - -**cp\_group** – Process group for context parallel; use None to disable. - -#### `is_context_parallel_enabled` - -```text -is_context_parallel_enabled ( ) → bool -``` - -Return True if context parallelism is active. - -#### `context_parallel_size` - -```text -context_parallel_size ( ) → int -``` - -Return the context parallel world size, or 1 if disabled. - -#### `forward` - -```text -forward ( query : torch.Tensor , key : torch.Tensor , value : torch.Tensor ) → torch.Tensor -``` - -Run context-parallel SDPA (or single-rank SDPA when CP is disabled). - -##### `` - -```text -Parameters : -``` - -* **query** – Query tensor in configured `qkv_format`. -* **key** – Key tensor in configured `qkv_format`. -* **value** – Value tensor in configured `qkv_format`. - - -### `ContextParallelAttention` - -```text -class ContextParallelAttention ( * args : Any , ** kwargs : Any ) -``` - -Bases: `NativeAttention` - -Context-parallel attention with selectable method and SDPA backend. - - -### `BlockKVCache` - -```text -class BlockKVCache ( k_shape : tuple [ int , ... ] , v_shape : tuple [ int , ... ] , seq_dim : int , chunk_size : int , window_size : int , sink_size : int = 0 , device : torch.device | str = torch.device , dtype : torch.dtype = torch.float16 , _prev_chunk_idx : int = -1 , _curr_chunk_idx : int | None = None , _n_cached : int = 0 ) -``` - -Bases: `object` - -KV cache for causal attention with a fixed-size local window, CUDA-graph compatible. - -Keys and values can have arbitrary shape `[..., total_size, ...]`; the sequence -(rolling) dimension is given by `seq_dim` (dimension index, can be negative). -Layout along that dimension: [sink tokens | local window tokens]. Sink tokens are -never evicted; the local window rolls left as new chunks are added if full. Chunks are -non-overlapping: each update adds one chunk of `chunk_size` tokens at the -next logical position in the full sequence. - -#### `` - -```text -Phases : -``` - -* **- Filling** – cache not yet full; tokens are written contiguously; - `cached_k()` / `cached_v()` return only the valid prefix. -* **- Steady-state** – if adding a chunk would exceed the fixed cache size, the - local window rolls left by the overflow amount and the new chunk - overwrites the rightmost positions; `cached_k()` / `cached_v()` - return the full buffer. -The argument `chunk_idx` (0, 1, 2, …) is the index of the new chunk in the full -sequence (not an index into the cache). If `chunk_idx` is greater than -the previous one, the chunk is appended (or, in steady-state, written after -the roll). If `chunk_idx` equals the previous one, the same cache positions -are overwritten. - -#### `` - -```text -Per-step usage: -``` - -1. before\_update(chunk\_idx) — prepare (roll local window if steady-state). -2. update(k, v) — write the new chunk’s keys/values into the cache. -3. cached\_k() / cached\_v() — get cached keys/values for attention. -4. after\_update(chunk\_idx) — update internal bookkeeping. - -#### `k_shape` - -```text -k_shape : tuple [ int , ... ] -``` - -Shape of the keys. Must be the same as the values shape except for the last dimension. - -#### `v_shape` - -```text -v_shape : tuple [ int , ... ] -``` - -Shape of the values. Must be the same as the keys shape except for the last dimension. - -#### `seq_dim` - -```text -seq_dim : int -``` - -Sequence dimension that will be rolled. Can be negative. - -#### `chunk_size` - -```text -chunk_size : int -``` - -Number of tokens processed each time. - -#### `window_size` - -```text -window_size : int -``` - -Size of the local attention window (excluding sink tokens). - -#### `sink_size` - -```text -sink_size : int = 0 -``` - -Number of sink tokens at the start of the cache that are never evicted. Defaults to 0. - -#### `device` - -```text -device : torch.device | str = 'cuda' -``` - -Device to store the cache on. - -#### `dtype` - -```text -dtype : torch.dtype -``` +::: flashdreams.core.attention.NativeAttention -Data type to store the cache in. - -#### `size` +::: flashdreams.core.attention.ContextParallelAttention -```text -property size : int -``` - -Number of valid cached tokens visible to attention. - -#### `write_end` - -```text -property write_end : int -``` - -Right edge of the current chunk in the physical cache layout. - -#### `from_tensor` - -```text -classmethod from_tensor ( k : torch.Tensor , v : torch.Tensor , seq_dim : int ) → Self -``` - -Build a single-chunk cache pre-filled with the given key and value tensors. - -#### `is_steady_state` - -```text -is_steady_state ( ) → bool -``` - -Return True if the cache is full (steady-state phase). - -#### `before_update` - -```text -before_update ( chunk_idx : int ) → None -``` - -Prepare the cache before writing new tokens. - -If `chunk_idx` equals the previous chunk index, this is a no-op. Otherwise, -we expect the `chunk_idx` to be +1 from the previous chunk index. In this case, -we will roll the local window left if the cache is in steady-state, or no op -if the cache is in filling phase. - -##### `` - -```text -Parameters : -``` - -**chunk\_idx** – Chunk index of the new chunk in the full sequence. - -#### `update` - -```text -update ( k : torch.Tensor , v : torch.Tensor ) → None -``` - -Write the new chunk’s keys and values into the cache. - -Must be called after `before_update()` and before `after_update()`. - -##### `` - -```text -Parameters : -``` - -* **k** – Keys; shape must match cached keys except at seq\_dim, where length must be chunk\_size. -* **v** – Values; shape must match cached values except at seq\_dim, where length must be chunk\_size. - -#### `after_update` - -```text -after_update ( chunk_idx : int ) → None -``` - -Finalize bookkeeping after writing new tokens. - -Updates `_prev_chunk_idx` and, in filling phase, `_n_cached`. - -##### `` - -```text -Parameters : -``` - -**chunk\_idx** – The index of the new chunk in the full sequence. - -#### `cached_k` - -```text -cached_k ( ) → torch.Tensor -``` - -Return cached keys for attention (valid prefix in filling phase, full buffer in steady-state). - -#### `cached_v` - -```text -cached_v ( ) → torch.Tensor -``` - -Return cached values for attention (valid prefix in filling phase, full buffer in steady-state). - -#### `reset` - -```text -reset ( ) → None -``` - -Reset bookkeeping while preserving the allocated tensor storage. - -#### `clone_kv` - -```text -clone_kv ( ) → tuple [ torch.Tensor , torch.Tensor ] -``` - -Return clones of the full physical K/V buffers. - -#### `overwrite_kv_` - -```text -overwrite_kv_ ( k : torch.Tensor , v : torch.Tensor ) → None -``` - -Overwrite the full K/V buffers without changing their addresses. - -##### `` - -```text -Parameters : -``` - -* **k** – Replacement keys with the exact cache shape. -* **v** – Replacement values with the exact cache shape. +::: flashdreams.core.attention.BlockKVCache ## Distributed -Helpers for multi-GPU and multi-node inference. `init` boots the NCCL -process group with NVML-derived CPU affinity, a configurable heartbeat timeout, -and a larger L2 fetch granularity. - - -### `init` - -```text -init ( ) → int | None -``` - -Initialize distributed training. - - -### `Device` - -```text -class Device ( device_idx : int ) -``` - -Bases: `object` - -Lightweight wrapper around an NVML device handle for CPU-affinity queries. - - -#### `get_name` - -```text -get_name ( ) → str -``` - -Return the marketing name reported by NVML for this device. - -#### `get_cpu_affinity` +Helpers for multi-GPU / multi-node inference. `init` boots the NCCL process +group with sensible defaults and replaces the boilerplate at the top of +example launchers. -```text -get_cpu_affinity ( ) → list [ int ] -``` +::: flashdreams.core.distributed.init -Return the indices of CPUs ideally affined to this GPU per NVML. +::: flashdreams.core.distributed.Device diff --git a/docs/source/documentation/inferencing_api/api_reference/pipeline.md b/docs/source/documentation/inferencing_api/api_reference/pipeline.md index fb173d69e..78ffdad83 100644 --- a/docs/source/documentation/inferencing_api/api_reference/pipeline.md +++ b/docs/source/documentation/inferencing_api/api_reference/pipeline.md @@ -3,7 +3,6 @@ title: 'Pipeline API Reference' --- - The `flashdreams.infra` package defines the swappable abstractions that @@ -19,59 +18,11 @@ built via `config.setup()`. `PrintableConfig` provides readable configuration dumps, while `derive_config` deep-copies an existing config before applying nested overrides. - -### `PrintableConfig` - -```text -class PrintableConfig -``` - -Bases: `object` - -Config base class providing a multi-line `__str__` for human-readable dumps. - - -### `InstantiateConfig` - -```text -class InstantiateConfig ( _target : type ) -``` - -Bases: `PrintableConfig` - -Config carrying a `_target` class plus its kwargs, instantiable via `setup`. - - -#### `setup` - -```text -setup ( ** kwargs : Any ) → Any -``` - -Instantiate the configured object. - - -### `derive_config` - -```text -derive_config ( base_config : T , ** changes : Any ) → T -``` +::: flashdreams.infra.config.PrintableConfig -Deep-copy a base config and apply nested keyword overrides. +::: flashdreams.infra.config.InstantiateConfig -Nested `dict` values walk into both dataclass attributes and nested -dicts; leaf values overwrite directly. Raises `KeyError` on unknown -paths. - -Example: - -``` -new_config = derive_config( - base_config, - tokenizer=WanVAEInterfaceConfig(checkpoint_path=...), - dit=dict(len_t=3, checkpoint_path=...), -) -``` +::: flashdreams.infra.config.derive_config ## Pipeline @@ -81,1464 +32,80 @@ the diffusion model, and an optional decoder, with per-session caches for the configured streaming components. Call `finalize()` after `generate()` to advance the diffusion model's autoregressive cache. - -### `StreamInferencePipelineConfig` - -```text -class StreamInferencePipelineConfig ( * , _target: type[StreamInferencePipeline] = , name: str , diffusion_model: ~flashdreams.infra.diffusion.model.base.DiffusionModelConfig , decoder: ~flashdreams.infra.decoder.base.DecoderConfig | None = None , encoder: ~flashdreams.infra.encoder.base.EncoderConfig | None = None ) -``` - -Bases: `InstantiateConfig` - -Config for the streaming inference pipeline. - -Set `encoder=None` when the pipeline has no per-AR-step control input -(pure T2V). Set `decoder=None` to return the clean latent directly -(training, latent-space evaluation, or pipelines that own decoding). - - -#### `name` - -```text -name : str -``` - -Stable slug for this pipeline variant; the primary key of -`_CONFIGS`. Runners mirror it as `runner_name` so -`flashdreams-run ` resolves to this pipeline. - -#### `diffusion_model` - -```text -diffusion_model : DiffusionModelConfig -``` - -Transformer + scheduler config. - -#### `decoder` - -```text -decoder : DecoderConfig | None = None -``` - -Optional output `StreamingDecoder` with a per-rollout cache, -called as `decoder(input, autoregressive_index, cache)`. Use -`None` to return the clean latent unchanged. - -#### `encoder` - -```text -encoder : EncoderConfig | None = None -``` - -Optional per-AR-step input encoder. Must be a -`StreamingEncoder`; one-shot encoders go on -`transformer.context_encoder` instead. - -#### `enable_sync_and_profile` - -```text -property enable_sync_and_profile : bool -``` - -Whether to record synchronized per-stage CUDA timings. - -Enabled process-wide with `FLASHDREAMS_SYNC_AND_PROFILE=1` - - -### `StreamInferencePipeline` - -```text -class StreamInferencePipeline ( config : StreamInferencePipelineConfig ) -``` - -Bases: `Module`, `Generic`[`StreamingEncoderCacheT`, `TransformerCacheT`, `StreamingDecoderCacheT`] - -End-to-end streaming inference pipeline. - -Generic over the encoder, transformer, and decoder cache types. The -encoder’s input/output types are forwarded as `Any` so the -transformer’s `predict_flow` / `postprocess_clean_latent` overrides -own the typing on the `input` argument they receive. - -Examples - -cache = pipeline.initialize\_cache(transformer\_context={…}) -output = pipeline.generate(0, cache, input=…) -pipeline.finalize(0, cache) -output = pipeline.generate(1, cache, input=…) -pipeline.finalize(1, cache) # optional for the last rollout - - -#### `initialize_cache` - -```text -initialize_cache ( transformer_context : dict [ str , Any ] | None = None , encoder_context : dict [ str , Any ] | None = None , decoder_context : dict [ str , Any ] | None = None ) → StreamInferencePipelineCache [ StreamingEncoderCacheT , TransformerCacheT , StreamingDecoderCacheT ] -``` - -Build a fresh per-rollout cache. - -Each `*_context` dict is forwarded as keyword arguments to the -corresponding component’s `initialize_autoregressive_cache`. - -##### `` - -```text -Parameters : -``` - -* **transformer\_context** – Per-rollout state for the transformer - (e.g. `{"text_embeddings": ..., "image_embeddings": ...}`). -* **encoder\_context** – Per-rollout state for the encoder. Ignored - when there is no encoder. -* **decoder\_context** – Per-rollout state for the decoder. Ignored - when there is no decoder. - -#### `generate` - -```text -generate ( autoregressive_index : int , cache : StreamInferencePipelineCache [ StreamingEncoderCacheT , TransformerCacheT , StreamingDecoderCacheT ] , input : Any = None ) → Tensor -``` - -Generate one chunk for this AR step. - -##### `` - -```text -Parameters : -``` +::: flashdreams.infra.pipeline.StreamInferencePipelineConfig -* **autoregressive\_index** – Must be `cache.autoregressive_index + 1`, - or `0` for the first call after `initialize_cache`. -* **cache** – Per-rollout cache from `initialize_cache`. -* **input** – Raw input fed to the encoder. Required when an encoder - is configured, must be `None` otherwise. Use - `NullEncoderConfig` to pass an already-encoded tensor - straight through. - -#### `finalize` +::: flashdreams.infra.pipeline.StreamInferencePipeline -```text -finalize ( autoregressive_index : int , cache : StreamInferencePipelineCache [ StreamingEncoderCacheT , TransformerCacheT , StreamingDecoderCacheT ] ) → dict [ str , float ] | None -``` - -Advance the diffusion AR cache for the next AR step. - -##### `` - -```text -Parameters : -``` - -* **autoregressive\_index** – Must match the index passed to the most - recent `generate` (asserted). -* **cache** – Same cache used by `generate`. Consumes - `cache.final_state`. - - -### `StreamInferencePipelineCache` - -```text -class StreamInferencePipelineCache ( * , transformer_cache : TransformerCacheT , encoder_cache : StreamingEncoderCacheT | None = None , decoder_cache : StreamingDecoderCacheT | None = None , final_state : FinalState [ TransformerCacheT ] | None = None , autoregressive_index : int | None = None , event_profiler : EventProfiler | None = None ) -``` - -Bases: `Generic`[`StreamingEncoderCacheT`, `TransformerCacheT`, `StreamingDecoderCacheT`] - -Per-rollout cache held by the pipeline. - - -#### `transformer_cache` - -```text -transformer_cache : TransformerCacheT -``` - -Long-lived transformer AR cache (always present). - -#### `encoder_cache` - -```text -encoder_cache : StreamingEncoderCacheT | None = None -``` - -Encoder AR cache; `None` iff the pipeline has no encoder. - -#### `decoder_cache` - -```text -decoder_cache : StreamingDecoderCacheT | None = None -``` - -Decoder AR cache; `None` iff the pipeline has no decoder. - -#### `final_state` - -```text -final_state : FinalState [ TransformerCacheT ] | None = None -``` - -Diffusion-model state from the most recent `generate`, consumed -by `finalize`. - -#### `autoregressive_index` - -```text -autoregressive_index : int | None = None -``` - -AR step index of the most recent `generate`. - -#### `event_profiler` - -```text -event_profiler : EventProfiler | None = None -``` - -Per-step profiler, populated only when profiling is on. +::: flashdreams.infra.pipeline.StreamInferencePipelineCache ## Diffusion model -Combines a transformer backbone with a denoising scheduler. The denoising -iteration is hidden inside -`~flashdreams.infra.diffusion.model.DiffusionModel.generate`, which -returns the clean latent and the final state needed by `finalize()` to -advance the autoregressive cache. - - -### `DiffusionModelConfig` - -```text -class DiffusionModelConfig ( * , _target: type[DiffusionModel] = , transformer: ~flashdreams.infra.diffusion.transformer.base.TransformerConfig , scheduler: ~flashdreams.infra.diffusion.scheduler.base.SchedulerConfig , seed: int | None = None , context_noise: int = 0 , noise_in_unpatchified_shape: bool = False ) -``` - -Bases: `InstantiateConfig` - -Config for the autoregressive diffusion model. - - -#### `transformer` - -```text -transformer : TransformerConfig -``` - -Flow-prediction network config. - -#### `scheduler` - -```text -scheduler : SchedulerConfig -``` - -Denoising-loop config. - -#### `seed` - -```text -seed : int | None = None -``` - -RNG seed for initial-noise draws and scheduler sampling. -`None` uses the global RNG. - -#### `context_noise` - -```text -context_noise : int = 0 -``` - -Timestep used by `finalize` for the AR cache-update forward. -`0` skips `add_noise`. - -#### `noise_in_unpatchified_shape` - -```text -noise_in_unpatchified_shape : bool = False -``` - -draw the initial noise in the unpatchified shape, then -patchify. Slower than the default patchified path; useful when matching -another implementation’s RNG sequence. - -##### `` - -```text -Type : -``` - -Debug-only - - -### `DiffusionModel` - -```text -class DiffusionModel ( config : DiffusionModelConfig ) -``` - -Bases: `Module`, `Generic`[`TransformerCacheT`] - -Autoregressive diffusion model (scheduler + transformer). - -Generic over the transformer’s AR cache type so user-facing typing on -`cache` is preserved end-to-end. - -Examples - -model = config.setup().to(“cuda”) -cache = model.transformer.initialize\_autoregressive\_cache(…) -clean, final\_state = model.generate(autoregressive\_index=0, cache=cache) -model.finalize(final\_state) - - -#### `FinalState` - -```text -class FinalState ( * , clean_latent : Tensor , autoregressive_index : int , cache : _FinalStateCacheT , input : Any | None = None ) -``` +The diffusion model wraps a transformer backbone with a denoising scheduler. +Callers see only noise to clean latent conversion; the per-step flow prediction +and iteration loop remain inside `DiffusionModel.generate`. -Bases: `Generic`[`_FinalStateCacheT`] +::: flashdreams.infra.diffusion.model.DiffusionModelConfig -State passed from `generate` to `finalize`. - - -##### `clean_latent` - -```text -clean_latent : Tensor -``` - -Patchified clean latent at the end of denoising. - -##### `autoregressive_index` - -```text -autoregressive_index : int -``` - -AR step this state was produced at. - -##### `cache` - -```text -cache : _FinalStateCacheT -``` - -Long-lived AR cache used during generation. - -##### `input` - -```text -input : Any = None -``` - -Patchified per-AR-step encoder output, or `None`. - -#### `rng` - -```text -property rng : Generator | None -``` - -Per-model generator, lazily built on the current device. - -Returns `None` when `config.seed` is `None`. Rebuilt the first -time the model’s device changes after a `.to(...)`. A device move -resets the RNG stream — fine for “construct on CPU, `.to(gpu)` -once” but mid-rollout device hops lose RNG state. - -#### `generate` - -```text -generate ( autoregressive_index : int , cache : TransformerCacheT , input : Any | None = None ) → tuple [ Tensor , DiffusionModel.FinalState [ TransformerCacheT ] ] -``` - -Run the denoising loop for one AR step. - -##### `` - -```text -Parameters : -``` - -* **autoregressive\_index** – AR step index. -* **cache** – Long-lived AR cache, mutated in place. -* **input** – Optional per-AR-step encoder output. Patchified here and - forwarded to `predict_flow` / `postprocess_clean_latent`, - then stashed on the returned `FinalState` for `finalize`. - -#### `finalize` - -```text -finalize ( final_state : FinalState [ TransformerCacheT ] ) → None -``` - -Advance the AR cache using the clean latent from `generate`. - -Re-noises the clean latent to `config.context_noise` and runs the -transformer’s `finalize_kv_cache` (one forward for vanilla -transformers, multiple for dual-network DiTs). - -`context_noise == 0` skips `add_noise` (sigma=0 is identity) and -feeds the clean latent directly. This also dodges the requirement -for schedulers to support a `t=0` lookup (UniPC’s inference -schedule has no `t=0` entry). +::: flashdreams.infra.diffusion.model.DiffusionModel ## Transformer - -### `TransformerConfig` - -```text -class TransformerConfig ( * , _target: type[Transformer] = ) -``` - -Bases: `InstantiateConfig` - -Category base for every flow-prediction transformer config. - - -### `Transformer` - -```text -class Transformer ( config : TransformerConfig ) -``` - -Bases: `Module`, `ABC`, `Generic`[`TransformerCacheT`] - -Flow-prediction transformer, generic over its AR cache subclass. - -Subclasses implement `predict_flow` and the patchify hooks. AR -transformers also subclass `TransformerAutoregressiveCache` and -override `initialize_autoregressive_cache`. - -Example: - -``` -class MyTransformer(Transformer[MyCache]): - def predict_flow(self, noisy_latent, timestep, cache, input=None): - ... - - def initialize_autoregressive_cache(self, **context) -> MyCache: - ... -``` - - -#### `latent_shape` - -```text -abstract property latent_shape : tuple [ int , ... ] -``` - -Shape of the input/output latent tensor for this rank. - -Includes batch dims. May depend on hierarchical context-parallel -group sizes (V/T/HW), so subclasses typically derive this from -`self.cp_groups` rather than from static config alone. - -#### `predict_flow` - -```text -abstract predict_flow ( noisy_latent : Tensor , timestep : Tensor , cache : TransformerCacheT , input : Any | None = None ) → Tensor -``` - -Predict the flow at `timestep`. - -##### `` - -```text -Parameters : -``` - -* **noisy\_latent** – Patchified noisy latent for this denoising step. -* **timestep** – Scalar timestep tensor. -* **cache** – Per-rollout AR cache. -* **input** – Patchified encoder output for this AR step, or `None` - when the pipeline has no encoder. Subclasses should narrow - the type to their encoder’s output type. - -#### `finalize_kv_cache` - -```text -finalize_kv_cache ( noisy_latent : Tensor , timestep : Tensor , cache : TransformerCacheT , input : Any | None = None ) → None -``` - -Advance the AR cache so it is ready for the next AR step. - -Called by `DiffusionModel.finalize` after the denoising loop; the -flow is discarded — only the cache side effect matters. Default -runs a single `predict_flow` forward. Override for transformers -with multiple parallel networks that must stay in lock-step. - -##### `` - -```text -Parameters : -``` - -* **noisy\_latent** – Patchified latent at the AR-step’s context noise - (or the clean latent when `context_noise == 0`). -* **timestep** – 0-d context-noise timestep tensor. -* **cache** – Per-rollout AR cache. -* **input** – Same patchified encoder output passed to `predict_flow`. - -#### `initialize_autoregressive_cache` - -```text -initialize_autoregressive_cache ( ** context : Any ) → TransformerCacheT -``` - -Build a fresh AR cache for a new rollout. +::: flashdreams.infra.diffusion.transformer.TransformerConfig -Default returns an empty cache, correct for non-AR transformers. -Subclasses with custom cache types must override this and may -declare typed per-rollout context (e.g. `text_embeddings`). +::: flashdreams.infra.diffusion.transformer.Transformer -##### `` - -```text -Parameters : -``` - -**context** – Per-rollout state forwarded as keyword arguments. - -#### `initial_noise` - -```text -initial_noise ( * , latent_shape : tuple [ int , ... ] , rng : Generator | None , cache : TransformerCacheT , input : Any | None = None ) → Tensor -``` - -Draw the starting latent for one denoising loop. - -Default is Gaussian noise in `latent_shape`. Model integrations can -override this to seed from encoder outputs, pin I2V frames, or attach -per-step state to the cache while still using `DiffusionModel`. - -##### `` - -```text -Parameters : -``` - -* **latent\_shape** – Shape requested by the diffusion model. -* **rng** – Per-model generator on `self.device`, or `None`. -* **cache** – Per-rollout AR cache. -* **input** – Same patchified encoder output passed to `predict_flow`. - -#### `postprocess_clean_latent` - -```text -postprocess_clean_latent ( clean_latent : Tensor , cache : TransformerCacheT , input : Any | None = None ) → Tensor -``` - -Optional postprocessing hook for the predicted clean latent. - -Default is identity. Override to clamp or re-inject regions whose -clean value is known a priori (e.g. I2V first-frame pinning). -Called at the end of `DiffusionModel.generate`. - -##### `` - -```text -Parameters : -``` - -* **clean\_latent** – Patchified `x0` from the denoising loop. -* **cache** – Per-rollout AR cache. -* **input** – Same patchified encoder output passed to `predict_flow`. - -#### `patchify_and_maybe_split_cp` - -```text -abstract patchify_and_maybe_split_cp ( x : Any ) → Any -``` - -Patchify and (optionally) CP-split a noisy latent or encoder payload. - -Tensors patchify and split. Structured payloads (e.g. an image-control -struct with `latent` + `mask`) patchify each tensor field and -return the same struct type. Implement as identity when neither -token packing nor CP sharding applies. Output preserves the input -Python type — only shapes change. - -#### `unpatchify_and_maybe_gather_cp` - -```text -abstract unpatchify_and_maybe_gather_cp ( x : Tensor ) → Tensor -``` - -Inverse of `patchify_and_maybe_split_cp` for the network output. - - -### `TransformerAutoregressiveCache` - -```text -class TransformerAutoregressiveCache -``` - -Bases: `object` - -Cache that persists across an AR rollout. - -Empty by default; safe to instantiate directly for non-AR transformers -(default `start` / `finalize` are no-ops). Subclass and add fields -plus AR bookkeeping for real per-rollout state. - -Example: - -``` -cache.start(autoregressive_index) -# one or more denoising steps... -cache.finalize(autoregressive_index) -``` - - -#### `start` - -```text -start ( autoregressive_index : int ) → None -``` - -Mark the start of an AR step. Default is a no-op. - -##### `` - -```text -Parameters : -``` - -**autoregressive\_index** – Index of the AR step being started. - -#### `finalize` - -```text -finalize ( autoregressive_index : int ) → None -``` - -Finalize bookkeeping after use at this AR step. Default is a no-op. - -##### `` - -```text -Parameters : -``` - -**autoregressive\_index** – Index of the AR step just finalized. +::: flashdreams.infra.diffusion.transformer.TransformerAutoregressiveCache ## Schedulers -A scheduler owns the entire denoising loop. It is shape-agnostic: every -internal op is a broadcast against per-step scalar sigmas, so the same -scheduler works for any latent layout. - - -### `Scheduler` - -```text -class Scheduler ( config : SchedulerConfig ) -``` - -Bases: `Module`, `ABC` - -Denoising scheduler. - -Owns the entire denoising loop. Callers see only `noise → clean`; -the loop shape (renoise / multistep / plain ODE) is private. - -Concrete configs inherit `SchedulerConfig` and declare their own -`num_inference_steps` / `shift` fields (the base holds no -shared dataclass fields). - -Examples - -scheduler = config.setup() -clean = scheduler.sample(initial\_noise=noise, predict\_flow=predictor) -noisy = scheduler.add\_noise(clean\_input=clean, timestep=t) - - -#### `sample` - -```text -abstract sample ( initial_noise : Tensor , predict_flow : FlowPredictor , rng : Generator | None = None ) → Tensor -``` - -Run the full denoising loop and return the clean latent. +A scheduler owns the denoising loop. Its internal operations broadcast +per-step scalar sigmas, so the same scheduler works for any latent layout. -Schedulers are shape-agnostic: every internal op broadcasts against -per-step scalar sigmas. In practice `initial_noise` is a video -latent `[B, C, T, H, W]`, conventionally treated as a sample at -`sigma=1`. +::: flashdreams.infra.diffusion.scheduler.Scheduler -##### `` +::: flashdreams.infra.diffusion.scheduler.SchedulerConfig -```text -Parameters : -``` +::: flashdreams.infra.diffusion.scheduler.FlowPredictor -* **initial\_noise** – Gaussian noise on the caller’s device/dtype. -* **predict\_flow** – Per-step closure invoked - `num_inference_steps` times. -* **rng** – Generator on the same device. Used by self-forcing renoise - loops; pure ODE solvers ignore it. - -#### `add_noise` +::: flashdreams.infra.diffusion.scheduler.FlowMatchSchedulerConfig -```text -abstract add_noise ( clean_input : Tensor , timestep : Tensor , rng : Generator | None = None ) → Tensor -``` +::: flashdreams.infra.diffusion.scheduler.FlowMatchScheduler -Apply the forward corruption `x_t = (1 - sigma(t)) * x_0 + sigma(t) * eps`. +::: flashdreams.infra.diffusion.scheduler.FlowMatchEulerDiscreteSchedulerConfig -Timestep value semantics are scheduler-specific: all schedulers snap -to the nearest entry of their inference schedule. +::: flashdreams.infra.diffusion.scheduler.FlowMatchUniPCSchedulerConfig - -### `SchedulerConfig` - -```text -class SchedulerConfig ( * , _target: type[Scheduler] = ) -``` - -Bases: `InstantiateConfig` - -Category base for every denoising-scheduler config. - - -### `FlowPredictor` - -```text -class FlowPredictor ( * args , ** kwargs ) -``` - -Bases: `Protocol` - -Closure `(noisy_latent, timestep) -> predicted_flow`. - -Built by `DiffusionModel.generate` by binding the per-AR-step `cache` -/ `input` to the transformer’s `predict_flow`. A scheduler invokes -it once per denoising iteration. The scheduler decides the timestep -dtype (UniPC uses int64, flow-match uses float). - - -### `FlowMatchSchedulerConfig` - -```text -class FlowMatchSchedulerConfig ( * , _target: type[FlowMatchScheduler] = , num_inference_steps: int = 4 , shift: float = 8.0 , denoising_timesteps: list[int] = , warp_denoising_step: bool = True , num_train_timesteps: int = 1000 , sigma_max: float = 1.0 , sigma_min: float = 0.0 , extra_one_step: bool = True , timestep_dtype: ~torch.dtype = torch.float32 , enable_tqdm: bool = False ) -``` - -Bases: `SchedulerConfig` - -Config for the flow-matching scheduler. - - -#### `num_inference_steps` - -```text -num_inference_steps : int = 4 -``` - -Must equal `len(denoising_timesteps)`. - -#### `shift` - -```text -shift : float = 8.0 -``` - -Schedule warp factor. - -#### `denoising_timesteps` - -```text -denoising_timesteps : list [ int ] -``` - -Per-step diffusion timesteps in `[0, num_train_timesteps]`. - -#### `warp_denoising_step` - -```text -warp_denoising_step : bool = True -``` - -Map `denoising_timesteps` through the warped sigma schedule. - -#### `num_train_timesteps` - -```text -num_train_timesteps : int = 1000 -``` - -Length of the training sigma table. - -#### `sigma_max` - -```text -sigma_max : float = 1.0 -``` - -Top of the linspace before warping; `1.0` matches DiffSynth, upstream -Wan / Lingbot ships `0.999`. - -#### `sigma_min` - -```text -sigma_min : float = 0.0 -``` - -Bottom of the linspace before warping. Reserved for upstream parity; -only `0.0` is exercised. - -#### `extra_one_step` - -```text -extra_one_step : bool = True -``` - -If `True`, build the schedule from -`linspace(sigma_max, sigma_min, N+1)[:-1]` (matches DiffSynth / -upstream Wan); `False` uses `N` points and is kept for non-Wan -recipes. - -#### `timestep_dtype` - -```text -timestep_dtype : dtype = torch.float32 -``` - -Dtype of `denoising_step_list`. Set to an integer dtype (e.g. -`torch.int64`) when the network’s time embedding is sensitive to the -fractional part of the warped timestep — upstream Wan stores -`scheduler.timesteps` as `int64` and lets the embedding upcast to -`float64` internally. - -#### `enable_tqdm` - -```text -enable_tqdm : bool = False -``` - -Whether to enable tqdm progress bar. - - -### `FlowMatchScheduler` - -```text -class FlowMatchScheduler ( config : FlowMatchSchedulerConfig ) -``` - -Bases: `Scheduler` - -Flow-matching scheduler with self-forcing renoise (DiffSynth-style). - -Each iteration converts the predicted flow to an `x0` estimate, then -re-noises at the same sigma to feed the next iteration. The final -`x0` is returned: - -``` -x_t = initial_noise -for t in denoising_step_list: - v = predict_flow(x_t, t) - x0 = x_t - sigma(t) * v - x_t = (1 - sigma(t)) * x0 + sigma(t) * eps -return x0 -``` - -Example: - -``` -scheduler = FlowMatchSchedulerConfig( - num_inference_steps=4, - shift=8.0, - denoising_timesteps=[1000, 750, 500, 250], -).setup().to("cuda") -clean = scheduler.sample(initial_noise=noise, predict_flow=fn) -``` - -Schedule buffers are pinned to fp32 even after `module.to(bf16)`; -integer timesteps like 1000 would otherwise round to 1024. - - -#### `sample` - -```text -sample ( initial_noise : Tensor , predict_flow : FlowPredictor , rng : Generator | None = None ) → Tensor -``` - -Run the self-forcing flow-match denoising loop. - -Iteration 0 trusts `initial_noise` as the `sigma=1` sample; -later iterations re-noise the previous `x0` estimate to the new -sigma before the network forward. Schedule arithmetic auto-promotes -to fp32; the result is cast back to `initial_noise.dtype`. - -#### `add_noise` - -```text -add_noise ( clean_input : Tensor , timestep : Tensor , rng : Generator | None = None ) → Tensor -``` - -Apply the forward corruption at an arbitrary timestep. - -Snaps `timestep` to the nearest entry of the warped training table -and uses it as sigma in the standard lerp. - - -### `FlowMatchEulerDiscreteSchedulerConfig` - -```text -class FlowMatchEulerDiscreteSchedulerConfig ( * , _target: type[FlowMatchEulerDiscreteScheduler] = , num_inference_steps: int = 4 , shift: float = 5.0 , num_train_timesteps: int = 1000 , fixed_timesteps: tuple[float , ...] | None = None , enable_tqdm: bool = False ) -``` - -Bases: `SchedulerConfig` - -Config for the flow-matching Euler-discrete scheduler. - -Defaults match diffusers’ `FlowMatchEulerDiscreteScheduler` -with `num_train_timesteps=1000` and the standard -`linspace + shift` warp. Set `fixed_timesteps` to a length -`num_inference_steps + 1` tuple (terminal value typically `0.0`) -to pin an externally-derived distilled schedule. - - -#### `num_inference_steps` - -```text -num_inference_steps : int = 4 -``` - -Number of Euler steps. Matches the distilled WAN-5B 4-step path -out of the box; bump to 30-50 for non-distilled checkpoints. - -#### `shift` - -```text -shift : float = 5.0 -``` - -Schedule warp factor applied to the linspaced sigma grid. Ignored -when `fixed_timesteps` is set. - -#### `num_train_timesteps` - -```text -num_train_timesteps : int = 1000 -``` - -Length of the training sigma table. Used to convert between -integer timesteps and `[0, 1]` sigmas (`sigma = timestep / N`). - -#### `fixed_timesteps` - -```text -fixed_timesteps : tuple [ float , ... ] | None = None -``` - -Override the derived schedule with a precomputed list of -timesteps. When set, must have length `num_inference_steps + 1`; -the trailing entry is typically `0.0` so the last Euler step -lands on the clean latent. `None` (default) derives the schedule -from `num_inference_steps` + `shift` via the standard -flow-matching linspace + warp. - -The HY-WorldPlay distilled WAN-5B path pins this to -`(1000.0, 960.0, 888.8889, 727.2728, 0.0)`. - -#### `enable_tqdm` - -```text -enable_tqdm : bool = False -``` - -Whether to enable the tqdm progress bar inside `sample()`. - - Euler-discrete flow-matching scheduler. - - -### `FlowMatchUniPCSchedulerConfig` - -```text -class FlowMatchUniPCSchedulerConfig ( * , _target: type[FlowMatchUniPCScheduler] = , num_inference_steps: int = 50 , shift: float = 5.0 , num_train_timesteps: int = 1000 , solver_order: int = 2 , use_kerras_sigma: bool = False , enable_tqdm: bool = False ) -``` - -Bases: `SchedulerConfig` - -Config for the flow-matching UniPC scheduler. - -Defaults match the official Wan 2.1 inference integration (UniPC, BH2, -order 2, shift 5.0). Override `shift` per checkpoint as recommended -upstream (e.g. 3.0 for Wan 2.1 14B I2V 480P). - - -#### `num_inference_steps` - -```text -num_inference_steps : int = 50 -``` - -Number of UniPC denoising steps. - -#### `shift` - -```text -shift : float = 5.0 -``` - -Schedule warp factor. - -#### `num_train_timesteps` - -```text -num_train_timesteps : int = 1000 -``` - -Length of the training sigma table. - -#### `solver_order` - -```text -solver_order : int = 2 -``` - -UniPC solver order; only 2 is supported. - -#### `use_kerras_sigma` - -```text -use_kerras_sigma : bool = False -``` - -Whether to use the exact sigma used in edm sampler. - -#### `enable_tqdm` - -```text -enable_tqdm : bool = False -``` - -Whether to enable tqdm progress bar. - - -### `FlowMatchUniPCScheduler` - -```text -class FlowMatchUniPCScheduler ( config : FlowMatchUniPCSchedulerConfig ) -``` - -Bases: `Scheduler` - -Order-2 UniPC predictor-corrector for flow-matching. - -Specialized + pre-baked variant of the upstream Wan 2.1 UniPC solver. -Schedule buffers (sigmas + per-step coefficients) stay fp32 regardless -of `module.to(dtype)`. - -Example: - -``` -scheduler = FlowMatchUniPCSchedulerConfig( - num_inference_steps=50, - shift=5.0, -).setup().to("cuda") -clean = scheduler.sample(initial_noise=noise, predict_flow=fn) -``` - - -#### `sample` - -```text -sample ( initial_noise : Tensor , predict_flow : FlowPredictor , rng : Generator | None = None ) → Tensor -``` - -Run the order-2 UniPC predictor-corrector denoising loop. - -Each iteration: network → flow → `x0` → corrector (skipped at -step 0) → predictor. All per-step coefficients are pre-baked at -construction; the loop is pure tensor ops. Internal arithmetic is -fp32; the result is cast back to `initial_noise.dtype`. `rng` -is unused (deterministic ODE) but accepted for interface conformance. - -#### `add_noise` - -```text -add_noise ( clean_input : Tensor , timestep : Tensor , rng : Generator | None = None ) → Tensor -``` - -Apply the forward corruption at an arbitrary timestep. - -Snaps `timestep` to the nearest entry of the inference schedule -on-device (no Python sync) and uses the matching sigma in the lerp. +::: flashdreams.infra.diffusion.scheduler.FlowMatchUniPCScheduler ## Encoder -Encoders turn raw conditioning (text prompts, reference images, per-AR-step -control inputs, …) into latent tensors. Two flavours: - -- `Encoder` is stateless and one-shot. `forward(self, input)`. - Used as `transformer.context_encoder` for text / CLIP-image / identity. -- `StreamingEncoder` is stateful and per-AR-step. ``forward(self, - input, autoregressive_index, cache)`` with an - `StreamingEncoderCache`. Used as `pipeline.encoder` for - per-step control (HDMap, camera trajectory, I2V first-frame VAE). -- `StreamingVideoEncoder` extends `StreamingEncoder` with - the contracts a streaming pixel-video encoder always needs: spatial / - temporal compression ratios plus AR-step-aware temporal size mappers - between pixel and latent space. - - -### `EncoderConfig` - -```text -class EncoderConfig ( * , _target: type[Encoder] = ) -``` - -Bases: `InstantiateConfig` - -Category base for every encoder config (stateless or streaming). - - -### `Encoder` - -```text -class Encoder ( config : EncoderConfig ) -``` - -Bases: `ABC`, `Module` - -Stateless encoder. - -`forward` is not pinned by the base. Encoders used as a -`context_encoder` (one-shot, called once inside -`Transformer.initialize_autoregressive_cache()`) must match the -slim call shape `forward(self, input)`. - -For per-AR-step encoders that need a per-rollout cache, inherit -from `StreamingEncoder` instead. - - -### `StreamingEncoder` - -```text -class StreamingEncoder ( config : EncoderConfig ) -``` - -Bases: `ABC`, `Module`, `Generic`[`StreamingEncoderCacheT`] - -Streaming encoder, generic over the per-rollout cache type. - -`forward` is not pinned by the base. Streaming encoders called by -`StreamInferencePipeline` must match its call shape: -`forward(self, input, autoregressive_index=0, cache=None)`. - - -#### `initialize_autoregressive_cache` - -```text -abstract initialize_autoregressive_cache ( ** context : Any ) → StreamingEncoderCacheT -``` - -Build a fresh per-rollout cache. - -Override to return the encoder’s concrete cache type. - - -### `StreamingVideoEncoder` - -```text -class StreamingVideoEncoder ( config : EncoderConfig ) -``` - -Bases: `StreamingEncoder`[`StreamingEncoderCacheT`] - -Streaming pixel-video encoder. - -Pins down the contracts that every streaming pixel→latent video -encoder satisfies in addition to `StreamingEncoder`: - -* Spatial and temporal compression ratios between the pixel and - latent grids (constants of the architecture). -* AR-step-aware temporal size mappers, so a pipeline can size its - inputs and outputs without knowing the encoder’s concrete - temporal cache topology (causal first-frame padding, sliding - windows, etc.). - -Spatial scaling is trivially `side // spatial_compression_ratio` -in either direction; the AR-step-asymmetric piece is the temporal -size, which gets its own mapper. Typically AR 0 takes -`1 + (T_lat - 1) * r` pixel frames to produce `T_lat` latent -frames because of causal first-frame padding, while AR ≥ 1 takes -`T_lat * r` pixel frames. - - -#### `spatial_compression_ratio` - -```text -abstract property spatial_compression_ratio : int -``` - -Pixel side ÷ latent side. Constant across AR steps. - -#### `temporal_compression_ratio` - -```text -abstract property temporal_compression_ratio : int -``` - -Pixel frames ÷ latent frames in steady state (AR ≥ 1). - -AR 0 typically takes one extra (un-grouped) pixel frame to -produce its first latent frame because of causal first-frame -padding; that asymmetry lives inside -`get_output_temporal_size()` / -`get_input_temporal_size()`. - -#### `get_output_temporal_size` - -```text -abstract get_output_temporal_size ( autoregressive_index : int , input_temporal_size : int ) → int -``` - -Latent frame count produced from `input_temporal_size` pixel frames. - -##### `` - -```text -Parameters : -``` - -* **autoregressive\_index** – AR step index (0-based). -* **input\_temporal\_size** – Number of pixel frames fed at this step. - -#### `get_input_temporal_size` - -```text -abstract get_input_temporal_size ( autoregressive_index : int , output_temporal_size : int ) → int -``` - -Pixel frame count needed to produce `output_temporal_size` latents. - -Inverse of `get_output_temporal_size()`. Implementations -should assert `output_temporal_size` is achievable at this AR -step (i.e. the corresponding pixel count comes out as a -positive integer). - -##### `` - -```text -Parameters : -``` - -* **autoregressive\_index** – AR step index (0-based). -* **output\_temporal\_size** – Desired number of latent frames. - - -### `StreamingEncoderCache` - -```text -class StreamingEncoderCache -``` - -Bases: `object` +Encoders turn raw conditioning into latent tensors. `Encoder` is stateless and +one-shot, while `StreamingEncoder` carries per-step state. The video variant +adds spatial and temporal size contracts. -Per-rollout cache for `StreamingEncoder`. +::: flashdreams.infra.encoder.EncoderConfig -Empty by default; subclass to add fields (e.g. last-frame latent, -cross-step accumulators). +::: flashdreams.infra.encoder.Encoder - -### `NullEncoderConfig` +::: flashdreams.infra.encoder.StreamingEncoder -```text -class NullEncoderConfig ( * , _target: type[NullEncoder] = ) -``` +::: flashdreams.infra.encoder.StreamingVideoEncoder -Bases: `EncoderConfig` +::: flashdreams.infra.encoder.StreamingEncoderCache -Config for the identity encoder. +::: flashdreams.infra.encoder.NullEncoderConfig - -### `NullEncoder` - -```text -class NullEncoder ( config : EncoderConfig ) -``` - -Bases: `Encoder` - -Identity encoder: returns its input unchanged. - -Wire as the transformer’s `context_encoder` slot to pass already- -encoded tensors straight to the diffusion model. - -Example: - -``` -config = TransformerConfig( - context_encoder=NullEncoderConfig(), - ..., -) -``` - - -#### `forward` - -```text -forward ( input : Any ) → Any -``` - -Return `input` unchanged. +::: flashdreams.infra.encoder.NullEncoder ## Decoder -Decoders turn the latents emitted by the diffusion model back into pixel -frames. Single base class with two specialisations: - -- `StreamingDecoder` is stateful. ``forward(self, input, - autoregressive_index, cache)`` with a `StreamingDecoderCache`. - Use for chunk-by-chunk streaming decoders (e.g. WAN VAE that maintains - a temporal cache across AR steps); stateless decoders just return an - empty `StreamingDecoderCache` from - `StreamingDecoder.initialize_autoregressive_cache` and ignore - `autoregressive_index` / `cache` in `forward`. -- `StreamingVideoDecoder` extends `StreamingDecoder` with - the contracts a streaming pixel-video decoder always needs: spatial / - temporal compression ratios plus AR-step-aware temporal size mappers - between latent and pixel space. - - -### `DecoderConfig` - -```text -class DecoderConfig ( * , _target: type[StreamingDecoder] = ) -``` - -Bases: `InstantiateConfig` - -Category base for every decoder config. - - -### `StreamingDecoder` - -```text -class StreamingDecoder ( config : DecoderConfig ) -``` - -Bases: `ABC`, `Module`, `Generic`[`StreamingDecoderCacheT`] - -Streaming decoder, generic over the per-rollout cache type. - -`forward` is not pinned by the base. Streaming decoders called by -`StreamInferencePipeline` must match its call shape: -`forward(self, input, autoregressive_index=0, cache=None)`. - - -#### `initialize_autoregressive_cache` - -```text -abstract initialize_autoregressive_cache ( ** context : Any ) → StreamingDecoderCacheT -``` - -Build a fresh per-rollout cache. - -Override to return the decoder’s concrete cache type. - - -### `StreamingVideoDecoder` - -```text -class StreamingVideoDecoder ( config : DecoderConfig ) -``` - -Bases: `StreamingDecoder`[`StreamingDecoderCacheT`] - -Streaming pixel-video decoder. - -Pins down the contracts that every streaming latent→pixel video -decoder satisfies in addition to `StreamingDecoder`: - -* Spatial and temporal compression ratios between the latent and - pixel grids (constants of the architecture). -* AR-step-aware temporal size mappers, so a pipeline can size its - inputs and outputs without knowing the decoder’s concrete - temporal cache topology (causal first-frame padding, sliding - windows, etc.). - -Spatial scaling is trivially `side * spatial_compression_ratio` -in either direction; the AR-step-asymmetric piece is the temporal -size, which gets its own mapper. Typically AR 0 produces fewer -pixel frames per latent frame than AR ≥ 1 because of causal -first-frame padding. - - -#### `spatial_compression_ratio` - -```text -abstract property spatial_compression_ratio : int -``` - -Pixel side ÷ latent side. Constant across AR steps. - -#### `temporal_compression_ratio` - -```text -abstract property temporal_compression_ratio : int -``` - -Pixel frames ÷ latent frames in steady state (AR ≥ 1). - -AR 0 typically yields fewer pixel frames per latent frame -because of causal first-frame padding; that asymmetry lives -inside `get_output_temporal_size()` / -`get_input_temporal_size()`. - -#### `get_output_temporal_size` - -```text -abstract get_output_temporal_size ( autoregressive_index : int , input_temporal_size : int ) → int -``` - -Pixel frame count produced by `input_temporal_size` latent frames. - -##### `` - -```text -Parameters : -``` - -* **autoregressive\_index** – AR step index (0-based). -* **input\_temporal\_size** – Number of latent frames fed at this step. - -#### `get_input_temporal_size` - -```text -abstract get_input_temporal_size ( autoregressive_index : int , output_temporal_size : int ) → int -``` - -Latent frame count needed to produce `output_temporal_size` pixels. - -Inverse of `get_output_temporal_size()`. Implementations -should assert `output_temporal_size` is achievable at this AR -step (i.e. divisible by the right ratio after subtracting any -causal padding). - -##### `` - -```text -Parameters : -``` - -* **autoregressive\_index** – AR step index (0-based). -* **output\_temporal\_size** – Desired number of pixel frames. - - -### `StreamingDecoderCache` +Decoders turn emitted latents into frames. `StreamingDecoder` carries +per-rollout state; the video variant adds spatial and temporal size contracts. -```text -class StreamingDecoderCache -``` +::: flashdreams.infra.decoder.DecoderConfig -Bases: `object` +::: flashdreams.infra.decoder.StreamingDecoder -Per-rollout cache for `StreamingDecoder`. +::: flashdreams.infra.decoder.StreamingVideoDecoder -Empty by default; subclass to add fields (e.g. temporal feature -buffers carried across AR steps). +::: flashdreams.infra.decoder.StreamingDecoderCache diff --git a/docs/source/documentation/inferencing_api/api_reference/runtime.md b/docs/source/documentation/inferencing_api/api_reference/runtime.md deleted file mode 100644 index b0a6d4466..000000000 --- a/docs/source/documentation/inferencing_api/api_reference/runtime.md +++ /dev/null @@ -1,584 +0,0 @@ ---- -title: 'Experimental Inference Runtime API Reference' ---- - - - - - -`flashdreams.runtime` is the experimental inference API. Its lifecycle is -`ModelAdapter` → reusable `InferenceRuntime` → isolated -`InferenceSession`. See the [Demo API overview](../../demo_api/index.md) for -the application layer above it, or the -[Inferencing API overview](../index.md) for the surrounding model-side guides -and references. - -## Minimal direct use - -Integrations provide the adapter and its model-specific input schema. - -```python - -from flashdreams.runtime import ( - InferenceConfig, - InferenceInput, - ModelAdapter, -) - -def run_one_step(adapter: ModelAdapter, global_conditioning, step): - config = InferenceConfig(model_id=adapter.model_id) - adapter.validate_config(config) - runtime = adapter.create_runtime(config) - try: - initial_input = InferenceInput(global_conditioning=global_conditioning) - session = runtime.start_session(initial_input) - try: - request = session.next_step_request() - return None if request is None else session.step(InferenceInput(step=step)) - finally: - session.close() - finally: - runtime.close() - -``` - -Close sessions and runtimes even after errors. Session calls are sequential; -the caller owns concurrency unless an integration says otherwise. - -## API Reference - -### Runtime lifecycle - - -### `InferenceConfig` - -```text -class InferenceConfig ( * , model_id: str , preset_id: str | None = None , checkpoint: str | ~pathlib.Path | None = None , backend: ~typing.Literal['local' , 'local-distributed' , 'external' , 'hosted'] = 'local' , device: str | None = None , precision: ~typing.Literal['auto' , 'fp32' , 'fp16' , 'bf16'] = 'auto' , compile: bool | None = None , cuda_graph: bool | None = None , attention_backend: str | None = None , cache_policy: str | None = None , seed: int | None = None , runtime_options: ~collections.abc.Mapping[str , ~typing.Any] = , resource_hints: ~collections.abc.Mapping[str , ~typing.Any] = ) -``` - -Bases: `object` - -Runtime settings that affect model execution. - -Prompts, user controls, browser settings, output paths, and benchmark -directories intentionally live outside this object. The typed optimization -fields cover common cross-backend knobs; open-ended adapter-specific choices -can use `runtime_options`. - - -#### `model_id` - -```text -model_id : str -``` - -Stable identity for the model adapter or runtime integration. - -#### `preset_id` - -```text -preset_id : str | None -``` - -Optional preset identity under `model_id`. - -#### `checkpoint` - -```text -checkpoint : str | Path | None -``` - -Optional checkpoint or model-asset selector understood by the adapter. - -#### `backend` - -```text -backend : Literal [ 'local' , 'local-distributed' , 'external' , 'hosted' ] -``` - -Execution placement and backend family for inference compute. - -#### `device` - -```text -device : str | None -``` - -Optional device selector such as `cuda` or `cuda:0`; `None` leaves placement to the adapter/backend. - -#### `precision` - -```text -precision : Literal [ 'auto' , 'fp32' , 'fp16' , 'bf16' ] -``` - -Preferred compute precision. - -#### `compile` - -```text -compile : bool | None -``` - -Optional - Whether model compilation is requested or disabled. None means left to the adapter to decide. - -#### `cuda_graph` - -```text -cuda_graph : bool | None -``` - -Optional - Whether CUDA graph capture is requested or disabled. None means left to the adapter to decide. - -#### `attention_backend` - -```text -attention_backend : str | None -``` - -Optional attention implementation selector; `None` leaves the choice to the adapter. - -#### `cache_policy` - -```text -cache_policy : str | None -``` - -Optional cache policy selector; `None` leaves the choice to the adapter. - -#### `seed` - -```text -seed : int | None -``` - -Optional seed used when resolving deterministic demo/runtime behavior. - -#### `runtime_options` - -```text -runtime_options : Mapping [ str , Any ] -``` - -Adapter/backend-specific runtime options. - -#### `resource_hints` - -```text -resource_hints : Mapping [ str , Any ] -``` - -Resource hints for launchers, schedulers, or hosted backends. - - -### `ModelAdapter` - -```text -class ModelAdapter ( * args , ** kwargs ) -``` - -Bases: `Protocol` - -Model-specific boundary that declares defaults and creates runtimes. - -Adapters declare model-facing input requirements, the canonical modalities -their default mapping consumes, and an optional default mapping between the -two. Runtime, application, or benchmark code may override that mapping while -preserving the same `CanonicalInputs` to `InferenceInput` boundary. - - -#### `model_id` - -```text -property model_id : str -``` - -Stable identity for the model adapter or runtime integration. - -#### `inference_input_schema` - -```text -property inference_input_schema : InferenceInputSchema -``` - -Model-facing global conditioning and per-step input requirements. - -#### `canonical_input_schema` - -```text -property canonical_input_schema : CanonicalInputSchema | None -``` - -Canonical modalities the adapter’s default mapping consumes. - -#### `default_input_mapping` - -```text -default_input_mapping ( ) → InputMapping | None -``` - -Return the model-provided default canonical-to-model mapping. - -#### `validate_config` - -```text -validate_config ( config : InferenceConfig ) → None -``` - -Fail early for unsupported runtime settings. - -#### `create_runtime` - -```text -create_runtime ( config : InferenceConfig ) → InferenceRuntime -``` - -Initialize and return the heavyweight runtime. - - -### `InferenceRuntime` - -```text -class InferenceRuntime ( * args , ** kwargs ) -``` - -Bases: `Protocol` - -Heavyweight reusable runtime created from `InferenceConfig`. - - -#### `start_session` - -```text -start_session ( inputs : InferenceInput ) → InferenceSession -``` - -Create an isolated session from global conditioning inputs. - -#### `close` - -```text -close ( ) → None -``` - -Release model/backend resources. - - -### `InferenceSession` - -```text -class InferenceSession ( * args , ** kwargs ) -``` - -Bases: `Protocol` - -One rollout or stream with isolated model/cache state. - - -#### `next_step_request` - -```text -next_step_request ( ) → StepRequest | None -``` - -Return the next step’s runtime request, or `None` when complete. - -#### `step` - -```text -step ( inputs : InferenceInput ) → StepResult -``` - -Run one sequential inference step. - -#### `reset` - -```text -reset ( inputs : InferenceInput | None = None ) → None -``` - -Reset this session’s rollout state when the backend supports it. - -#### `close` - -```text -close ( ) → None -``` - -Release per-session resources. - -### Inputs and results - - -### `InferenceInput` - -```text -class InferenceInput ( * , global_conditioning: ~collections.abc.Mapping[str , ~typing.Any] = , step: ~collections.abc.Mapping[str , ~typing.Any] = , metadata: ~collections.abc.Mapping[str , ~typing.Any] = ) -``` - -Bases: `object` - -Encoded inputs for one `InferenceSession` call. - -Two conditioning slots: - -* `global_conditioning`: values that condition the whole rollout, such as - the conditioning frame or prompt. Session start/reset establishes this - state; a step call may carry a non-empty payload to request an update when - the model supports it. -* `step`: values needed to generate the next chunk or frame. - - -#### `for_phase` - -```text -for_phase ( phase : Literal [ 'global_conditioning' , 'step' ] ) → Mapping [ str , Any ] -``` - -Return the payload mapping for `phase`. - - -### `InferenceInputSchema` - -```text -class InferenceInputSchema ( * , global_conditioning_fields : tuple [ InputField , ... ] = () , step_fields : tuple [ InputField , ... ] = () , description : str = '' ) -``` - -Bases: `object` - -Minimal metadata for global conditioning and per-step inputs. - - -#### `global_conditioning_fields` - -```text -global_conditioning_fields : tuple [ InputField , ... ] -``` - -Model inputs carried in the global conditioning slot. - -#### `step_fields` - -```text -step_fields : tuple [ InputField , ... ] -``` - -Model inputs required for one session step. - -#### `fields_for` - -```text -fields_for ( phase : Literal [ 'global_conditioning' , 'step' ] ) → tuple [ InputField , ... ] -``` - -Return every declared field for `phase`. - -#### `required_fields` - -```text -required_fields ( phase : Literal [ 'global_conditioning' , 'step' ] | None = None ) → tuple [ tuple [ Literal [ 'global_conditioning' , 'step' ] , InputField ] , ... ] -``` - -Return required fields as `(phase, field)`, optionally filtered. - -#### `optional_fields` - -```text -optional_fields ( phase : Literal [ 'global_conditioning' , 'step' ] | None = None ) → tuple [ tuple [ Literal [ 'global_conditioning' , 'step' ] , InputField ] , ... ] -``` - -Return optional fields as `(phase, field)`, optionally filtered. - -#### `field_for` - -```text -field_for ( * , name : str , phase : Literal [ 'global_conditioning' , 'step' ] ) → InputField | None -``` - -Return one declared field, if present. - -#### `missing_global_conditioning` - -```text -missing_global_conditioning ( inputs : InferenceInput ) → tuple [ str , ... ] -``` - -Return required global conditioning fields absent from `inputs`. - -#### `missing_step` - -```text -missing_step ( inputs : InferenceInput ) → tuple [ str , ... ] -``` - -Return required per-step fields absent from `inputs`. - -#### `require_global_conditioning` - -```text -require_global_conditioning ( inputs : InferenceInput ) → None -``` - -Raise if required global conditioning fields are absent. - -#### `require_step` - -```text -require_step ( inputs : InferenceInput ) → None -``` - -Raise if required per-step fields are absent. - - -### `StepRequest` - -```text -class StepRequest ( * , step_index: int , inference_input_schema: ~flashdreams.runtime.inputs.InferenceInputSchema | None = None , user_input_window: ~flashdreams.infra.time.TimeWindow | None = None , metadata: ~collections.abc.Mapping[str , ~typing.Any] = ) -``` - -Bases: `object` - -Per-step runtime request emitted by an inference session. - -This is not a schema declaration. `user_input_window` lets a runner drain -or slice timestamped user events for the current step before invoking the -selected `InputMapping`. - - -### `StepResult` - -```text -class StepResult ( * , step_index: int , output: ~typing.Any | None = None , frame_count: int = 0 , layout: ~typing.Literal['tchw' , 'btchw' , 'bcthw' , 'bvtchw'] | None = None , output_window: ~flashdreams.infra.time.TimeWindow | None = None , metadata: ~collections.abc.Mapping[str , ~typing.Any] = , metrics: ~collections.abc.Mapping[str , float | int] = ) -``` - -Bases: `object` - -Generated output and metadata returned by one inference step. - -Video results use `from_video_chunk()`, which records a required tensor -layout and derives the frame count once. Non-video results may use the -regular constructor without a layout. - - -#### `from_video_chunk` - -```text -classmethod from_video_chunk ( * , step_index : int , video_chunk : Tensor , layout : Literal [ 'tchw' , 'btchw' , 'bcthw' , 'bvtchw' ] , output_window : TimeWindow | None = None , metadata : Mapping [ str , Any ] | None = None , metrics : Mapping [ str , float | int ] | None = None ) → StepResult -``` - -Build one layout-aware generated-video result. - -#### `video_chunk` - -```text -property video_chunk : Tensor -``` - -Return the video tensor or fail if this is not a video result. - -#### `lazy_rgb_frames` - -```text -lazy_rgb_frames ( * , batch_index : int = 0 , view_index : int = 0 , record_cuda_event : bool = True ) → list [ LazyCudaFrame ] -``` - -Expose this video result as lazy per-frame RGB handles. - -#### `video_hwc_uint8` - -```text -video_hwc_uint8 ( * , batch_index : int = 0 , view_index : int = 0 ) → Tensor -``` - -Return this video result as uint8 `[T,H,W,C]` on its device. - -### Input mapping - - -### `CanonicalInputs` - -```text -class CanonicalInputs ( * , values: ~collections.abc.Mapping[str , ~typing.Any] = , metadata: ~collections.abc.Mapping[str , ~typing.Any] = ) -``` - -Bases: `object` - -Canonicalized user input for one step, keyed by modality name. - -Values are level-triggered and normally present every step: a key held down -emits no events but still means full throttle. Global conditioning does not -appear here; it is application-owned and reaches `InferenceInput` -directly. - - -### `InputMapping` - -```text -class InputMapping ( * args , ** kwargs ) -``` - -Bases: `Protocol` - -Convert user-facing inputs into model-facing inputs. - -A mapping may be supplied by the model adapter as a default or by an -application/runtime override. Step mappings usually receive a timestamped -event window selected by the runner for the current model step or chunk. - - -#### `validate` - -```text -validate ( * , canonical_schema : CanonicalInputSchema | None = None , inference_input_schema : InferenceInputSchema | None = None ) → None -``` - -Fail early for obvious app, event-source, and model mismatches. - -#### `map_global_conditioning_inputs` - -```text -map_global_conditioning_inputs ( * , canonical_inputs : CanonicalInputs , inference_input : InferenceInput ) → InferenceInput -``` - -Build global conditioning inputs for session start or reset. - -#### `map_step_inputs` - -```text -map_step_inputs ( * , canonical_inputs : CanonicalInputs , inference_input : InferenceInput , request : StepRequest ) → InferenceInput -``` - -Build model inputs for one session step from the current input window. - - -### `InputMappingSchema` - -```text -class InputMappingSchema ( * , name: str = 'input-mapping' , consumes: tuple[~flashdreams.runtime.inputs.CanonicalModality , ...] = () , produces_global_conditioning: tuple[~flashdreams.runtime.inputs.InputField , ...] = () , produces_step: tuple[~flashdreams.runtime.inputs.InputField , ...] = () , metadata: ~collections.abc.Mapping[str , ~typing.Any] = ) -``` - -Bases: `object` - -Declarative compatibility surface for one mapping. - -`InputMapping.validate` fails a run late and opaquely: it raises, but it -cannot answer which optional model inputs a source would enable, or which -missing user capability is responsible for an unreachable model input. This -schema makes those questions answerable before runtime initialization. - - -#### `produces_for` - -```text -produces_for ( phase : Literal [ 'global_conditioning' , 'step' ] ) → tuple [ InputField , ... ] -``` - -Return the fields this mapping produces for `phase`. - -#### `can_produce` - -```text -can_produce ( phase : Literal [ 'global_conditioning' , 'step' ] , required : InputField ) → bool -``` - -Return whether this mapping can produce `required` in `phase`. diff --git a/docs/source/documentation/inferencing_api/guides/accelerated.md b/docs/source/documentation/inferencing_api/guides/accelerated.md index af2e501f1..4eae7036d 100644 --- a/docs/source/documentation/inferencing_api/guides/accelerated.md +++ b/docs/source/documentation/inferencing_api/guides/accelerated.md @@ -10,9 +10,8 @@ integrations. It currently covers tensor/linear quantization and streaming multi-head attention. These APIs are implementation tools, not a switch that makes every pipeline faster. -Start with the [latency tuning guide](latency_tuning.md). Use an accelerated -component only after profiling identifies its stage as important, and keep the -existing precision/backend path as the correctness reference. +See the [Accelerated API reference](../api_reference/accelerated.md) for generated +signatures and docstrings. ## Quantization diff --git a/docs/source/documentation/inferencing_api/guides/create_model.md b/docs/source/documentation/inferencing_api/guides/create_model.md index 59071c9d6..1ab8366b5 100644 --- a/docs/source/documentation/inferencing_api/guides/create_model.md +++ b/docs/source/documentation/inferencing_api/guides/create_model.md @@ -5,50 +5,53 @@ title: 'Create a model' -A model integration is a standalone workspace package under -`integrations_v2//`. It owns model code, checkpoint translation, -pipeline configs, and model-specific tests. It does not copy reusable demo, -UI, or transport behavior. +A model integration is a workspace package in `integrations_v2//`. +It contains model code, checkpoint conversion, pipeline configs, and tests. +Reuse existing apps for demos, UI, and serving. -## 1. Scope before implementing + -Record the facts that determine the integration: +## 1. Check the model requirements + +Check the following before writing code: - closest existing recipe or integration; - checkpoint source, format, dtype, license, and access requirements; - native spatial and temporal shape; - scheduler, denoising steps, guidance, and random-seed behavior; - streaming or one-shot execution, chunk length, window, and sink policy; -- model-specific conditioning, attention, memory, or decoder deltas; +- differences in conditioning, attention, memory use, or decoding; - upstream revision, inputs, and command used for parity. Reuse `flashdreams.core`, `flashdreams.infra`, and `flashdreams.recipes`. If the -model is a variant of an existing backbone, implement only the delta. Keep +model is a variant of an existing backbone, implement only the differences. Keep model-specific branches out of shared framework layers. -## 2. Scaffold the package + + +## 2. Create the package ```text integrations_v2// __init__.py - config.py # public pipeline literals + config.py # pipeline configs impl/ __init__.py - checkpoint.py # only when keys or envelopes need translation + checkpoint.py # checkpoint conversion, if needed ... # model-specific implementation - apps/ # added when binding a reusable demo + apps/ # model adapters for existing apps /adapter.py tests/ pyproject.toml - README.md + +docs/source/models/.md # model documentation ``` Use a distribution name such as `flashdreams-` and depend on -`flashdreams`. Add a reusable app dependency only when adding its adapter. Do -not make `flashdreams` import the integration; dependency direction is -framework to recipe to integration. +`flashdreams`. Add a reusable app dependency only when adding its adapter. The +integration can import `flashdreams`; `flashdreams` must not import the integration. -## 3. Compose a stable pipeline +## 3. Configure the pipeline Start from the closest recipe and replace only the components that differ: @@ -58,11 +61,10 @@ Start from the closest recipe and replace only the components that differ: - scheduler and exact denoising schedule; - `StreamingDecoder`; - checkpoint path and state-dict transform; -- a pipeline subclass only when cache initialization needs a genuinely - different public signature. +- a pipeline subclass only when cache initialization needs different arguments. -Export one module-level config literal per supported variant and a name-keyed -mapping: +Define a config for each supported variant and a dictionary keyed by config +name: ```python from flashdreams.infra.pipeline import StreamInferencePipelineConfig @@ -80,28 +82,11 @@ MY_MODEL_CONFIGS = { } ``` -Use `derive_config` for small variants. Keep model-defining values explicit: +Use `derive_config` for small variants. Set these values explicitly: checkpoint, scheduler, steps, chunk and cache sizes, guidance, precision, compile, CUDA graphs, and attention backend. -## 4. Prove checkpoint compatibility on CPU - -A checkpoint transform should unwrap only known envelopes, remove known -prefixes, apply ordered key renames, and leave tensor values unchanged. Before -loading a GPU model, compare the transformed checkpoint with -`network.state_dict()` using CPU or meta tensors: - -- no missing model keys; -- no unexpected checkpoint keys; -- identical shape for every matching key; -- representative real-key spot checks for every rename family. - -Prefer checkpoint metadata or safetensors headers when full weights are too -large. If switching checkpoint sources, compare the transformed tensors from -both sources before claiming equivalence. A successful import is not enough; -unexplained missing or extra keys are integration failures. - -## 5. Test the model boundary +## 5. Test the integration Put model tests in `integrations_v2//tests/`. Every pytest test must have exactly one of `ci_cpu`, `ci_gpu`, or `manual`. @@ -110,33 +95,25 @@ Keep these checks CPU-safe when possible: - package imports and metadata; - config names, component types, and derived-variant independence; -- checkpoint key and shape bijection; +- matching checkpoint keys and tensor shapes; - small-tensor component shapes and cache/reset behavior; -- adapter-free pipeline flow with a stand-in; +- pipeline execution with a fake model; - application adapter flow with the reusable app's testing helpers. Use `ci_gpu` or `manual` for real checkpoints, generation, parity, quality, and performance. Validate at least one step beyond initial cache fill for streaming models, because rolling-window bugs often appear only after the boundary. -## 6. Bind and document after the model works - -A model package can exist without a public application slug. Once its config -and model tests are stable, follow -[Integrate a model with a demo](../../demo_api/guides/integrate_model.md) to add -`integrations_v2//apps//adapter.py` and a -`flashdreams.applications_v2` entry point. - -Add or update `docs/source/models/.md` with requirements, installation, -supported application slugs, canonical commands, and validated limitations. -Keep implementation and parity details in the package README or tests rather -than duplicating the public model page. - +Command to run all tests: ```bash uv sync --package flashdreams- --extra dev --inexact uv run --no-sync pytest integrations_v2//tests -m ci_cpu -uv run --group lint pre-commit run -a ``` -Default CPU verification must not download large checkpoints or start GPU -generation. +## 6. Add documentation + +Add or update `docs/source/models/.md` with the new model. Example: [Wan 2.1](../../../models/wan21.md) + +## 7. [Optional] Add a demo adapter + +A model package can exist without a demo implementation. To add one, follow the [integrate model adapter for a demo](../../demo_api/guides/integrate_model_adapter_for_a_demo.md) guide. diff --git a/docs/source/documentation/inferencing_api/guides/latency_tuning.md b/docs/source/documentation/inferencing_api/guides/latency_tuning.md deleted file mode 100644 index 97415c50f..000000000 --- a/docs/source/documentation/inferencing_api/guides/latency_tuning.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: 'Latency tuning' ---- - - - - -Tune latency from measurements, not from model size or code shape. Interactive -latency includes model work, decode, transfer, queueing, and presentation; a -faster kernel cannot fix a backed-up presenter, and transport changes cannot -make a slow denoising loop faster. - -## 1. Establish a reproducible baseline - -Fix the model slug, checkpoint, input, seed, resolution, block count, precision, -compile flags, cache policy, and presentation mode. Record the commit, GPU, -driver, CUDA, PyTorch, cuDNN, and compiler-cache state. - -Use `--stats-path` before the application separator. It enables synchronized -pipeline profiling and writes model-step metrics: - -```bash -uv run --package flashdreams-self-forcing flashdreams-run-v2 \ - t2v-self-forcing-wan2.1-t2v-1.3b \ - --mode mp4 \ - --output-path artifacts/latency/baseline.mp4 \ - --stats-path artifacts/latency/baseline.json \ - --timeout unbound -- \ - --no-ui \ - --prompt "A fixed benchmark prompt." \ - --total-blocks 14 \ - --seed 1 -``` - -Profiling synchronizes CUDA and changes throughput. Use it to attribute stages, -not as the headline uninstrumented result. - -## 2. Separate cold and steady state - -The first steps may include checkpoint loading, text/context encoding, -`torch.compile`, kernel autotuning, graph capture, and cache fill. Report at -least: - -- application initialization or preload time; -- first visible chunk latency; -- warmup steps excluded; -- steady-state median and p90 chunk time; -- frames per chunk and effective generated FPS; -- peak and reserved GPU memory. - -Run long enough to reach a full rolling cache. Use fresh processes when -comparing compile, autotune, or persistent-cache behavior. - -## 3. Read the pipeline stages - -The base pipeline reports `encode_ms`, `diffuse_ms`, `decode_ms`, -`finalize_ms`, `total_ms_wo_finalize`, and `total_ms` when those stages are -present. It also reports allocated, reserved, and peak GPU memory. - -| Dominant observation | Investigate first | -| --- | --- | -| `encode_ms` | control preprocessing, VAE encoder, input layout/copies | -| `diffuse_ms` | denoising steps, attention/GEMM backend, compile, CUDA graphs, model cache | -| `decode_ms` | decoder choice, streaming cache, layout conversion, memory format | -| `finalize_ms` | K/V maintenance, synchronization, overlap opportunity | -| Pipeline fast but display late | device-to-host transfer, encoding, queue depth, pacing, network | -| Latency grows over time | unbounded cache/history, allocator growth, presenter backlog | - -Measure transfer and presentation separately when they are not represented by -pipeline metrics. For interactive generation, also report estimated -input-to-visible latency rather than only model FPS. - -## 4. Change one bottleneck at a time - -Prefer the smallest option that addresses the measured hot stage: - -- reduce resolution, denoising steps, or context only when the quality tradeoff - is acceptable; -- bound and preallocate K/V caches before compiler or graph work; -- compile the smallest fixed-shape compute region; -- capture CUDA graphs only with stable shapes, pointers, and stream ordering; -- compare attention backends on the target GPU instead of assuming portability; -- keep the quality decoder as the reference when evaluating a faster decoder; -- tune ordered pacing and bounded queues after generation gets faster. - -Keep optimized paths opt-in until their startup, reset, scene-switch, and -shape-change behavior is validated. Hardware-specific results belong on the -model page, not as universal defaults in this generic guide. - -## 5. Validate performance and quality together - -Use the same latent for decoder comparisons, compare cache changes before and -after the rolling-window boundary, and use controlled short schedules for -attention or compile changes. Preserve the fallback path. - -A defensible result includes the exact command and environment, warmup policy, -median and p90 totals, stage timings, memory, output artifacts, quality method, -and whether the candidate is recommended, useful only as an opt-in, rejected, -or still unvalidated. - -For repeatable command execution and reports, use the -[local benchmark guide](../../demo_api/guides/local_benchmarks.md). For low-level -attention and quantization options, continue with -[Accelerated building blocks](accelerated.md) only after profiling identifies -that stage as the bottleneck. diff --git a/docs/source/documentation/inferencing_api/guides/stream_inference_pipeline.md b/docs/source/documentation/inferencing_api/guides/stream_inference_pipeline.md index d6c6da442..7ddf9b79a 100644 --- a/docs/source/documentation/inferencing_api/guides/stream_inference_pipeline.md +++ b/docs/source/documentation/inferencing_api/guides/stream_inference_pipeline.md @@ -80,6 +80,14 @@ rejects skipped, repeated, or mismatched indices. When synchronized profiling is enabled, `finalize` returns stage timings and GPU-memory metrics; otherwise it returns `None`. +An application does not always own this loop directly. The V2V application +accepts a `VideoPostProcessorConfig`; each model's processor session owns the +pipeline cache and calls `generate`/`finalize` after converting and buffering +input chunks. FlashVSR builds its pipeline after the input shape is known, +while SwiftVR keeps a resident pipeline and creates an isolated stream cache. +See [Integrate model adapter for a demo](../../demo_api/guides/integrate_model_adapter_for_a_demo.md) +for the paired adapters. + ## Shape and state rules Outside the transformer network, video tensors are normally pre-patchify @@ -128,3 +136,10 @@ for uncontrolled streaming T2V, for per-step camera control, and [Wan 2.1](https://github.com/NVIDIA/flashdreams/blob/main/integrations_v2/wan21/config.py) for single-step bidirectional generation. + +Pipeline-backed V2V processors include +[FlashVSR](https://github.com/NVIDIA/flashdreams/blob/main/integrations_v2/flashvsr/impl/postprocess.py) +and +[SwiftVR](https://github.com/NVIDIA/flashdreams/blob/main/integrations_v2/swiftvr/impl/postprocess.py). +They keep application-level video handling independent from each model's tensor +layout, cache initialization, buffering, and flush behavior. diff --git a/docs/source/documentation/inferencing_api/index.md b/docs/source/documentation/inferencing_api/index.md index 95d065cc3..fc469f925 100644 --- a/docs/source/documentation/inferencing_api/index.md +++ b/docs/source/documentation/inferencing_api/index.md @@ -5,20 +5,13 @@ title: 'Inferencing API' -Use the Inferencing API to implement model components, pipeline configuration, -streaming generation, caching, and optimized execution behind a Demo API model -loop. - -`flashdreams.runtime` provides the experimental model and session contracts. -`flashdreams.infra`, `flashdreams.recipes`, and `flashdreams.core` provide -the lower-level pipeline and model building blocks. +Use the Inferencing API to implement model components, and pipelines to run inference on World Models. ## Guides @@ -26,7 +19,7 @@ the lower-level pipeline and model building blocks. ## API Reference diff --git a/flashdreams/tools/benchmarks/README.md b/docs/source/documentation/tools/benchmarking.md similarity index 96% rename from flashdreams/tools/benchmarks/README.md rename to docs/source/documentation/tools/benchmarking.md index 983880594..14d4b8422 100644 --- a/flashdreams/tools/benchmarks/README.md +++ b/docs/source/documentation/tools/benchmarking.md @@ -1,3 +1,7 @@ +--- +title: 'Benchmarking' +--- + -Choose a tool for packaging applications or maintaining the documentation website. +Tools for packaging applications, building documentation, running tests, and benchmarking models. diff --git a/docs/source/documentation/tools/testing.md b/docs/source/documentation/tools/testing.md new file mode 100644 index 000000000..4a4e36d6b --- /dev/null +++ b/docs/source/documentation/tools/testing.md @@ -0,0 +1,69 @@ +--- +title: 'Testing' +--- + + + + +Pytest collection is configured in the root `pyproject.toml`. There is +no `testpaths` setting, so `pytest` from the repo root walks the tree: + +- Files named `test_*.py`. Pytest's default also collects `*_test.py`; + don't use that name here. +- Classes named `Test*` (pytest default). Some modules use classes, + some only module-level functions. +- Functions and methods named `test_*` (pytest default), including + `async def test_*`. + +Same test filename in different folders is fine (several +`test_application.py` files exist). Root `pyproject.toml` already sets +`--import-mode=importlib` so pytest keeps them separate. Do not remove +that flag. + +A `test_*.py` next to the code it validates is collected with no CI +change. The root `pyproject.toml` skips `parity_check`, +`parity_check_v2`, `baseline_fastvideo`, and `baseline_lightx2v` via +`norecursedirs`. + +Every test function must be marked with exactly one **CI tier marker**. +A pytest plugin (`flashdreams._pytest_plugins.marker_enforcement`) +enforces this at collection time -- tests without a marker are +rejected, and tests with both `ci_cpu` and `ci_gpu` are rejected. + +| Marker | When to use | CI runner | +|--------|-------------|-----------| +| `@pytest.mark.ci_cpu` | Pure CPU logic, no GPU or `libGL` needed | CPU runner | +| `@pytest.mark.ci_gpu` | Needs CUDA, `libGL`, or transitive `cv2` | GPU runner (RTX Pro 6000) | +| `@pytest.mark.manual` | Heavy (OOM risk), flaky, needs credentials or large downloads | Not run in CI | + +Use a module-level `pytestmark` when every test in a file shares the +same marker: + +```python +import pytest + +pytestmark = pytest.mark.ci_cpu +``` + +Use per-function markers when tests in the same file have different +tiers: + +```python +@pytest.mark.ci_cpu +def test_basic_math(): ... + +@pytest.mark.ci_gpu +def test_cudagraph_path(): ... +``` + +Running tests locally: + +```bash +uv run pytest -m ci_cpu # CPU-safe tests only +uv run pytest -m ci_gpu # GPU tests only (needs CUDA) +uv run pytest -m "not manual" # everything that runs in CI +uv run pytest # all tests including manual +``` + +For local and Docker test-runner scripts, see +[tests/README.md](https://github.com/NVIDIA/flashdreams/blob/main/tests/README.md). diff --git a/docs/source/index.md b/docs/source/index.md index f3ae0fe85..9f39b95a8 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -9,6 +9,8 @@ hide: + +

FlashDreams

diff --git a/docs/source/models/index.md b/docs/source/models/index.md index fb4b18607..2554d7100 100644 --- a/docs/source/models/index.md +++ b/docs/source/models/index.md @@ -67,7 +67,7 @@ These models enhance an existing video stream rather than generating a scene fro Follow [Create a model](../documentation/inferencing_api/guides/create_model.md) for the model package, then -[Integrate a model with a demo](../documentation/demo_api/guides/integrate_model.md) +[Integrate model adapter for a demo](../documentation/demo_api/guides/integrate_model_adapter_for_a_demo.md) to register a runnable application. ## Related diff --git a/docs/source/models/wan21.md b/docs/source/models/wan21.md index 6e46f26ab..b9b57036b 100644 --- a/docs/source/models/wan21.md +++ b/docs/source/models/wan21.md @@ -35,10 +35,22 @@ uv run --no-sync flashdreams-run-v2 \ ``` +### Minimal T2V + +```bash +uv sync --package flashdreams-wan21 --inexact +uv run --no-sync flashdreams-run-v2 minimal-t2v-wan21 --mode native-window -- --default-prompt "A cat surfing" +``` + + +
## Demo Presets @@ -51,6 +63,14 @@ uv run --no-sync flashdreams-run-v2 \ | --- | --- | | `t2v-wan21-t2v-1.3b-480p` | Wan 2.1 T2V 1.3B at 480p with a single bidirectional block. | + + +### Minimal T2V + +| Preset | Description | +| --- | --- | +| `minimal-t2v-wan21` | [Minimal T2V app](../demos/minimal_t2v.md) with Wan 2.1 T2V 1.3B at 480p, generating one block per prompt. | +
## Developer Details diff --git a/docs/source/quickstart/index.md b/docs/source/quickstart/index.md index 44e6a28f9..ac1475e67 100644 --- a/docs/source/quickstart/index.md +++ b/docs/source/quickstart/index.md @@ -63,6 +63,19 @@ See our [How to Run a Model](../models/index.md#running-a-model) for more inform - See our [Omnidreams Overview](../models/omnidreams.md) for a brief Omnidreams world-model overview. - See our [Interactive Drive Overview](../demos/interactive_drive.md) for more information on the demo. +## Install one integration + +Install only the model package you need with `--package`. Add `--extra dev` +when working on its code: + +```bash +uv sync --package flashdreams-omnidreams --extra dev --inexact +``` + +Use the distribution name from the integration's `pyproject.toml`. +See [Models](../models/index.md) for available integrations, requirements, and +setup commands. See [Testing](../documentation/tools/testing.md) for test commands. + ## Where to next - [Model Gallery](../models/index.md): Every FlashDreams default-shipped model & how to run models! diff --git a/docs/zensical.toml b/docs/zensical.toml index 8fcb3c864..ba55481b6 100644 --- a/docs/zensical.toml +++ b/docs/zensical.toml @@ -45,9 +45,24 @@ md_in_html = {} tables = {} pymdownx.details = {} pymdownx.superfences = {} +pymdownx.snippets = { base_path = ["."], check_paths = true } pymdownx.arithmatex.generic = true [project.plugins.awesome-nav] +[project.plugins.mkdocstrings.handlers.python] +paths = ["../flashdreams"] + +[project.plugins.mkdocstrings.handlers.python.options] +docstring_style = "google" +members_order = "source" +merge_init_into_class = true +separate_signature = true +show_root_full_path = true +show_root_heading = true +show_signature_annotations = true +show_source = true +signature_crossrefs = true + [project.extra] generator = false diff --git a/flashdreams/flashdreams/accelerated/__init__.py b/flashdreams/flashdreams/accelerated/__init__.py new file mode 100644 index 000000000..52a7a9daf --- /dev/null +++ b/flashdreams/flashdreams/accelerated/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/flashdreams/flashdreams/accelerated/quantization/__init__.py b/flashdreams/flashdreams/accelerated/quantization/__init__.py new file mode 100644 index 000000000..52a7a9daf --- /dev/null +++ b/flashdreams/flashdreams/accelerated/quantization/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/flashdreams/flashdreams/infra/__init__.py b/flashdreams/flashdreams/infra/__init__.py new file mode 100644 index 000000000..9ef615b94 --- /dev/null +++ b/flashdreams/flashdreams/infra/__init__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Shared infrastructure for model integrations.""" diff --git a/flashdreams/flashdreams/infra/diffusion/__init__.py b/flashdreams/flashdreams/infra/diffusion/__init__.py new file mode 100644 index 000000000..592a63868 --- /dev/null +++ b/flashdreams/flashdreams/infra/diffusion/__init__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Diffusion model, transformer, and scheduler interfaces.""" diff --git a/flashdreams/flashdreams/runtime_v2/README.md b/flashdreams/flashdreams/runtime_v2/README.md index 064974c7c..6f2cd1398 100644 --- a/flashdreams/flashdreams/runtime_v2/README.md +++ b/flashdreams/flashdreams/runtime_v2/README.md @@ -218,7 +218,7 @@ then the new session receives zero remaining time and stops at its first boundar results as they are published, not the UI loop's output, so a benchmark measures what the model generated while the window still sees one composited frame per tick. See [`configs/v2_model_benchmarks.json`](../../../configs/v2_model_benchmarks.json) -and the [benchmark README](../../tools/benchmarks/README.md) for the suite. +and the [benchmarking guide](https://nvidia.github.io/flashdreams/main/documentation/tools/benchmarking/) for the suite. The runtime augments those model records with step wall time and presentation-queue depth/publish-wait measurements under the reserved `runtime_` metric prefix. UI and window timings are not folded into a later diff --git a/integrations_v2/README.md b/integrations_v2/README.md index 33a715db2..21ea59067 100644 --- a/integrations_v2/README.md +++ b/integrations_v2/README.md @@ -6,10 +6,10 @@ SPDX-License-Identifier: Apache-2.0 Applications built on the v2 API. Each directory is a standalone package that depends on `flashdreams` and holds no framework code of its own. -This is the guide to writing one. If the model generates video from a prompt, -read [`apps/t2v`](../apps/t2v/README.md) -instead, that path is a subclass and a `pyproject.toml`, and most of what -follows is already done for you. +For new work, follow the website's +[Create a model](../docs/source/documentation/inferencing_api/guides/create_model.md) +and [Integrate model adapter for a demo](../docs/source/documentation/demo_api/guides/integrate_model_adapter_for_a_demo.md) +guides. They are the canonical authoring instructions. ## What is here @@ -42,7 +42,6 @@ follows is already done for you. ```text integrations_v2// pyproject.toml - README.md __init__.py config.py # collection of pipeline definitions for a particular `` impl/ # implementation details of a model @@ -51,7 +50,8 @@ integrations_v2// / __init__.py adapter.py # all entry point definitions for `` (ex: `create_app`) - README.md # launch instructions only + +docs/source/models/.md # canonical website article ``` The workspace glob in the root `pyproject.toml` picks up any directory here @@ -278,5 +278,6 @@ halves for text-to-video models. in detail. - [Runtime](../flashdreams/flashdreams/runtime_v2/README.md) - the buffering between those threads, and the command line that starts them. -- [`apps/t2v`](../apps/t2v/README.md) - adding a - text-to-video model. +- [Create a model](../docs/source/documentation/inferencing_api/guides/create_model.md) + and [Integrate model adapter for a demo](../docs/source/documentation/demo_api/guides/integrate_model_adapter_for_a_demo.md) + cover new model and text-to-video integrations. diff --git a/integrations_v2/wan21/apps/minimal_t2v/__init__.py b/integrations_v2/wan21/apps/minimal_t2v/__init__.py new file mode 100644 index 000000000..52a7a9daf --- /dev/null +++ b/integrations_v2/wan21/apps/minimal_t2v/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 diff --git a/integrations_v2/wan21/apps/minimal_t2v/adapter.py b/integrations_v2/wan21/apps/minimal_t2v/adapter.py new file mode 100644 index 000000000..236307d0d --- /dev/null +++ b/integrations_v2/wan21/apps/minimal_t2v/adapter.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Wan 2.1 adapter for the Minimal T2V application.""" + +from minimal_t2v import MinimalT2VApplication + +from flashdreams.runtime_v2.session_desc import SessionDesc +from wan21.config import PIPELINE_WAN21_T2V_1PT3B_480P + + +def create_app() -> MinimalT2VApplication: + """Return Minimal T2V with Wan 2.1's single-block pipeline.""" + return MinimalT2VApplication( + pipeline_config=PIPELINE_WAN21_T2V_1PT3B_480P, + desc=SessionDesc( + video_width=832, + video_height=480, + frames_per_second_for_step=16, + ), + total_blocks=1, + ) diff --git a/integrations_v2/wan21/pyproject.toml b/integrations_v2/wan21/pyproject.toml index 2881c915f..d5977188c 100644 --- a/integrations_v2/wan21/pyproject.toml +++ b/integrations_v2/wan21/pyproject.toml @@ -16,10 +16,12 @@ dependencies = [ "mediapy>=1.1", "opencv-python-headless>=4.5", "flashdreams-t2v", + "flashdreams-minimal-t2v", ] [tool.uv.sources] flashdreams-t2v = { workspace = true } +flashdreams-minimal-t2v = { workspace = true } flashdreams = { workspace = true } [project.optional-dependencies] @@ -29,9 +31,10 @@ dev = [ [project.entry-points."flashdreams.applications_v2"] "t2v-wan21-t2v-1.3b-480p" = "wan21.apps.t2v.adapter:create_app" +"minimal-t2v-wan21" = "wan21.apps.minimal_t2v.adapter:create_app" [tool.setuptools] -packages = ["wan21", "wan21.apps", "wan21.apps.t2v", "wan21.impl"] +packages = ["wan21", "wan21.apps", "wan21.apps.t2v", "wan21.apps.minimal_t2v", "wan21.impl"] [tool.setuptools.package-dir] wan21 = "." diff --git a/integrations_v2/wan21/tests/test_minimal_t2v.py b/integrations_v2/wan21/tests/test_minimal_t2v.py new file mode 100644 index 000000000..6a79ca819 --- /dev/null +++ b/integrations_v2/wan21/tests/test_minimal_t2v.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""CPU check for the separately packaged Minimal T2V adapter.""" + +from importlib.metadata import EntryPoint +from pathlib import Path +from unittest.mock import Mock + +import pytest +import tomli +import torch +from minimal_t2v import MinimalT2VApplication +from wan21.config import PIPELINE_WAN21_T2V_1PT3B_480P + +from flashdreams.runtime_v2.user_input_events import UserInputEvents + +pytestmark = pytest.mark.ci_cpu + + +def test_registered_minimal_t2v_adapter() -> None: + metadata = tomli.loads((Path(__file__).parents[1] / "pyproject.toml").read_text()) + assert "wan21.apps.minimal_t2v" in metadata["tool"]["setuptools"]["packages"] + target = metadata["project"]["entry-points"]["flashdreams.applications_v2"][ + "minimal-t2v-wan21" + ] + assert target == "wan21.apps.minimal_t2v.adapter:create_app" + app = EntryPoint( + name="minimal-t2v-wan21", value=target, group="flashdreams.applications_v2" + ).load()() + assert isinstance(app, MinimalT2VApplication) + assert app.pipeline is None + assert app.pipeline_config is PIPELINE_WAN21_T2V_1PT3B_480P + assert app.total_blocks == 1 + desc = app.session_desc() + assert (desc.video_width, desc.video_height) == (832, 480) + assert desc.frames_per_second_for_step == 16 + + pipeline = Mock() + pipeline.to.return_value = pipeline + pipeline.eval.return_value = pipeline + pipeline.decoder.spatial_compression_ratio = 8 + pipeline.generate.return_value = torch.zeros(1, 3, 480, 832) + pipeline.finalize.return_value = {} + app.pipeline_config = Mock() + app.pipeline_config.setup.return_value = pipeline + app.init(["--default-prompt", "A cat surfing", "--device", "cpu"]) + session = app.create_session(desc) + session.init() + result = session.model_loop.step(0, UserInputEvents([]))[0] + pipeline.initialize_cache.assert_called_once_with( + text=["A cat surfing"], height=60, width=104 + ) + assert result.read_output().shape == (1, 3, 480, 832) + assert session.model_loop.step(1, UserInputEvents([])) == [] + session.model_loop.close() + app.close() diff --git a/pyproject.toml b/pyproject.toml index fea735b03..c6802a8ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ extraPaths = [ "apps", "apps/cam2v", "apps/t2v", + "apps/minimal_t2v", "apps/crazy_robotaxi", "apps/interactive_drive", "apps/omnidreams_game_engine", @@ -98,6 +99,7 @@ extra-paths = [ "apps", "apps/cam2v", "apps/t2v", + "apps/minimal_t2v", "apps/action2v", "apps/crazy_robotaxi", "apps/interactive_drive", @@ -195,6 +197,7 @@ lint = [ # Documentation site generator. docs = [ + "mkdocstrings-python>=2.0.9", "zensical==0.0.67", ] dev = [ diff --git a/tests/README.md b/tests/README.md index d762841c4..bcc20d235 100644 --- a/tests/README.md +++ b/tests/README.md @@ -25,9 +25,9 @@ Both scripts resolve paths relative to their own location and can be invoked fro ## What gets run -When no `TEST_TARGET` is given, each script runs `pytest` from the repo -root. See `CONTRIBUTING.md` Testing for how files and functions are -named. +When no `TEST_TARGET` is given, each script runs `pytest` from the repo root. +See [Testing](https://nvidia.github.io/flashdreams/main/documentation/tools/testing/) for discovery +and CI tier markers. Pytest is invoked with `-m "not manual"` so any test marked `@pytest.mark.manual` is skipped. diff --git a/tests/test_docs_links.py b/tests/test_docs_links.py index 21c946487..2a155215f 100644 --- a/tests/test_docs_links.py +++ b/tests/test_docs_links.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""Check local media and script paths in the documentation sources.""" +"""Check local links and media paths in the documentation sources.""" import re from pathlib import Path @@ -12,6 +12,11 @@ pytestmark = pytest.mark.ci_cpu _DOCS_SOURCE = Path(__file__).parents[1] / "docs" / "source" +_REPO_ROOT = _DOCS_SOURCE.parents[1] +_MARKDOWN_LINK_RE = re.compile(r"\[[^]]*\]\(([^)#]+)(?:#([a-z0-9-]+))?\)") +_CUSTOM_ANCHOR_RE = re.compile(r']*\btype="video/' @@ -48,3 +53,115 @@ def test_documentation_motion_media_uses_avif() -> None: legacy.append(f"{page.relative_to(_DOCS_SOURCE)}:{line}: {match.group(0)}") assert not legacy, "Documentation motion media must use AVIF:\n" + "\n".join(legacy) + + +def _anchors(path: Path) -> set[str]: + source = path.read_text(encoding="utf-8") + anchors = set(_CUSTOM_ANCHOR_RE.findall(source)) + for heading in _HEADING_RE.findall(source): + slug = re.sub(r"[^\w -]", "", heading.lower()).strip().replace(" ", "-") + anchors.add(slug) + return anchors + + +def _assert_local_section_link(source: Path, target: str, fragment: str) -> None: + page = (source.parent / target).resolve() + assert page.is_relative_to(_DOCS_SOURCE), ( + f"{source.relative_to(_REPO_ROOT)}: canonical link leaves docs/source: " + f"{target}#{fragment}" + ) + assert page.is_file(), ( + f"{source.relative_to(_REPO_ROOT)}: missing canonical page: {target}" + ) + assert not fragment or fragment in _anchors(page), ( + f"{source.relative_to(_REPO_ROOT)}: missing section #{fragment} in " + f"{page.relative_to(_REPO_ROOT)}" + ) + + +def test_contributing_stays_on_github() -> None: + source = (_REPO_ROOT / "CONTRIBUTING.md").read_text(encoding="utf-8") + headings = set(_HEADING_RE.findall(source)) + assert { + "Ways to contribute", + "Submitting a pull request", + "Adding documentation", + } <= headings + assert not (_DOCS_SOURCE / "community/contributing.md").exists() + assert _CONTRIBUTING_URL in (_DOCS_SOURCE / "community/index.md").read_text() + + +def test_repository_links_resolve_to_canonical_sections() -> None: + sources = [_REPO_ROOT / "README.md", _REPO_ROOT / "tests/README.md"] + sources.extend(sorted((_REPO_ROOT / "skills").glob("*/SKILL.md"))) + + found = 0 + for source in sources: + for target, fragment in _MARKDOWN_LINK_RE.findall( + source.read_text(encoding="utf-8") + ): + if target == _CONTRIBUTING_URL: + assert not fragment or fragment in _anchors( + _REPO_ROOT / "CONTRIBUTING.md" + ) + found += 1 + continue + website = "https://nvidia.github.io/flashdreams/main/" + if target.startswith(website): + route = target.removeprefix(website).rstrip("/") + page = _DOCS_SOURCE / route + assert page.suffix != ".md", ( + f"Website link must use a rendered route: {target}" + ) + if page.suffix == ".html": + page = page.with_suffix(".md") + elif page.is_dir(): + page = page / "index.md" + elif not page.suffix: + page = page.with_suffix(".md") + _assert_local_section_link(source, str(page), fragment) + found += 1 + continue + if urlsplit(target).scheme: + continue + page = (source.parent / target).resolve() + if source.name == "README.md": + assert page.exists(), f"Missing README link: {target}" + if page.is_relative_to(_DOCS_SOURCE): + _assert_local_section_link(source, target, fragment) + found += 1 + + assert found, "expected canonical links from repository guidance to docs/source" + + +def test_readme_reference_links_have_definitions() -> None: + source = (_REPO_ROOT / "README.md").read_text(encoding="utf-8") + references = set(re.findall(r"\[[^]]+\]\[([^]]+)\]", source)) + definitions = set(re.findall(r"^\[([^]]+)\]:", source, re.MULTILINE)) + assert references <= definitions, ( + f"Undefined README links: {references - definitions}" + ) + + +def test_demo_guide_navigation_matches_pages_and_cards() -> None: + navigation = (_DOCS_SOURCE / ".nav.yml").read_text(encoding="utf-8") + overview = (_DOCS_SOURCE / "documentation/demo_api/index.md").read_text( + encoding="utf-8" + ) + entries = re.findall( + r"^\s+- ([^:]+): (documentation/demo_api/guides/[^\s]+\.md)$", + navigation, + re.MULTILINE, + ) + assert entries, "expected Demo API guides in the sidebar" + for label, target in entries: + page = _DOCS_SOURCE / target + assert page.is_file(), f"Sidebar links to missing guide: {target}" + title = re.search(r"^title: ['\"](.+?)['\"]$", page.read_text(), re.MULTILINE) + assert title and title[1] == label, f"Sidebar title differs from {target}" + route = f"guides/{page.stem}/" + assert f'href="{route}"' in overview, f"Missing guide card: {route}" + + routes = re.findall(r'href="(guides/[^\"]+/)"', overview) + expected = {f"guides/{Path(target).stem}/" for _, target in entries} + assert set(routes) == expected, "Guide cards and sidebar link to different pages" diff --git a/uv.lock b/uv.lock index ab665c037..91b21b3e0 100644 --- a/uv.lock +++ b/uv.lock @@ -31,6 +31,7 @@ members = [ "flashdreams-imgui-ui-demo", "flashdreams-interactive-drive-v2", "flashdreams-lingbot", + "flashdreams-minimal-t2v", "flashdreams-null-model", "flashdreams-omnidreams", "flashdreams-red-screen", @@ -60,7 +61,10 @@ build-constraints = [{ name = "setuptools", specifier = ">=83.0.0,<84" }] [manifest.dependency-groups] dev = [{ name = "python-dotenv", specifier = ">=1.2.2" }] -docs = [{ name = "zensical", specifier = "==0.0.67" }] +docs = [ + { name = "mkdocstrings-python", specifier = ">=2.0.9" }, + { name = "zensical", specifier = "==0.0.67" }, +] lint = [ { name = "imageio-ffmpeg", specifier = ">=0.5" }, { name = "pre-commit", specifier = ">=4.3.0" }, @@ -1303,6 +1307,17 @@ requires-dist = [ ] provides-extras = ["dev"] +[[package]] +name = "flashdreams-minimal-t2v" +version = "0.1.0" +source = { editable = "apps/minimal_t2v" } +dependencies = [ + { name = "flashdreams", extra = ["local-window"] }, +] + +[package.metadata] +requires-dist = [{ name = "flashdreams", extras = ["local-window"], editable = "flashdreams" }] + [[package]] name = "flashdreams-null-model" version = "0.1.0" @@ -1562,6 +1577,7 @@ version = "0.1.0" source = { editable = "integrations_v2/wan21" } dependencies = [ { name = "flashdreams" }, + { name = "flashdreams-minimal-t2v" }, { name = "flashdreams-t2v" }, { name = "mediapy" }, { name = "opencv-python-headless" }, @@ -1575,6 +1591,7 @@ dev = [ [package.metadata] requires-dist = [ { name = "flashdreams", editable = "flashdreams" }, + { name = "flashdreams-minimal-t2v", editable = "apps/minimal_t2v" }, { name = "flashdreams-t2v", editable = "apps/t2v" }, { name = "mediapy", specifier = ">=1.1" }, { name = "opencv-python-headless", specifier = ">=4.5" }, @@ -1772,6 +1789,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/6e/81d47999aebc1b155f81eca4477a616a70f238a2549848c38983f3c22a82/ftfy-6.3.1-py3-none-any.whl", hash = "sha256:7c70eb532015cd2f9adb53f101fb6c7945988d023a085d127d1573dc49dd0083", size = 44821, upload-time = "2024-10-26T00:50:33.425Z" }, ] +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + [[package]] name = "google-crc32c" version = "1.8.0" @@ -1797,6 +1826,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, ] +[[package]] +name = "griffelib" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'win32'", + "python_full_version < '3.11' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/27/af/018c10bc9edd42b6ef6db2e96b09542050d5253f9b195e74bc910b2d13ab/griffelib-2.3.0.tar.gz", hash = "sha256:7b0952caf5bca6afa4bb5ee8c6a2d183fe3f21b62efc5f6c7243cb2b26d2d115", size = 234534, upload-time = "2026-09-04T15:08:17.472Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/63/e876e789525063c840ccfa8857febdabd6523bcef9ce7eb979b9305ea895/griffelib-2.3.0-py3-none-any.whl", hash = "sha256:1b8f9cd525681c26b1d6d574faa1371651e8459ca51d209684f50b8096ae06e0", size = 169423, upload-time = "2026-09-04T15:08:12.956Z" }, +] + +[[package]] +name = "griffelib" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'win32'", + "python_full_version >= '3.12' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/27/b55f1a5278918be765fb2fd8b20966bc72bbdd3f789f031937cceea7834a/griffelib-2.3.2.tar.gz", hash = "sha256:df00c7a0dee3d86268d76788997a1859272cb1fb7b865658e043d2c0c3d52e60", size = 234729, upload-time = "2026-10-06T09:54:37.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/e5/0ae74c83c1cab2c14daadf28bd0143eb14bb81503834af987b66badc7b61/griffelib-2.3.2-py3-none-any.whl", hash = "sha256:8e710afededd5607f95bf3c8ccc175ee306e7084459faf27b9f9f44ef2a7a274", size = 169420, upload-time = "2026-10-06T09:54:32.038Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -2440,6 +2497,130 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/37/8c/52f0299f1675cdfa1ab39a6028a2e5adf9032ae1118c9895c84b08af162b/mediapy-1.2.6-py3-none-any.whl", hash = "sha256:0a0ea00eb0da83c3c54d588b49c49a41ba456174aa33e530ffe13e17269c9072", size = 27494, upload-time = "2026-02-03T10:29:30.245Z" }, ] +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "ghp-import" }, + { name = "jinja2" }, + { name = "markdown", version = "3.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "markdown", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-autorefs" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown", version = "3.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "markdown", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "markupsafe" }, + { name = "mkdocs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089", size = 25530, upload-time = "2026-02-10T15:23:53.817Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mergedeep" }, + { name = "platformdirs" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, +] + +[[package]] +name = "mkdocstrings" +version = "1.0.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11' and sys_platform == 'win32'", + "python_full_version < '3.11' and sys_platform != 'win32'", +] +dependencies = [ + { name = "jinja2", marker = "python_full_version < '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "markdown", version = "3.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "markupsafe", marker = "python_full_version < '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "mkdocs", marker = "python_full_version < '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "mkdocs-autorefs", marker = "python_full_version < '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "pymdown-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/71/f85bdf13355073ae15a7375f09879375a830553552e58c1c4b7e0bbc5c8b/mkdocstrings-1.0.6.tar.gz", hash = "sha256:a0b8c2bdd29a6416c80d717aa369bbf7831946bd9f23c2a66db1b1dbe7693dbd", size = 100649, upload-time = "2026-07-11T19:38:05.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/5b/4c1902e8bdd5c4db63284e9d101dece4038d4025d6d88850ffe0a1578980/mkdocstrings-1.0.6-py3-none-any.whl", hash = "sha256:2703708697487d1b6d6d7b412e176fa436edf120c1bf81dc9e126b12d00893c7", size = 35787, upload-time = "2026-07-11T19:38:04.417Z" }, +] + +[[package]] +name = "mkdocstrings" +version = "1.0.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and sys_platform == 'win32'", + "python_full_version >= '3.12' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'win32'", +] +dependencies = [ + { name = "jinja2", marker = "python_full_version >= '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "markdown", version = "3.11", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "markupsafe", marker = "python_full_version >= '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "mkdocs", marker = "python_full_version >= '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "mkdocs-autorefs", marker = "python_full_version >= '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "pymdown-extensions", marker = "python_full_version >= '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/e8/445b19834bf9559ebbbaa76897e965665b83d2861461eed8d8de4d122057/mkdocstrings-1.0.7.tar.gz", hash = "sha256:0951b147f352118a45a3db85f0f3b660833d34cb5120a2751315b8d08dafd159", size = 103739, upload-time = "2026-10-10T13:50:59.957Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/c3/9062531735ff1fd27f3a79c353c4cf4d822eeffd062fd90b2c553a96f4bb/mkdocstrings-1.0.7-py3-none-any.whl", hash = "sha256:9563828667eeb35256f87a6b61fe857676ac628ec1abbce9aea9839a502c9cb1", size = 41597, upload-time = "2026-10-10T13:50:58.43Z" }, +] + +[[package]] +name = "mkdocstrings-python" +version = "2.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffelib", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "griffelib", version = "2.3.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "mkdocs-autorefs" }, + { name = "mkdocstrings", version = "1.0.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "mkdocstrings", version = "1.0.7", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, + { name = "typing-extensions", marker = "python_full_version < '3.11' or (extra == 'extra-11-flashdreams-dev' and extra == 'group-11-flashdreams-cuda12') or (extra == 'group-11-flashdreams-cuda12' and extra == 'group-11-flashdreams-cuda13')" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/11/640067d1ad713ea99c233e53e57a4c300d9885f904a61371d7ef90dea549/mkdocstrings_python-2.0.9.tar.gz", hash = "sha256:ae945637dc0618c6beedbee169f10c0234b77f322c53db3fc9fb4c29b3013038", size = 203006, upload-time = "2026-09-22T13:43:27.712Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/0f/f7f926cfa06317bbdbd3cf0ff82f9e3a91767352eb01519c81499fddf575/mkdocstrings_python-2.0.9-py3-none-any.whl", hash = "sha256:c0233eff3f84d78110df50541918e7ec2bcff8e1614faddea34290a90321a416", size = 105698, upload-time = "2026-09-22T13:43:26.345Z" }, +] + [[package]] name = "ml-dtypes" version = "0.5.4" @@ -3921,6 +4102,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, ] +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + [[package]] name = "regendoc" version = "0.9.0" @@ -5012,6 +5205,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/02/3623e6169bed617ed1e2d372f7c69f92ec28d54c4dfc997055c8578ec148/virtualenv-21.5.1-py3-none-any.whl", hash = "sha256:55aa670b67bbfb991b03fda39bd3276d92c419d702376e98c5df1c9989a26783", size = 4558820, upload-time = "2026-06-16T16:23:56.963Z" }, ] +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + [[package]] name = "wcwidth" version = "0.8.1"