diff --git a/README.md b/README.md index 11d2a7a8..8cfc065c 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ runner slugs, multi-GPU commands, and (where available) profiling benchmarks. | [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 | +| [Helios](https://nvidia.github.io/flashdreams/main/models/helios.html) | Real-time streaming T2V (14B) | 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) diff --git a/docs/source/api/integrations.rst b/docs/source/api/integrations.rst index dce106f6..14513ae8 100644 --- a/docs/source/api/integrations.rst +++ b/docs/source/api/integrations.rst @@ -58,6 +58,7 @@ Reference integration folders - `fastvideo_causal_wan22 `_ - `flashvsr `_ - `cosmos_predict2 `_ +- `helios `_ NVIDIA OmniDreams ----------------- diff --git a/docs/source/index.rst b/docs/source/index.rst index 16530b78..e01737c2 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -232,6 +232,13 @@ invocation, the checkpoint source, and the per-implementation knobs. Autoregressive text-to-video based on Wan 2.2 from FastVideo. + .. grid-item-card:: Helios + :class-card: fd-feature + :link: models/helios + :link-type: doc + + Real-time 14B streaming text-to-video (33-frame chunks). + .. grid-item-card:: LingBot-World :class-card: fd-feature :link: models/lingbot_world diff --git a/docs/source/models/helios.rst b/docs/source/models/helios.rst new file mode 100644 index 00000000..50c1073f --- /dev/null +++ b/docs/source/models/helios.rst @@ -0,0 +1,100 @@ +.. SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +.. SPDX-License-Identifier: Apache-2.0 +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. + +Helios +=================================== + +.. raw:: html + + + +`Helios `_ is a 14B real-time streaming +text-to-video model. This integration wraps ``HeliosPyramidPipeline`` from +``diffusers`` and exposes Helios' native **33-frame chunks** through the +FlashDreams streaming ``generate()`` interface. + +Requirements +------------ + +- **Minimum VRAM**: ~80 GB (14B transformer + VAE; tested on H100 80GB). +- **PyTorch**: >= 2.9 (CUDA 13.x recommended; see :doc:`/quickstart/installation`). +- **diffusers**: ``HeliosPyramidPipeline`` requires a recent ``diffusers`` build + (install from source if the PyPI release on your platform does not yet export it): + + .. code-block:: bash + + pip install git+https://github.com/huggingface/diffusers.git + +Installation +------------ + +.. code-block:: bash + + # from the repo root + uv sync --project integrations/helios + +Running the method +------------------ + +Launch one of the registered runner slugs via ``flashdreams-run``: + +.. code-block:: bash + + export HF_TOKEN= + + uv run --project integrations/helios \ + flashdreams-run \ + helios-distilled-t2v-14b \ + --prompt "A coastal road at dusk, waves breaking on rocky cliffs, cinematic wide shot" \ + --pixel-height 384 --pixel-width 640 \ + --total-blocks 3 + +We provide the following variants: + +.. list-table:: + :header-rows: 1 + :widths: 45 55 + + * - Method + - Description + * - ``helios-distilled-t2v-14b`` + - ``BestWishYsh/Helios-Distilled`` — fastest inference (pyramid ``[2,2,2]``, no CFG). + * - ``helios-base-t2v-14b`` + - ``BestWishYsh/Helios-Base`` — highest quality (pyramid ``[20,20,20]``, CFG 5.0). + * - ``helios-distilled-t2v-14b-2gpu`` + - Distilled checkpoint with Ulysses context parallelism (``torchrun``, 2+ GPUs). + +Multi-GPU (2× H100) +------------------- + +.. code-block:: bash + + torchrun --nproc_per_node=2 --no-python \ + flashdreams-run helios-distilled-t2v-14b-2gpu \ + --total-blocks 8 + +Each ``generate()`` call produces one 33-frame chunk and yields decoded pixels +immediately, matching Helios' native streaming cadence. + +Tests +----- + +.. code-block:: bash + + uv run pytest integrations/helios/tests/test_smoke.py -v diff --git a/docs/source/models/index.rst b/docs/source/models/index.rst index fb55d0ac..475d214b 100644 --- a/docs/source/models/index.rst +++ b/docs/source/models/index.rst @@ -26,6 +26,7 @@ Models causal_wan22 cosmos_predict2 flashvsr + helios lingbot_world wan21 @@ -106,6 +107,13 @@ uses, and the settings you can tune. Autoregressive text-to-video based on Wan 2.2 from FastVideo. + .. grid-item-card:: Helios + :class-card: fd-feature + :link: /models/helios + :link-type: doc + + Real-time 14B streaming text-to-video (33-frame chunks). + .. grid-item-card:: LingBot-World :class-card: fd-feature :link: /models/lingbot_world diff --git a/integrations/helios/README.md b/integrations/helios/README.md new file mode 100644 index 00000000..826362d3 --- /dev/null +++ b/integrations/helios/README.md @@ -0,0 +1,53 @@ +# Helios × FlashDreams + +First-party FlashDreams integration for [Helios](https://github.com/PKU-YuanGroup/Helios) 14B real-time streaming video generation (closes [flashdreams#276](https://github.com/NVIDIA/flashdreams/issues/276)). + +## Runner slugs + +| Slug | Checkpoint | Notes | +|------|------------|-------| +| `helios-distilled-t2v-14b` | `BestWishYsh/Helios-Distilled` | Fastest — pyramid `[2,2,2]`, no CFG | +| `helios-base-t2v-14b` | `BestWishYsh/Helios-Base` | Highest quality — pyramid `[20,20,20]`, CFG 5.0 | +| `helios-distilled-t2v-14b-2gpu` | `BestWishYsh/Helios-Distilled` | Ulysses context parallelism (`torchrun`) | + +Helios generates in **33-frame chunks** natively. Each FlashDreams `generate()` call produces one chunk and yields decoded pixels immediately. + +## Install + +```bash +# from the repo root +export HF_TOKEN= + +# HeliosPyramidPipeline requires a recent diffusers build +pip install git+https://github.com/huggingface/diffusers.git + +uv sync --project integrations/helios +``` + +## Run + +```bash +uv run --project integrations/helios \ + flashdreams-run helios-distilled-t2v-14b \ + --prompt "A coastal road at dusk, waves breaking on rocky cliffs, cinematic wide shot" \ + --total-blocks 3 \ + --pixel-height 384 --pixel-width 640 +``` + +Multi-GPU (2× H100): + +```bash +torchrun --nproc_per_node=2 --no-python flashdreams-run helios-distilled-t2v-14b-2gpu \ + --total-blocks 8 +``` + +## Tests + +```bash +uv run pytest integrations/helios/tests/test_smoke.py -v + +# GPU benchmark (requires CUDA + model weights) +python integrations/helios/tests/benchmark/run_benchmark.py --mode all +``` + +See the [Helios model page](https://nvidia.github.io/flashdreams/main/models/helios.html) for full setup notes. diff --git a/integrations/helios/config/helios-base-t2v-14b.yaml b/integrations/helios/config/helios-base-t2v-14b.yaml new file mode 100644 index 00000000..02a2af17 --- /dev/null +++ b/integrations/helios/config/helios-base-t2v-14b.yaml @@ -0,0 +1,15 @@ +# Helios-Base 14B T2V — highest quality (pyramid [20,20,20]) +runner: helios-base-t2v-14b +pipeline: + _target_: helios.pipeline.HeliosStreamingPipeline +checkpoint: BestWishYsh/Helios-Base +generation: + total_blocks: 8 + width: 640 + height: 384 + fps: 24 + pyramid_steps: [20, 20, 20] + guidance_scale: 5.0 + amplify_first_chunk: true + compile: false + flash_attention: true diff --git a/integrations/helios/config/helios-distilled-t2v-14b-2gpu.yaml b/integrations/helios/config/helios-distilled-t2v-14b-2gpu.yaml new file mode 100644 index 00000000..35e47545 --- /dev/null +++ b/integrations/helios/config/helios-distilled-t2v-14b-2gpu.yaml @@ -0,0 +1,17 @@ +# Helios-Distilled 14B T2V — Ulysses context parallelism (torchrun --nproc_per_node=2) +runner: helios-distilled-t2v-14b-2gpu +pipeline: + _target_: helios.pipeline.HeliosStreamingPipeline +checkpoint: BestWishYsh/Helios-Distilled +generation: + total_blocks: 8 + width: 640 + height: 384 + fps: 24 + pyramid_steps: [2, 2, 2] + guidance_scale: 1.0 + amplify_first_chunk: true + compile: false + flash_attention: true + enable_parallelism: true + cp_backend: ulysses diff --git a/integrations/helios/config/helios-distilled-t2v-14b.yaml b/integrations/helios/config/helios-distilled-t2v-14b.yaml new file mode 100644 index 00000000..1be2a81e --- /dev/null +++ b/integrations/helios/config/helios-distilled-t2v-14b.yaml @@ -0,0 +1,15 @@ +# Helios-Distilled 14B T2V — fastest inference (pyramid [2,2,2]) +runner: helios-distilled-t2v-14b +pipeline: + _target_: helios.pipeline.HeliosStreamingPipeline +checkpoint: BestWishYsh/Helios-Distilled +generation: + total_blocks: 8 + width: 640 + height: 384 + fps: 24 + pyramid_steps: [2, 2, 2] + guidance_scale: 1.0 + amplify_first_chunk: true + compile: false + flash_attention: true diff --git a/integrations/helios/helios/__init__.py b/integrations/helios/helios/__init__.py new file mode 100644 index 00000000..f4012f76 --- /dev/null +++ b/integrations/helios/helios/__init__.py @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""FlashDreams Helios streaming integration.""" + +from helios.pipeline import HELIOS_CHUNK_FRAMES, HeliosStreamingPipeline + +__all__ = ["HELIOS_CHUNK_FRAMES", "HeliosStreamingPipeline"] diff --git a/integrations/helios/helios/cache.py b/integrations/helios/helios/cache.py new file mode 100644 index 00000000..eadf9070 --- /dev/null +++ b/integrations/helios/helios/cache.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Optional + +import torch +from torch import Tensor + +from flashdreams.infra.pipeline import StreamInferencePipelineCache +from helios.encoder import HeliosConditionings + + +@dataclass(kw_only=True) +class HeliosPipelineCache(StreamInferencePipelineCache): + """State shared across AR steps.""" + + cond: Optional[HeliosConditionings] = None + decoded_chunks: list[Tensor] = field(default_factory=list) + history_frames: Optional[Tensor] = None + pending_history: Optional[Tensor] = None + + transformer_cache: Any = field(default_factory=dict) + encoder_cache: Any = None + decoder_cache: Any = None diff --git a/integrations/helios/helios/compiler.py b/integrations/helios/helios/compiler.py new file mode 100644 index 00000000..8e0d3f63 --- /dev/null +++ b/integrations/helios/helios/compiler.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""torch.compile and FlashAttention helpers for Helios.""" + +from __future__ import annotations + +import torch +import torch.nn as nn + + +def enable_flash_attention() -> None: + """Enable FlashAttention via cuDNN SDPA backend.""" + torch.backends.cuda.enable_flash_sdp(True) + torch.backends.cuda.enable_mem_efficient_sdp(True) + torch.backends.cuda.enable_math_sdp(False) + print("[Helios compiler] Flash attention (cuDNN) enabled") + + +def compile_transformer(transformer: nn.Module) -> nn.Module: + """Compile the Helios DiT for repeated AR chunk calls.""" + compiled = torch.compile( + transformer, + mode="default", + fullgraph=False, + dynamic=True, + ) + print("[Helios compiler] torch.compile applied to DiT transformer (dynamic=True)") + return compiled diff --git a/integrations/helios/helios/config.py b/integrations/helios/helios/config.py new file mode 100644 index 00000000..80f6e535 --- /dev/null +++ b/integrations/helios/helios/config.py @@ -0,0 +1,145 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Pipeline and runner config literals for Helios integration.""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import cast + +from flashdreams.infra.config import derive_config +from flashdreams.infra.diffusion.model import DiffusionModelConfig +from flashdreams.infra.diffusion.scheduler.fm import FlowMatchSchedulerConfig +from flashdreams.infra.pipeline import StreamInferencePipelineConfig +from flashdreams.infra.runner import RunnerConfig +from flashdreams.recipes.template.transformer import TemplateTransformerConfig +from flashdreams.recipes.template.transformer.network import TemplateDiTConfig +from helios.pipeline import HeliosStreamingPipeline +from helios.runner import HeliosT2VRunnerConfig + +CHECKPOINT_DISTILLED = "BestWishYsh/Helios-Distilled" +CHECKPOINT_BASE = "BestWishYsh/Helios-Base" + +_PLACEHOLDER_DIFFUSION = DiffusionModelConfig( + seed=42, + transformer=TemplateTransformerConfig( + network=TemplateDiTConfig( + in_channels=16, + context_channels=16, + model_channels=128, + num_heads=2, + ), + patch_size=(1, 2, 2), + len_t=9, + window_size_t=9, + sink_size_t=0, + guidance_scale=1.0, + ), + scheduler=FlowMatchSchedulerConfig( + num_inference_steps=6, + denoising_timesteps=[1000, 800, 600, 400, 200, 0], + warp_denoising_step=True, + shift=5.0, + num_train_timesteps=1000, + ), +) + + +@dataclass(kw_only=True) +class HeliosPipelineConfig(StreamInferencePipelineConfig): + """Config for :class:`HeliosStreamingPipeline`.""" + + _target: type[HeliosStreamingPipeline] = field( + default_factory=lambda: HeliosStreamingPipeline + ) + + name: str = "helios-distilled-t2v-14b" + checkpoint: str = CHECKPOINT_DISTILLED + device: str = "cuda" + pyramid_steps: list[int] = field(default_factory=lambda: [2, 2, 2]) + guidance_scale: float = 1.0 + amplify_first_chunk: bool = True + history_len: int = 8 + compile: bool = False + warmup_discard_chunks: int = 0 + flash_attention: bool = True + enable_parallelism: bool = False + cp_backend: str = "ulysses" + group_offload: bool = False + diffusion_model: DiffusionModelConfig = field( + default_factory=lambda: _PLACEHOLDER_DIFFUSION + ) + encoder: None = None + decoder: None = None + + +PIPELINE_HELIOS_DISTILLED_T2V_14B = HeliosPipelineConfig( + name="helios-distilled-t2v-14b", + checkpoint=CHECKPOINT_DISTILLED, + pyramid_steps=[2, 2, 2], + guidance_scale=1.0, + compile=False, +) + +PIPELINE_HELIOS_BASE_T2V_14B = cast( + HeliosPipelineConfig, + derive_config( + PIPELINE_HELIOS_DISTILLED_T2V_14B, + name="helios-base-t2v-14b", + checkpoint=CHECKPOINT_BASE, + pyramid_steps=[20, 20, 20], + guidance_scale=5.0, + ), +) + +PIPELINE_HELIOS_DISTILLED_T2V_14B_2GPU = cast( + HeliosPipelineConfig, + derive_config( + PIPELINE_HELIOS_DISTILLED_T2V_14B, + name="helios-distilled-t2v-14b-2gpu", + enable_parallelism=True, + cp_backend="ulysses", + ), +) + +PIPELINE_HELIOS_DISTILLED_T2V_14B_OPTIMIZED = cast( + HeliosPipelineConfig, + derive_config( + PIPELINE_HELIOS_DISTILLED_T2V_14B, + name="helios-distilled-t2v-14b-optimized", + # Helios pyramid DiT hits Inductor/sympy errors under torch.compile; Panel C + # uses a discarded warmup chunk to prime FlashAttention/cuDNN instead. + compile=False, + warmup_discard_chunks=1, + ), +) + +RUNNER_HELIOS_DISTILLED_T2V_14B = HeliosT2VRunnerConfig( + runner_name=PIPELINE_HELIOS_DISTILLED_T2V_14B.name, + description=( + "Helios-Distilled 14B T2V streaming (33-frame chunks, pyramid [2,2,2], ~19 FPS)." + ), + pipeline=PIPELINE_HELIOS_DISTILLED_T2V_14B, +) + +RUNNER_HELIOS_BASE_T2V_14B = HeliosT2VRunnerConfig( + runner_name=PIPELINE_HELIOS_BASE_T2V_14B.name, + description="Helios-Base 14B T2V streaming (50-step pyramid, highest quality).", + pipeline=PIPELINE_HELIOS_BASE_T2V_14B, +) + +RUNNER_HELIOS_DISTILLED_T2V_14B_2GPU = HeliosT2VRunnerConfig( + runner_name=PIPELINE_HELIOS_DISTILLED_T2V_14B_2GPU.name, + description="Helios-Distilled 14B T2V with Ulysses context parallelism (2+ GPUs).", + pipeline=PIPELINE_HELIOS_DISTILLED_T2V_14B_2GPU, +) + +RUNNER_CONFIGS: dict[str, RunnerConfig] = { + cfg.runner_name: cfg + for cfg in ( + RUNNER_HELIOS_DISTILLED_T2V_14B, + RUNNER_HELIOS_BASE_T2V_14B, + RUNNER_HELIOS_DISTILLED_T2V_14B_2GPU, + ) +} diff --git a/integrations/helios/helios/encoder.py b/integrations/helios/helios/encoder.py new file mode 100644 index 00000000..c29d6a1f --- /dev/null +++ b/integrations/helios/helios/encoder.py @@ -0,0 +1,54 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING, Optional + +import torch +from PIL import Image + +if TYPE_CHECKING: + from diffusers import HeliosPyramidPipeline + + +@dataclass +class HeliosConditionings: + """Conditioning tensors computed once and reused across AR steps.""" + + prompt_embeds: torch.Tensor + negative_prompt_embeds: torch.Tensor | None + image: Optional[Image.Image] = None + + +class HeliosEncoder: + """Thin wrapper around Helios T5 text encoder.""" + + def __init__(self, pipe: HeliosPyramidPipeline) -> None: + self.pipe = pipe + + @torch.no_grad() + def encode( + self, + prompt: list[str], + negative_prompt: list[str], + device: torch.device, + *, + guidance_scale: float, + image: Optional[Image.Image] = None, + ) -> HeliosConditionings: + do_cfg = guidance_scale > 1.0 and not getattr( + self.pipe.config, "is_distilled", False + ) + pe, npe = self.pipe.encode_prompt( + prompt=prompt, + negative_prompt=negative_prompt, + do_classifier_free_guidance=do_cfg, + device=device, + ) + return HeliosConditionings( + prompt_embeds=pe, + negative_prompt_embeds=npe if do_cfg else None, + image=image, + ) diff --git a/integrations/helios/helios/helios_loader.py b/integrations/helios/helios/helios_loader.py new file mode 100644 index 00000000..8c669cd4 --- /dev/null +++ b/integrations/helios/helios/helios_loader.py @@ -0,0 +1,91 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from typing import Any, Type + +import torch + + +def get_helios_pipeline_class() -> Type[Any]: + try: + from diffusers import HeliosPyramidPipeline + + return HeliosPyramidPipeline + except ImportError as exc: + raise ImportError( + "HeliosPyramidPipeline requires diffusers from source or a recent release. " + "Install with: pip install git+https://github.com/huggingface/diffusers.git" + ) from exc + + +def get_helios_vae_class() -> Type[Any]: + try: + from diffusers import AutoencoderKLWan + + return AutoencoderKLWan + except ImportError: + from diffusers.models import AutoencoderKLWan + + return AutoencoderKLWan + + +def load_helios_pipeline( + checkpoint: str, + device: str | torch.device, + *, + dtype: torch.dtype = torch.bfloat16, + enable_parallelism: bool = False, + cp_backend: str = "ulysses", +) -> Any: + """Load Helios pyramid pipeline with float32 VAE and bfloat16 transformer.""" + pipeline_cls = get_helios_pipeline_class() + vae_cls = get_helios_vae_class() + + vae = vae_cls.from_pretrained( + checkpoint, + subfolder="vae", + torch_dtype=torch.float32, + low_cpu_mem_usage=True, + ) + pipe = pipeline_cls.from_pretrained( + checkpoint, + vae=vae, + torch_dtype=dtype, + low_cpu_mem_usage=True, + ) + dev = torch.device(device) + vae = vae.to(dev) + pipe.vae = vae + pipe.text_encoder = pipe.text_encoder.to(dev) + pipe.transformer = pipe.transformer.to(dev, dtype=dtype) + + if enable_parallelism: + _try_enable_parallelism(pipe, cp_backend) + + return pipe + + +def _try_enable_parallelism(pipe: Any, cp_backend: str) -> None: + """Enable diffusers context parallelism when the runtime supports it.""" + enable_fn = getattr(pipe, "enable_parallelism", None) + if enable_fn is not None: + enable_fn(backend=cp_backend) + print(f"[Helios loader] Context parallelism enabled ({cp_backend})") + return + + transformer = getattr(pipe, "transformer", None) + if transformer is not None: + xf_enable = getattr(transformer, "enable_parallelism", None) + if xf_enable is not None: + xf_enable(backend=cp_backend) + print( + f"[Helios loader] Transformer context parallelism enabled ({cp_backend})" + ) + return + + print( + "[Helios loader] Context parallelism requested but not supported by this " + "diffusers build; run with torchrun and upgrade diffusers if needed." + ) diff --git a/integrations/helios/helios/pipeline.py b/integrations/helios/helios/pipeline.py new file mode 100644 index 00000000..59467190 --- /dev/null +++ b/integrations/helios/helios/pipeline.py @@ -0,0 +1,356 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""FlashDreams streaming integration for Helios (HeliosPyramidPipeline).""" + +from __future__ import annotations + +import gc +import time +from typing import Any, Generator + +import torch +import torch.nn as nn +from PIL import Image +from torch import Tensor + +from flashdreams.infra.pipeline import StreamInferencePipeline +from helios.cache import HeliosPipelineCache +from helios.compiler import compile_transformer, enable_flash_attention +from helios.encoder import HeliosEncoder +from helios.helios_loader import load_helios_pipeline + +# Helios native pixel chunk size (33 = (9-1)*4+1 latent frames × temporal scale). +HELIOS_CHUNK_FRAMES = 33 +DEFAULT_NEGATIVE_PROMPT = ( + "Bright tones, overexposed, static, blurred details, subtitles, style, works, " + "paintings, images, static, overall gray, worst quality, low quality, " + "JPEG compression residue, ugly, incomplete, extra fingers, poorly drawn hands, " + "poorly drawn faces, deformed, disfigured, misshapen limbs, fused fingers, " + "still picture, messy background, three legs, many people in the background, " + "walking backwards" +) + + +class HeliosStreamingPipeline(StreamInferencePipeline): + """Expose Helios' native 33-frame chunks as a FlashDreams streaming interface.""" + + diffusion_model: Any = None + encoder: Any = None + decoder: Any = None + + def __init__(self, config: Any) -> None: + nn.Module.__init__(self) + self.config = config + self._device = torch.device(getattr(config, "device", None) or "cuda") + + checkpoint = getattr(config, "checkpoint", "BestWishYsh/Helios-Distilled") + self.checkpoint = checkpoint + self.pyramid_steps: list[int] = list( + getattr(config, "pyramid_steps", [2, 2, 2]) + ) + self.guidance_scale: float = float(getattr(config, "guidance_scale", 1.0)) + self.use_compile: bool = bool(getattr(config, "compile", False)) + self.warmup_discard_chunks: int = int( + getattr(config, "warmup_discard_chunks", 0) + ) + self.amplify_first: bool = bool(getattr(config, "amplify_first_chunk", True)) + self.use_flash_attention: bool = bool(getattr(config, "flash_attention", True)) + self.history_len: int = int(getattr(config, "history_len", 8)) + self.enable_parallelism: bool = bool( + getattr(config, "enable_parallelism", False) + ) + self.cp_backend: str = str(getattr(config, "cp_backend", "ulysses")) + self.group_offload: bool = bool(getattr(config, "group_offload", False)) + + if self.use_flash_attention: + enable_flash_attention() + + self.pipe = load_helios_pipeline( + checkpoint, + self._device, + enable_parallelism=self.enable_parallelism, + cp_backend=self.cp_backend, + ) + + if self.group_offload: + offload_fn = getattr(self.pipe, "enable_group_offload", None) + if offload_fn is not None: + offload_fn(offload_type="leaf_level") + print("[Helios pipeline] Group offloading enabled (leaf_level)") + + self._transformer_orig = self.pipe.transformer + self._transformer_compiled: nn.Module | None = None + if self.use_compile: + self._transformer_compiled = compile_transformer(self.pipe.transformer) + self.pipe.transformer = self._transformer_compiled + self._ensure_transformer_on_device() + + self.encoder_wrapper = HeliosEncoder(self.pipe) + + self._optimization_warmup_done = False + self._warmup_prompt: str | None = None + self._compile_disabled_reason: str | None = None + + def _disable_compile(self, reason: str) -> None: + """Permanently fall back to eager DiT after a compile failure.""" + if not self.use_compile: + return + self.use_compile = False + self._compile_disabled_reason = reason + self.pipe.transformer = self._transformer_orig + print(f"[Helios pipeline] torch.compile disabled: {reason}", flush=True) + + @property + def optimization_warmup_done(self) -> bool: + return self._optimization_warmup_done + + @torch.no_grad() + def warmup_optimizations( + self, + prompt: str, + width: int = 640, + height: int = 384, + *, + discard_ar_steps: int | None = None, + ) -> dict[str, float | int | bool]: + """Run discarded chunk(s) to compile kernels or prime cuDNN before timed streaming.""" + if discard_ar_steps is None: + discard_ar_steps = ( + 1 if self.use_compile else max(0, self.warmup_discard_chunks) + ) + if discard_ar_steps <= 0: + self._optimization_warmup_done = True + self._warmup_prompt = prompt + return {"seconds": 0.0, "discarded_chunks": 0, "already_warm": True} + + self._ensure_transformer_on_device() + t0 = time.perf_counter() + cache = self.initialize_cache(text=[prompt]) + for ar_step in range(discard_ar_steps): + self.generate(ar_step, cache, width=width, height=height) + self.finalize(ar_step, cache) + if torch.cuda.is_available(): + torch.cuda.synchronize() + elapsed = time.perf_counter() - t0 + self._optimization_warmup_done = True + self._warmup_prompt = prompt + del cache + gc.collect() + if torch.cuda.is_available(): + torch.cuda.empty_cache() + torch.cuda.synchronize(self.device) + label = "compile" if self.use_compile else "kernel" + print( + f"[Helios pipeline] Optimization warmup complete " + f"({discard_ar_steps} discarded chunk(s), {elapsed:.1f}s, {label})" + ) + return { + "seconds": elapsed, + "discarded_chunks": discard_ar_steps, + "already_warm": False, + "compile_active": self.use_compile, + } + + @property + def device(self) -> torch.device: + return self._device + + def _ensure_transformer_on_device(self) -> None: + """Keep DiT weights on the inference device (compile wraps the same module).""" + dev = self._device + base = self._transformer_orig + if any(p.device != dev for p in base.parameters()): + print(f"[Helios pipeline] Moving DiT weights to {dev}", flush=True) + base.to(dev) + if self.use_compile and self._transformer_compiled is not None: + self.pipe.transformer = self._transformer_compiled + else: + self.pipe.transformer = base + + @torch.no_grad() + def initialize_cache( + self, + transformer_context: dict[str, Any] | None = None, + encoder_context: dict[str, Any] | None = None, + decoder_context: dict[str, Any] | None = None, + *, + text: list[str] | None = None, + prompt: list[str] | None = None, + negative_prompt: list[str] | None = None, + image: Image.Image | None = None, + **kwargs: Any, + ) -> HeliosPipelineCache: + prompts = text or prompt + if prompts is None: + raise ValueError("initialize_cache requires text= or prompt=") + if negative_prompt is None: + negative_prompt = [DEFAULT_NEGATIVE_PROMPT] * len(prompts) + + cond = self.encoder_wrapper.encode( + prompt=prompts, + negative_prompt=negative_prompt, + device=self.device, + guidance_scale=self.guidance_scale, + image=image, + ) + return HeliosPipelineCache(cond=cond) + + def _video_conditioning(self, cache: HeliosPipelineCache) -> Tensor | None: + """Convert prior decoded frames to [B, T, C, H, W] for Helios V2V conditioning. + + Helios requires at least 33 pixel frames for ``video=`` conditioning. + """ + if not cache.decoded_chunks: + return None + # decoded_chunks entries are [T, C, H, W] in [-1, 1] + prior = torch.cat(cache.decoded_chunks, dim=0) + if prior.shape[0] < HELIOS_CHUNK_FRAMES: + return None + # Helios needs ≥33 frames; use the most recent window for continuity. + prior = prior[-HELIOS_CHUNK_FRAMES:] + prior = (prior.float() + 1.0) / 2.0 + # VideoProcessor.preprocess_video expects [B, T, C, H, W]. + video = prior.unsqueeze(0) + return video.to(device=self.device, dtype=torch.float32) + + @torch.no_grad() + def generate( + self, + autoregressive_index: int, + cache: HeliosPipelineCache, + input: Any = None, + *, + width: int = 640, + height: int = 384, + **kwargs: Any, + ) -> Tensor: + prev = cache.autoregressive_index + expected = (prev + 1) if prev is not None else 0 + assert autoregressive_index == expected, ( + f"AR step out of order: previous={prev}, expected={expected}, " + f"got={autoregressive_index}" + ) + cache.autoregressive_index = autoregressive_index + + cond = cache.cond + assert cond is not None + + pipe_kwargs: dict[str, Any] = dict( + prompt_embeds=cond.prompt_embeds, + negative_prompt_embeds=cond.negative_prompt_embeds, + num_frames=HELIOS_CHUNK_FRAMES, + height=height, + width=width, + pyramid_num_inference_steps_list=self.pyramid_steps, + guidance_scale=self.guidance_scale, + is_amplify_first_chunk=(autoregressive_index == 0 and self.amplify_first), + output_type="pt", + generator=torch.Generator(device="cuda").manual_seed( + 42 + autoregressive_index + ), + ) + + if cond.image is not None and autoregressive_index == 0: + pipe_kwargs["image"] = cond.image + + video_cond = self._video_conditioning(cache) + if video_cond is not None and autoregressive_index > 0: + pipe_kwargs["video"] = video_cond + + if self.use_compile: + self._ensure_transformer_on_device() + torch.compiler.cudagraph_mark_step_begin() + + try: + result = self.pipe(**pipe_kwargs) + except Exception as exc: + if self.use_compile: + print( + f"[Helios pipeline] chunk {autoregressive_index}: compiled forward " + f"failed ({exc!r}), retrying with uncompiled DiT", + flush=True, + ) + self.pipe.transformer = self._transformer_orig + try: + result = self.pipe(**pipe_kwargs) + except Exception as exc2: + if video_cond is not None: + print( + f"[Helios pipeline] chunk {autoregressive_index}: video conditioning " + f"failed ({exc2!r}), retrying without history", + flush=True, + ) + pipe_kwargs.pop("video", None) + result = self.pipe(**pipe_kwargs) + else: + raise + self._disable_compile(f"chunk {autoregressive_index} forward failed") + elif video_cond is not None: + print( + f"[Helios pipeline] chunk {autoregressive_index}: video conditioning " + f"failed ({exc!r}), retrying without history", + flush=True, + ) + pipe_kwargs.pop("video", None) + result = self.pipe(**pipe_kwargs) + else: + raise + frames = result.frames + if isinstance(frames, list): + frames = frames[0] + if frames.ndim == 5 and frames.shape[0] == 1: + frames = frames[0] + + normalized = self._normalize_frames_to_flashdreams(frames) + cache.decoded_chunks.append(normalized.detach().cpu()) + cache.pending_history = normalized[-self.history_len :].unsqueeze(0) + + return normalized + + @staticmethod + def _normalize_frames_to_flashdreams(frames: Tensor) -> Tensor: + """Return [T, C, H, W] float in [-1, 1] for FlashDreams runners.""" + if frames.ndim == 5: + frames = frames[0] + if frames.shape[0] <= 4 and frames.shape[1] > 4: + frames = frames.permute(1, 0, 2, 3) + if frames.max() <= 1.0 and frames.min() >= 0.0: + frames = frames * 2.0 - 1.0 + return frames.float() + + @torch.no_grad() + def finalize( + self, + autoregressive_index: int, + cache: HeliosPipelineCache, + ) -> dict[str, float] | None: + assert cache.autoregressive_index == autoregressive_index + if cache.pending_history is not None: + cache.history_frames = cache.pending_history + cache.pending_history = None + return {"history_len": float(self.history_len)} + + def stream( + self, + prompt: str, + total_blocks: int = 8, + width: int = 640, + height: int = 384, + image: Image.Image | None = None, + ) -> Generator[Tensor, None, None]: + cache = self.initialize_cache(text=[prompt], image=image) + for ar_step in range(total_blocks): + frames = self.generate(ar_step, cache, width=width, height=height) + self.finalize(ar_step, cache) + yield frames.unsqueeze(0) + + @property + def active_optimizations(self) -> dict[str, bool]: + return { + "flash_attention": self.use_flash_attention, + "compile": self.use_compile, + "kernel_warmup": self.warmup_discard_chunks > 0, + "parallelism": self.enable_parallelism, + "group_offload": self.group_offload, + } diff --git a/integrations/helios/helios/runner.py b/integrations/helios/helios/runner.py new file mode 100644 index 00000000..4ae36428 --- /dev/null +++ b/integrations/helios/helios/runner.py @@ -0,0 +1,96 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Helios T2V streaming runner.""" + +from __future__ import annotations + +import json +from dataclasses import dataclass, field +from pathlib import Path + +import mediapy as media +import torch +from einops import rearrange +from loguru import logger + +from flashdreams.infra.runner import Runner, RunnerConfig +from helios.cache import HeliosPipelineCache +from helios.pipeline import HeliosStreamingPipeline + +__all__ = ["HeliosT2VRunnerConfig", "HeliosT2VRunner"] + +DEFAULT_PROMPT = ( + "A coastal road at dusk, waves breaking on rocky cliffs, cinematic wide shot" +) + + +@dataclass(kw_only=True) +class HeliosT2VRunnerConfig(RunnerConfig): + """Runner config for Helios T2V streaming.""" + + _target: type["HeliosT2VRunner"] = field(default_factory=lambda: HeliosT2VRunner) + + prompt: str | Path = DEFAULT_PROMPT + total_blocks: int = 8 + pixel_height: int = 384 + pixel_width: int = 640 + fps: int = 24 + + +class HeliosT2VRunner(Runner[HeliosT2VRunnerConfig, HeliosStreamingPipeline]): + """Drive Helios autoregressive streaming and write outputs.""" + + config: HeliosT2VRunnerConfig + + def _resolve_prompt(self) -> str: + value = self.config.prompt + if isinstance(value, Path): + lines = [ln.strip() for ln in value.read_text().splitlines() if ln.strip()] + assert lines, f"prompt file {value} has no non-empty lines" + return lines[0] + assert value, "--prompt must be a non-empty string or a path to a .txt file" + return str(value) + + def run(self) -> None: + config = self.config + prompt = self._resolve_prompt() + cache: HeliosPipelineCache = self.pipeline.initialize_cache(text=[prompt]) + + chunks: list[torch.Tensor] = [] + stats_history: list[dict[str, float | int]] = [] + for i in range(config.total_blocks): + video_chunk = self.pipeline.generate( + i, + cache, + width=config.pixel_width, + height=config.pixel_height, + ) + stats = self.pipeline.finalize(i, cache) + if stats is not None: + stats_history.append({"autoregressive_index": i, **stats}) + chunks.append(video_chunk.cpu()) + + generated = torch.cat(chunks, dim=0) + if not self.is_rank_zero: + return + + config.output_dir.mkdir(parents=True, exist_ok=True) + video_path = config.output_dir / f"{config.runner_name}.mp4" + canvas = rearrange(generated, "t c h w -> t h w c") + arr = (canvas.float().numpy() + 1.0) / 2.0 + arr = (arr * 255).clip(0, 255).astype("uint8") + media.write_video(str(video_path), arr, fps=config.fps) + + logger.info( + f"[{config.runner_name}] wrote video {tuple(generated.shape)} " + f"-> {video_path.resolve()}" + ) + + if stats_history: + stats_path = config.output_dir / f"stats_{config.runner_name}.json" + stats_path.write_text(json.dumps(stats_history, indent=2)) + logger.info( + f"[{config.runner_name}] wrote per-AR-step stats -> " + f"{stats_path.resolve()}" + ) diff --git a/integrations/helios/pyproject.toml b/integrations/helios/pyproject.toml new file mode 100644 index 00000000..87cf3f1d --- /dev/null +++ b/integrations/helios/pyproject.toml @@ -0,0 +1,45 @@ +# 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-helios" +version = "0.1.0" +description = "Helios 14B real-time streaming FlashDreams integration." +readme = "README.md" +requires-python = ">=3.10" +dependencies = [ + "flashdreams", + "mediapy>=1.1", + "diffusers>=0.33.0", + "transformers>=4.40.0", + "accelerate>=0.30.0", + "sentencepiece", + "regex", +] + +[tool.uv.sources] +flashdreams = { workspace = true } + +[project.optional-dependencies] +dev = [ + "pytest>=8.0", + "imageio>=2.34", + "imageio-ffmpeg>=0.5", + "tomli>=2.0", +] + +[project.entry-points."flashdreams.runner_configs"] +"helios-distilled-t2v-14b" = "helios.config:RUNNER_HELIOS_DISTILLED_T2V_14B" +"helios-base-t2v-14b" = "helios.config:RUNNER_HELIOS_BASE_T2V_14B" +"helios-distilled-t2v-14b-2gpu" = "helios.config:RUNNER_HELIOS_DISTILLED_T2V_14B_2GPU" + +[tool.setuptools.packages.find] +include = ["helios*"] +exclude = ["tests"] + +[tool.uv] +managed = true diff --git a/integrations/helios/tests/benchmark/run_benchmark.py b/integrations/helios/tests/benchmark/run_benchmark.py new file mode 100644 index 00000000..bd5a895b --- /dev/null +++ b/integrations/helios/tests/benchmark/run_benchmark.py @@ -0,0 +1,146 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""DiT/chunk runtime benchmark: baseline diffusers vs FlashDreams Helios streaming.""" + +from __future__ import annotations + +import argparse +import json +import statistics +import time +from typing import Any + +import torch + +PROMPT = "A coastal road at dusk, waves breaking on rocky cliffs, cinematic wide shot" +CHUNK_FRAMES = 33 +N_WARMUP = 3 +N_MEASURE = 10 +STEP_TO_MEASURE = 6 + + +def measure(fn, n_warmup: int = N_WARMUP, n_measure: int = N_MEASURE) -> list[float]: + for _ in range(n_warmup): + fn() + torch.cuda.synchronize() + times: list[float] = [] + for _ in range(n_measure): + t0 = time.perf_counter() + fn() + torch.cuda.synchronize() + times.append((time.perf_counter() - t0) * 1000) + return times + + +def run_baseline() -> dict[str, Any]: + from diffusers import AutoencoderKLWan, HeliosPyramidPipeline + + torch.backends.cuda.enable_flash_sdp(True) + vae = AutoencoderKLWan.from_pretrained( + "BestWishYsh/Helios-Distilled", + subfolder="vae", + torch_dtype=torch.float32, + ).to("cuda") + pipe = HeliosPyramidPipeline.from_pretrained( + "BestWishYsh/Helios-Distilled", + vae=vae, + torch_dtype=torch.bfloat16, + ).to("cuda") + pe, npe = pipe.encode_prompt( + prompt=[PROMPT], + negative_prompt=["worst quality"], + device="cuda", + do_classifier_free_guidance=False, + ) + + def step() -> None: + with torch.no_grad(): + pipe( + prompt_embeds=pe, + negative_prompt_embeds=npe, + num_frames=CHUNK_FRAMES, + height=384, + width=640, + pyramid_num_inference_steps_list=[2, 2, 2], + guidance_scale=1.0, + is_amplify_first_chunk=False, + output_type="pt", + ) + + return {"mode": "baseline", "times_ms": measure(step)} + + +def run_flashdreams(mode: str) -> dict[str, Any]: + from flashdreams.infra.config import derive_config + from helios.config import PIPELINE_HELIOS_DISTILLED_T2V_14B + from helios.pipeline import HeliosStreamingPipeline + + cfg = derive_config( + PIPELINE_HELIOS_DISTILLED_T2V_14B, + device="cuda:0", + compile=(mode == "compiled"), + amplify_first_chunk=False, + ) + pipe = HeliosStreamingPipeline(cfg) + cache = pipe.initialize_cache(text=[PROMPT]) + for s in range(STEP_TO_MEASURE): + pipe.generate(s, cache, width=640, height=384) + pipe.finalize(s, cache) + + def step() -> None: + pipe.generate(STEP_TO_MEASURE, cache, width=640, height=384) + + return {"mode": mode, "times_ms": measure(step)} + + +def summarise(r: dict[str, Any], baseline_mean: float | None = None) -> dict[str, Any]: + ts = r["times_ms"] + s: dict[str, Any] = { + "mode": r["mode"], + "chunk_time_ms_mean": round(statistics.mean(ts), 1), + "chunk_time_ms_p50": round(statistics.median(ts), 1), + "chunk_time_ms_p95": round(sorted(ts)[int(len(ts) * 0.95)], 1), + "fps": round(CHUNK_FRAMES / (statistics.mean(ts) / 1000), 1), + } + if baseline_mean: + s["speedup_vs_baseline"] = round(baseline_mean / s["chunk_time_ms_mean"], 2) + return s + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument( + "--mode", + choices=["baseline", "streaming", "compiled", "all"], + default="all", + ) + parser.add_argument("--output", default="helios_benchmark_results.json") + args = parser.parse_args() + + if not torch.cuda.is_available(): + raise SystemExit("CUDA required for Helios benchmark") + + results: dict[str, Any] = {} + if args.mode in ("baseline", "all"): + r = run_baseline() + results["baseline"] = summarise(r) + print("Baseline:", results["baseline"]) + if args.mode in ("streaming", "all"): + r = run_flashdreams("streaming") + bm = results.get("baseline", {}).get("chunk_time_ms_mean") + results["streaming"] = summarise(r, bm) + print("Streaming:", results["streaming"]) + if args.mode in ("compiled", "all"): + r = run_flashdreams("compiled") + bm = results.get("baseline", {}).get("chunk_time_ms_mean") + results["compiled"] = summarise(r, bm) + print("Compiled:", results["compiled"]) + + with open(args.output, "w") as f: + json.dump(results, f, indent=2) + print(f"\nSaved to {args.output}") + + +if __name__ == "__main__": + main() diff --git a/integrations/helios/tests/parity_check/compare.py b/integrations/helios/tests/parity_check/compare.py new file mode 100644 index 00000000..3010ff55 --- /dev/null +++ b/integrations/helios/tests/parity_check/compare.py @@ -0,0 +1,90 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Frame-level quality comparison between streaming and reference videos.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import numpy as np + + +def load_video_frames(path: Path) -> np.ndarray: + try: + import mediapy as media + + return media.read_video(str(path)) + except ImportError: + import imageio.v3 as iio + + return iio.imread(str(path)) + + +def compute_psnr(a: np.ndarray, b: np.ndarray) -> float: + mse = np.mean((a.astype(np.float64) - b.astype(np.float64)) ** 2) + if mse == 0: + return float("inf") + return float(10 * np.log10(255.0**2 / mse)) + + +def compute_ssim_frame(a: np.ndarray, b: np.ndarray) -> float: + a = a.astype(np.float64) + b = b.astype(np.float64) + c1 = (0.01 * 255) ** 2 + c2 = (0.03 * 255) ** 2 + mu_a, mu_b = a.mean(), b.mean() + sigma_a, sigma_b = a.var(), b.var() + sigma_ab = ((a - mu_a) * (b - mu_b)).mean() + num = (2 * mu_a * mu_b + c1) * (2 * sigma_ab + c2) + den = (mu_a**2 + mu_b**2 + c1) * (sigma_a + sigma_b + c2) + return float(num / den) if den else 1.0 + + +def compare_videos(streaming: Path, reference: Path) -> dict: + stream_frames = load_video_frames(streaming) + ref_frames = load_video_frames(reference) + n = min(len(stream_frames), len(ref_frames)) + stream_frames = stream_frames[:n] + ref_frames = ref_frames[:n] + + if stream_frames.shape[1:3] != ref_frames.shape[1:3]: + from PIL import Image + + resized = [] + for frame in stream_frames: + img = Image.fromarray(frame).resize( + (ref_frames.shape[2], ref_frames.shape[1]), Image.Resampling.BILINEAR + ) + resized.append(np.array(img)) + stream_frames = np.stack(resized) + + psnrs = [compute_psnr(s, r) for s, r in zip(stream_frames, ref_frames, strict=True)] + ssims = [ + compute_ssim_frame(s, r) for s, r in zip(stream_frames, ref_frames, strict=True) + ] + return { + "frames_compared": n, + "psnr_mean": float(np.mean(psnrs)), + "psnr_min": float(np.min(psnrs)), + "ssim_mean": float(np.mean(ssims)), + "ssim_min": float(np.min(ssims)), + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--streaming", type=Path, required=True) + parser.add_argument("--reference", type=Path, required=True) + parser.add_argument("--output", type=Path, default=Path("parity_results.json")) + args = parser.parse_args() + + result = compare_videos(args.streaming, args.reference) + args.output.write_text(json.dumps(result, indent=2)) + print(json.dumps(result, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/integrations/helios/tests/test_smoke.py b/integrations/helios/tests/test_smoke.py new file mode 100644 index 00000000..37cd9e28 --- /dev/null +++ b/integrations/helios/tests/test_smoke.py @@ -0,0 +1,82 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Cheap import-time checks for the helios plugin.""" + +from __future__ import annotations + +import sys +from pathlib import Path +from typing import cast + +import pytest + +pytest.importorskip("tomli") +import tomli as tomllib +from helios import config as config_mod +from helios.config import RUNNER_CONFIGS + +from flashdreams.infra.runner import RunnerConfig + +pytestmark = pytest.mark.ci_cpu + +ENTRY_POINT_GROUP = "flashdreams.runner_configs" + + +def test_runners_dict_is_non_empty() -> None: + assert RUNNER_CONFIGS, "RUNNER_CONFIGS is empty" + + +def test_three_runners_registered() -> None: + assert set(RUNNER_CONFIGS) == { + "helios-distilled-t2v-14b", + "helios-base-t2v-14b", + "helios-distilled-t2v-14b-2gpu", + } + + +def test_runner_name_mirrors_pipeline_name() -> None: + drifted = { + slug: (cfg.runner_name, cfg.pipeline.name) + for slug, cfg in RUNNER_CONFIGS.items() + if cfg.runner_name != cfg.pipeline.name + } + assert not drifted, f"runner_name != pipeline.name: {drifted}" + + +def test_entry_points_match_module_literals() -> None: + pyproject = Path(__file__).resolve().parents[1] / "pyproject.toml" + with pyproject.open("rb") as fh: + meta = tomllib.load(fh) + entries = meta["project"]["entry-points"][ENTRY_POINT_GROUP] + declared_slugs = set(entries) + module_slugs = set(RUNNER_CONFIGS) + assert declared_slugs == module_slugs + + for slug, target in entries.items(): + module_name, attr = target.split(":", 1) + assert module_name == "helios.config" + cfg = cast(RunnerConfig, getattr(config_mod, attr)) + assert cfg.runner_name == slug + + +@pytest.mark.skipif( + sys.version_info < (3, 11), + reason="entry-point discovery test relies on importlib.metadata 3.10+ shape", +) +def test_entry_points_discoverable_when_installed() -> None: + from importlib.metadata import entry_points + + eps = entry_points(group=ENTRY_POINT_GROUP) + discovered = {ep.name for ep in eps if ep.value.startswith("helios.")} + if not discovered: + pytest.skip( + "plugin not installed; run `uv sync --project integrations/helios` first" + ) + assert discovered == set(RUNNER_CONFIGS) + + +def test_chunk_frames_constant() -> None: + from helios.pipeline import HELIOS_CHUNK_FRAMES + + assert HELIOS_CHUNK_FRAMES == 33 diff --git a/pyproject.toml b/pyproject.toml index d383b386..35653def 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ extraPaths = [ "integrations/fastvideo_causal_wan22", "integrations/hy_worldplay", "integrations/lingbot", + "integrations/helios", "integrations/self_forcing", "integrations/wan21", "integrations/wan22", @@ -70,6 +71,7 @@ extra-paths = [ "integrations/fastvideo_causal_wan22", "integrations/hy_worldplay", "integrations/lingbot", + "integrations/helios", "integrations/self_forcing", "integrations/wan21", "integrations/wan22", diff --git a/uv.lock b/uv.lock index 39e436ab..3716de7f 100644 --- a/uv.lock +++ b/uv.lock @@ -25,6 +25,7 @@ members = [ "flashdreams-cosmos-predict2", "flashdreams-fastvideo-causal-wan22", "flashdreams-flashvsr", + "flashdreams-helios", "flashdreams-hy-worldplay", "flashdreams-lingbot", "flashdreams-omnidreams", @@ -77,6 +78,26 @@ test = [ { name = "tomli", specifier = ">=2.0" }, ] +[[package]] +name = "accelerate" +version = "1.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyyaml" }, + { name = "safetensors" }, + { name = "torch", version = "2.11.0+cu130", source = { registry = "https://download.pytorch.org/whl/cu130" }, marker = "sys_platform == 'win32'" }, + { name = "torch", version = "2.12.0", source = { registry = "https://pypi.org/simple" }, marker = "sys_platform != 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/75/94cd5d389649578aca399e5aa822637eec18319a1dadc400ffe2f9a7493f/accelerate-1.14.0.tar.gz", hash = "sha256:41b9c4377a54e0b460a959b0defa1b736e4ca0a2373252d9a539964c2afe3c8d", size = 412167, upload-time = "2026-06-11T13:45:52.326Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/db/253133d7e7cb40d3af384bb2f5c0b4a2b7fdcffbc95c688cc67a20a3c103/accelerate-1.14.0-py3-none-any.whl", hash = "sha256:e94390c2863b873be18f623f9df48a0d8fe5eff13ea7f1a00092b0a7904888c6", size = 389246, upload-time = "2026-06-11T13:45:50.477Z" }, +] + [[package]] name = "accessible-pygments" version = "0.0.5" @@ -1294,6 +1315,44 @@ requires-dist = [ ] provides-extras = ["dev"] +[[package]] +name = "flashdreams-helios" +version = "0.1.0" +source = { editable = "integrations/helios" } +dependencies = [ + { name = "accelerate" }, + { name = "diffusers" }, + { name = "flashdreams" }, + { name = "mediapy" }, + { name = "regex" }, + { name = "sentencepiece" }, + { name = "transformers" }, +] + +[package.optional-dependencies] +dev = [ + { name = "imageio" }, + { name = "imageio-ffmpeg" }, + { name = "pytest" }, + { name = "tomli" }, +] + +[package.metadata] +requires-dist = [ + { name = "accelerate", specifier = ">=0.30.0" }, + { name = "diffusers", specifier = ">=0.33.0" }, + { name = "flashdreams", editable = "flashdreams" }, + { name = "imageio", marker = "extra == 'dev'", specifier = ">=2.34" }, + { name = "imageio-ffmpeg", marker = "extra == 'dev'", specifier = ">=0.5" }, + { name = "mediapy", specifier = ">=1.1" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0" }, + { name = "regex" }, + { name = "sentencepiece" }, + { name = "tomli", marker = "extra == 'dev'", specifier = ">=2.0" }, + { name = "transformers", specifier = ">=4.40.0" }, +] +provides-extras = ["dev"] + [[package]] name = "flashdreams-hy-worldplay" version = "0.1.0" @@ -4653,6 +4712,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] +[[package]] +name = "sentencepiece" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/15/2e7a025fc62d764b151ae6d0f2a92f8081755ebe8d4a64099accc6f77ba6/sentencepiece-0.2.1.tar.gz", hash = "sha256:8138cec27c2f2282f4a34d9a016e3374cd40e5c6e9cb335063db66a0a3b71fad", size = 3228515, upload-time = "2025-08-12T07:00:51.718Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/31/5b7cccb307b485db1a2372d6d2980b0a65d067f8be5ca943a103b4acd5b3/sentencepiece-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e10fa50bdbaa5e2445dbd387979980d391760faf0ec99a09bd7780ff37eaec44", size = 1942557, upload-time = "2025-08-12T06:59:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/1f/41/0ac923a8e685ad290c5afc8ae55c5844977b8d75076fcc04302b9a324274/sentencepiece-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f27ae6deea72efdb6f361750c92f6c21fd0ad087445082770cc34015213c526", size = 1325384, upload-time = "2025-08-12T06:59:14.334Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ef/3751555d67daf9003384978f169d31c775cb5c7baf28633caaf1eb2b2b4d/sentencepiece-0.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:60937c959e6f44159fdd9f56fbdd302501f96114a5ba436829496d5f32d8de3f", size = 1253317, upload-time = "2025-08-12T06:59:16.247Z" }, + { url = "https://files.pythonhosted.org/packages/46/a5/742c69b7bd144eb32b6e5fd50dbd8abbbc7a95fce2fe16e50156fa400e3b/sentencepiece-0.2.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8b1d91545578852f128650b8cce4ec20f93d39b378ff554ebe66290f2dabb92", size = 1316379, upload-time = "2025-08-12T06:59:17.825Z" }, + { url = "https://files.pythonhosted.org/packages/c8/89/8deeafbba2871e8fa10f20f17447786f4ac38085925335728d360eaf4cae/sentencepiece-0.2.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27e38eee653abc3d387862e67bc5c8b6f428cd604e688b85d29170b7e725c26c", size = 1387926, upload-time = "2025-08-12T06:59:19.395Z" }, + { url = "https://files.pythonhosted.org/packages/c3/ca/67fe73005f0ab617c6a970b199754e28e524b6873aa7025224fad3cda252/sentencepiece-0.2.1-cp310-cp310-win32.whl", hash = "sha256:251874d720ac7f28024a168501f3c7bb15d1802245f6e66de565f18bbb9b5eaa", size = 999550, upload-time = "2025-08-12T06:59:20.844Z" }, + { url = "https://files.pythonhosted.org/packages/6d/33/dc5b54042050d2dda4229c3ce1f862541c99966390b6aa20f54d520d2dc2/sentencepiece-0.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:e52144670738b4b477fade6c2a9b6af71a8d0094514c9853ac9f6fc1fcfabae7", size = 1054613, upload-time = "2025-08-12T06:59:22.255Z" }, + { url = "https://files.pythonhosted.org/packages/fa/19/1ea47f46ff97fe04422b78997da1a37cd632f414aae042d27a9009c5b733/sentencepiece-0.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:9076430ac25dfa7147d9d05751dbc66a04bc1aaac371c07f84952979ea59f0d0", size = 1033884, upload-time = "2025-08-12T06:59:24.194Z" }, + { url = "https://files.pythonhosted.org/packages/d8/15/46afbab00733d81788b64be430ca1b93011bb9388527958e26cc31832de5/sentencepiece-0.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6356d0986b8b8dc351b943150fcd81a1c6e6e4d439772e8584c64230e58ca987", size = 1942560, upload-time = "2025-08-12T06:59:25.82Z" }, + { url = "https://files.pythonhosted.org/packages/fa/79/7c01b8ef98a0567e9d84a4e7a910f8e7074fcbf398a5cd76f93f4b9316f9/sentencepiece-0.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8f8ba89a3acb3dc1ae90f65ec1894b0b9596fdb98ab003ff38e058f898b39bc7", size = 1325385, upload-time = "2025-08-12T06:59:27.722Z" }, + { url = "https://files.pythonhosted.org/packages/bb/88/2b41e07bd24f33dcf2f18ec3b74247aa4af3526bad8907b8727ea3caba03/sentencepiece-0.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:02593eca45440ef39247cee8c47322a34bdcc1d8ae83ad28ba5a899a2cf8d79a", size = 1253319, upload-time = "2025-08-12T06:59:29.306Z" }, + { url = "https://files.pythonhosted.org/packages/a0/54/38a1af0c6210a3c6f95aa46d23d6640636d020fba7135cd0d9a84ada05a7/sentencepiece-0.2.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a0d15781a171d188b661ae4bde1d998c303f6bd8621498c50c671bd45a4798e", size = 1316162, upload-time = "2025-08-12T06:59:30.914Z" }, + { url = "https://files.pythonhosted.org/packages/ef/66/fb191403ade791ad2c3c1e72fe8413e63781b08cfa3aa4c9dfc536d6e795/sentencepiece-0.2.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f5a3e0d9f445ed9d66c0fec47d4b23d12cfc858b407a03c194c1b26c2ac2a63", size = 1387785, upload-time = "2025-08-12T06:59:32.491Z" }, + { url = "https://files.pythonhosted.org/packages/a9/2d/3bd9b08e70067b2124518b308db6a84a4f8901cc8a4317e2e4288cdd9b4d/sentencepiece-0.2.1-cp311-cp311-win32.whl", hash = "sha256:6d297a1748d429ba8534eebe5535448d78b8acc32d00a29b49acf28102eeb094", size = 999555, upload-time = "2025-08-12T06:59:34.475Z" }, + { url = "https://files.pythonhosted.org/packages/32/b8/f709977f5fda195ae1ea24f24e7c581163b6f142b1005bc3d0bbfe4d7082/sentencepiece-0.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:82d9ead6591015f009cb1be1cb1c015d5e6f04046dbb8c9588b931e869a29728", size = 1054617, upload-time = "2025-08-12T06:59:36.461Z" }, + { url = "https://files.pythonhosted.org/packages/7a/40/a1fc23be23067da0f703709797b464e8a30a1c78cc8a687120cd58d4d509/sentencepiece-0.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:39f8651bd10974eafb9834ce30d9bcf5b73e1fc798a7f7d2528f9820ca86e119", size = 1033877, upload-time = "2025-08-12T06:59:38.391Z" }, + { url = "https://files.pythonhosted.org/packages/4a/be/32ce495aa1d0e0c323dcb1ba87096037358edee539cac5baf8755a6bd396/sentencepiece-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57cae326c8727de58c85977b175af132a7138d84c764635d7e71bbee7e774133", size = 1943152, upload-time = "2025-08-12T06:59:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/88/7e/ff23008899a58678e98c6ff592bf4d368eee5a71af96d0df6b38a039dd4f/sentencepiece-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:56dd39a3c4d6493db3cdca7e8cc68c6b633f0d4195495cbadfcf5af8a22d05a6", size = 1325651, upload-time = "2025-08-12T06:59:41.536Z" }, + { url = "https://files.pythonhosted.org/packages/19/84/42eb3ce4796777a1b5d3699dfd4dca85113e68b637f194a6c8d786f16a04/sentencepiece-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9381351182ff9888cc80e41c632e7e274b106f450de33d67a9e8f6043da6f76", size = 1253645, upload-time = "2025-08-12T06:59:42.903Z" }, + { url = "https://files.pythonhosted.org/packages/89/fa/d3d5ebcba3cb9e6d3775a096251860c41a6bc53a1b9461151df83fe93255/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99f955df238021bf11f0fc37cdb54fd5e5b5f7fd30ecc3d93fb48b6815437167", size = 1316273, upload-time = "2025-08-12T06:59:44.476Z" }, + { url = "https://files.pythonhosted.org/packages/04/88/14f2f4a2b922d8b39be45bf63d79e6cd3a9b2f248b2fcb98a69b12af12f5/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cdfecef430d985f1c2bcbfff3defd1d95dae876fbd0173376012d2d7d24044b", size = 1387881, upload-time = "2025-08-12T06:59:46.09Z" }, + { url = "https://files.pythonhosted.org/packages/fd/b8/903e5ccb77b4ef140605d5d71b4f9e0ad95d456d6184688073ed11712809/sentencepiece-0.2.1-cp312-cp312-win32.whl", hash = "sha256:a483fd29a34c3e34c39ac5556b0a90942bec253d260235729e50976f5dba1068", size = 999540, upload-time = "2025-08-12T06:59:48.023Z" }, + { url = "https://files.pythonhosted.org/packages/2d/81/92df5673c067148c2545b1bfe49adfd775bcc3a169a047f5a0e6575ddaca/sentencepiece-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4cdc7c36234fda305e85c32949c5211faaf8dd886096c7cea289ddc12a2d02de", size = 1054671, upload-time = "2025-08-12T06:59:49.895Z" }, + { url = "https://files.pythonhosted.org/packages/fe/02/c5e3bc518655d714622bec87d83db9cdba1cd0619a4a04e2109751c4f47f/sentencepiece-0.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:daeb5e9e9fcad012324807856113708614d534f596d5008638eb9b40112cd9e4", size = 1033923, upload-time = "2025-08-12T06:59:51.952Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4a/85fbe1706d4d04a7e826b53f327c4b80f849cf1c7b7c5e31a20a97d8f28b/sentencepiece-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dcd8161eee7b41aae57ded06272905dbd680a0a04b91edd0f64790c796b2f706", size = 1943150, upload-time = "2025-08-12T06:59:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/c2/83/4cfb393e287509fc2155480b9d184706ef8d9fa8cbf5505d02a5792bf220/sentencepiece-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c6c8f42949f419ff8c7e9960dbadcfbc982d7b5efc2f6748210d3dd53a7de062", size = 1325651, upload-time = "2025-08-12T06:59:55.073Z" }, + { url = "https://files.pythonhosted.org/packages/8d/de/5a007fb53b1ab0aafc69d11a5a3dd72a289d5a3e78dcf2c3a3d9b14ffe93/sentencepiece-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:097f3394e99456e9e4efba1737c3749d7e23563dd1588ce71a3d007f25475fff", size = 1253641, upload-time = "2025-08-12T06:59:56.562Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d2/f552be5928105588f4f4d66ee37dd4c61460d8097e62d0e2e0eec41bc61d/sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7b670879c370d350557edabadbad1f6561a9e6968126e6debca4029e5547820", size = 1316271, upload-time = "2025-08-12T06:59:58.109Z" }, + { url = "https://files.pythonhosted.org/packages/96/df/0cfe748ace5485be740fed9476dee7877f109da32ed0d280312c94ec259f/sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7f0fd2f2693309e6628aeeb2e2faf6edd221134dfccac3308ca0de01f8dab47", size = 1387882, upload-time = "2025-08-12T07:00:00.701Z" }, + { url = "https://files.pythonhosted.org/packages/ac/dd/f7774d42a881ced8e1739f393ab1e82ece39fc9abd4779e28050c2e975b5/sentencepiece-0.2.1-cp313-cp313-win32.whl", hash = "sha256:92b3816aa2339355fda2c8c4e021a5de92180b00aaccaf5e2808972e77a4b22f", size = 999541, upload-time = "2025-08-12T07:00:02.709Z" }, + { url = "https://files.pythonhosted.org/packages/dd/e9/932b9eae6fd7019548321eee1ab8d5e3b3d1294df9d9a0c9ac517c7b636d/sentencepiece-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:10ed3dab2044c47f7a2e7b4969b0c430420cdd45735d78c8f853191fa0e3148b", size = 1054669, upload-time = "2025-08-12T07:00:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/c9/3a/76488a00ea7d6931689cda28726a1447d66bf1a4837943489314593d5596/sentencepiece-0.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac650534e2251083c5f75dde4ff28896ce7c8904133dc8fef42780f4d5588fcd", size = 1033922, upload-time = "2025-08-12T07:00:06.496Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b6/08fe2ce819e02ccb0296f4843e3f195764ce9829cbda61b7513f29b95718/sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8dd4b477a7b069648d19363aad0cab9bad2f4e83b2d179be668efa672500dc94", size = 1946052, upload-time = "2025-08-12T07:00:08.136Z" }, + { url = "https://files.pythonhosted.org/packages/ab/d9/1ea0e740591ff4c6fc2b6eb1d7510d02f3fb885093f19b2f3abd1363b402/sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c0f672da370cc490e4c59d89e12289778310a0e71d176c541e4834759e1ae07", size = 1327408, upload-time = "2025-08-12T07:00:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/99/7e/1fb26e8a21613f6200e1ab88824d5d203714162cf2883248b517deb500b7/sentencepiece-0.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad8493bea8432dae8d6830365352350f3b4144415a1d09c4c8cb8d30cf3b6c3c", size = 1254857, upload-time = "2025-08-12T07:00:11.021Z" }, + { url = "https://files.pythonhosted.org/packages/bc/85/c72fd1f3c7a6010544d6ae07f8ddb38b5e2a7e33bd4318f87266c0bbafbf/sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b81a24733726e3678d2db63619acc5a8dccd074f7aa7a54ecd5ca33ca6d2d596", size = 1315722, upload-time = "2025-08-12T07:00:12.989Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e8/661e5bd82a8aa641fd6c1020bd0e890ef73230a2b7215ddf9c8cd8e941c2/sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a81799d0a68d618e89063fb423c3001a034c893069135ffe51fee439ae474d6", size = 1387452, upload-time = "2025-08-12T07:00:15.088Z" }, + { url = "https://files.pythonhosted.org/packages/99/5e/ae66c361023a470afcbc1fbb8da722c72ea678a2fcd9a18f1a12598c7501/sentencepiece-0.2.1-cp313-cp313t-win32.whl", hash = "sha256:89a3ea015517c42c0341d0d962f3e6aaf2cf10d71b1932d475c44ba48d00aa2b", size = 1002501, upload-time = "2025-08-12T07:00:16.966Z" }, + { url = "https://files.pythonhosted.org/packages/c1/03/d332828c4ff764e16c1b56c2c8f9a33488bbe796b53fb6b9c4205ddbf167/sentencepiece-0.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:33f068c9382dc2e7c228eedfd8163b52baa86bb92f50d0488bf2b7da7032e484", size = 1057555, upload-time = "2025-08-12T07:00:18.573Z" }, + { url = "https://files.pythonhosted.org/packages/88/14/5aee0bf0864df9bd82bd59e7711362908e4935e3f9cdc1f57246b5d5c9b9/sentencepiece-0.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:b3616ad246f360e52c85781e47682d31abfb6554c779e42b65333d4b5f44ecc0", size = 1036042, upload-time = "2025-08-12T07:00:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/24/9c/89eb8b2052f720a612478baf11c8227dcf1dc28cd4ea4c0c19506b5af2a2/sentencepiece-0.2.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5d0350b686c320068702116276cfb26c066dc7e65cfef173980b11bb4d606719", size = 1943147, upload-time = "2025-08-12T07:00:21.809Z" }, + { url = "https://files.pythonhosted.org/packages/82/0b/a1432bc87f97c2ace36386ca23e8bd3b91fb40581b5e6148d24b24186419/sentencepiece-0.2.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c7f54a31cde6fa5cb030370566f68152a742f433f8d2be458463d06c208aef33", size = 1325624, upload-time = "2025-08-12T07:00:23.289Z" }, + { url = "https://files.pythonhosted.org/packages/ea/99/bbe054ebb5a5039457c590e0a4156ed073fb0fe9ce4f7523404dd5b37463/sentencepiece-0.2.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c83b85ab2d6576607f31df77ff86f28182be4a8de6d175d2c33ca609925f5da1", size = 1253670, upload-time = "2025-08-12T07:00:24.69Z" }, + { url = "https://files.pythonhosted.org/packages/19/ad/d5c7075f701bd97971d7c2ac2904f227566f51ef0838dfbdfdccb58cd212/sentencepiece-0.2.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1855f57db07b51fb51ed6c9c452f570624d2b169b36f0f79ef71a6e6c618cd8b", size = 1316247, upload-time = "2025-08-12T07:00:26.435Z" }, + { url = "https://files.pythonhosted.org/packages/fb/03/35fbe5f3d9a7435eebd0b473e09584bd3cc354ce118b960445b060d33781/sentencepiece-0.2.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01e6912125cb45d3792f530a4d38f8e21bf884d6b4d4ade1b2de5cf7a8d2a52b", size = 1387894, upload-time = "2025-08-12T07:00:28.339Z" }, + { url = "https://files.pythonhosted.org/packages/dc/aa/956ef729aafb6c8f9c443104c9636489093bb5c61d6b90fc27aa1a865574/sentencepiece-0.2.1-cp314-cp314-win32.whl", hash = "sha256:c415c9de1447e0a74ae3fdb2e52f967cb544113a3a5ce3a194df185cbc1f962f", size = 1096698, upload-time = "2025-08-12T07:00:29.764Z" }, + { url = "https://files.pythonhosted.org/packages/b8/cb/fe400d8836952cc535c81a0ce47dc6875160e5fedb71d2d9ff0e9894c2a6/sentencepiece-0.2.1-cp314-cp314-win_amd64.whl", hash = "sha256:881b2e44b14fc19feade3cbed314be37de639fc415375cefaa5bc81a4be137fd", size = 1155115, upload-time = "2025-08-12T07:00:32.865Z" }, + { url = "https://files.pythonhosted.org/packages/32/89/047921cf70f36c7b6b6390876b2399b3633ab73b8d0cb857e5a964238941/sentencepiece-0.2.1-cp314-cp314-win_arm64.whl", hash = "sha256:2005242a16d2dc3ac5fe18aa7667549134d37854823df4c4db244752453b78a8", size = 1133890, upload-time = "2025-08-12T07:00:34.763Z" }, + { url = "https://files.pythonhosted.org/packages/a1/11/5b414b9fae6255b5fb1e22e2ed3dc3a72d3a694e5703910e640ac78346bb/sentencepiece-0.2.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a19adcec27c524cb7069a1c741060add95f942d1cbf7ad0d104dffa0a7d28a2b", size = 1946081, upload-time = "2025-08-12T07:00:36.97Z" }, + { url = "https://files.pythonhosted.org/packages/77/eb/7a5682bb25824db8545f8e5662e7f3e32d72a508fdce086029d89695106b/sentencepiece-0.2.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e37e4b4c4a11662b5db521def4e44d4d30ae69a1743241412a93ae40fdcab4bb", size = 1327406, upload-time = "2025-08-12T07:00:38.669Z" }, + { url = "https://files.pythonhosted.org/packages/03/b0/811dae8fb9f2784e138785d481469788f2e0d0c109c5737372454415f55f/sentencepiece-0.2.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:477c81505db072b3ab627e7eab972ea1025331bd3a92bacbf798df2b75ea86ec", size = 1254846, upload-time = "2025-08-12T07:00:40.611Z" }, + { url = "https://files.pythonhosted.org/packages/ef/23/195b2e7ec85ebb6a547969f60b723c7aca5a75800ece6cc3f41da872d14e/sentencepiece-0.2.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:010f025a544ef770bb395091d57cb94deb9652d8972e0d09f71d85d5a0816c8c", size = 1315721, upload-time = "2025-08-12T07:00:42.914Z" }, + { url = "https://files.pythonhosted.org/packages/7e/aa/553dbe4178b5f23eb28e59393dddd64186178b56b81d9b8d5c3ff1c28395/sentencepiece-0.2.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:733e59ff1794d26db706cd41fc2d7ca5f6c64a820709cb801dc0ea31780d64ab", size = 1387458, upload-time = "2025-08-12T07:00:44.56Z" }, + { url = "https://files.pythonhosted.org/packages/66/7c/08ff0012507297a4dd74a5420fdc0eb9e3e80f4e88cab1538d7f28db303d/sentencepiece-0.2.1-cp314-cp314t-win32.whl", hash = "sha256:d3233770f78e637dc8b1fda2cd7c3b99ec77e7505041934188a4e7fe751de3b0", size = 1099765, upload-time = "2025-08-12T07:00:46.058Z" }, + { url = "https://files.pythonhosted.org/packages/91/d5/2a69e1ce15881beb9ddfc7e3f998322f5cedcd5e4d244cb74dade9441663/sentencepiece-0.2.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e4366c97b68218fd30ea72d70c525e6e78a6c0a88650f57ac4c43c63b234a9d", size = 1157807, upload-time = "2025-08-12T07:00:47.673Z" }, + { url = "https://files.pythonhosted.org/packages/f3/16/54f611fcfc2d1c46cbe3ec4169780b2cfa7cf63708ef2b71611136db7513/sentencepiece-0.2.1-cp314-cp314t-win_arm64.whl", hash = "sha256:105e36e75cbac1292642045458e8da677b2342dcd33df503e640f0b457cb6751", size = 1136264, upload-time = "2025-08-12T07:00:49.485Z" }, +] + [[package]] name = "setuptools" version = "81.0.0"