[RFC]: LingBot-World-Fast — Interactive video world model port
#3,072 opened on Apr 23, 2026
Repository metrics
- Stars
- (4,990 stars)
- PR merge metrics
- (PR metrics pending)
Description
Motivation
Port LingBot-World-Fast
(Robbyant/lingbot-world, Apache 2.0) to vllm-omni as a new diffusion pipeline under vllm_omni/diffusion/models/lingbot_world_fast/. LingBot-World-Fast (LWF-Fast) is the accelerated, streaming variant of LingBot-World (arXiv 2601.20540), a single-image → camera-controlled interactive video world model built on the Wan 2.2 A14B DiT backbone. This RFC plans the first supported model for the interactive-video track of RFC #1987, and sits alongside the robotics-control track represented by PR #2885 (LingBot-VA). The model is Apache-2.0 end-to-end, fits a single 80 GB device in BF16 at 480×832, and reuses the Wan 2.2 DiT primitives, Wan 2.1 VAE, and UMT5 text encoder already in-tree.
Proposed Change
1. Overview
LWF-Fast takes a single image, a text prompt, and a camera trajectory (per-frame intrinsics + poses) and produces a video rendered from the camera's point of view, streamed chunk-by-chunk in real time. The model is a causal autoregressive DiT (distilled to 4 inference steps per chunk), chunk-decoded three latent frames at a time, with two per-layer KV caches (self-attention andcross-attention) persisted across chunks in a per-session state container. Camera conditioning is injected per block as AdaLN-style scale+shift from a per-pixel 6-dimensional Plücker ray representation (get_plucker_embeddings(intrinsics, poses)).
LWF-Fast supports exactly one control modality: camera poses. It is not action-conditioned; the sibling LingBot-World-Base (Act) checkpoint that accepts WASD keyboard inputs is a separate, not-yet Apache-unblocked release and is explicitly out of scope here (see §2.2). This is confirmed by the upstream Model Download table at github.com/Robbyant/lingbot-world (Base (Cam): Camera Poses; Base (Act): Actions; Fast: Camera Poses) and by the 4-step Fast pipeline's control_type='cam' code path in wan/image2video_fast.py.
Serving shape. LWF-Fast is exposed through a realtime WebSocket endpoint at /v1/realtime/world/camera, aligned
with the interactive-streaming API pattern already established by PR #2162 (DreamZero's /v1/realtime/robot/openpi) and by vllm-omni's TTS realtime endpoint. The URL names the protocol family — "realtime camera-trajectory → video" — not the model (following PR #2162's precedent, where /robot/openpi names the wire shape, not DreamZero specifically; the loaded model is determined by the vllm serve <model> CLI at startup). A client opens a session, receives a server config handshake, then sends one camera-pose request per chunk and receives one encoded video chunk back; the session's KV cache and frame buffer persist across requests for the lifetime of the connection. A request/response /v1/videos convenience wrapper (bundling a full precomputed trajectory into one session under the hood) is a natural follow-up for offline / simulation use; not in v1.
Context: LWF-Fast complements PR #2885 (LingBot-VA) by establishing the other endpoint of RFC #1987's "world model" scenario table. Both share the Wan 2.2 backbone, Wan 2.1 VAE, UMT5 text encoder, and a session-scoped KV-cache streaming pattern. LWF-Fast adopts the realtime-WebSocket + session-state-container serving shape that PR #2162 introduced; the per-session state container (holding KV cache, frame buffer, and RoPE current_start offset) is the vllm-omni precedent we align with. No engine-level KV-cache abstraction is introduced here; a unified PageAttention-style KV manager for autoregressive diffusion is explicitly RFC #1987's downstream plan, not this PR's concern.
2. Scope & Objectives
2.1 In scope for this PR
- New diffusion model directory at
vllm_omni/diffusion/models/lingbot_world_fast/with the Fast-variant transformer, pipeline, weight loader, per-session state container, and single-GPU stage config. - Realtime WebSocket endpoint at
/v1/realtime/world/camera, mirroring the shape PR #2162 introduced for DreamZero (msgpack binary framing, handshake with a model-specificServerConfig, per-chunkinferandresetendpoints within one session). Serving handler undervllm_omni/entrypoints/openai/realtime/world/. - Camera-pose control carried in the per-chunk request payload as
cameras: {intrinsics: [N,4], poses: [N,4,4]}(msgpack dict). OpenCV convention, ViPE / Nerfstudio / COLMAP compatible. Text prompt carried asprompt: strin the same payload. - Session-scoped state container (
LingBotWorldFastState) owning: the two KV caches (self + cross, per-layer) with their_pos/_negCFG-parallel variants, the frame buffer, the RoPEcurrent_startoffset, and the reset logic triggered by session-id change or prompt change. Modelled onDreamZeroStatefrom PR #2162. - 4-step distilled inference, schedule applied at pipeline setup against the in-tree
FlowUniPCMultistepScheduler(timestep indices[0, 179, 358, 679]out of 1000; no new scheduler class). - Chunked causal decoding with an explicit cache-update forward at
t≈0on clean latents after each chunk; chunk boundaries are client-triggered (oneinfermessage per chunk). Chunk size 3 latent frames (~12 video frames at 4× VAE temporal stride). - CFG-parallel support via in-tree
CFGParallelMixin(two caches:_pos/_negwithin the state container). - BF16 correctness, SDPA attention baseline, FA2/FA3 auto-used when available via the in-tree attention-backend selection.
- TP-parallel linear wiring preserved from the in-tree Wan 2.2 primitives we compose against. Not a strip-and-fork.
- Example usage under
examples/online_serving/video_generation/: one WebSocket Python client showing handshake + per-chunkinferloop withintrinsics/posestrajectory playback and base64-MP4-chunk reassembly.
2.2 Out of scope for this PR (explicit deferrals)
- LingBot-World-Base (Cam) — bidirectional diffusion, different denoising schedule, distinct checkpoint. Same model family, follow-up port.
- LingBot-World-Base (Act) — action-conditioned (WASD multi-hot concatenated with ray directions), distinct checkpoint, reference inference is
generate.pywith--action_path/--action_string/--allow_act2cam. Follow-up port; would extend the protocol with a sibling field (e.g.actions: {keys: […]}) or a discriminated control union. - Engine-level / unified KV-cache service for AR diffusion. Session-scoped state container in v1, modelled on PR #2162's
DreamZeroStateand adjacent to in-treeglm_image_transformer's module-resident cache and PR #2885'sattn_cachesdict. A unified PageAttention-style KV manager for AR diffusion is RFC #1987's stated downstream plan; this PR adopts the current workaround shape, not the future abstraction. - Sequence-parallel (Ulysses SP=8). Reference upstream config is SP=8; we land single-GPU first. SP follow-up depends on how RFC #2322 (hadipash's wan2.2 PP draft) and the communication-layer refactor (RFC #1546) settle.
- FA3 explicit wiring, INT8 attention, INT8 DiT, LightVAE-style VAE distillation, compiled VAE. All are separate optimisation PRs orthogonal to this port. Paper's 40 FPS steady-state on 8× H100 is attained with the product of these; we state the gap but don't chase it in v1 (see §3.8).
- Request/response
/v1/videosconvenience wrapper. Bundling a precomputed trajectory into one session server-side is a natural follow-up for offline / simulation callers. Not v1; realtime WebSocket is the primary (and only) serving shape we ship now, matching PR #2162's precedent. - Dormant rolling-window eviction code. Released checkpoint has
local_attn_size=-1, sink_size=0; eviction path is gated onlocal_attn_size != -1and off by default in v1.
2.3 Correctness criteria
- A WebSocket session that plays the authors'
examples/04inputs (image + 533-pose trajectory + Great-Wall prompt) as 27 sequentialinferrequests produces a reassembled 81-frame, 480×832 video visually consistent with the referencegenerate_fast.pyoffline output at the same seed, within numerical tolerance expected from stochastic 4-step flow-match sampling (no strict bit-parity claim). - A shorter variant of the same session (9
inferrequests, 25 frames) produces a reference-comparable video without temporal collapse or scene-identity loss. - The realtime endpoint's handshake returns a well-formed
CameraServerConfig(msgpack) on connect;inferrequests with malformedcameraspayloads return a typed{type: "error"}frame rather than closing the connection; aresetrequest clears all session state (caches, frame buffer, RoPE offset). - Session state persists correctly across chunks — the same trajectory played as one 27-chunk session produces the same output as the reference's single offline invocation on the full trajectory (within BF16 numerical tolerance).
- Peak single-GPU VRAM at 480×832 over a full 81-frame session stays below 70 GiB; at 25 frames, below 50 GiB.
- Cache-update pass produces the same post-chunk KV state as would be obtained by a full forward on clean
x0(invariant, not a performance target — see §4.1).
3. Design
3.1 Pipeline shape
client ──────────────────► server: WS upgrade /v1/realtime/world/camera
server ──msgpack(CameraServerConfig)──► client
(patch_size, chunk_frames,
fps, image_resolution, ...)
┌─── one session, N chunks ─────────────────────────────────────┐
│ │
│ client ──msgpack({endpoint:"infer", │
│ session_id, image?, prompt?, │
│ cameras:{intrinsics, poses}})──► server │
│ │
│ ┌──── ServingRealtimeWorldCamera.infer() ──────┐ │
│ │ │ │
│ │ [first chunk of session only:] │ │
│ │ T5 encode(prompt) ─► cross KV cache │ │
│ │ VAE encode(image) ─► y (first latent) │ │
│ │ │ │
│ │ [every chunk:] │ │
│ │ get_plucker(K_slice, │ │
│ │ poses_slice) │ │
│ │ ─► Plücker emb [B,6,f,H/2,W/2] │ │
│ │ current_start = state.global_end_index │ │
│ │ │ │
│ │ for t in [0, 179, 358]: (3 noisy steps) │ │
│ │ eps = WanModelFast(x, t, plucker, │ │
│ │ self_kv_cache, │ │
│ │ cross_kv_cache, │ │
│ │ current_start, ...) │ │
│ │ x0 = flow_pred_to_x0(eps, x, t) │ │
│ │ x = add_noise(x0, randn, next_t) │ │
│ │ # last step: keep x0 (no re-noise) │ │
│ │ │ │
│ │ # cache-update pass, t ≈ 0, clean x0 │ │
│ │ WanModelFast(x0, t≈0, plucker, │ │
│ │ self_kv_cache, ..., update=True)│ │
│ │ │ │
│ │ state.append_chunk(x0) │ │
│ │ video = VAE.decode(x0) │ │
│ └────────────────────────────────────────────────┘ │
│ │
│ server ──msgpack({type:"frame", chunk_id, video:<mp4 bytes>, │
│ stage_durations:{...}})──► client │
│ │
│ (repeat for next chunk; session-scoped │
│ LingBotWorldFastState persists self_kv_cache, │
│ cross_kv_cache, frame buffer, current_start, │
│ CFG _pos/_neg variants across chunks) │
│ │
│ client ──msgpack({endpoint:"reset"})──► server │
│ state.reset() clears caches + frame buffer │
│ │
└────────────────────────────────────────────────────────────────┘
3.2 Model architecture
From the released lingbot_world_fast/config.json + wan/modules/model_fast.py:
dim=5120, num_heads=40, num_layers=40, ffn_dim=13824
patch_size=(1,2,2), in_dim=36, out_dim=16
control_type='cam' → Plücker camera embeddings (6-d per voxel)
local_attn_size=-1
sink_size=0 → global attention, no rolling eviction (v1)
in_dim=36 = 16 (noise latent) + 16 (image conditioning) + 4 (temporal mask). Input is concatenated on the channel axis.
Per-block additions vs. stock Wan 2.2:
PluckerCamInjector(new module) — AdaLN-style scale+shift injection of the Plücker embedding at every transformer block. FourLinear(dim, dim)layers:cam_injector_layer1/2(2-layer SiLU MLP residual),cam_scale_layer,cam_shift_layer.CausalWanSelfAttention(new module) — self-attention reading from and writing to a module-resident KV cache, with an externally-suppliedcurrent_startoffset fed to RoPE so chunk-to-chunk positions remain continuous.
Top-level additions:
patch_embedding_wancamctrl: Linear(6·64·∏patch_size, dim)— per-patch Plücker embedding projector. The constant 64 is the tiled-voxel count (trace during port; see §5 open questions).c2ws_hidden_states_layer1/2— 2-layer MLP residual on the Plücker embedding prior to per-block injection.
Unchanged composition points (imported from vllm_omni/diffusion/models/wan2_2/wan2_2_transformer.py):
WanRotaryPosEmbed— 3D RoPE.WanTimeTextImageEmbedding— timestep / text / image-condition embedding bundle.WanFeedForward— per-block FFN.WanTransformerBlockshape, TP linear wiring (ColumnParallelLinear,RowParallelLinear,QKVParallelLinear),sp_planconventions.
3.3 Inference loop (detail)
Already shown in §3.1 as a flow diagram. Noteworthy details:
- Chunk boundary is client-triggered. One
inferWebSocket message per chunk. Same driver model as PR #2162. The server does not autonomously emit frames; it produces one chunk per receivedinferand waits. - 4 inference steps per chunk. The schedule is applied at pipeline setup time as indices into
FlowUniPCMultistepScheduler(num_train_timesteps=1000).timesteps; no new scheduler class. - Cache-update pass is a deliberate separate forward at
t≈0on the final clean latent. Ensures the KV cache for the next chunk sees clean tokens, not intermediate noisy ones. - Text encoder runs once per session. On the first
inferof a session, the prompt is T5-encoded and the cross-attention cache is populated;crossattn_cache.is_initgates the reuse from chunk 2 onward. Aresetclears it; a prompt change within a session triggers an implicit reset (matchingDreamZeroState.should_reset()behaviour). - CFG-parallel: positive and negative branches each maintain their own
self_kv_cacheandcross_kv_cache, perCFGParallelMixinconvention. Cache-update pass runs on both. - Session state persists across chunks within one WebSocket connection. Closing the connection drops the state; opening a new session ID on a live connection resets it. Both paths are the same
state.reset()call.
3.4 API surface — /v1/realtime/world/camera WebSocket
The serving shape is a realtime WebSocket with msgpack binary framing, directly modelled on PR #2162's /v1/realtime/robot/openpi endpoint (see vllm_omni/entrypoints/openai/realtime/robot/openpi_connection.py and openpi_serving.py). We adopt that precedent verbatim where possible and diverge only in payload shape (camera poses instead of robot observations; video chunks instead of action tensors).
Upgrade and handshake.
GET /v1/realtime/world/camera (HTTP → WebSocket upgrade)
On accept, the server immediately sends a msgpack-serialised
CameraServerConfig frame advertising model capabilities:
CameraServerConfig = {
"patch_size": [1, 2, 2],
"chunk_latent_frames": 3,
"chunk_video_frames": 12, # 4× VAE temporal stride
"vae_temporal_stride": 4,
"image_resolution": [480, 832], # H, W
"frame_dtype": "uint8",
"fps": 16,
"needs_session_id": true,
"supported_modalities": ["camera_pose"],
"coordinate_convention": "opencv_c2w"
}
Shape and framing follow PolicyServerConfig in PR #2162 (openpi_serving.py:34). Field set is LWF-specific; mechanism is
identical.
Per-chunk infer request (client → server).
{
"endpoint": "infer", # or "reset"
"session_id": "<uuid string>",
# required on the first infer of a session only:
"image": <bytes, png/jpg> | None,
"prompt": "<text prompt>" | None,
# required on every infer:
"cameras": {
"intrinsics": [[fx, fy, cx, cy], ...], # [N, 4] float32
"poses": [[[...], ...]], # [N, 4, 4] OpenCV c2w
},
}
- N per chunk covers
chunk_video_framesoutput frames; intrinsics and poses may be over- or under-specified and are interpolated tochunk_latent_frameslatent frames by the pipeline (get_Ks_transformed+ linear pose interpolation), matching the reference implementation. image+promptare session-scoped: accepted on the firstinferof a newsession_id, ignored on subsequent chunks (or trigger an implicit reset if they change, matchingDreamZeroState.should_reset()semantics).- Validation. Shape / dtype checked; malformed payloads return an error frame (
{type: "error", message: ...}, msgpack) without closing the connection.camerasis required on everyinfer.
Per-chunk response (server → client).
{
"type": "frame",
"chunk_id": 0, # monotonic per session
"video": <bytes, mp4 fragment>,
"stage_durations": { ... }, # per-stage timings
}
Video payload is an MP4 fragment holding chunk_video_frames frames at fps. Client reassembles by concatenation (fragmented-MP4 conventions). Rationale for MP4 over raw RGB: keeps the wire small (~100–500 KB per chunk at 480×832 vs. ~20 MB raw) and requires no codec work on either side beyond stdlib av / ffmpeg.
reset request (client → server).
{ "endpoint": "reset", "session_id": "<uuid>" }
Server calls state.reset(), drops all KV caches and the frame buffer for this session, and replies with a confirmation: {type: "reset_ok", session_id: "..."}. Next infer on the same session_id re-runs the first-chunk initialisation (T5 encode, VAE encode of initial image).
Session semantics. One session per session_id. A single WebSocket connection may carry one session (the common case) or multiple sequential sessions separated by reset. Server-side state lives on the pipeline (one LingBotWorldFastState instance per process, mirroring PR #2162's DreamZeroState); session_id is a change-detector — a fresh value triggers state.reset() on the next infer, matching DreamZero's serving-layer behaviour. The v1 server is single-tenant: one active session at a time per process. Connection loss drops the shared state via a reset() call on the disconnect path. (Multi-tenant operation — concurrent sessions on one server, isolated per session_id — is out of scope for v1; see §3.5 decision 8. Aligned with the deferred session-management RFC referenced on #1987 by @tysoncung / @Sy0307, 2026-03-23.)
Error frames. Non-fatal errors (malformed payload, missing required field on first-chunk, unsupported cameras shape) are returned as {type: "error", code: <str>, message: <str>} and do not close the connection. Fatal errors (unrecoverable state, protocol violation) close with a 1011 code and a text reason.
Why not /v1/videos multipart. RFC #3072 v1 proposed a /v1/videos multipart form extension. Reviewer feedback
(TKONIY on #3072, 2026-04-23) directed the design to mirror the realtime streaming precedent set by PR #2162 and vllm-omni's TTS realtime endpoint, because /v1/videos is request/response-only and cannot carry a multi-chunk streaming
session. We adopt the WebSocket shape as the single v1 API; an offline /v1/videos convenience wrapper that plays a full
precomputed trajectory as one session server-side is a natural follow-up, not blocking v1.
To the best of our knowledge, no published HTTP schema exists for full-pose camera-controlled video streaming. We ground the camera field shape in established research-tooling conventions (Nerfstudio / ViPE / COLMAP) and the framing / session conventions in PR #2162's precedent. No vllm-omni-specific convention is invented beyond the concrete field names.
3.5 Key decisions, with rejection rationale
- Composition, not fork, of the Wan 2.2 transformer. The in-tree
wan2_2/wan2_2_transformer.pyalready has TP-parallel linear wiring,sp_planintegration, attention-backend wiring, andAutoWeightsLoader. Importing those primitives and addingPluckerCamInjectorandCausalWanSelfAttentionas new composable modules preserves all of it. Rejected alternative: strip the TP/SP/attention-backend code and fork the transformer. Rejected because it discards machinery we would then need to re-add piecemeal to scale beyond 1× GPU. - Session-scoped state container holding the KV caches, adopted directly from PR #2162's
DreamZeroState. OneLingBotWorldFastStateinstance per session, owning: per-layer self-KV dicts{k, v, global_end_index, local_end_index}, per-layer cross-KV dicts with anis_initflag, their_pos/_negCFG-parallel variants, a frame buffer for output assembly, and the RoPEcurrent_startoffset. The pipeline threads the state through each forward pass; the state persists for the lifetime of the session. Rejected alternative: a new engine-level KV-cache service with PageAttention-style paging. Rejected because RFC #1987 explicitly flags a unified AR-diffusion KV manager as the downstream plan (TKONIY, #3072 review), - Dormant rolling-window eviction. Released checkpoint has
local_attn_size=-1, sink_size=0. Eviction code is gated onlocal_attn_size != -1and off by default. Rejected alternative: delete the code path entirely. Rejected because it preserves the option to re-enable when a future checkpoint ships withlocal_attn_size ≠ -1. - Single in-tree scheduler, 4-step indices applied at setup (
FlowUniPCMultistepScheduler). Rejected alternative: introduce a newLingBotFastSchedulerclass. Rejected because the 4-step schedule is a configuration of an existing scheduler, not a new algorithm. - Camera-pose-only control in v1. Rejected alternative: include a
control_type='act'path now, with the WASD channel broadcast-concatenated to ray directions (the code delta is small). Rejected because the 'act' branch ofimage2video_fast.pyis not backed by a publicly-released Fast checkpoint; the released weights are the cam-only model (confirmed by the upstream README's Model Download table). Base (Act) is a separate follow-up port against the Base (non-fast) model family. - msgpack over the WebSocket, not JSON. Rejected alternative: JSON framing. Rejected for wire consistency
with PR #2162 (which uses msgpack throughout via the
openpi_client.msgpack_numpycodec) and for efficiency on numeric payloads (pose / intrinsics arrays). We pick up msgpack's numpy extension for the camera arrays so clients can send float32 tensors natively without a JSON round-trip. - Realtime WebSocket as the v1 API shape. Rejected alternative: ship request/response
/v1/videosfirst and add streaming later. Rejected on reviewer feedback (TKONIY, #3072, 2026-04-23) that/v1/videosdoes not support multiturn / streaming and that interactive world models should share the realtime API pattern with DreamZero (PR #2162), the TTS realtime endpoint, and upstream vLLM's Realtime API. - Single-tenant v1 server — one active session at a time per process, mirroring PR #2162. The
LingBotWorldFastStateinstance lives on the pipeline as a flat object with scalar attributes;session_idis a change-detector that triggersstate.reset()on mismatch (matchingopenpi_serving.py:120-125), not a lookup key. Cleanup on WebSocket disconnect is a singlestate.reset()call from the connection layer. Rejected alternative: Multi-tenancy is part of the downstream plan
3.6 Files and layout
vllm_omni/diffusion/models/lingbot_world_fast/
├── __init__.py
├── modeling_lingbot_world_fast.py # transformer + blocks (composes wan2_2 primitives)
├── pipeline_lingbot_world_fast.py # chunked inference, CFG-parallel, calls into state
├── state_lingbot_world_fast.py # LingBotWorldFastState (session-scoped state container,
│ # modelled on DreamZeroState; see §3.5 decision 2)
└── plucker.py # get_plucker_embeddings port from wan/utils/cam_utils.py
vllm_omni/diffusion/registry.py
# add "LingBotWorldFastPipeline" entry
vllm_omni/model_executor/stage_configs/lingbot_world_fast.yaml
# single-GPU stage config (pre-install; stage configs are package data)
vllm_omni/entrypoints/openai/realtime/world/
├── __init__.py
├── camera_connection.py # WorldCameraRealtimeConnection — WebSocket lifecycle,
│ # msgpack framing; modelled on openpi_connection.py
└── camera_serving.py # ServingRealtimeWorldCamera — per-chunk infer,
# session tracking, request building;
# modelled on openpi_serving.py
vllm_omni/entrypoints/openai/api_server.py
# add @router.websocket("/v1/realtime/world/camera") registration,
# alongside the existing openpi_robot and TTS realtime routes
examples/online_serving/video_generation/
├── lingbot_world_fast_ws_client.py # WebSocket client: handshake + per-chunk
│ # infer loop; reassembles fragmented MP4
└── lingbot_world_fast_trajectory.py # helper that loads the reference
# examples/04 trajectory as msgpack-ready dicts
No separate scheduler file — indices-into-in-tree-scheduler are handled inside the pipeline (§3.5 decision 4). No /v1/videos
server-side changes in v1 — the realtime endpoint is the only v1 serving surface.
3.7 Dependencies
- Upstream repositories.
github.com/Robbyant/lingbot-worldis the reference implementation. We vendor translated code, not dependency imports. - HuggingFace checkpoints.
robbyant/lingbot-world-fast(DiT shards, ~70 GiB),robbyant/lingbot-world-base-cam(T5 + VAE + tokenizer subset, ~10 GiB). Fast download ~80 GiB. - In-tree dependencies.
vllm_omni.diffusion.models.wan2_2.*(primitives — imported),vllm_omni.diffusion.distributed.cfg_parallel.CFGParallelMixin,vllm_omni.diffusion.distributed.autoencoders.autoencoder_kl_wan.DistributedAutoencoderKLWan,vllm_omni.diffusion.models.schedulers.scheduling_flow_unipc_multistep.FlowUniPCMultistepScheduler,vllm_omni.diffusion.attention.*(FA2 / FA3 / SDPA selection). - No new third-party packages. The reference implementation's
einops,numpy,torchdependencies are already in-tree.
3.8 Performance targets
Targets below are for the reference operating point: single A100 80 GB PCIe, BF16, SDPA or FA2 attention (both measured at
parity; see below), 480×832 output resolution, 4 denoising steps, FlowUniPCMultistepScheduler with the [0, 179, 358, 679] timestep schedule, chunk size of 3 latent frames (≈ 12 video frames at the 4× VAE temporal stride). Each target is followed by the reference measurement it derives from; per-chunk growth accounts for the non-rolling KV-cache path retained in v1 (§3.5 decision 3).
Wall-time / throughput (reference DiT configuration):
- Per-chunk DiT denoising ≤ 8.5 s for the first 7 chunks (covers up to
frame_num=81). Reference: chunks 1–7 measured at 6.2, 6.3, 6.6, 6.9, 7.3, 7.6, 8.0 s — average 7.4 s, maximum 8.0 s. Growth per chunk is monotonic and reflects the KV cache size carried forward; the target's 8.5 s ceiling bounds the worst-case chunk in this range with a modest margin. - End-to-end DiT denoising ≤ 55 s at
frame_num=81(7 chunks). Reference measurement: 51.94 s FA2, 53 s SDPA-fallback — the two backends are at parity within 2%. - End-to-end wall time ≤ 250 s at
frame_num=81, excluding the one-time per-process setup cost (T5 / VAE / first DiT checkpoint load, which depend on disk and are not part of the per-request budget). Reference: 242 s measured for a full first-request run including setup; warm per-request budget will be reported in the PR description once the test-harness pattern is settled.
Memory targets (same reference point):
- Peak VRAM ≤ 70 GiB at
frame_num=81(reference: 67.4 GiB measured on the 04-22 SDPA baseline at identical shape; FA2 does not materially change peak memory). - Peak VRAM ≤ 50 GiB at
frame_num=25(estimated from the frame-count scaling).
4. Correctness & Testing Plans
Following docs/contributing/ci/CI_5levels.md. Five-level test system; v1 targets L1 + L2 + L3. L4 (nightly perf, doc example
tests) is nice-to-have; L5 (stability / reliability) is follow-up.
4.1 Invariants to check
- Plücker embedding parity. Our in-tree
get_plucker_embeddingsoutput matches upstreamwan/utils/cam_utils.py:get_plucker_embeddingsbit-for-bit on fixed(intrinsics, poses)input. Basis case for all downstream equivalence. - Cache-update invariance. After a chunk's cache-update pass, the self-KV cache state equals the state obtained by a full forward over the clean
x0att=0, within BF16 numerical tolerance. Tests this on a single chunk with fabricated timesteps. - Chunk boundary continuity. The
current_startRoPE offset in chunk N+1 aligns with the trailing position of chunk N's written cache entries (global_end_index). - CFG-parallel cache separation.
_posand_negKV caches do not share storage; mutating one does not mutate the other. - Protocol round-trip. An
inferpayload with known-goodcameras: {intrinsics, poses}decodes server-side (through the msgpack numpy codec) to the same float32 tensors the client produced. Handshake / error / reset framings match the documented shapes. - Session-id change resets cleanly. Within a single WebSocket connection, switching
session_id(without an explicitresetRPC) causes the nextinferto enter with a clean state container — KV caches re-initialised, frame buffer empty, RoPE offset zero, language re-encoded — with no carryover from the previous session. Mirrors PR #2162's serving-layer change-detector behaviour (openpi_serving.py:120-125). (Concurrent-session isolation acrosssession_ids is out of v1 scope; the v1 server runs one session at a time — see §3.4 session semantics.)
4.2 L1 — Unit tests (CPU, <15 min, PR-ready label, @pytest.mark.core_model)
Files under tests/diffusion/models/lingbot_world_fast/:
test_plucker.py— Plücker embedding equivalence vs. upstream reference on synthetic(K, poses). Includes edge cases (identity pose, pure translation, pure rotation).test_session_state.py—LingBotWorldFastStateallocation shapes, index arithmetic (local_end_index,global_end_index), rolling eviction path gated off, reset semantics (session-id change, prompt change), CFG_pos/_negcache separation.test_protocol_validation.py— msgpack-encodedinfer/resetpayload validation: missing required fields, malformedcamerasarrays, bad dtypes, session-id churn. Asserts error-frame responses (not connection-close).test_schedule.py— timestep-index selection againstFlowUniPCMultistepSchedulerproduces the expected 4 steps.
4.3 L2 — Basic E2E (GPU, <15 min, PR-ready, @pytest.mark.core_model)
tests/e2e/offline_inference/test_lingbot_world_fast.py— load the Fast pipeline with a dummy / mock-weight model; drive a 2-chunk session through the pipeline (bypassing the WebSocket layer), asserting state-container persistence between chunks, output shape(chunk_latent_frames, 16, lat_h, lat_w)per chunk, no NaN / Inf.tests/e2e/online_serving/test_lingbot_world_fast.py— start a minimal realtime server; open a WebSocket to/v1/realtime/world/camera; assert handshake returns a well-formedCameraServerConfig; send oneinferwith dummy image + prompt + cameras; assert aframeresponse with non-empty mp4 payload (content ignored; envelope / framing / msgpack shape checked). Repeat with aresetand one moreinferto exercise the state-clear path.
4.4 L3 — Real-model integration, accuracy, performance (GPU, <30 min, post-merge, @pytest.mark.advanced_model)
tests/e2e/offline_inference/test_lingbot_world_fast_expansion.py:- Load real Fast weights from HF cache (fixture skip if unavailable).
- Drive the pipeline directly through 9 chunks with upstream's
run_fast.shcase2 inputs (examples/04image + 533-pose trajectory sliced into 9 chunks + Great-Wall prompt, seed 42, 480×832), reassemble a 25-frame video. - Assert peak VRAM ≤ 50 GiB and end-to-end DiT denoising ≤ 20 s per §3.8 (reference: 12.88 s measured at
frame_num=25). - Structural-similarity (SSIM) of the first and last output frame vs. a stored golden tensor ≥ 0.85 (perceptual check, not bit-parity — accommodates 4-step stochastic sampling).
tests/e2e/online_serving/test_lingbot_world_fast_expansion.py:- Same inputs, driven through the
/v1/realtime/world/cameraWebSocket endpoint as 9 sequentialinferrequests. Reassemble returned MP4 fragments; apply the same SSIM check. Additionally verify: session-id churn resets state correctly; a mid-sessionresetproduces a new first-chunk-initialisation on the followinginfer; connection-close drops server-side state (observable by assertingpipeline.state.current_start_frame == 0and empty KV caches after the disconnect handler runs).
- Same inputs, driven through the
- Both marked
@pytest.mark.advanced_model @pytest.mark.core_model(the basic run and deep-validation split per the CI doc's §2.4.1 guidance).
4.5 L4 / L5 — deferred
- L4 functional expansion — 720p, longer frame counts (
frame_num=125), alternate prompts, CFG on/off. Nightly cadence once L3 is landed. - L4 doc-example tests —
tests/example/online_serving/test_lingbot_world_fast.pythat executes the curl + Python examples fromexamples/online_serving/video_generation/and asserts successful completion. Added when the examples land. - L4 perf tests —
tests/dfx/perf/tests/test.jsonentry with the reference config + target throughput / latency thresholds derived from §3.8. Added when the in-tree perf harness matures around diffusion workloads. - L5 stability — long-running WebSocket session with repeated chunk requests (and periodic
reset+ new-session churn); watch for KV cache / frame-buffer memory growth or state leakage across sessions on the same connection. Follow-up.
6. References
- RFC #1987 — World Model Support (umbrella RFC).
- PR #2162 — [WIP][Diffusion] DreamZero world model integration (direct precedent for both the realtime WebSocket serving shape in §3.4 and the session-scoped state container pattern in §3.5 decision 2).
- PR #2885 — [WIP][Diffusion] LingBot-VA world-action model support (robotics-control sibling).
- Issue #3072 — this RFC's own thread; 2026-04-23 review from @TKONIY directed the alignment with PR #2162's realtime API and stage-manager KV-cache pattern.
- Upstream repo: https://github.com/Robbyant/lingbot-world
- Paper (LingBot-World): arXiv:2601.20540.
- Figure 5 — pipeline diagram showing "Action Scale & Shift with Plücker Encoder" as the per-block conditioning mechanism.
- README Model Download table — canonical variant / control-signal mapping (Base Cam / Base Act / Fast).
- Paper (LingBot-VA): arXiv:2601.21998 (sibling model, PR #2885).
- In-tree testing framework:
docs/contributing/ci/CI_5levels.md— multi-level test system (L1-L5). - In-tree adjacent precedents:
vllm_omni/diffusion/models/dreamzero/state_dreamzero.py(session-scoped state container — the direct precedent for §3.5 decision 2 and §3.4 API shape, via PR #2162);vllm_omni/entrypoints/openai/realtime/robot/{openpi_connection,openpi_serving}.py(realtime-WebSocket serving handler — the direct precedent for §3.4, via PR #2162);vllm_omni/diffusion/models/glm_image/glm_image_transformer.py(module-resident KV cache — earlier in-tree KV-cache precedent);vllm_omni/diffusion/models/wan2_2/wan2_2_transformer.py(Wan 2.2 primitives imported). - Camera representation: Plücker ray embedding convention from CameraCtrl (arXiv 2404.02101 §3.2); field shape follows Nerfstudio / ViPE / COLMAP conventions.
Feedback Period.
One week
CC List.
@yinpeiqi @ywang96 @Gaohan123 @hsliuustc0106 @asukaqaq-s @IlyasFardaouix @matchyc @TKONIY @shanghuaye504-byte @kevinzeng08 @zhujiapeng @zzhang-fr
Any Other Things.
No response
Before submitting a new issue...
- Make sure you already searched for relevant issues, and asked the chatbot living at the bottom right corner of the documentation page, which can answer lots of frequently asked questions.