Skip to content

Collect model input after cadence pacing - #573

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

ZenAlexa wants to merge 7 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

I collect input after cadence pacing so events received during the wait reach the upcoming model step. Reset also clears buffered step timing at that final input snapshot.

Addresses #543.

Validation

  • Deterministic regressions cover input received during pacing and reset before and during pacing.
  • V2 CPU suite: 210 passed, 3 deselected.
  • Ruff formatting, focused lint, and diff checks passed.

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

RetriggerConfidence Score: 2/5

The PR does not appear safe to merge because three previously reported message-settlement paths can still silently discard accepted model-loop operations.

Findings

  1. P1 Post-cadence messages are dropped

Summary

  • Extracts user-event incorporation into a reusable helper.
  • Re-reads and incorporates events immediately before model execution.
  • Adds deterministic cadence and reset regression coverage.

Diagram

sequenceDiagram
  participant UI as UI thread
  participant Buffer as EventBuffer
  participant Model as Model loop
  Model->>Buffer: Read current events
  Model->>Model: Prepare run and pace
  UI->>Buffer: Append event during pacing
  Model->>Buffer: Read fresh events
  Model->>Model: Incorporate events
  alt Step remains runnable
    Model->>Model: Execute upcoming step
  else Terminal lifecycle event
    Model->>Model: End loop
  end
Loading

Reviews (7) · Last reviewed commit: "Fix reset timing at the paced input snap..."

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.

Signed-off-by: Ziming Wang <zimingwang945@gmail.com>
Comment on lines +349 to +350
run = self._incorporate_user_events(events, generation)
if run.step_index is None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Post-cadence messages are dropped

When an asynchronous model-loop operation is accepted during cadence pacing and the second event read observes a close event or another terminal condition, _incorporate_user_events() terminates the loop without running another message batch. _shutdown() then discards the accepted operation, causing reset, restart, or other model-state requests to be silently lost.

@ZenAlexa

Copy link
Copy Markdown
Author

I've synced the cadence fix with current main and rerun the input-buffer and session lifecycle tests.

The regression continues to reproduce the stale input on the base branch and passes with the post-wait read.

@ZenAlexa

Copy link
Copy Markdown
Author

I checked the post-cadence queue concern against the shutdown contract: queued operations are discarded when the loop closes.

Reset events use the event-buffer generation, and session replacement goes through the UI loop. I'm keeping one message snapshot per iteration here.

Signed-off-by: Ziming Wang <zimingwang945@gmail.com>
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