Skip to content

Collect model input after cadence pacing - #573

Open
ZenAlexa wants to merge 5 commits into
NVIDIA:mainfrom
ZenAlexa:contrib/543-api-cleanup-investigate-the-large-input-to-model-step
Open

Collect model input after cadence pacing#573
ZenAlexa wants to merge 5 commits into
NVIDIA:mainfrom
ZenAlexa:contrib/543-api-cleanup-investigate-the-large-input-to-model-step

Conversation

@ZenAlexa

@ZenAlexa ZenAlexa commented Sep 3, 2026

Copy link
Copy Markdown

Summary

  • collect model-thread input after cadence pacing so events received during the wait reach the upcoming model step
  • add one deterministic CPU regression for input freshness

This removes up to one configured model-step interval from the avoidable input-to-model-step latency tracked in #543. _pace() is driven by frames_per_second_for_step on each model-loop iteration. BackpressureMode.BLOCK adds a separate wait when the presentation queue is full.

Terminal message settlement remains unchanged from main; its lifecycle contract stays outside this ordering fix.

Validation

  • PYTHONPATH=flashdreams uv run --no-project --with 'pytest>=8' --with 'pytest-manual-marker>=2' --with 'numpy>=1.24,<2.5' --with 'torch>=2.9' python -m pytest flashdreams/test_v2/test_event_buffer.py flashdreams/test_v2/test_session_runner.py -q — 56 passed
  • uvx --from ruff==0.12.7 ruff format --check flashdreams/flashdreams/api_v2/loop.py flashdreams/test_v2/test_session_runner.py
  • uvx --from ruff==0.12.7 ruff check flashdreams/flashdreams/api_v2/loop.py flashdreams/test_v2/test_session_runner.py
  • focused ty check on both changed files

Addresses #543

Signed-off-by: Ziming Wang <zimingwang945@gmail.com>
Copilot AI lite review requested due to automatic review settings September 3, 2026 14:19
@copy-pr-bot

copy-pr-bot Bot commented Sep 3, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@greptile-apps

greptile-apps Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR refreshes model-loop input after cadence pacing so events received during the wait are available to the upcoming model step.

  • Extracts event incorporation from _begin_run() for reuse after pacing.
  • Adds a second event-buffer read immediately before model execution.
  • Adds deterministic CPU coverage for input received during the cadence wait.

Confidence Score: 3/5

The PR does not appear safe to merge because finished model-loop paths can still discard asynchronously accepted state operations.

ZenAlexa reported both terminal-settlement races fixed, but current HEAD no longer contains the atomic finished-state settlement: it checks completion and exits before message acceptance is closed, allowing accepted operations to be drained without execution.

Files Needing Attention: flashdreams/flashdreams/api_v2/loop.py

Important Files Changed

Filename Overview
flashdreams/flashdreams/api_v2/loop.py Refreshes buffered input after pacing, while the previously reported terminal message-settlement races remain present at HEAD.
flashdreams/test_v2/test_session_runner.py Adds deterministic coverage proving that input appended during cadence waiting reaches the next model step.

Reviews (5): Last reviewed commit: "Merge remote-tracking branch 'upstream/m..." | Re-trigger Greptile

Comment thread flashdreams/flashdreams/api_v2/loop.py Outdated
Signed-off-by: Ziming Wang <zimingwang945@gmail.com>
Comment thread flashdreams/flashdreams/api_v2/loop.py Outdated
Signed-off-by: Ziming Wang <zimingwang945@gmail.com>
@gtong-nv

gtong-nv commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

The model loop snapshots EventBuffer before _pace(). Input arriving during that cadence wait reaches the following model step, adding up to one configured step interval.

Thanks @ZenAlexa , this is indeed an issue. With our current design, we need to do two paces for the model loop and UI loop.

[[collect events] -> [run] -> [model pace for BACKPRESSUE]  -> [present] -> [UI/present pace for PRESENTATION MODE]] -> [[collect events]

Your PR should address issue with the model pace order

read events A
    |
    +------ model rate-limit wait ------+
                                        |
                 event B arrives here --+--> not included
                                        |
                              model.step(N, A)

But model pace will only happen when BLOCK mode is used in BACKPRESSUE, and happens when model generation is faster than UI rate (which defaults to 60hz), which is rare.
I would suggest clear the PR to only address this issue.
The other changes listed below, don't seem to be relevant

make finished-loop settlement atomic with both reset generation and asynchronous message acceptance
execute every message accepted before the terminal cutoff as one finite batch, then resume generation when that batch reopens model state

Keep event collection immediately after model pacing.
Restore the terminal lifecycle path for separate follow-up.

Signed-off-by: Ziming Wang <zimingwang945@gmail.com>
@ZenAlexa

ZenAlexa commented Sep 3, 2026

Copy link
Copy Markdown
Author

Yep, I agree on keeping this PR tight 👍

One detail I found while tracing it: _pace() is currently driven by frames_per_second_for_step on every model-loop iteration. BackpressureMode.BLOCK adds a separate wait when the presentation queue fills. The ordering issue therefore exists across both modes, and its visible cost is largest when pacing actually sleeps.

I’ve trimmed #573 down to the ordering change and one focused regression. The terminal message settlement is a real lifecycle issue with its own boundary, so I’ve taken it out of this PR and will keep that work separate.

…anup-investigate-the-large-input-to-model-step

Signed-off-by: Ziming Wang <zimingwang945@gmail.com>

# Conflicts:
#	flashdreams/flashdreams/api_v2/loop.py
@ZenAlexa

ZenAlexa commented Sep 3, 2026

Copy link
Copy Markdown
Author

Caught up with #548's multi-session lifecycle in 8e174e70.

The model loop now reads once before cadence for lifecycle and finished-state decisions, then folds in one more event-buffer snapshot after cadence before the step. Queued state messages keep their once-per-model-step ordering. The focused CPU suite passes all 61 tests (•̀ᴗ•́)و

@ZenAlexa

ZenAlexa commented Sep 3, 2026

Copy link
Copy Markdown
Author

Yep, the race exists on current main. #573 intentionally removed that lifecycle change in f7913e96, and the current two-file diff leaves terminal settlement unchanged, matching the scope request above. invoke_async currently documents queued shutdown drops. This makes it a lifecycle-contract decision, and the cadence ordering diff remains isolated. I split that decision into #578.

while not self._shutdown_event.is_set() and (
max_steps is None or steps_run < max_steps
):
events, generation = event_buffer.read(reader_id)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

event_buffer.read(reader_id) is called twice in this loop.
here and in line 333.
This doesn't seem right.. I will look into this

@ZenAlexa

ZenAlexa commented Sep 5, 2026

Copy link
Copy Markdown
Author

The second event_buffer.read(reader_id) is intentional and defines the cadence boundary:

  • The first read feeds _begin_run() before the cadence wait. It handles lifecycle events, reset generation, and the pre-wait snapshot.
  • The second read runs after the wait and advances the same reader cursor. It collects events appended during the wait and folds them into the pending events through _incorporate_user_events() before step().

EventBuffer.read() advances each reader to the buffer end, so the first snapshot is not duplicated. The regression test_model_loop_collects_input_after_pacing appends a keyboard event from the patched cadence wait and verifies that it reaches the next model step. The focused test_session_runner.py suite passes 61 tests on this head.

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.

3 participants