Skip to content

Overworld Waypoint1.5B integration - #251

Open
garv901 wants to merge 19 commits into
mainfrom
waypoint-integration
Open

garv901 wants to merge 19 commits into
mainfrom
waypoint-integration

Conversation

@garv901

@garv901 garv901 commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Overview

This PR adds Waypoint 1.5 1B, an interactive video world model, to mstar. Given a seed clip and a sequence of user actions, Waypoint maintains an internal world and streams generated RGB frames as that world advances.

Model shape

Prime once:
seed clip -> VAE encoder -> DiT [seed world state; no client output]-> VAE decoder

Roll out N times:
action + world state -> DiT -> VAE decoder -> 4 RGB frames -> client

This maps to three mstar nodes and two graph walks:

  • prime initializes the DiT world state and the streaming decoder.
  • rollout generates one latent frame per iteration and streams the decoded frames immediately.
  • Each DiT rollout performs four denoising passes followed by one pass that commits the new frame to the world state.

Added video_frame as a separate modality

The existing video modality represents a complete encoded video artifact, such as an H.264 MP4. The container is finalized after generation and returned as one playable result. Waypoint has a different output shape. It generates four frames per rollout step and must send them immediately while the world continues running.video_frame therefore represents an ordered batch of raw RGB24 frames.

Resource boundary

In mstar, a resource is state or execution support that the engine must coordinate around a forward pass through admission, planning, stable allocation, commit, and cleanup.

Waypoint’s ring KV cache meets that definition: it is the persistent world state, must survive CUDA graph capture, assigns isolated world slots to requests, and must be reset when a request ends. FlexAttention is also an engine resource planned over that cache. The encoder and decoder remain normal model submodules because they do not require shared capacity or engine-level admission.

KV configuration

The previous KVConfig described both common KV geometry and paged-cache behavior. Waypoint uses a fixed-size ring that overwrites old frames, so fields such as page size, maximum pages, and offload policy do not apply.

We split the configuration into:

  • KVConfig: geometry shared by all KV caches.
  • PagedKVConfig: the existing append-only paged storage policy.
  • RingKVConfig: Waypoint’s fixed-horizon, layer-specific ring policy.

KVSpec selects the appropriate manager from the concrete config. Existing models continue to use the same paged behavior, now named explicitly, while invalid storage-specific options fail instead of being silently ignored.

Async Execution

  • Current design would cause loop external inputs to land inside ready_signals each iteration, hence would block speculative execution as it would not fall in either ready_next_iter or spec_signals bucket which is needed for same node speculation. Added a third bucket carried_names to address this.
  • The DiT node's completion can allow 2 nodes to execute after it -> either the TAEHV decode node for the Nth iteration or N+1th on the DiT node itself. Such fanout scenarios are currently not addressed in mstar. For the first cut, I have fused these nodes but I will explore the ideal approach to handle this within this PR.

Benchmarking numbers [FLASH backend]

360p

Batch size Startup (s) TTFF p50 (ms) TTFF p95 (ms) Gap p50 (ms) Gap p95 (ms) Aggregate fps On-time chunk % Realtime
1 154.15 49.14 49.14 12.68 14.96 297.57 100.00 yes
2 279.32 87.46 117.46 13.84 18.81 520.21 100.00 yes
4 400.44 146.53 217.29 19.54 30.01 705.03 100.00 yes
8 276.33 268.05 308.57 32.87 52.12 856.86 98.41 yes
16 388.42 386.95 640.00 62.69 105.41 887.55 57.14 no

720p

Batch size Startup (s) TTFF p50 (ms) TTFF p95 (ms) Gap p50 (ms) Gap p95 (ms) Aggregate fps On-time chunk % Realtime
1 197.22 125.19 125.19 20.61 41.25 156.24 100.00 yes
2 471.55 208.42 278.84 42.04 82.86 177.41 82.54 no
4 637.73 382.25 585.63 81.68 130.97 183.72 17.46 no
6 786.87 560.36 841.88 124.51 193.03 180.00 4.76 no

Gap p50 = median across streams; Gap p95 = worst stream's p95. On-time chunk % = worst stream's fraction of chunks within the 66.7 ms budget. Realtime = every stream sustained ≥1× with gap p95 ≤ 66.7 ms and no stalls. Headline: 360p realtime to batch 8; 720p realtime only at batch 1.

Follow-up TODOs

  • Async sched enabled but current implementation fuses the DiT and Decoder nodes into 1 which is not ideal.
  • Add long-lived interactive sessions. Requests currently provide all actions up front. Real interaction requires live action ingress, reconnect and cancellation behavior, idle timeouts, and a policy for late or missing actions.
  • Finish PR cleanup. Resolve outstanding review comments, remove temporary implementation notes, and retain only NVTX markers with a clear long-term profiling use.

@garv901 garv901 changed the title Overworld Waypoint1.5B integration [Draft] Overworld Waypoint1.5B integration Sep 13, 2026
@garv901 garv901 changed the title [Draft] Overworld Waypoint1.5B integration [WIP] Overworld Waypoint1.5B integration Sep 14, 2026
@stephen-dwq

Copy link
Copy Markdown
Collaborator

In the new KV-ring resource:

Writes can only accept a fixed number of tokens per step; however, I am not sure if future models with ring KV mechanics will depart from this, or if it will be easy to book-keep for a dynamic write-size system.

Why is admit restricted to max_batch_size=1, with the restriction not applying elsewhere? Why can't we admit multiple RID at once?

Nit: num_worlds (in kv/ring/) is waypoint-specific, and should be replaced with num_slots or num_sessions; world_idx should be replaced by span_idx.

@garv901
garv901 requested a review from kamahori September 14, 2026 23:13
@garv901
garv901 force-pushed the waypoint-integration branch 2 times, most recently from 5a3c562 to 25ffb5e Compare September 17, 2026 00:17
@garv901

garv901 commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator Author

In the new KV-ring resource:

Writes can only accept a fixed number of tokens per step; however, I am not sure if future models with ring KV mechanics will depart from this, or if it will be easy to book-keep for a dynamic write-size system.

Why is admit restricted to max_batch_size=1, with the restriction not applying elsewhere? Why can't we admit multiple RID at once?

Nit: num_worlds (in kv/ring/) is waypoint-specific, and should be replaced with num_slots or num_sessions; world_idx should be replaced by span_idx.

@stephen-dwq Yeah that makes although for the scope of this model port I was thinking we can stuck to the fixed KV ring structure ? I have enabled batching in my latest commit, had deferred for ease of implementation and code review for myself initially. Addressed the nit.

@garv901
garv901 marked this pull request as ready for review September 19, 2026 20:25
@garv901 garv901 changed the title [WIP] Overworld Waypoint1.5B integration Overworld Waypoint1.5B integration Sep 19, 2026
garv901 and others added 14 commits September 22, 2026 04:16
Native port of the Waypoint 1.5-1B autoregressive video world model.
Additive only -- no existing mstar file is touched.

- config.py         WaypointConfig, 720P and 360P variants, ring sizing.
- components/       rope.py (OrthoRoPE over disjoint head-dim bands),
                    layers.py (AdaLN-Zero, MLPFusion, CondHead),
                    attention.py (GQA with value-residual threading),
                    kv_backend.py (model-owned ring + FlexAttention
                    BlockMask), dit.py (24 blocks, the 4+1 pass loop).
- weight_loader.py  maps the 1.86B-parameter checkpoint onto the 1.28B
                    instantiated model, including the cond_proj tie shared
                    24x and re-established after to_empty().

Three choices worth flagging at review time.

The KV cache is a model-owned ring and get_node_resources() returns [].
For this model the cache *is* the world state, so eviction is amnesia
rather than a cost. An engine-owned ring is not currently expressible:
SequenceView asserts start == 0 ("dense attention reads a stream from its
first page"), and RetentionPolicy is declared but unimplemented. When both
land the swap is local to kv_backend.py and nothing else changes.

FlexAttention is used instead of FlashInfer, at a model-local seam, for
numerical parity with the reference. Attention is per-layer heterogeneous
(18 local dense window-16, 6 global stride-8 dilated window-128), which the
BlockMask expresses directly.

There is no working eager mode. Eager ignores a no-op-mask_mod BlockMask
and silently attends to unwritten ring slots -- wrong output, no error --
so the flex_attention call is compiled unconditionally.
120 cases across three files.

- test_waypoint_components.py  38 cases: RoPE band layout, ring wraparound,
                               BlockMask shape, AdaLN-Zero, CondHead tying.
- test_waypoint_dit.py         21 cases: the 4+1 pass structure, value
                               residual threading, per-layer local/global
                               heterogeneity, ring commit semantics.
- test_waypoint_weight_loader.py  61 cases, covering four defects an
                               independent audit found -- two of them
                               silent. A pre-fused qkv_proj alongside split
                               shards would load both, because conflicts
                               were keyed on (target, shard_id) and so
                               (t, None) never collided with (t, "q");
                               safetensors' sorted key order then gave Q
                               and K from the fused blob and V from v_proj.
                               And the cond_proj tie probe compared only the
                               first 64 columns, so a divergent block passed
                               clean. Each regression test was confirmed to
                               fail against the pre-fix loader.

Mutation-checked rather than coverage-checked: 10 injected mutants in the
components and DiT, all killed.

What these do NOT cover, since they run on CPU in fp32 against synthetic
state dicts: the real checkpoint, bf16, the compiled regions, and a
mislabelled K/V pair on disk. The last is worth calling out -- n_heads=32
over n_kv_heads=16 means a checkpoint with k_proj and v_proj transposed
relative to what the reference wrote would load with matching shapes and
produce only wrong output. Nothing static distinguishes it; only a forward
parity run against the reference can.
…rame streaming

Model: dit, taehv, rope, attention, layers, weight_loader, checkpoint, submodules.
Engine: ring KV backend, flex attention resource, CUDA graph buckets.
Serving: video_frame modality across api_server, data_worker and client SDK.
Tests: component, equivalence, pixel, packaging and streaming benchmark suites.
The api_server process emitted no NVTX at all: APIServer was constructed
without enable_nvtx, so the flag only ever reached the conductor. Traces
therefore ended at worker.send_outputs, leaving ~90ms of the 116.9ms 720p
inter-chunk gap unattributed.

Adds: apiserver.b64encode / json_dumps / chunk_available / yield_line,
dataworker.get_tensor / postprocess / queue_output, cg.replay.slot, and
benchmark.stream / await_chunk / chunk_arrival to anchor the client clock.
All gated on the existing --enable-nvtx flag.
NDJSON has to base64 each chunk so it fits on one line. At 720p that
turns 11 MB into 14.7 MB, and json.dumps then scans every character of
it. That is more CPU than the 66.67 ms frame budget allows, so the event
loop stalls and the socket jams on backpressure.

Binary framing sends a JSON header with nbytes, then the payload
untouched. Nothing has to scan for a delimiter, so nothing needs
escaping or encoding. NDJSON stays the default; clients opt in via
Accept.

Also lands the client-side NVTX ranges used to measure this, gated on
enable_nvtx.
Startup p95 -7.6% at 720p and -14.2% at 360p for 0.12 MB more graph memory.
…waypoint

Loop-external inputs (re-injected into ready_signals each iteration) now count as
ready for same-node speculation. Waypoint fuses TAEHV decode into the DiT step and
runs the rollout loop async with a clock self-edge; 720p payload SHA unchanged.
…ame_noise drew fp32 on a CPU generator and copied to device. That pageable H2D was a blocking copy
…9x/1.74x faster per call at 720p/360p

The Triton flex kernel runs one 4-warp CTA per SM (~27% of tensor peak on H100). The
sm90 kernel from the flash-attn-4 CuTe package runs 3 warpgroups per CTA with TMA loads
and softmax/GEMM overlap across warpgroups, so the same ring-masked attention gets far
more parallelism per SM. Ring-full median per call: 720p 129.4 -> 72.3 us (12.5 -> 7.0
ms/step, 2.62 -> 3.10x realtime), 360p 35.1 -> 20.2 us (3.46 -> 2.03 ms/step, 4.19 ->
4.99x). MSTAR_FLEX_BACKEND=TRITON restores the previous kernel bit-for-bit. flash-attn-4 is
installed separately (docs/installation.rst). flashinfer FA3 paged prefill was only
1.12x at batch 1; revisit it once batch size > 1 is in play.
The ring KV cache called its per-request partition a "world", which is
waypoint-specific jargon. Rename it to "session" across the KV resource
layer and the code that drives it: num_worlds->num_sessions,
world_idx->session_idx, world_span->session_span, world_base->
session_base, _free_worlds->_free_sessions, total_worlds->total_sessions,
world_of->session_of and the remaining world_* helpers, plus the bare
world/worlds nouns inside kv/ring, flex and the kv config.

Used session_idx rather than the suggested span_idx: "span" already names
the (lo, hi) KV tensor slice a session occupies (session_span), so
span_idx there would be ambiguous.

Left untouched: the distributed rank count (world_size / tp_world_size),
the separate world-model / world_engine abstraction in model/waypoint,
and the benchmark harness --worlds flag.
@garv901
garv901 force-pushed the waypoint-integration branch from ebd1f69 to b643c9b Compare September 22, 2026 04:26
CI runs `ruff check --output-format=github .` and was failing on 12
findings across waypoint code:

- B905 zip() without strict=: flex._stage, serve_rollout diff summary,
  test_waypoint_shell, and 5 sites in benchmark_streaming. Parallel
  arrays that must match length get strict=True; the adjacent-pairs
  zip(timestamps, timestamps[1:]) keeps its length-off-by-one intent
  with strict=False.
- W291 trailing whitespace in the frame-noise docstring (submodules).
- PLW0108 lambda: object() -> object for the graph_pool_handle stub
  (identical: both return a fresh object when called).
- PLR1730 if/assign -> max(max_abs_diff, diff) in serve_rollout.
…nce gates

Untrack (kept local) the benchmark/profiler/summary meta-tests and the nsys replay parser; no peer model port (wan22, orpheus, pi05, vjepa2, ...) commits benchmark/profiler pytest tests. Drops test_batch_sweep_summary.py (also a hardcoded out-of-repo _tools path), test_waypoint_streaming_benchmark.py, test_waypoint_profiler.py, and test/waypoint/check_nsys_replay.py from the tree.

Share _load_reference and _seed_clip across the equivalence gates via test_waypoint_reference_equivalence.py instead of the 360p file re-implementing them, and skip the pixel tests that need the second 'repro' oracle recording when it is absent rather than hard-failing.
Condense essay docstrings and drop historical notes, baked-in perf numbers, and references to local-only docs (VALIDATION.md, OPTIMIZATION_BACKLOG.md) across the model port, matching the terse functional comment style of peer ports (orpheus). Keeps the non-obvious why (numerics/perf hazards, ordering constraints, shape/dtype contracts). Comments and docstrings only; no code changed (AST-verified, py_compile clean). ~370 lines removed across 9 files.
benchmark_streaming.py gains --protocol {binary,ndjson} (default binary) to force the base64 NDJSON path for A/B runs against the raw-bytes streaming format; wires enable_nvtx and prefer_binary through MStarClient and records stream_protocol in the report.
Comment/docstring-only cleanup across the Waypoint test suite, oracle/serving
scripts, and config, matching peer model ports (wan22, pi05, orpheus). No code
changed - verified by comparing docstring-stripped ASTs against HEAD.

- test/modular/test_waypoint_*.py: trim narrative docstrings, drop dead-doc
  references left by the model-folder prune (T*/PARAM_TREE/S* labels), and
  remove verbatim duplicates between module and per-test docstrings; keep
  WHY-only comments, the F1-F4 regression catalog, and cross-referenced banners.
- test/waypoint/record_oracle.py: 81-line module docstring -> 38, keeping the
  artifact layout, numerics/reproducibility contract, and usage.
- test/waypoint/{serve_rollout,benchmark_streaming}.py: tighten docstrings;
  keep the isolation-gate invariants and protocol rationale.
- configs/waypoint.yaml: essays -> terse invariants (values unchanged).
- .gitignore: revert the branch's additions (.claude_scratch/, docs/waypoint/,
  WAYPOINT_PROGRESS.md) to keep main's .gitignore unpolluted.
@garv901
garv901 force-pushed the waypoint-integration branch from efcb345 to 330cfed Compare September 23, 2026 00:53

This branch has not been deployed

No deployments
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.

2 participants