Repository navigation
Cosmos3-edge PR 6: Cosmos3-Edge as a cosmos3 config variant, with streaming rollout and action loop - #297
Conversation
347302d to
b20bb5e
Compare
NSagan271
left a comment
There was a problem hiding this comment.
I have several concerns about the handling of persistent sessions, which make the case for giving M* primitives for handling sessions instead of purely putting it into model code:
- The decoder and DiT also don't agree on the LRU ordering: the DiT promotes on read at submodules.py:701, the decoder doesn't at :2737
- If there are 8 in-flight requests with live sessions and a 9th is added, then one of the in-flight sessions gets evicted from the session store and will fail with "Cosmos3 resume_session: unknown or expired session {session_id!r}." when it's next scheduled.
- Two concurrent requests can specify the same session_id, in which case both are writing to the same session (with potentially different data), and the last write wins.
- There's no TTL on the sessions, which is fine w.r.t. memory usage since states are small and it's a bounded queue, but does mean that a client that crashes mid-rollout leaves a resumable world state with no way to release it.
I loosely envision adding the following to /generate:
start_session: boolsession_timeout_s: int(with some default and maximum; we don't want sessions living on forever)session_id: str | None(ifNoneandstart_sessionis set, then the server creates a UUID for the session ID and returns it as an initial "special" result chunk.resume_session: bool(requires a session id)end_session: bool(requires a session id)
Then, the system can handle the resume session logic, persistent session state (e.g., having a sessions_states handled by the engine that mirrors the NodeSubmodule.request_states, giving Resources the option to retain state over a session instead of just at the request level), session timeout / cleanup, handling what happens if there are multiple in-flight requests with the same session (at least in the cosmos case, it seems like two requests with the same session is undefined behavior, but that might not be the case in the instance of bidirectional streaming).
@merceod I'm not sure if system-level abstractions for session state should be a blocker for this PR, but I can get started on it and put up a PR, since I have an idea of what it should look like.
There was a problem hiding this comment.
Nit: it might be better organizationally for the cosmos3/tests files to all be in test/cosmos3 or test/modular/cosmos3 for consistency with other models
There was a problem hiding this comment.
mstar/model/cosmos3/tests/ is where main already keeps the ten cosmos3 test files (test_action, test_droid, test_engine_cache, ...), so the new ones followed it. Happy to move all of them to test/cosmos3/ in a follow-up PR if you want one layout across models; doing it here would touch files this PR otherwise leaves alone.
| "Cosmos3 window_mode='kv' does not support overlap_frames; " | ||
| "cross-window conditioning comes from the committed context." | ||
| ) | ||
| overlap_units = min(max(round(overlap_frames / tf), 0), window_units - 1) |
There was a problem hiding this comment.
Nit: If overlap_frames < tf/2, this could round down to zero, which I'd say is the expected behavior but maybe makes sense to emit a warning
There was a problem hiding this comment.
Added a warning when a non-zero overlap_frames rounds to no latent frames (bc5a5d6), with a test.
| self._session_tails[st["ar_session_id"]] = st["ar_tail"] | ||
| self._session_tails.move_to_end(st["ar_session_id"]) | ||
| while len(self._session_tails) > max(1, int(self.config.session_store_size)): | ||
| self._session_tails.popitem(last=False) |
There was a problem hiding this comment.
This LRU ordering disagrees with the DiT (submodules.py:701), which promotes on read
| return | ||
| self._session_tails[str(session_id)] = window_latents.detach().clone() | ||
| self._session_tails.move_to_end(str(session_id)) | ||
| while len(self._session_tails) > max(1, int(self.config.session_store_size)): |
There was a problem hiding this comment.
If there are 8 in-flight requests with live sessions and a 9th is added, then one of the in-flight sessions gets evicted from the session store and will fail with "Cosmos3 resume_session: unknown or expired session {session_id!r}." when it's next scheduled.
There was a problem hiding this comment.
Fixed by pinning: a request pins its session at the prefill, pinned sessions are never evicted (only idle ones count against session_store_size), and the pin goes when the request stores its last window or is removed (cleanup_request). A second request on a live session is refused with "session 'x' is in use by request 'y'" instead of both writing it (a3c1d7c, tests in d4fa718).
…s into Cosmos3Config
…k_norm_und_for_gen, lm_head, text_forward)
…(the reference kernel)
|
Agreed on the split, and yes please, go ahead with the system-level session state; I'll move the cosmos3 store onto it when it lands. For now the model-side store handles the four points: one Also on this branch since your review: the KV retention now rides on the commit step's |
NSagan271
left a comment
There was a problem hiding this comment.
LGTM (pending the prerequisite branches being merged + a rebase0
Conflicts from the stacked PRs main squash-merged (#293-#296, #335) take main's reviewed versions; the action ws client's --warmup and the streamed-video router test stay. Cosmos3 code from this branch moves to fwd_info.rid_handle (main keys request state by it, and cleanup_request releases session pins with it), and the edge configs' warmups use files:. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kokoro (#278). The only conflicts are adjacent entries in the model table (docs/models.rst) and DEFAULT_CONFIGS (cli/main.py); both sides kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kokoro is text to audio; Waypoint takes a seed image and streams raw frames. Cosmos3-Edge's reasoner (mstar-project#297) answers chat in text, which intake refused.
* cosmos3 tests: set rid_handle in the engine-cache harness The DiT keys request state on rid_handle since #320. The harness left it at -1, so the engine checks failed with KeyError: 'scheduler'. * cosmos3 tests: same rid_handle fix in the action, sound and reasoner harnesses Also read per_rid_outputs from the graph runner, which returns a BatchedModelOutput since #258. The broad excepts in these checks had turned both problems into silent skips. * cosmos3 tests: read golden mp4s with PyAV torchvision 0.27 dropped io.read_video. Also read the PyAV stream before the container closes, newer PyAV refuses that. * cosmos3 tests: tokenize sound and action engine runs like the fused reference The reference pipeline stopped using the system prompt and templates in #297. These two tests still used them on the engine side, so they compared different prompts.
Adds
nvidia/Cosmos3-Edge(4B) to thecosmos3family as a config-driven variant, registrycosmos3_edgeandcosmos3_edge_droid. No fork. The dense Nemotron backbone (relu2 MLP, Nemotron RMSNorm ordering, no text QK-norm,k_norm_und_for_gen), the 480p defaults, the native flow schedule and the diffusers-0.40 i2v conditioning recipe areCosmos3Configknobs read from the checkpoint. The Edge reasoner (understanding tower + SigLIP2-style vision encoder) is served as a VLM on/v1/chat/completionsfrom the same transformer instance and KV pool as the DiT, with its decode step compiled before the CUDA-graph capture. Also registers the 4-step distilled Super checkpoints (cosmos3_super_t2i_4step,cosmos3_super_i2v_4step).New for serving a world model is the streaming rollout. A long clip is generated window by window (8 latent = 29 pixel frames at 480p), each finished window leaves the denoise loop on a streaming edge to a decoder node in its own partition, and the client receives frames while the next window is still denoising.
chainedmode re-pins the previous window's tail as clean frames,kvmode commits the window's K/V into the paged cache so later windows attend to it block-causally, with the context bounded by the retention policy from #293 (declared by the window's commit step). Asession_idkeeps that world state across requests: the DiT and the decoder keep it in aSessionStorethat pins a session while a request runs (never evicted, a second request on it is refused), ages idle ones out aftersession_timeout_s, and drops one onend_session. The action policy runs over the/generate/wsWebSocket from #294.Depends on #293 #294 #295 #296 (#292 merged). Their commits, review fixes included, are carried on this branch until they merge (a rebase drops them), so merge order is those four first, then this one. Parity against diffusers 0.40 and transformers 5.17 (fixed-seed PSNR per frame, engine-cache harness, reasoner greedy tokens) lives in the tests under
mstar/model/cosmos3/tests/.Benchmarks
H100 80GB, one GPU of one node, every system back-to-back on that GPU, warmup excluded, n=3 medians (2026-09-27; clocks, driver and versions in the run's
environment.txt). 832x480 unless noted, 20 UniPC steps, guidance 6, seed 0. M* isconfigs/cosmos3_edge.yaml(the DROID rowconfigs/cosmos3_edge_droid.yaml) at716bd83c.The reasoner is the one row M* loses at more than one stream. The GPU is not the reason: the decode graph runs at the weight-streaming floor, and a profile shows the step at 32 streams spending ~8 of its 10 ms in per-row CPU work in the worker, spread over the routing stages (route, register and send outputs, collect outputs, prepare inputs, ~30 us per row each) plus an eager 25 ms vision prefill that stalls every stream. Batching the stop check's host copies (#334, closed) changed nothing measurable there and cost 1-2 ms per step on small image tiers, so it was reverted after this run (the table's tree carried it; the video and action rows do not touch that path, and the t2i row can only have improved). The fix for the reasoner tail is a batched loop-back path in the worker, an engine design item, not a property of the model integration. The t2i number is the eager 640x640 tier, 20 launch-bound steps: it read 0.44 s on an idle node (2026-09-23) and 0.49 s on a node shared with seven other jobs; the GPU-bound rows did not move.
Two servers side by side on the same GPU, 2026-09-27: session-store stress (a second request on a live session is refused, live sessions are never evicted, sessions time out and can be ended), 869-frame kv and chained rollouts at 14 fps with flat memory, cancel storms, a mixed t2i+t2v+i2v+chat+action round, and reasoner cancel rounds all clean. Parity against diffusers 0.40 in fp32: i2v 56.8 dB (frame 0 exact), t2v 39.0 dB.
Also measured earlier (2026-09-17/18, two GPUs of one node at once, n=2, not redone under the one-GPU protocol): Nano at 832x480x121 with 35 steps 30.6 / 30.4 s on M* vs 30.9 / 30.8 on vLLM-Omni (i2v / t2v); the Nano 241-frame rollout streams its first window at 7.0 s where vLLM-Omni returns the clip whole after 76.4 s; Super 4-step distilled on TP2 does t2i 1024x1024 in 0.45 s and i2v 832x480x121 in 6.5 s.
Reproduce with
benchmark/cosmos3/reproduce_edge.sh(setSNAP,MSTAR,VLLM_OMNI_PY,VLLM_PY,SGLANGfirst):