Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
414 changes: 414 additions & 0 deletions configs/v2_model_benchmarks.json

Large diffs are not rendered by default.

59 changes: 48 additions & 11 deletions flashdreams/flashdreams/api_v2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ SPDX-License-Identifier: Apache-2.0
Protocols for the FlashDreams API.

- `application.py` / `session.py`: `IApplication` creates an `ISession` from a
`SessionDesc`, and the session reports what it resolved to.
`SessionDesc`, and the session reports what it resolved to. `session_desc`
is the description the application would choose for itself, for a caller with
none of its own.
- `input_source.py` / `output_sink.py` / `client_window.py`: `IClientWindow` is
one client's input and output together. It is given the session's `SessionDesc`
in `OutputSink.open`.
Expand All @@ -22,6 +24,47 @@ run whose output is a file goes the same way, against
input and encodes every result. Since it never reports a close, such a run needs
a session that finishes.

`flashdreams-run-v2` is that run from a shell: `flashdreams.runtime_v2.cli` finds
an application by slug, gives it the arguments after `--`, and hands it to
`ApplicationRunner` with the window `--mode` asked for, an MP4 file or a client
over WebRTC. Applications are found through the `flashdreams.applications_v2`
entry point group, or by the name of the package an integration ships when it
has registered nothing, which is
`flashdreams.runtime_v2.application_registry`'s job.

What the modes are belongs to
`flashdreams.runtime_v2.client_window_factory`, not to the command. A mode owns
the arguments only it takes, such as `--output-path` for a file or `--port` for
a browser, and what to say about where the run went: a URL to open before it
starts, or the file once there is something in it. So a new way of watching a
run is a mode added there, and the command is unchanged.

The session it asks for comes from `IApplication.session_desc`, with
`--pixel-width`, `--pixel-height`, `--fps`, and `--layout` overriding whatever
they name. That is the whole of what the command knows about the kind of
application it is running: a model answers with the clip its checkpoint was
trained for, and an application that generates whatever it is asked for answers
nothing and is described by those arguments alone.

`--stats-path` asks a run to record what it cost as well as what it generated.
`Mp4ClientWindow` takes that path and adds a `MetricsOutputSink` beside the MP4
writer, which records each step's measurements as the artifact
`flashdreams-benchmark` reads. The measurements are the model's own: a step reports what
it measured and this writes it down, converting milliseconds to seconds because
a report cannot compare two units. Nothing is measured unless a run asks, so an
ordinary run pays nothing for this.
[`configs/v2_model_benchmarks.json`](../../../configs/v2_model_benchmarks.json)
is the suite that uses it, comparing every t2v model on one prompt and seed, and
[running it](../../tools/benchmarks/README.md) is written down beside the
harness.

`flashdreams.t2v_v2` is text-to-video on top of these protocols rather than part
of them: one `T2VApplication` owns the command line every t2v model needs, an
integration supplies only its own defaults, and `testing.check_t2v_model_impl`
is the check its tests run to cover the batch path in one call. See
[its README](../t2v_v2/README.md). The five `integrations_v2/t2v_*` packages are
the models behind it, and each is a factory of about forty lines.

Ownership
---------

Expand All @@ -31,7 +74,10 @@ Agreed design decisions. Change them by discussion.
creates every other protocol here and passes it in.
- `IApplication` lasts as long as the process. It holds what its sessions share,
such as a checkpoint or a compiled pipeline, and outlives every session it
creates.
creates. It also says what session it would generate unasked, through
`session_desc`, since only it knows what its model was trained for. The
default says nothing, for an application that generates whatever it is asked
for.
- `ISession` is one run: KV cache, game state, and anything else that must not
carry into another run. It also says when that run is over, through
`is_finished`. The default never finishes.
Expand Down Expand Up @@ -112,14 +158,5 @@ Not built yet
- Input that keeps up with generation. Input is polled at the UI rate, so a run of
fast steps can finish several of them between polls and hand them all the same
batch. Pacing generation is what would fix it.
- `ApplicationRunner`: takes an `IApplication` and an `IClientWindow` and drives
the main loop. `run_session` is what exists today; it drives a session the
caller already created.
- `flashdreams-run`: a CLI that creates the requested kind of client window,
loads an application module, and hands both to `ApplicationRunner`. Until it
exists, the caller wires that up and an integration ships no entry point.
- An output path for `ISession.step_ui`, so UI work can reach the window rather
than only updating session state.
- Shared per-domain test entry points, so a model integration gets coverage from
one call — for example `test_t2v_model_impl(model_config, expected_frame_stats)`
returning a pass or fail result.
14 changes: 14 additions & 0 deletions flashdreams/flashdreams/api_v2/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@ def init(self, commandline_args: Sequence[str]) -> None:
"""Parse application arguments and validate startup state."""
...

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
:meth:`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.
"""
return None

@abstractmethod
def create_session(self, session_desc: SessionDesc) -> ISession:
"""Create one isolated, uninitialized session for ``session_desc``.
Expand Down
104 changes: 104 additions & 0 deletions flashdreams/flashdreams/runtime_v2/application_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""The applications installed here, and finding the one a runner was asked for.

The registry is the entry points an install wrote down, rather than anything
this holds.
"""

import importlib
from importlib.metadata import EntryPoint, entry_points
from typing import Any

from flashdreams.api_v2.application import IApplication

APPLICATION_ENTRY_POINT_GROUP = "flashdreams.applications_v2"
"""Entry-point group whose values expose a zero-argument ``create_app`` factory.

Separate from the v1 ``flashdreams.applications`` group, which resolves to
``IFlashDreamsApplication`` and would refuse a v2 application registered there.
"""


def registered_application_slugs() -> tuple[str, ...]:
"""Return the installed application slugs, in a stable order."""
return tuple(
sorted(
{item.name for item in entry_points(group=APPLICATION_ENTRY_POINT_GROUP)}
)
)


def create_application(slug: str) -> IApplication:
"""Return a new, uninitialized application for ``slug``.

A registered entry point is preferred. Failing that the slug is read as a
module name, so an integration that has not registered itself is still
reachable by the name of the package it ships.

Args:
slug: Registered application name, such as ``t2v-self-forcing``, or an
importable module exposing ``create_app``.

Raises:
ValueError: ``slug`` is empty.
LookupError: Nothing installed matches ``slug``.
TypeError: The factory returned something other than an
:class:`IApplication`, or the module has no ``create_app``.
"""
if not slug.strip():
raise ValueError("An application slug is required.")

for entry_point in entry_points(group=APPLICATION_ENTRY_POINT_GROUP):
if entry_point.name == slug:
return _from_entry_point(entry_point)

module = _import_application_module(slug)
factory = getattr(module, "create_app", None)
if not callable(factory):
raise TypeError(
f"Application module {module.__name__!r} does not expose create_app()."
)
return _validated(factory(), origin=module.__name__)


def _from_entry_point(entry_point: EntryPoint) -> IApplication:
"""Build the application an entry point points at."""
value = entry_point.load()
return _validated(value() if callable(value) else value, origin=entry_point.value)


def _validated(value: Any, *, origin: str) -> IApplication:
"""Return ``value`` if it is an application, and say what it was if not.

An integration still on the v1 contract lands here.
"""
if not isinstance(value, IApplication):
raise TypeError(
f"Application factory {origin!r} returned {type(value).__name__}; "
"expected an IApplication."
)
return value


def _import_application_module(slug: str) -> Any:
"""Import the module a slug names.

Raises:
LookupError: There is no such module, and no entry point matched either.
"""
module_name = slug.replace("-", "_")
try:
return importlib.import_module(module_name)
except ModuleNotFoundError as exc:
# A module that exists but imports something missing is a broken
# install rather than an unknown slug.
if exc.name != module_name:
raise

installed = ", ".join(registered_application_slugs())
raise LookupError(
f"No FlashDreams v2 application matches {slug!r}. "
f"Installed applications: {installed or '(none)'}."
)
24 changes: 23 additions & 1 deletion flashdreams/flashdreams/runtime_v2/application_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from flashdreams.runtime_v2.session_runner import run_session

_LOGGER = logging.getLogger(__name__)
"""Logger for an application that could not be closed."""
"""Logger for an application or window that could not be closed."""


class ApplicationRunner:
Expand All @@ -38,20 +38,42 @@ def run(

The application is closed before this method returns or raises.

The window is closed too when the run never starts, since ``run_session``
is what otherwise owns it, and a window may already be serving a client
before the application has loaded anything.

Args:
session_desc: Output shape and timing requested for the session.
commandline_args: Arguments owned and parsed by the application.
"""
run_started = False
try:
self._application.init(commandline_args)
session = self._application.create_session(session_desc)
run_started = True
run_session(session, self._client_window)
finally:
if not run_started:
_close_client_window(self._client_window)
_close_application(
self._application, run_failed=sys.exc_info()[0] is not None
)


def _close_client_window(client_window: IClientWindow) -> None:
"""Close a window the run never reached, so what it was serving goes with it.

The run has already failed by the time this is called, so a failure here is
logged rather than raised over the top of it.
"""
try:
client_window.close()
except Exception:
_LOGGER.exception(
"The client window failed to close after a run that never started."
)


def _close_application(application: IApplication, *, run_failed: bool) -> None:
"""Close an application, keeping its close from hiding an earlier failure.

Expand Down
Loading
Loading