Skip to content

[feat]: complete safe CUDA-graph artifact dispatch V1 - #25

Merged
aryan5v merged 17 commits into
agent/graph-executable-irfrom
agent/v1-r4-dispatch-fix
Aug 2, 2026
Merged

[feat]: complete safe CUDA-graph artifact dispatch V1#25
aryan5v merged 17 commits into
agent/graph-executable-irfrom
agent/v1-r4-dispatch-fix

Conversation

@aryan5v

@aryan5v aryan5v commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

Completes the FastVideo runtime required to execute MotionKernel graph-derived artifacts safely and economically.

  • adds per-artifact dispatch isolation and hot-path diagnostics
  • replays rewritten subgraphs through CUDA graphs to remove Python/FX dispatch overhead
  • falls back cleanly when capture is unavailable
  • supports constant and non-tensor graph inputs/outputs
  • gives repeated blocks independent CUDA graph memory pools
  • validates captured parameter addresses and complete tensor layouts before replay
  • resets aborted captures and verifies replay is bit-identical to eager execution

Why

Eager replay of a 621-node exported transformer subgraph cost more than the native block. CUDA-graph replay preserves the same kernels, parameters, and order while eliminating host dispatch overhead. Capture remains fail-closed and is accepted only after the artifact entry point and eager replay match bitwise.

Validation

  • pytest -q fastvideo/tests/optimization: 113 passed
  • focused Ruff checks: passed
  • LTX artifact selected for 6,143 calls with zero runtime fallbacks
  • 15-run full-generation frame arrays were byte equal
  • verified median end-to-end speedup: 1.0857x
  • independent replication: 1.2514x

Evidence: /mnt/nfs/vlm-aryan/ltx-v1-r4-targeted-fix-20260801-203751

Summary by CodeRabbit

  • New Features

    • Added configurable artifact allowlisting through an environment variable.
    • Added CUDA graph replay with validation, profiling, warmup, and eager fallback.
    • Added optional timing reports and diagnostic modes.
    • Preserved autocast settings during deferred tracing.
  • Bug Fixes

    • Improved artifact integrity checks, dispatch diagnostics, and capture cleanup.
    • Added validation for graph inputs, outputs, buffers, and aliases.
  • Tests

    • Expanded coverage for artifact filtering, CUDA graph execution, timing, diagnostics, and autocast behavior.

Greptile Summary

This PR completes the CUDA-graph replay layer for MotionKernel graph-derived artifacts. It adds _CudaGraphRunner (per-block capture with bitwise verification, per-scope shared static input buffers, and graceful eager fallback), _CudaGraphScope (one buffer per input position shared across repeated blocks), _CudaGraphScope.buffer_for with full layout/stride/device checking, _CudaGraphRunner._release_capture for deterministic pool cleanup, and a new timing.py module for opt-in per-phase host/device profiling with shadow mode.

  • CUDA graph capture/replay (subgraph.py): warmup for allocator stability, capture on a private side stream, bitwise verification of replay vs. eager, attribute-address staleness checks on every replay, and explicit pool release on capture failure.
  • Artifact allowlisting (artifact.py, envs.py): new FASTVIDEO_OPTIMIZATION_ARTIFACT_ENABLE env var for per-artifact A/B isolation; all four previously undeclared env vars are now registered in envs.py.
  • Diagnostics (dispatch.py, timing.py): parameter snapshot deferred to failure path only; timing phases instrumented around every hot-path branch; shadow mode for live native-vs-candidate comparison."

Confidence Score: 5/5

Safe to merge. The CUDA-graph path is fail-closed at every stage, fallback to eager is tested on CPU, and bitwise verification guards the captured result before it is ever used.

All failure paths fall back to native execution without demoting the artifact permanently. The capture, verification, and replay logic is internally consistent, and the previous review findings (stride check in buffer_for, env var registration, timing.reset at session start) are all addressed. The one asymmetry noted — explicit pool release on capture-fail but not on post-capture validation-fail — is benign in CPython where refcount GC is immediate.

Files Needing Attention: fastvideo/optimization/subgraph.py — the post-capture CudaGraphUnavailable paths (arity mismatch, moved attribute, etc.) skip the explicit _release_capture() call that the capture-fail path uses for deterministic pool cleanup.

Important Files Changed

Filename Overview
fastvideo/optimization/subgraph.py Adds full CUDA-graph capture/replay pipeline inside _CudaGraphRunner, including per-block buffer sharing via _CudaGraphScope, bitwise verification, attribute staleness checks, and graceful eager fallback; also adds _strip_runtime_assertions and _placeholder_contract for hot-path reduction. One asymmetry in explicit pool release between capture-fail and post-capture-fail paths.
fastvideo/optimization/timing.py New module providing opt-in per-phase host/device timing with shadow mode, bounded note accumulation, atomic temp-file report writing, and a shared no-op context manager for the disabled hot path. reset() is now called in attach_graph_dispatch addressing the prior review concern.
fastvideo/optimization/dispatch.py Integrates timing phases around every dispatch hot-path branch, moves parameter snapshot to the failure path only, adds shadow-mode native replay, and wires timing report emission at detach time. All previous materialization snapshot concerns addressed.
fastvideo/envs.py Registers four previously undeclared env vars (ENABLE, CUDA_GRAPHS, DUMP_GRAPH, TIMING) in both the TYPE_CHECKING block and the environment_variables registry, resolving the prior review finding.
fastvideo/optimization/artifact.py Adds enabled_ids allowlisting to ArtifactRegistry: all bundles are still verified; selection is a separate narrowing step. Missing-ID requests are surfaced as errors rather than silent no-ops.
fastvideo/tests/optimization/test_dispatch.py Extensive new coverage for artifact filtering, CUDA graph warmup/capture/decline/replay, constant inputs, moved-parameter detection, parameter-snapshot-free success path, and timing counters. Tests are well-isolated with a dedicated timing fixture.
fastvideo/tests/optimization/test_fx_capture.py Adds GPU-gated test verifying that deferred export replays preserve CUDA autocast dtype and clean up observed_autocast after finalization.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[dispatch called] --> B{entry cached?}
    B -- No --> C[build runnable + _Entry with _CudaGraphRunner]
    C --> D
    B -- Yes --> D{cuda_graph is not None?}
    D -- No --> E[eager runnable]
    D -- Yes --> F[_CudaGraphRunner.__call__]
    F --> G{self._graph is None?}
    G -- warmups < WARMUP_ITERATIONS --> H[record addresses / raise CudaGraphWarmingUp]
    H --> I[fall through to eager]
    I --> E
    G -- warmups reached --> J[_capture: build inputs, warmup 3x, graph.capture, bitwise verify]
    J -- success --> K[self._graph set / next call replays]
    J -- CudaGraphUnavailable --> L[_release_capture / raise]
    J -- unexpected error --> M[_release_capture / synchronize / wrap in CudaGraphUnavailable]
    L --> N[entry.cuda_graph = None / eager forever]
    M --> N
    G -- self._graph set --> O{validate inputs and attributes}
    O -- mismatch --> P[raise CudaGraphUnavailable / NO explicit _release_capture]
    P --> N
    O -- OK --> Q[capture_stream.wait / graph.replay / current_stream.wait / clone outputs]
    Q --> R[return _unflatten]
    E --> R
Loading

Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'fork/agent..." | Re-trigger Greptile

aryan5v added 15 commits August 1, 2026 08:39
…om the hot path

Run ltx-v1-overnight-20260801-r4-sol loaded four artifacts into
vae.decoder.up_blocks.6.res_blocks at once, executed 56 candidate calls with
zero runtime fallbacks, failed byte_equal parity, and regressed end-to-end from
3.2818s to 3.9410s (0.8327x). The evidence cannot attribute either the parity
change or the latency to any individual artifact, because there was no way to
enable one at a time short of staging a separate directory per trial.

Add FASTVIDEO_OPTIMIZATION_ARTIFACT_ENABLE, a comma-separated allowlist of
artifact IDs. Every bundle under the root is still discovered, verified and
hashed, so a corrupt one is still reported; selection only narrows which
verified manifests may be matched. Requesting an ID no bundle provides is an
error rather than a silently empty run, which would otherwise read as "this
artifact changes nothing". The registry summary records enabled_filter and
excluded_ids so a trial's own dispatch.json says what was under test.

Two hot-path costs, both paid per candidate call:

- _dispatch built three _parameter_snapshot dictionaries per call -- walking
  module parameters, sorting hook names and listing up to 16 tensor shapes --
  to diagnose FSDP parameter materialization. A run that never fails never
  reads them. The snapshot now happens on the failure path, where it is used.
- _validate_runtime_inputs walked every node in the rewritten graph on every
  call to rediscover its placeholders. That graph is immutable for the
  lifetime of the dispatcher, so the placeholder contract is now frozen once
  at build time and the per-call check iterates only the placeholders.

Neither changes what is accepted: the same shape/dtype contract is enforced,
and the same failures raise with the same messages.

pytest fastvideo/tests/optimization/ -> 93 passed (6 new).
The transformer artifact costs 3.104ms per call end-to-end while its kernel
saves 124us, so gate 5 fails on framework overhead. Attributing that needs the
cost broken down in situ -- same module, same live tensors, same stream -- not
in a micro-harness that rebuilds the region separately.

FASTVIDEO_OPTIMIZATION_ARTIFACT_TIMING enables per-phase accounting across
shape-key construction, pytree flatten, input validation, rewritten-graph
execution and unflatten. 'sync' synchronizes around each phase to attribute
device time; 'shadow' additionally runs the native forward on every dispatched
call and times it, which is the only honest comparison for whether the artifact
path is cheaper than the one it replaced.

Inert unless the variable is set: phase() returns a shared no-op context
manager, so the hot path pays one module-level boolean check.
The dispatcher executes the rewritten export graph in place of the module's own
forward, and shadow timing shows that path costs 11.57ms against the native
forward's 8.18ms on the same inputs. An export graph is decomposed, so a single
high-level call in the module can become many primitives here; identifying
which ops the replay actually runs is the next question.

FASTVIDEO_OPTIMIZATION_ARTIFACT_DUMP_GRAPH writes op names and counts only.
Gate 5 fails on dispatch cost, not arithmetic. Shadow timing on
transformer.model.transformer_blocks measured the rewritten-graph replay at
11.57ms against the module's own forward at 8.18ms on identical inputs, and the
op histogram explains it: the export graph is decomposed into 621
call_function nodes per call, each paying Python and PyTorch dispatcher cost.
At ~5us per op that is the whole 3.39ms penalty, against an artifact saving of
124us. Attention is not the problem -- it survives capture as a single
fastvideo._flash_attn_default_forward op.

Two changes, both numerics-preserving:

- Strip export's aten._assert_tensor_metadata nodes from the rewritten graph.
  68 of the 621 compute nothing, and the dispatcher already validates the same
  metadata once per call via the placeholder contract. Only nodes with no users
  are removed.

- Replay the graph from a torch.cuda.CUDAGraph capture. A CUDA graph runs the
  same kernels with the same parameters in the same order, so it is bitwise
  identical to the eager replay by construction. That matters here: a compiler
  backend could fuse or reassociate and put the workload's byte_equal parity at
  risk, which is the gate this work exists to keep.

Capture is guarded rather than assumed. Non-CUDA or non-tensor inputs, a
changed shape or dtype, a missing pool, a non-tensor output, or any capture
failure raises CudaGraphUnavailable and the entry falls back to eager replay
permanently. Declining only costs speed.

Static input buffers and the graph memory pool are shared across a scope's
blocks. Every repeated block is called with the same input signature, so one
buffer set serves all of them; per-block buffers would have added roughly 19GB
for this stack's 48 blocks and 27 placeholders, past the workload's 5%
peak-memory allowance.

Off via FASTVIDEO_OPTIMIZATION_ARTIFACT_CUDA_GRAPHS=0.

pytest fastvideo/tests/optimization/ -> 101 passed (8 new).
…back

A capture failure raising an arbitrary exception escapes to the dispatcher's
candidate-runtime handler, which demotes the artifact to native execution for
the rest of the run. That reads a missing acceleration as a broken kernel: the
candidate is fine, only the capture is unavailable.

Wrap capture, drop any partial state, synchronize, and re-raise as
CudaGraphUnavailable so the eager replay proceeds and the artifact keeps
running.
CUDA graph replay cut 1.382ms per call on
transformer.model.transformer_blocks but left 3.381ms, and copying the
boundary tensors into static buffers is a large part of what remains: the 27
placeholders total roughly 565MB per call, so a copy-in is over 1.1GB of
traffic before the graph runs.

Most of those tensors do not move. Timesteps, positional embeddings and the
text context are the same tensor object for all 48 blocks and all 8 steps of a
generation; only the residual stream changes. The runner now records input
addresses across warmup, captures reading the unchanged ones where they
already live, and allocates static buffers only for the positions that move.

Correctness rests on the pinned addresses still holding at replay time, so
every pinned input's data_ptr is checked on every call. A tensor that moved
makes the runner decline and the eager replay answer instead -- replaying
would read whatever now occupies that address, which is the one failure this
path must never have.

pytest fastvideo/tests/optimization/ -> 104 passed (3 new).
Profiling showed subgraph.execute_cuda_graph entered 192 times on
transformer.model.transformer_blocks -- 48 blocks x 3 warmups, plus 48 capture
attempts -- while the eager path still ran all 1151 calls. Every capture
failed, and the reason went only to logger.info, which the host's log level
dropped. A silently declined fast path is indistinguishable from one that was
never attempted.

Reasons are now counted in the timing report and logged at warning level.
…lining

Every CUDA graph capture on transformer.model.transformer_blocks was failing
with 'runtime input 10 is bool, not a tensor', so the fast path never engaged
once across 1151 calls -- the eager replay ran every time and the measured
improvement was node variance, not the capture.

Export flattens Python scalars and flags through as graph inputs. They hold no
device memory and need no static buffer; the capture bakes them in as the
constants they are. The graph is only valid while they keep that value, so
every call checks type and equality and declines if either changed, because
the captured kernels encode the old value.

pytest fastvideo/tests/optimization/ -> 107 passed (2 new).
…nstant outputs

Two failures stopped every capture on transformer.model.transformer_blocks:

- Sharing one graph_pool_handle across the stack's 48 blocks tripped the
  caching allocator's 'use_count > 0 INTERNAL ASSERT FAILED'. Each graph keeps
  its output buffers alive for the life of the run, so the pool is never free
  of live allocations when the next capture begins. Each capture now gets its
  own pool.

- The rewritten graph returns a bool alongside its tensors. Like a non-tensor
  input, it is a constant the graph reproduces identically on every replay, so
  it is returned as captured rather than copied out.

Static input buffers stay shared across the scope; only pools are private.
A review of the capture path found defects that a byte_equal workload cannot
carry. In order of severity:

- The capture bakes in every parameter and buffer the graph reads through
  get_attr, and nothing re-checked them. FSDP2's reshard frees the
  all-gathered storage the capture recorded pointers into, so the next replay
  would read freed memory or another tensor; weight offload and dequantization
  caches move them the same way. Their identity is now recorded at capture and
  re-checked on every replay.

- Non-tensor outputs were returned as captured on the assumption that
  'not a Tensor' means 'immutable constant'. A list or dict leaf would have
  handed the caller the graph's own static buffers, which the next replay
  overwrites in place. Only None and scalars are treated as constants now;
  anything else declines.

- Pinned inputs were validated by data_ptr alone. data_ptr folds in
  storage_offset but says nothing about strides, so a permuted view handed back
  the same allocator block passed every check while the captured kernels read
  elements in a different order. Pinning is now on data_ptr, shape, stride,
  dtype, device and storage_offset.

- An aborted capture never called graph.reset(), pinning its private memory
  pool for the life of the run.

- The constant comparison could raise a non-RuntimeError, escaping both
  handlers and permanently demoting a working artifact.

Also: 'warming up' was signalled by comparing an exception message string, so
rewording it would have silently turned every warmup into a permanent disable.
It is a distinct exception type now.

Most importantly, the argument that a capture must be bitwise identical to the
eager replay is now checked rather than asserted. The graph contains one node
export did not functionalize -- the artifact's own entry point -- so purity is
an assumption about third-party code. After capture, the runner replays once,
runs the eager graph, and refuses the capture unless every output is bitwise
equal.

pytest fastvideo/tests/optimization/ -> 111 passed.
Three findings from Greptile, all real:

- A dead 'if True:' guard in the decline handler, left behind when the warmup
  signal moved from a message-string comparison to a distinct exception type.

- The private memory pool was released on the unexpected-error path but not
  when _capture itself refused. That mattered most for the bitwise-verification
  step, which ran *after* self._graph was assigned: a rejected capture stayed
  published on the runner and its pool's lifetime depended on the caller
  dropping the runner and refcounting collecting it. The graph is now published
  only once verified, and both abort paths go through _release_capture.

- timing's counters are process-global with no way to clear them, so two
  dispatch sessions in one process would produce a report attributable to
  neither. Added timing.reset().

pytest fastvideo/tests/optimization/ -> 113 passed (2 new).
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The optimization runtime now supports artifact-ID allowlisting, opt-in timing reports, autocast replay during deferred tracing, and validated CUDA graph capture with eager fallback.

Optimization runtime

Layer / File(s) Summary
Artifact allowlisting and admission
fastvideo/envs.py, fastvideo/optimization/artifact.py, fastvideo/optimization/dispatch.py, fastvideo/tests/optimization/test_dispatch.py
The runtime reads enabled artifact IDs, verifies all bundles, admits matching verified artifacts, and reports excluded or missing IDs.
Timing and dispatch diagnostics
fastvideo/optimization/timing.py, fastvideo/optimization/dispatch.py, fastvideo/tests/optimization/test_dispatch.py
The runtime records dispatch phases, supports shadow-native execution, limits parameter snapshots to failures, and writes timing.json.
Deferred autocast replay
fastvideo/optimization/fx_capture.py, fastvideo/tests/optimization/test_fx_capture.py
Shape variants retain autocast metadata for tracing and clear it after processing or failure.
CUDA graph capture and dispatch
fastvideo/optimization/subgraph.py, fastvideo/tests/optimization/test_dispatch.py
Dispatch caches contracts, validates inputs and outputs, manages CUDA graph warmup and replay, strips unused assertions, and falls back to eager execution when capture is unavailable or unsafe.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Dispatch
  participant CudaGraphRunner
  participant CUDARuntime
  participant EagerReplay
  Dispatch->>CudaGraphRunner: Validate inputs and contracts
  CudaGraphRunner->>CUDARuntime: Warm up and capture graph
  CUDARuntime-->>CudaGraphRunner: Return capture result
  CudaGraphRunner->>CUDARuntime: Replay static buffers
  CUDARuntime-->>CudaGraphRunner: Return graph outputs
  CudaGraphRunner-->>Dispatch: Reconstruct outputs
  CudaGraphRunner->>EagerReplay: Use eager fallback when capture is refused
  EagerReplay-->>Dispatch: Return eager outputs
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.91% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: completing safe V1 CUDA-graph artifact dispatch.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/v1-r4-dispatch-fix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@aryan5v

aryan5v commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Comment thread fastvideo/optimization/subgraph.py
Comment thread fastvideo/optimization/subgraph.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (10)
fastvideo/tests/optimization/test_fx_capture.py (1)

496-520: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add CUDA autocast replay coverage.

The current test only exercises CPU bfloat16 autocast replay. Add a CUDA-gated regression in fastvideo/tests/optimization/test_fx_capture.py that captures CUDA autocast state, exercises replay through finalize, and documents the GPU assumption.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/tests/optimization/test_fx_capture.py` around lines 496 - 520, Add
a CUDA-gated regression test alongside
test_deferred_export_replays_observed_autocast_dtype that runs the same capture
and finalize flow under CUDA autocast, verifies the captured executable IR
preserves the expected CUDA autocast dtype, and confirms autocast state is
restored after execution and finalize. Document the test’s GPU requirement and
skip it when CUDA is unavailable.

Source: Coding guidelines

fastvideo/optimization/dispatch.py (1)

284-289: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document that shadow mode can change results for stateful forwards.

The shadow run calls native(*args, **kwargs) an extra time per dispatch. If the native forward consumes RNG, updates a cache, or mutates module state, the extra call changes the generation output. The timing docstring states only that the result is discarded and that cost roughly doubles.

Add that constraint to the SHADOW docstring in fastvideo/optimization/timing.py, so nobody enables it during a parity run.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/optimization/dispatch.py` around lines 284 - 289, Update the SHADOW
documentation in the timing module to state that shadow mode performs an extra
native forward and can change outputs for stateful forwards that consume RNG,
update caches, or mutate module state. Explicitly warn against enabling SHADOW
during parity or generation-result comparisons, while retaining the existing
notes about discarded results and increased cost.
fastvideo/optimization/timing.py (2)

105-107: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Bound _notes or reconsider recording notes while timing is disabled.

note() records unconditionally, but snapshot() and write_report() return data only when ENABLED is true. In the default configuration the notes accumulate and nothing ever reads them. Key cardinality is bounded only by the distinct message strings callers build, and fastvideo/optimization/subgraph.py formats a decline reason into the key.

Add a size cap, so a caller that interpolates a variable value into a note cannot grow the dictionary without limit.

♻️ Proposed change
 def note(message: str) -> None:
     """Count one structured observation. Always on, so it survives log config."""
-    _notes[str(message)[:200]] += 1
+    key = str(message)[:200]
+    if key in _notes or len(_notes) < 256:
+        _notes[key] += 1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/optimization/timing.py` around lines 105 - 107, Bound the `_notes`
dictionary updated by `note()` so variable message values cannot create
unlimited distinct keys. Add a fixed maximum cardinality and ensure new note
keys are ignored once the cap is reached, while continuing to increment existing
keys and preserving the current message truncation behavior.

151-163: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider matching the atomic write used by write_diagnostics.

GraphDispatchSession.write_diagnostics in fastvideo/optimization/dispatch.py writes to a .tmp file and then calls replace, so a reader never sees a partial report. This function writes in place. Both files are produced by the same teardown call, so a consistent strategy avoids a truncated timing.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/optimization/timing.py` around lines 151 - 163, The write_report
function currently writes timing.json in place, allowing readers to observe
partial output. Match GraphDispatchSession.write_diagnostics by writing the
serialized snapshot to a temporary file in the destination directory, then
atomically replacing the target with it; preserve the existing None-on-failure
behavior and ensure temporary artifacts are cleaned up as appropriate.
fastvideo/tests/optimization/test_dispatch.py (2)

1894-1907: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Isolate the process-global timing state with a fixture.

The test mutates timing's module-level counters directly. monkeypatch restores ENABLED, but _totals, _counts, and _notes are left in whatever state the test produced. note() also ignores ENABLED, so it records during every other test in the session.

Add an autouse or explicit fixture that calls timing.reset() before and after, so a future test that asserts accumulated totals cannot become order-dependent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/tests/optimization/test_dispatch.py` around lines 1894 - 1907, Add
a fixture for the imported timing module that calls timing.reset() both before
and after tests, and apply it to test_timing_state_can_be_reset_between_sessions
(or make it autouse for the module). Keep the existing ENABLED monkeypatch and
assertions, ensuring _totals, _counts, and _notes are cleared before and after
each test.

1541-1586: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the real export assertion target in these tests.

_ASSERTION_TARGETS is hardcoded to aten._assert_tensor_metadata.default, but the tests only cover monkeypatched local function targets. Add one export-based or target-string assertion so a PyTorch export rename fails the suite instead of leaving the stripping logic as a no-op.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/tests/optimization/test_dispatch.py` around lines 1541 - 1586,
Update the assertion-stripping tests around _assertion_graph and
test_export_runtime_assertions_are_stripped to include a real torch export
assertion target, or explicitly assert its expected target string. Ensure the
test verifies that _ASSERTION_TARGETS contains the actual
aten._assert_tensor_metadata.default target and that stripping removes it, so
PyTorch target renames cannot silently make the logic a no-op.
fastvideo/optimization/subgraph.py (4)

182-204: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The profile file is overwritten once per live block.

_dump_graph_profile is called from build, and build runs once for each repeated block in a stack. Every call writes the same destination path, so only the last block's histogram survives. For a 48-block stack the earlier profiles are lost.

Include the block identity in the filename, so each build produces its own record.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/optimization/subgraph.py` around lines 182 - 204, The
_dump_graph_profile output path currently overwrites prior block profiles;
update its filename construction to include the current block’s unique identity,
using available manifest or build-context symbols, while preserving the
configured destination directory and JSON output behavior.

950-961: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

A process-wide decline reason is logged once per block.

entry.cuda_graph = None disables the accelerator for one block only. A reason that applies to the whole run, for example "CUDA is not available", is therefore re-derived and re-logged for every block in the stack. For a 48-block transformer that is 48 identical warnings.

Use logger.warning_once, which fastvideo/logger.py provides, or hoist a run-wide refusal to the shared graph_scope.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/optimization/subgraph.py` around lines 950 - 961, The
CudaGraphUnavailable warning in the graph replay exception path is emitted once
per block instead of once per process. Update the logger.warning call in the
CudaGraphUnavailable handler to use the existing logger.warning_once mechanism,
preserving the current message and eager-replay behavior while still clearing
entry.cuda_graph for the affected block.

209-214: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Three new environment settings bypass the fastvideo/envs.py registry. This PR adds four environment settings for the optimization runtime. Only FASTVIDEO_OPTIMIZATION_ARTIFACT_ENABLE is registered centrally; the other three call os.getenv at module import, so they are undiscoverable through envs, fixed for the process lifetime, and not patchable from a test.

  • fastvideo/optimization/subgraph.py#L209-L214: move FASTVIDEO_OPTIMIZATION_ARTIFACT_CUDA_GRAPHS into environment_variables in fastvideo/envs.py and read it through envs where _Entry is built. Do the same for FASTVIDEO_OPTIMIZATION_ARTIFACT_DUMP_GRAPH at Line 182.
  • fastvideo/optimization/timing.py#L41-L48: register FASTVIDEO_OPTIMIZATION_ARTIFACT_TIMING in fastvideo/envs.py. Keep ENABLED, SYNCHRONIZE, and SHADOW as module constants derived from that value, so the hot path still pays only a boolean check.
  • fastvideo/envs.py#L355-L361: add the three entries next to the existing FASTVIDEO_OPTIMIZATION_ARTIFACT_* block, with the same comment style.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/optimization/subgraph.py` around lines 209 - 214, Register
FASTVIDEO_OPTIMIZATION_ARTIFACT_CUDA_GRAPHS and
FASTVIDEO_OPTIMIZATION_ARTIFACT_DUMP_GRAPH in fastvideo/envs.py alongside the
existing optimization artifact entries, then update subgraph.py to read them
through envs when building _Entry instead of os.getenv. Register
FASTVIDEO_OPTIMIZATION_ARTIFACT_TIMING there as well and update timing.py so
ENABLED, SYNCHRONIZE, and SHADOW remain module-level constants derived from the
registered value. Apply these changes at fastvideo/optimization/subgraph.py
lines 209-214, fastvideo/optimization/timing.py lines 41-48, and
fastvideo/envs.py lines 355-361; preserve the existing comment style and boolean
hot-path behavior.

461-472: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Two small hardening items in the runner lifecycle.

Line 466 uses a tuple assignment to read _pending_graph and clear it in one statement. The evaluation order is correct, but the intent is hard to read, and the getattr default is redundant because __init__ always sets the attribute.

Line 540 indexes self._moving[index] directly. _capture assigns every index to _pinned, _moving, or _constants, so the key is always present today. If that invariant ever breaks, the KeyError is not a CudaGraphUnavailable, so GraphDispatchSession._dispatch demotes the artifact permanently — the outcome Lines 498-508 exist to prevent.

♻️ Proposed changes
     def _release_capture(self) -> None:
         """Drop a capture and free the private memory pool it reserved."""
         aborted, self._graph = self._graph, None
         self._static_outputs = ()
         self._attributes = []
-        self._pending_graph, pending = None, getattr(self, "_pending_graph", None)
+        pending = self._pending_graph
+        self._pending_graph = None
         for candidate in (aborted, pending):
-            buffer = self._moving[index]
+            buffer = self._moving.get(index)
+            if buffer is None:
+                raise CudaGraphUnavailable(
+                    f"runtime input {index} has no captured role"
+                )
             if live.shape != buffer.shape or live.dtype != buffer.dtype:

Also applies to: 540-543

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/optimization/subgraph.py` around lines 461 - 472, In
_release_capture, read self._pending_graph into pending and clear the attribute
in separate statements, relying on its initialization rather than getattr. In
the capture/replay path around self._moving[index], replace direct indexing with
a guarded lookup that raises CudaGraphUnavailable when the index is absent,
preserving GraphDispatchSession._dispatch’s recoverable failure handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@fastvideo/optimization/dispatch.py`:
- Around line 310-326: Guard the diagnostic snapshot inside the runtime-failure
handler around the fallback logic near the artifact execution path. Ensure any
exception from self._parameter_snapshot(wrapper) is caught or replaced with a
safe diagnostic value, then still emit the warning and always reach return
native(*args, **kwargs), preserving native fallback even when snapshot
collection fails.
- Around line 683-686: Update the diagnostics handling in detach_graph_dispatch
so an invalid output path cannot escape teardown: guard the timing report path
construction and write, including Path.with_name, with error handling while
preserving session.write_diagnostics behavior. Ensure values such as "." or "/"
are safely ignored or logged without preventing detach_graph_dispatch from
completing.

In `@fastvideo/optimization/subgraph.py`:
- Around line 55-74: Update _capture to reject any pinned input whose
_layout_identity returns None, matching the existing unreadable bound-attribute
handling. Ensure the capture fails before storing None in self._pinned, and keep
replay validation in the pinned-input path from treating two None layouts as a
valid match.
- Around line 815-818: Update the cache comment above the WeakKeyDictionary in
the graph-scope initialization to describe the value as an _Entry, including its
rewritten module, frozen placeholder contract, and cuda_graph; keep the cache
declaration and behavior unchanged.

In `@fastvideo/optimization/timing.py`:
- Around line 118-128: Call timing.reset() at the beginning of every dispatch
session, before any timing.phase(), timing.record(), or timing.note() calls
occur. Update the dispatch-session entry point rather than the timing.reset()
implementation, ensuring each model/session starts with cleared process-global
timing state while preserving existing report generation.

In `@fastvideo/tests/optimization/test_dispatch.py`:
- Around line 1853-1882: Extract the output-leaf classification logic into a
shared helper near _CudaGraphRunner, such as _classify_output_leaves, returning
constant leaves and raising CudaGraphUnavailable for mutable values. Update
_CudaGraphRunner._capture to use this helper, then change
test_a_mutable_output_is_refused_rather_than_aliased to call the helper directly
and remove the unused runner, fake graph, and monkeypatch setup.
- Around line 1470-1498: Update
test_placeholder_contract_is_derived_once_not_per_call to verify its caching
claim by monkeypatching _placeholder_contract and asserting dispatch invokes it
only once across repeated calls, following the pattern used by
test_dispatch_does_not_snapshot_parameters_on_the_success_path. If dispatch
cannot be exercised here, rename the test to reflect only contract derivation
and runtime validation.
- Around line 1717-1732: Update the type annotations in _CudaGraphRunner to
match the layout identities stored by the warmup logic: change _observed from
nested integer lists to entries containing the _layout_identity return type, and
change _pinned to map integers to that same layout-identity type. Preserve the
existing initialization and storage behavior while ensuring mypy accepts the
assignments.

---

Nitpick comments:
In `@fastvideo/optimization/dispatch.py`:
- Around line 284-289: Update the SHADOW documentation in the timing module to
state that shadow mode performs an extra native forward and can change outputs
for stateful forwards that consume RNG, update caches, or mutate module state.
Explicitly warn against enabling SHADOW during parity or generation-result
comparisons, while retaining the existing notes about discarded results and
increased cost.

In `@fastvideo/optimization/subgraph.py`:
- Around line 182-204: The _dump_graph_profile output path currently overwrites
prior block profiles; update its filename construction to include the current
block’s unique identity, using available manifest or build-context symbols,
while preserving the configured destination directory and JSON output behavior.
- Around line 950-961: The CudaGraphUnavailable warning in the graph replay
exception path is emitted once per block instead of once per process. Update the
logger.warning call in the CudaGraphUnavailable handler to use the existing
logger.warning_once mechanism, preserving the current message and eager-replay
behavior while still clearing entry.cuda_graph for the affected block.
- Around line 209-214: Register FASTVIDEO_OPTIMIZATION_ARTIFACT_CUDA_GRAPHS and
FASTVIDEO_OPTIMIZATION_ARTIFACT_DUMP_GRAPH in fastvideo/envs.py alongside the
existing optimization artifact entries, then update subgraph.py to read them
through envs when building _Entry instead of os.getenv. Register
FASTVIDEO_OPTIMIZATION_ARTIFACT_TIMING there as well and update timing.py so
ENABLED, SYNCHRONIZE, and SHADOW remain module-level constants derived from the
registered value. Apply these changes at fastvideo/optimization/subgraph.py
lines 209-214, fastvideo/optimization/timing.py lines 41-48, and
fastvideo/envs.py lines 355-361; preserve the existing comment style and boolean
hot-path behavior.
- Around line 461-472: In _release_capture, read self._pending_graph into
pending and clear the attribute in separate statements, relying on its
initialization rather than getattr. In the capture/replay path around
self._moving[index], replace direct indexing with a guarded lookup that raises
CudaGraphUnavailable when the index is absent, preserving
GraphDispatchSession._dispatch’s recoverable failure handling.

In `@fastvideo/optimization/timing.py`:
- Around line 105-107: Bound the `_notes` dictionary updated by `note()` so
variable message values cannot create unlimited distinct keys. Add a fixed
maximum cardinality and ensure new note keys are ignored once the cap is
reached, while continuing to increment existing keys and preserving the current
message truncation behavior.
- Around line 151-163: The write_report function currently writes timing.json in
place, allowing readers to observe partial output. Match
GraphDispatchSession.write_diagnostics by writing the serialized snapshot to a
temporary file in the destination directory, then atomically replacing the
target with it; preserve the existing None-on-failure behavior and ensure
temporary artifacts are cleaned up as appropriate.

In `@fastvideo/tests/optimization/test_dispatch.py`:
- Around line 1894-1907: Add a fixture for the imported timing module that calls
timing.reset() both before and after tests, and apply it to
test_timing_state_can_be_reset_between_sessions (or make it autouse for the
module). Keep the existing ENABLED monkeypatch and assertions, ensuring _totals,
_counts, and _notes are cleared before and after each test.
- Around line 1541-1586: Update the assertion-stripping tests around
_assertion_graph and test_export_runtime_assertions_are_stripped to include a
real torch export assertion target, or explicitly assert its expected target
string. Ensure the test verifies that _ASSERTION_TARGETS contains the actual
aten._assert_tensor_metadata.default target and that stripping removes it, so
PyTorch target renames cannot silently make the logic a no-op.

In `@fastvideo/tests/optimization/test_fx_capture.py`:
- Around line 496-520: Add a CUDA-gated regression test alongside
test_deferred_export_replays_observed_autocast_dtype that runs the same capture
and finalize flow under CUDA autocast, verifies the captured executable IR
preserves the expected CUDA autocast dtype, and confirms autocast state is
restored after execution and finalize. Document the test’s GPU requirement and
skip it when CUDA is unavailable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ec8684f9-824e-45ac-bab4-7e779eb5ec8f

📥 Commits

Reviewing files that changed from the base of the PR and between 9d3135a and f75576b.

📒 Files selected for processing (8)
  • fastvideo/envs.py
  • fastvideo/optimization/artifact.py
  • fastvideo/optimization/dispatch.py
  • fastvideo/optimization/fx_capture.py
  • fastvideo/optimization/subgraph.py
  • fastvideo/optimization/timing.py
  • fastvideo/tests/optimization/test_dispatch.py
  • fastvideo/tests/optimization/test_fx_capture.py

Comment on lines +310 to 326
# The FSDP-lifecycle snapshot is built here, on the failure path,
# rather than three times per successful call. It exists to
# diagnose parameter materialization; a run that never fails never
# needs it, and paying for it on every dispatch charged the
# candidate's measured saving for diagnostics it did not use.
logger.warning(
"Artifact %s failed at runtime for %s; falling back to native "
"execution for the rest of this run; materialization=%s",
decision.artifact_id,
wrapper.scope,
json.dumps(materialization, sort_keys=True),
json.dumps(
{"at_failure": self._parameter_snapshot(wrapper)},
sort_keys=True,
),
exc_info=True,
)
return native(*args, **kwargs)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard the diagnostic snapshot so the fallback cannot be lost.

self._parameter_snapshot(wrapper) runs inside the except handler. If it raises, the new exception replaces the fallback and propagates to the caller, so return native(*args, **kwargs) at Line 326 never runs. Every other failure path in this module falls back to native execution instead of breaking generation.

Compute the snapshot defensively.

🛡️ Proposed fix
             decision.reason = f"{_REASON_RUNTIME_PREFIX}:{type(exc).__name__}"
+            try:
+                snapshot = self._parameter_snapshot(wrapper)
+            except Exception:  # noqa: BLE001 - diagnostics never break a run
+                snapshot = {"unavailable": True}
             # The FSDP-lifecycle snapshot is built here, on the failure path,
             # rather than three times per successful call. It exists to
             # diagnose parameter materialization; a run that never fails never
             # needs it, and paying for it on every dispatch charged the
             # candidate's measured saving for diagnostics it did not use.
             logger.warning(
                 "Artifact %s failed at runtime for %s; falling back to native "
                 "execution for the rest of this run; materialization=%s",
                 decision.artifact_id,
                 wrapper.scope,
-                json.dumps(
-                    {"at_failure": self._parameter_snapshot(wrapper)},
-                    sort_keys=True,
-                ),
+                json.dumps({"at_failure": snapshot}, sort_keys=True),
                 exc_info=True,
             )
🧰 Tools
🪛 ast-grep (0.45.0)

[info] 319-322: use jsonify instead of json.dumps for JSON output
Context: json.dumps(
{"at_failure": self._parameter_snapshot(wrapper)},
sort_keys=True,
)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/optimization/dispatch.py` around lines 310 - 326, Guard the
diagnostic snapshot inside the runtime-failure handler around the fallback logic
near the artifact execution path. Ensure any exception from
self._parameter_snapshot(wrapper) is caught or replaced with a safe diagnostic
value, then still emit the warning and always reach return native(*args,
**kwargs), preserving native fallback even when snapshot collection fails.

Comment thread fastvideo/optimization/dispatch.py Outdated
Comment on lines +683 to +686
output = envs.FASTVIDEO_OPTIMIZATION_ARTIFACT_DIAGNOSTICS
if output:
session.write_diagnostics(output)
timing.write_report(Path(str(output)).with_name("timing.json"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Protect teardown from an invalid diagnostics path.

Path(str(output)).with_name("timing.json") runs outside any error handling. with_name raises ValueError when the path has no name component, for example FASTVIDEO_OPTIMIZATION_ARTIFACT_DIAGNOSTICS="." or "/". The exception then escapes detach_graph_dispatch and breaks teardown. session.write_diagnostics handles the same input safely because it catches internally.

🛡️ Proposed fix
     output = envs.FASTVIDEO_OPTIMIZATION_ARTIFACT_DIAGNOSTICS
     if output:
         session.write_diagnostics(output)
-        timing.write_report(Path(str(output)).with_name("timing.json"))
+        try:
+            report = Path(str(output)).expanduser().with_name("timing.json")
+        except ValueError:
+            logger.warning("Cannot derive a timing report path from %s", output)
+        else:
+            timing.write_report(report)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
output = envs.FASTVIDEO_OPTIMIZATION_ARTIFACT_DIAGNOSTICS
if output:
session.write_diagnostics(output)
timing.write_report(Path(str(output)).with_name("timing.json"))
output = envs.FASTVIDEO_OPTIMIZATION_ARTIFACT_DIAGNOSTICS
if output:
session.write_diagnostics(output)
try:
report = Path(str(output)).expanduser().with_name("timing.json")
except ValueError:
logger.warning("Cannot derive a timing report path from %s", output)
else:
timing.write_report(report)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/optimization/dispatch.py` around lines 683 - 686, Update the
diagnostics handling in detach_graph_dispatch so an invalid output path cannot
escape teardown: guard the timing report path construction and write, including
Path.with_name, with error handling while preserving session.write_diagnostics
behavior. Ensure values such as "." or "/" are safely ignored or logged without
preventing detach_graph_dispatch from completing.

Comment on lines +55 to +74
def _layout_identity(value: Any) -> tuple[Any, ...] | None:
"""Everything a capture bakes in about one tensor's memory.

``data_ptr()`` alone is not enough: it folds in ``storage_offset`` but says
nothing about strides, so a contiguous tensor and a permuted view handed
back the same allocator block compare equal while the captured kernels read
elements in a different order. Device and dtype are included for the same
reason -- the capture encodes all of it.
"""
try:
return (
value.data_ptr(),
tuple(value.shape),
tuple(value.stride()),
str(value.dtype),
str(value.device),
value.storage_offset(),
)
except Exception: # noqa: BLE001 - anything unreadable is not capturable
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

An unreadable input layout passes the pinned-address check.

_layout_identity returns None when it cannot read a tensor. At Line 362 the capture stores that None into self._pinned[index] without rejecting it. At Line 535 the replay then compares _layout_identity(live) != pinned. If the live tensor is also unreadable, both sides are None, the comparison passes, and the graph replays against an address the runner never verified.

The capture already rejects this case for bound attributes at Lines 418-421. Apply the same rule to pinned inputs.

🛡️ Proposed fix in `_capture`
             if stable[index]:
+                if pointers[index] is None:
+                    raise CudaGraphUnavailable(
+                        f"runtime input {index} has no readable layout"
+                    )
                 capture_inputs.append(value)
                 self._pinned[index] = pointers[index]
                 continue

Also applies to: 530-539

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/optimization/subgraph.py` around lines 55 - 74, Update _capture to
reject any pinned input whose _layout_identity returns None, matching the
existing unreadable bound-attribute handling. Ensure the capture fails before
storing None in self._pinned, and keep replay validation in the pinned-input
path from treating two None layouts as a valid match.

Comment thread fastvideo/optimization/subgraph.py Outdated
Comment on lines +815 to +818
# Value is (rewritten module, frozen placeholder contract): the contract is
# derived once per build so the per-call path never walks the graph.
cache: weakref.WeakKeyDictionary[nn.Module, _Entry] = weakref.WeakKeyDictionary()
graph_scope = _CudaGraphScope()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the stale cache comment.

The comment states the value is "(rewritten module, frozen placeholder contract)". The value is now an _Entry, which also carries cuda_graph.

✏️ Proposed fix
-    # Value is (rewritten module, frozen placeholder contract): the contract is
-    # derived once per build so the per-call path never walks the graph.
+    # Value is an _Entry holding the rewritten module, the frozen placeholder
+    # contract, and the optional CUDA graph runner. The contract is derived
+    # once per build so the per-call path never walks the graph.
     cache: weakref.WeakKeyDictionary[nn.Module, _Entry] = weakref.WeakKeyDictionary()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Value is (rewritten module, frozen placeholder contract): the contract is
# derived once per build so the per-call path never walks the graph.
cache: weakref.WeakKeyDictionary[nn.Module, _Entry] = weakref.WeakKeyDictionary()
graph_scope = _CudaGraphScope()
# Value is an _Entry holding the rewritten module, the frozen placeholder
# contract, and the optional CUDA graph runner. The contract is derived
# once per build so the per-call path never walks the graph.
cache: weakref.WeakKeyDictionary[nn.Module, _Entry] = weakref.WeakKeyDictionary()
graph_scope = _CudaGraphScope()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/optimization/subgraph.py` around lines 815 - 818, Update the cache
comment above the WeakKeyDictionary in the graph-scope initialization to
describe the value as an _Entry, including its rewritten module, frozen
placeholder contract, and cuda_graph; keep the cache declaration and behavior
unchanged.

Comment on lines +118 to +128
def reset() -> None:
"""Clear all accumulated timing state.

The counters are process-global, so two dispatch sessions in one process --
serving two models in sequence, say -- would otherwise contribute to the
same totals and produce a report attributable to neither. Call this when
starting a session whose measurements must stand alone.
"""
_totals.clear()
_counts.clear()
_notes.clear()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find every call site of timing.reset outside the tests.
rg -n -C3 --type=py 'timing\.reset\s*\(|from fastvideo\.optimization import timing'

Repository: aryan5v/FastVideo

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)timing\.py$|dispatch.*\.py$|test.*timing.*\.py$' || true

echo
echo "== timing calls/import search case-insensitive exact =="
rg -n -C2 --type=py 'timing\.reset\s*\(|\.reset\s*\(\)|reset\s*\(\)' || true
rg -n -C2 --type=py 'from fastvideo\.optimization import timing|from fastvideo\.optimization\. import timing|fastvideo/optimization/timing' || true

echo
echo "== fastvideo/optimization/timing.py outline/contents =="
wc -l fastvideo/optimization/timing.py
sed -n '1,220p' fastvideo/optimization/timing.py

echo
echo "== dispatch imports and timing usage =="
rg -n -C2 --type=py 'timing|timing\.' fastvideo || true

Repository: aryan5v/FastVideo

Length of output: 394


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== timing.py relevant lines =="
sed -n '1,220p' fastvideo/optimization/timing.py

echo
echo "== dispatch.py timing-related context =="
rg -n -C3 'timing|write_report|record|reset|snapshot' fastvideo/optimization/dispatch.py || true

echo
echo "== timing calls across Python files (broad exact name) =="
rg -n -C2 --type=py '\.reset\s*\(|timing\.reset|record\s*\(|snapshot\s*\(|write_report\s*\(' fastvideo || true

echo
echo "== timing module references across all tracked text files =="
rg -n -C2 'timing\.record|timing\.snapshot|timing\.write_report|timing\.reset|reset\(\)' . || true

Repository: aryan5v/FastVideo

Length of output: 50373


Call timing.reset() before starting each dispatch session.

timing.phase(...), timing.record(...), and timing.note(...) all use process-global state. timing.write_report() is only at the diagnostics path, while production dispatch code never clears totals before accumulating new sessions. A multi-model single-process run can mix timing data unless timing.reset() is called at session start.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/optimization/timing.py` around lines 118 - 128, Call timing.reset()
at the beginning of every dispatch session, before any timing.phase(),
timing.record(), or timing.note() calls occur. Update the dispatch-session entry
point rather than the timing.reset() implementation, ensuring each model/session
starts with cleared process-global timing state while preserving existing report
generation.

Comment on lines +1470 to +1498
def test_placeholder_contract_is_derived_once_not_per_call():
"""The per-call path must not walk the graph to rediscover placeholders."""
import operator

from torch import fx

from fastvideo.optimization.subgraph import (
_placeholder_contract,
_validate_runtime_inputs,
)

graph = fx.Graph()
first = graph.placeholder("x")
first.meta["val"] = torch.zeros(2, 3)
second = graph.placeholder("y")
second.meta["val"] = torch.zeros(4)
for index in range(200):
graph.call_function(operator.add, args=(first, index))
graph.output(first)

contract = _placeholder_contract(graph)
assert contract == (("x", ((2, 3), "torch.float32")), ("y", ((4,), "torch.float32")))

_validate_runtime_inputs(contract, [torch.zeros(2, 3), torch.zeros(4)])

with pytest.raises(SubgraphRewriteError, match="metadata changed"):
_validate_runtime_inputs(contract, [torch.zeros(2, 5), torch.zeros(4)])
with pytest.raises(SubgraphRewriteError, match="input count differs"):
_validate_runtime_inputs(contract, [torch.zeros(2, 3)])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The test name claims more than the body verifies.

test_placeholder_contract_is_derived_once_not_per_call checks the contract value and the two _validate_runtime_inputs failure modes. It never checks that the per-call path avoids walking the graph. The 200 filler nodes at Lines 1486-1487 make the walk expensive but nothing measures or counts it.

Either rename the test to describe what it asserts, or add an assertion that dispatch derives the contract once. Counting calls to _placeholder_contract with monkeypatch would cover the caching claim, in the same style as test_dispatch_does_not_snapshot_parameters_on_the_success_path at Line 1501.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/tests/optimization/test_dispatch.py` around lines 1470 - 1498,
Update test_placeholder_contract_is_derived_once_not_per_call to verify its
caching claim by monkeypatching _placeholder_contract and asserting dispatch
invokes it only once across repeated calls, following the pattern used by
test_dispatch_does_not_snapshot_parameters_on_the_success_path. If dispatch
cannot be exercised here, rename the test to reflect only contract derivation
and runtime validation.

Comment on lines +1717 to +1732
def test_warmup_records_input_addresses_for_the_stability_decision():
from fastvideo.optimization.subgraph import (
CudaGraphUnavailable,
_CudaGraphRunner,
_CudaGraphScope,
)

runner = _CudaGraphRunner(runnable=None, scope=_CudaGraphScope())
tensor = torch.zeros(4)
for _ in range(_CudaGraphRunner.WARMUP_ITERATIONS):
with pytest.raises(CudaGraphUnavailable, match="warming up"):
runner([tensor])
from fastvideo.optimization.subgraph import _layout_identity

assert len(runner._observed) == _CudaGraphRunner.WARMUP_ITERATIONS
assert all(seen == [_layout_identity(tensor)] for seen in runner._observed)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

These assertions expose two wrong type annotations in the runner.

The test asserts that each entry of runner._observed is a _layout_identity tuple. In fastvideo/optimization/subgraph.py the annotations declare integers:

  • Line 313: self._observed: list[list[int | None]] = []
  • Line 315: self._pinned: dict[int, int] = {}

_layout_identity returns tuple[Any, ...] | None, and Line 362 stores that value into _pinned. The coding guidelines list mypy among the configured tools, so the annotations should match.

✏️ Proposed fix in `fastvideo/optimization/subgraph.py`
-        self._observed: list[list[int | None]] = []
+        self._observed: list[list[tuple[Any, ...] | None]] = []
         #: index -> captured address, for inputs read where they live.
-        self._pinned: dict[int, int] = {}
+        self._pinned: dict[int, tuple[Any, ...] | None] = {}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_warmup_records_input_addresses_for_the_stability_decision():
from fastvideo.optimization.subgraph import (
CudaGraphUnavailable,
_CudaGraphRunner,
_CudaGraphScope,
)
runner = _CudaGraphRunner(runnable=None, scope=_CudaGraphScope())
tensor = torch.zeros(4)
for _ in range(_CudaGraphRunner.WARMUP_ITERATIONS):
with pytest.raises(CudaGraphUnavailable, match="warming up"):
runner([tensor])
from fastvideo.optimization.subgraph import _layout_identity
assert len(runner._observed) == _CudaGraphRunner.WARMUP_ITERATIONS
assert all(seen == [_layout_identity(tensor)] for seen in runner._observed)
self._observed: list[list[tuple[Any, ...] | None]] = []
#: index -> captured address, for inputs read where they live.
self._pinned: dict[int, tuple[Any, ...] | None] = {}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/tests/optimization/test_dispatch.py` around lines 1717 - 1732,
Update the type annotations in _CudaGraphRunner to match the layout identities
stored by the warmup logic: change _observed from nested integer lists to
entries containing the _layout_identity return type, and change _pinned to map
integers to that same layout-identity type. Preserve the existing initialization
and storage behavior while ensuring mypy accepts the assignments.

Source: Coding guidelines

Comment on lines +1853 to +1882
def test_a_mutable_output_is_refused_rather_than_aliased(monkeypatch):
"""A list output would hand back the graph's own static buffers."""
from fastvideo.optimization.subgraph import (
CudaGraphUnavailable,
_CudaGraphRunner,
_CudaGraphScope,
)

runner = _CudaGraphRunner(runnable=None, scope=_CudaGraphScope())

class _FakeGraph:
def replay(self):
pass

def reset(self):
pass

# Drive just the output-classification branch of _capture.
leaves = ([torch.zeros(2)],)
with pytest.raises(CudaGraphUnavailable, match="mutable list"):
runner._output_constants = {}
for position, leaf in enumerate(leaves):
if isinstance(leaf, torch.Tensor):
continue
if leaf is None or isinstance(leaf, (bool, int, float, complex, str, bytes)):
continue
raise CudaGraphUnavailable(
f"output {position} is a mutable {type(leaf).__name__}; "
"returning it uncopied would alias the graph's static buffers"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

This test asserts against a copy of the source, not against the runner.

Lines 1874-1882 re-implement the output-classification loop from _capture inside the test body and then assert that the copy raises. No production code runs. runner, _FakeGraph, and the monkeypatch fixture are created and never used.

The test therefore passes even if the real branch at fastvideo/optimization/subgraph.py Lines 393-402 is deleted. That branch is safety-critical: it prevents returning the graph's static buffers to the caller, which the next replay would overwrite.

Extract the classification into a helper that both _capture and this test call, then assert against the helper. A helper also keeps the test honest if the accepted-constant type list changes.

♻️ Suggested direction in `fastvideo/optimization/subgraph.py`
def _classify_output_leaves(leaves: tuple[Any, ...]) -> dict[int, Any]:
    """Return the non-tensor output constants, or decline a mutable leaf."""
    import torch

    constants: dict[int, Any] = {}
    for position, leaf in enumerate(leaves):
        if isinstance(leaf, torch.Tensor):
            continue
        if leaf is None or isinstance(
            leaf, (bool, int, float, complex, str, bytes)
        ):
            constants[position] = leaf
            continue
        raise CudaGraphUnavailable(
            f"output {position} is a mutable {type(leaf).__name__}; "
            "returning it uncopied would alias the graph's static buffers"
        )
    return constants

_capture then calls self._output_constants = _classify_output_leaves(leaves), and the test calls _classify_output_leaves(([torch.zeros(2)],)).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@fastvideo/tests/optimization/test_dispatch.py` around lines 1853 - 1882,
Extract the output-leaf classification logic into a shared helper near
_CudaGraphRunner, such as _classify_output_leaves, returning constant leaves and
raising CudaGraphUnavailable for mutable values. Update
_CudaGraphRunner._capture to use this helper, then change
test_a_mutable_output_is_refused_rather_than_aliased to call the helper directly
and remove the unused runner, fake graph, and monkeypatch setup.

aryan5v added 2 commits August 1, 2026 19:40
…ent/v1-r4-dispatch-fix

# Conflicts:
#	fastvideo/tests/optimization/test_fx_capture.py
@aryan5v
aryan5v merged commit a2b9529 into agent/graph-executable-ir Aug 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant