FlashDreams Post-Processing
Use this skill when adding a video post-processor or changing the runner
post-processing stream. The reference implementation is
integrations_v2/flashvsr/impl/postprocess.py.
Mental Model
A post-processor is usually three classes, not one class inheriting everything:
VideoPostProcessorConfig: serializable config and CLI surface. It sets
_target to the processor factory and declares fields, output_spec(),
requires_all_ranks(), and validate_execution().
VideoPostProcessor: lightweight factory created from config. Its job is
start(spec) -> VideoPostProcessorSession.
VideoPostProcessorSession: mutable per-stream runtime. It owns buffers,
caches, lazy model instances, counters, and process() / flush().
Keep stream state in the session. Do not store per-rollout mutable state on the
config or processor factory.
Implementation Steps
Pick a home:
- Generic reusable post-processing belongs under
flashdreams/flashdreams/infra/postprocess/.
- Model-specific processors belong in their integration, for example
integrations/<name>/<pkg>/postprocess.py.
Define a config subclass:
@dataclass(kw_only=True)
class MyPostProcessorConfig(VideoPostProcessorConfig):
_target: type["MyPostProcessor"] = field(
default_factory=lambda: MyPostProcessor
)
scale: int = 2
def output_spec(self, input_spec: VideoSpec) -> VideoSpec:
return VideoSpec(
height=input_spec.height * self.scale,
width=input_spec.width * self.scale,
fps=input_spec.fps,
channels=input_spec.channels,
)
Override:
output_spec() when spatial size, channels, or timing changes.
requires_all_ranks() when the processor must run on nonzero ranks under
torchrun.
validate_execution() to reject unsupported distributed or shape modes
early.
Define the processor factory:
class MyPostProcessor(VideoPostProcessor[MyPostProcessorConfig]):
def start(self, spec: VideoSpec) -> VideoPostProcessorSession:
return _MyPostProcessorSession(self.config, spec)
Define the session:
class _MyPostProcessorSession(VideoPostProcessorSession):
def __init__(self, config: MyPostProcessorConfig, spec: VideoSpec) -> None:
self._config = config
self._spec = spec
self._buffer: Tensor | None = None
def process(self, chunk: VideoChunk) -> list[VideoChunk]:
...
def flush(self) -> list[VideoChunk]:
...
process() is synchronous but may return []: that means it consumed the
input chunk and is buffering frames until a later chunk or flush() can
complete an output window.
Handle layouts at the boundary:
- Accept
VideoChunk.tensor in chunk.layout.
- Use
to_bvtchw() only as a generic boundary helper.
- Convert once into the processor's native layout, make it contiguous if the
model kernels require that, and keep internal buffers in that native
layout.
- Document any forced
.contiguous() because it can copy.
Return VideoChunks:
- Preserve
[-1, 1] value range unless the API is intentionally changed.
- Set the correct
layout.
- Carry metadata only if it helps downstream processors or provenance.
Register presets when users should select it from CLI:
[project.entry-points."flashdreams.postprocess_presets"]
"my-postprocessor-v1" = "my_pkg.postprocess:POSTPROCESS_PRESET_MY_V1"
The exported object must be a VideoPostProcessorConfig, for example:
POSTPROCESS_PRESET_MY_V1 = MyPostProcessorConfig(...)
Users select it with --postprocess.preset my-postprocessor-v1.
Runner Interaction
Runners create a VideoPostprocessStream through
create_runner_postprocess_stream(). The stream:
- creates one chain session for whole-stream processing, or one session per
view when
postprocess_per_view=True;
- calls
session.process(VideoChunk(...)) for each AR output;
- turns
[] into a zero-frame tensor so process() remains tensor-only;
- skips collecting zero-time tensors in
_append_if_nonempty();
- calls
flush() once at end-of-stream and appends any tail output.
Use postprocess_output_layout to describe the runner's decoded output layout.
Use postprocess_per_view=True for bvtchw outputs when each camera/view needs
an independent processor session.
Tests
Add CPU-safe tests unless the behavior genuinely requires a GPU:
- Config/preset discovery:
flashdreams/tests/test_postprocess_presets.py.
- Stream contract and buffering:
flashdreams/tests/test_postprocess_stream.py.
- Processor-specific CPU fakes:
integrations/<name>/tests/test_postprocess.py.
- Runner distributed skip/all-rank behavior:
flashdreams/tests/test_runner_postprocess.py.
Every pytest test must use exactly one marker: ci_cpu, ci_gpu, or manual.
Prefer fake processor builders for CPU tests instead of loading checkpoints.
Useful focused validation:
uv run pytest flashdreams/tests/test_runner_postprocess.py \
flashdreams/tests/test_postprocess_stream.py \
flashdreams/tests/test_postprocess_presets.py \
integrations/<name>/tests/test_postprocess.py
1---2name: flashdreams-postprocessing3description: Add or modify FlashDreams video post-processing processors, sessions, presets, and runner stream wiring. Use when implementing a new VideoPostProcessorConfig / VideoPostProcessor / VideoPostProcessorSession, registering a --postprocess.preset entry point, changing VideoPostprocessStream behavior, or reasoning about streaming buffering, layouts, per-view processing, distributed execution, or postprocess tests.4---56# FlashDreams Post-Processing78Use this skill when adding a video post-processor or changing the runner9post-processing stream. The reference implementation is10`integrations_v2/flashvsr/impl/postprocess.py`.1112## Mental Model1314A post-processor is usually three classes, not one class inheriting everything:1516- `VideoPostProcessorConfig`: serializable config and CLI surface. It sets17 `_target` to the processor factory and declares fields, `output_spec()`,18 `requires_all_ranks()`, and `validate_execution()`.19- `VideoPostProcessor`: lightweight factory created from config. Its job is20 `start(spec) -> VideoPostProcessorSession`.21- `VideoPostProcessorSession`: mutable per-stream runtime. It owns buffers,22 caches, lazy model instances, counters, and `process()` / `flush()`.2324Keep stream state in the session. Do not store per-rollout mutable state on the25config or processor factory.2627## Implementation Steps28291. Pick a home:30 - Generic reusable post-processing belongs under `flashdreams/flashdreams/infra/postprocess/`.31 - Model-specific processors belong in their integration, for example32 `integrations/<name>/<pkg>/postprocess.py`.33342. Define a config subclass:3536 ```python37 @dataclass(kw_only=True)38 class MyPostProcessorConfig(VideoPostProcessorConfig):39 _target: type["MyPostProcessor"] = field(40 default_factory=lambda: MyPostProcessor41 )4243 scale: int = 24445 def output_spec(self, input_spec: VideoSpec) -> VideoSpec:46 return VideoSpec(47 height=input_spec.height * self.scale,48 width=input_spec.width * self.scale,49 fps=input_spec.fps,50 channels=input_spec.channels,51 )52 ```5354 Override:55 - `output_spec()` when spatial size, channels, or timing changes.56 - `requires_all_ranks()` when the processor must run on nonzero ranks under57 `torchrun`.58 - `validate_execution()` to reject unsupported distributed or shape modes59 early.60613. Define the processor factory:6263 ```python64 class MyPostProcessor(VideoPostProcessor[MyPostProcessorConfig]):65 def start(self, spec: VideoSpec) -> VideoPostProcessorSession:66 return _MyPostProcessorSession(self.config, spec)67 ```68694. Define the session:7071 ```python72 class _MyPostProcessorSession(VideoPostProcessorSession):73 def __init__(self, config: MyPostProcessorConfig, spec: VideoSpec) -> None:74 self._config = config75 self._spec = spec76 self._buffer: Tensor | None = None7778 def process(self, chunk: VideoChunk) -> list[VideoChunk]:79 ...8081 def flush(self) -> list[VideoChunk]:82 ...83 ```8485 `process()` is synchronous but may return `[]`: that means it consumed the86 input chunk and is buffering frames until a later chunk or `flush()` can87 complete an output window.88895. Handle layouts at the boundary:90 - Accept `VideoChunk.tensor` in `chunk.layout`.91 - Use `to_bvtchw()` only as a generic boundary helper.92 - Convert once into the processor's native layout, make it contiguous if the93 model kernels require that, and keep internal buffers in that native94 layout.95 - Document any forced `.contiguous()` because it can copy.96976. Return `VideoChunk`s:98 - Preserve `[-1, 1]` value range unless the API is intentionally changed.99 - Set the correct `layout`.100 - Carry metadata only if it helps downstream processors or provenance.1011027. Register presets when users should select it from CLI:103104 ```toml105 [project.entry-points."flashdreams.postprocess_presets"]106 "my-postprocessor-v1" = "my_pkg.postprocess:POSTPROCESS_PRESET_MY_V1"107 ```108109 The exported object must be a `VideoPostProcessorConfig`, for example:110111 ```python112 POSTPROCESS_PRESET_MY_V1 = MyPostProcessorConfig(...)113 ```114115 Users select it with `--postprocess.preset my-postprocessor-v1`.116117## Runner Interaction118119Runners create a `VideoPostprocessStream` through120`create_runner_postprocess_stream()`. The stream:121122- creates one chain session for whole-stream processing, or one session per123 view when `postprocess_per_view=True`;124- calls `session.process(VideoChunk(...))` for each AR output;125- turns `[]` into a zero-frame tensor so `process()` remains tensor-only;126- skips collecting zero-time tensors in `_append_if_nonempty()`;127- calls `flush()` once at end-of-stream and appends any tail output.128129Use `postprocess_output_layout` to describe the runner's decoded output layout.130Use `postprocess_per_view=True` for `bvtchw` outputs when each camera/view needs131an independent processor session.132133## Tests134135Add CPU-safe tests unless the behavior genuinely requires a GPU:136137- Config/preset discovery: `flashdreams/tests/test_postprocess_presets.py`.138- Stream contract and buffering: `flashdreams/tests/test_postprocess_stream.py`.139- Processor-specific CPU fakes: `integrations/<name>/tests/test_postprocess.py`.140- Runner distributed skip/all-rank behavior:141 `flashdreams/tests/test_runner_postprocess.py`.142143Every pytest test must use exactly one marker: `ci_cpu`, `ci_gpu`, or `manual`.144Prefer fake processor builders for CPU tests instead of loading checkpoints.145146Useful focused validation:147148```bash149uv run pytest flashdreams/tests/test_runner_postprocess.py \150 flashdreams/tests/test_postprocess_stream.py \151 flashdreams/tests/test_postprocess_presets.py \152 integrations/<name>/tests/test_postprocess.py153```