[diffusion] feat: add LingBot realtime prompt, KV window, and lazy VAE controls (#30040)
This commit is contained in:
@@ -11,6 +11,7 @@ from typing import Any
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
from sglang.multimodal_gen import envs
|
||||||
from sglang.multimodal_gen.configs.models import DiTConfig
|
from sglang.multimodal_gen.configs.models import DiTConfig
|
||||||
from sglang.multimodal_gen.configs.models.dits import LingBotWorldVideoConfig
|
from sglang.multimodal_gen.configs.models.dits import LingBotWorldVideoConfig
|
||||||
from sglang.multimodal_gen.configs.pipeline_configs.wan import Wan2_2_I2V_A14B_Config
|
from sglang.multimodal_gen.configs.pipeline_configs.wan import Wan2_2_I2V_A14B_Config
|
||||||
@@ -325,6 +326,44 @@ class LingBotWorldCausalDMDConfig(LingBotWorldI2VConfig):
|
|||||||
warp_denoising_step: bool = True
|
warp_denoising_step: bool = True
|
||||||
realtime_causal_sink_size: int | None = None
|
realtime_causal_sink_size: int | None = None
|
||||||
realtime_causal_kv_cache_num_frames: int | None = None
|
realtime_causal_kv_cache_num_frames: int | None = None
|
||||||
|
interactive_kv_window_enable: bool = False
|
||||||
|
interactive_kv_still_window: int | None = 3
|
||||||
|
interactive_kv_moving_window: int | None = 12
|
||||||
|
interactive_kv_still_chunks: int = 2
|
||||||
|
lazy_vae_encode_black_frames: int = 0
|
||||||
|
|
||||||
|
def preprocess_vae_encode(self, image, vae):
|
||||||
|
image = super().preprocess_vae_encode(image, vae)
|
||||||
|
lazy_black_frames = envs.SGLANG_LINGBOT_LAZY_VAE_ENCODE_BLACK_FRAMES
|
||||||
|
if lazy_black_frames is None:
|
||||||
|
lazy_black_frames = self.lazy_vae_encode_black_frames
|
||||||
|
lazy_black_frames = max(0, int(lazy_black_frames or 0))
|
||||||
|
if lazy_black_frames <= 0 or image.ndim != 5:
|
||||||
|
return image
|
||||||
|
|
||||||
|
num_frames = int(image.shape[2])
|
||||||
|
if num_frames <= 1:
|
||||||
|
return image
|
||||||
|
|
||||||
|
temporal_ratio = int(self.vae_config.arch_config.temporal_compression_ratio)
|
||||||
|
encode_frames = min(num_frames, 1 + lazy_black_frames)
|
||||||
|
if (encode_frames - 1) % temporal_ratio != 0:
|
||||||
|
encode_frames = (encode_frames - 1) // temporal_ratio + 1
|
||||||
|
encode_frames = encode_frames * temporal_ratio + 1
|
||||||
|
encode_frames = min(num_frames, encode_frames)
|
||||||
|
|
||||||
|
if encode_frames >= num_frames:
|
||||||
|
return image
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"LingBot lazy VAE encode: pixel_frames=%s encode_pixel_frames=%s "
|
||||||
|
"black_frames=%s temporal_ratio=%s",
|
||||||
|
num_frames,
|
||||||
|
encode_frames,
|
||||||
|
lazy_black_frames,
|
||||||
|
temporal_ratio,
|
||||||
|
)
|
||||||
|
return image[:, :, :encode_frames].contiguous()
|
||||||
|
|
||||||
def postprocess_image_latent(self, latent_condition, batch):
|
def postprocess_image_latent(self, latent_condition, batch):
|
||||||
"""Build condition tensor aligned to chunk_size (num_frames_per_block).
|
"""Build condition tensor aligned to chunk_size (num_frames_per_block).
|
||||||
@@ -342,9 +381,31 @@ class LingBotWorldCausalDMDConfig(LingBotWorldI2VConfig):
|
|||||||
latent_width = batch.width // spatial_ratio
|
latent_width = batch.width // spatial_ratio
|
||||||
|
|
||||||
# Align num_latent_frames to chunk_size
|
# Align num_latent_frames to chunk_size
|
||||||
num_latent_frames = latent_condition.shape[2]
|
target_latent_frames = (int(batch.num_frames) - 1) // temporal_ratio + 1
|
||||||
num_latent_frames = num_latent_frames - (num_latent_frames % chunk_size)
|
target_latent_frames = target_latent_frames - (
|
||||||
latent_condition = latent_condition[:, :, :num_latent_frames, :, :]
|
target_latent_frames % chunk_size
|
||||||
|
)
|
||||||
|
encoded_latent_frames = int(latent_condition.shape[2])
|
||||||
|
if encoded_latent_frames < target_latent_frames:
|
||||||
|
tail = latent_condition[:, :, -1:, :, :].repeat(
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
target_latent_frames - encoded_latent_frames,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
latent_condition = torch.cat([latent_condition, tail], dim=2)
|
||||||
|
elif encoded_latent_frames > target_latent_frames:
|
||||||
|
latent_condition = latent_condition[:, :, :target_latent_frames, :, :]
|
||||||
|
|
||||||
|
num_latent_frames = int(latent_condition.shape[2])
|
||||||
|
if encoded_latent_frames != num_latent_frames:
|
||||||
|
logger.info(
|
||||||
|
"LingBot lazy VAE condition: encoded_latent_frames=%s "
|
||||||
|
"target_latent_frames=%s",
|
||||||
|
encoded_latent_frames,
|
||||||
|
num_latent_frames,
|
||||||
|
)
|
||||||
|
|
||||||
# Number of initial frames that have actual image content
|
# Number of initial frames that have actual image content
|
||||||
# (latent_condition from VAE encode of [image, zeros...])
|
# (latent_condition from VAE encode of [image, zeros...])
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ if TYPE_CHECKING:
|
|||||||
SGLANG_CACHE_DIT_SECONDARY_TS_ORDER: int = 1
|
SGLANG_CACHE_DIT_SECONDARY_TS_ORDER: int = 1
|
||||||
# model loading
|
# model loading
|
||||||
SGLANG_USE_RUNAI_MODEL_STREAMER: bool = True
|
SGLANG_USE_RUNAI_MODEL_STREAMER: bool = True
|
||||||
|
SGLANG_LINGBOT_ENABLE_INTERACTIVE_KV_WINDOW: bool = False
|
||||||
|
SGLANG_LINGBOT_LAZY_VAE_ENCODE_BLACK_FRAMES: int | None = None
|
||||||
SGLANG_DIFFUSION_FLASHINFER_FP4_GEMM_BACKEND: str | None = None
|
SGLANG_DIFFUSION_FLASHINFER_FP4_GEMM_BACKEND: str | None = None
|
||||||
SGLANG_DIFFUSION_ENABLE_W8A8_FP8_GEMM: bool = False
|
SGLANG_DIFFUSION_ENABLE_W8A8_FP8_GEMM: bool = False
|
||||||
SGLANG_DIFFUSION_VAE_CHANNELS_LAST_3D: str = "auto"
|
SGLANG_DIFFUSION_VAE_CHANNELS_LAST_3D: str = "auto"
|
||||||
@@ -295,6 +297,12 @@ environment_variables: dict[str, Callable[[], Any]] = {
|
|||||||
"SGLANG_USE_RUNAI_MODEL_STREAMER": _lazy_bool(
|
"SGLANG_USE_RUNAI_MODEL_STREAMER": _lazy_bool(
|
||||||
"SGLANG_USE_RUNAI_MODEL_STREAMER", "true"
|
"SGLANG_USE_RUNAI_MODEL_STREAMER", "true"
|
||||||
),
|
),
|
||||||
|
"SGLANG_LINGBOT_ENABLE_INTERACTIVE_KV_WINDOW": _lazy_bool(
|
||||||
|
"SGLANG_LINGBOT_ENABLE_INTERACTIVE_KV_WINDOW"
|
||||||
|
),
|
||||||
|
"SGLANG_LINGBOT_LAZY_VAE_ENCODE_BLACK_FRAMES": _lazy_int(
|
||||||
|
"SGLANG_LINGBOT_LAZY_VAE_ENCODE_BLACK_FRAMES"
|
||||||
|
),
|
||||||
# FlashInfer FP4 GEMM backend override for diffusion NVFP4.
|
# FlashInfer FP4 GEMM backend override for diffusion NVFP4.
|
||||||
# When unset, diffusion ModelOpt NVFP4 defaults to flashinfer_trtllm.
|
# When unset, diffusion ModelOpt NVFP4 defaults to flashinfer_trtllm.
|
||||||
# Supported values:
|
# Supported values:
|
||||||
|
|||||||
+158
-15
@@ -14,7 +14,15 @@ from sglang.multimodal_gen.runtime.entrypoints.openai.realtime.realtime_adapter
|
|||||||
build_realtime_sampling_params,
|
build_realtime_sampling_params,
|
||||||
save_realtime_first_frame,
|
save_realtime_first_frame,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.realtime.control_signals import ControlSignalQueue
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.lingbot_world.constants import (
|
||||||
|
LINGBOT_CAMERA_ACTIONS_CONDITION,
|
||||||
|
LINGBOT_PROMPT_UPDATED_CONDITION,
|
||||||
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.realtime.control_signals import (
|
||||||
|
ControlSignalQueue,
|
||||||
|
ParsedControlEventPayload,
|
||||||
|
parse_control_event_payload,
|
||||||
|
)
|
||||||
from sglang.multimodal_gen.runtime.realtime.states import (
|
from sglang.multimodal_gen.runtime.realtime.states import (
|
||||||
RealtimeCameraControlState,
|
RealtimeCameraControlState,
|
||||||
)
|
)
|
||||||
@@ -29,6 +37,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
LINGBOT_REALTIME_DEFAULT_NUM_INFERENCE_STEPS = 4
|
LINGBOT_REALTIME_DEFAULT_NUM_INFERENCE_STEPS = 4
|
||||||
LINGBOT_REALTIME_MIN_CONDITION_CHUNKS = 2
|
LINGBOT_REALTIME_MIN_CONDITION_CHUNKS = 2
|
||||||
|
COMPOSITE_INPUT_EVENT_KIND = "composite_input"
|
||||||
|
|
||||||
|
|
||||||
class LingBotWorldRealtimeState(RealtimeCameraControlState):
|
class LingBotWorldRealtimeState(RealtimeCameraControlState):
|
||||||
@@ -47,16 +56,44 @@ class LingBotWorldRealtimeState(RealtimeCameraControlState):
|
|||||||
def receive_prompt(self, prompt: str, *, event_id: int | None = None) -> None:
|
def receive_prompt(self, prompt: str, *, event_id: int | None = None) -> None:
|
||||||
self.prompt_queue.push("prompt", prompt, event_id=event_id)
|
self.prompt_queue.push("prompt", prompt, event_id=event_id)
|
||||||
|
|
||||||
|
def parse_camera_control_event_payload(
|
||||||
|
self,
|
||||||
|
payload: Any,
|
||||||
|
*,
|
||||||
|
event_id: int | None,
|
||||||
|
) -> ParsedControlEventPayload:
|
||||||
|
return parse_control_event_payload(
|
||||||
|
payload,
|
||||||
|
event_id=event_id,
|
||||||
|
kind="camera_actions",
|
||||||
|
normalize_state_payload=self._normalize_state_actions,
|
||||||
|
validate_script_payload=LingBotWorldRealtimeAdapter._validate_camera_actions,
|
||||||
|
)
|
||||||
|
|
||||||
|
def receive_parsed_camera_control_event_payload(
|
||||||
|
self,
|
||||||
|
parsed: ParsedControlEventPayload,
|
||||||
|
*,
|
||||||
|
event_id: int | None,
|
||||||
|
) -> str:
|
||||||
|
if parsed.mode == "state":
|
||||||
|
transitions = parsed.payload
|
||||||
|
self.receive_camera_state_transitions(transitions)
|
||||||
|
return f"kind=camera_actions, mode=state, transitions={len(transitions)}"
|
||||||
|
|
||||||
|
camera_actions = parsed.payload
|
||||||
|
self.receive_camera_action_script(camera_actions, event_id=event_id)
|
||||||
|
return f"kind=camera_actions, mode=script, frames={len(camera_actions)}"
|
||||||
|
|
||||||
def receive_camera_control_event_payload(
|
def receive_camera_control_event_payload(
|
||||||
self,
|
self,
|
||||||
payload: Any,
|
payload: Any,
|
||||||
*,
|
*,
|
||||||
event_id: int | None,
|
event_id: int | None,
|
||||||
) -> str:
|
) -> str:
|
||||||
return super().receive_camera_control_event_payload(
|
parsed = self.parse_camera_control_event_payload(payload, event_id=event_id)
|
||||||
payload,
|
return self.receive_parsed_camera_control_event_payload(
|
||||||
event_id=event_id,
|
parsed, event_id=event_id
|
||||||
validate_camera_actions=LingBotWorldRealtimeAdapter._validate_camera_actions,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def sample_prompt(self) -> str:
|
def sample_prompt(self) -> str:
|
||||||
@@ -86,7 +123,7 @@ class LingBotWorldRealtimeAdapter(BaseRealtimeModelAdapter):
|
|||||||
request: RealtimeVideoGenerationsRequest,
|
request: RealtimeVideoGenerationsRequest,
|
||||||
) -> None:
|
) -> None:
|
||||||
condition_inputs = request.condition_inputs or {}
|
condition_inputs = request.condition_inputs or {}
|
||||||
camera_actions = condition_inputs.get("camera_actions")
|
camera_actions = condition_inputs.get(LINGBOT_CAMERA_ACTIONS_CONDITION)
|
||||||
if camera_actions is not None:
|
if camera_actions is not None:
|
||||||
state = self._state(session)
|
state = self._state(session)
|
||||||
state.receive_camera_action_script(
|
state.receive_camera_action_script(
|
||||||
@@ -113,17 +150,119 @@ class LingBotWorldRealtimeAdapter(BaseRealtimeModelAdapter):
|
|||||||
) -> str:
|
) -> str:
|
||||||
state = self._state(session)
|
state = self._state(session)
|
||||||
if event.kind == "camera_actions":
|
if event.kind == "camera_actions":
|
||||||
return state.receive_camera_control_event_payload(
|
return self._ingest_camera_actions(state, event.payload, event.event_id)
|
||||||
event.payload,
|
|
||||||
event_id=event.event_id,
|
|
||||||
)
|
|
||||||
elif event.kind == "prompt":
|
elif event.kind == "prompt":
|
||||||
if not isinstance(event.payload, str) or not event.payload:
|
return self._ingest_prompt(state, event.payload, event.event_id)
|
||||||
raise ValueError("prompt event payload must be a non-empty string")
|
elif event.kind == COMPOSITE_INPUT_EVENT_KIND:
|
||||||
state.receive_prompt(event.payload, event_id=event.event_id)
|
return self._ingest_composite_input(state, event.payload, event.event_id)
|
||||||
return f"kind=prompt, prompt_len={len(event.payload)}"
|
|
||||||
raise ValueError(f"unsupported event kind: {event.kind}")
|
raise ValueError(f"unsupported event kind: {event.kind}")
|
||||||
|
|
||||||
|
def _ingest_camera_actions(
|
||||||
|
self,
|
||||||
|
state: LingBotWorldRealtimeState,
|
||||||
|
payload: Any,
|
||||||
|
event_id: int | None,
|
||||||
|
) -> str:
|
||||||
|
return state.receive_camera_control_event_payload(
|
||||||
|
payload,
|
||||||
|
event_id=event_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _ingest_prompt(
|
||||||
|
self,
|
||||||
|
state: LingBotWorldRealtimeState,
|
||||||
|
payload: Any,
|
||||||
|
event_id: int | None,
|
||||||
|
) -> str:
|
||||||
|
prompt = self._validate_prompt_payload(payload)
|
||||||
|
state.receive_prompt(prompt, event_id=event_id)
|
||||||
|
return f"kind=prompt, prompt_len={len(prompt)}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _validate_prompt_payload(payload: Any) -> str:
|
||||||
|
if not isinstance(payload, str) or not payload:
|
||||||
|
raise ValueError("prompt event payload must be a non-empty string")
|
||||||
|
return payload
|
||||||
|
|
||||||
|
def _ingest_composite_input(
|
||||||
|
self,
|
||||||
|
state: LingBotWorldRealtimeState,
|
||||||
|
payload: Any,
|
||||||
|
event_id: int | None,
|
||||||
|
) -> str:
|
||||||
|
if not isinstance(payload, dict):
|
||||||
|
raise ValueError("composite_input event payload must be a map")
|
||||||
|
input_types = payload.get("input_types")
|
||||||
|
if not isinstance(input_types, list) or not input_types:
|
||||||
|
raise ValueError(
|
||||||
|
"composite_input event payload requires non-empty input_types"
|
||||||
|
)
|
||||||
|
|
||||||
|
parsed_inputs = []
|
||||||
|
for input_type in input_types:
|
||||||
|
if not isinstance(input_type, str) or not input_type:
|
||||||
|
raise ValueError(
|
||||||
|
"composite_input input_types must contain non-empty strings"
|
||||||
|
)
|
||||||
|
if input_type not in payload:
|
||||||
|
raise ValueError(f"composite_input event payload requires {input_type}")
|
||||||
|
parsed_inputs.append(
|
||||||
|
(
|
||||||
|
input_type,
|
||||||
|
self._parse_composite_input_item(
|
||||||
|
state,
|
||||||
|
input_type,
|
||||||
|
payload[input_type],
|
||||||
|
event_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
input_logs = []
|
||||||
|
for input_type, parsed_payload in parsed_inputs:
|
||||||
|
input_logs.append(
|
||||||
|
self._ingest_parsed_composite_input_item(
|
||||||
|
state,
|
||||||
|
input_type,
|
||||||
|
parsed_payload,
|
||||||
|
event_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return f"kind=composite_input, inputs={input_logs}"
|
||||||
|
|
||||||
|
def _parse_composite_input_item(
|
||||||
|
self,
|
||||||
|
state: LingBotWorldRealtimeState,
|
||||||
|
input_type: str,
|
||||||
|
payload: Any,
|
||||||
|
event_id: int | None,
|
||||||
|
) -> Any:
|
||||||
|
if input_type == "camera_actions":
|
||||||
|
return state.parse_camera_control_event_payload(
|
||||||
|
payload,
|
||||||
|
event_id=event_id,
|
||||||
|
)
|
||||||
|
if input_type == "prompt":
|
||||||
|
return self._validate_prompt_payload(payload)
|
||||||
|
raise ValueError(f"unsupported composite_input type: {input_type}")
|
||||||
|
|
||||||
|
def _ingest_parsed_composite_input_item(
|
||||||
|
self,
|
||||||
|
state: LingBotWorldRealtimeState,
|
||||||
|
input_type: str,
|
||||||
|
parsed_payload: Any,
|
||||||
|
event_id: int | None,
|
||||||
|
) -> str:
|
||||||
|
if input_type == "camera_actions":
|
||||||
|
return state.receive_parsed_camera_control_event_payload(
|
||||||
|
parsed_payload,
|
||||||
|
event_id=event_id,
|
||||||
|
)
|
||||||
|
if input_type == "prompt":
|
||||||
|
state.receive_prompt(parsed_payload, event_id=event_id)
|
||||||
|
return f"kind=prompt, prompt_len={len(parsed_payload)}"
|
||||||
|
raise ValueError(f"unsupported composite_input type: {input_type}")
|
||||||
|
|
||||||
def sample_chunk_inputs(
|
def sample_chunk_inputs(
|
||||||
self,
|
self,
|
||||||
session: GenerateSession,
|
session: GenerateSession,
|
||||||
@@ -137,18 +276,22 @@ class LingBotWorldRealtimeAdapter(BaseRealtimeModelAdapter):
|
|||||||
if request is None:
|
if request is None:
|
||||||
raise ValueError("realtime request is not initialized")
|
raise ValueError("realtime request is not initialized")
|
||||||
|
|
||||||
|
prompt_updated = False
|
||||||
if chunk.index == 0:
|
if chunk.index == 0:
|
||||||
prompt = request.prompt
|
prompt = request.prompt
|
||||||
elif state.has_prompt():
|
elif state.has_prompt():
|
||||||
prompt = state.sample_prompt()
|
prompt = state.sample_prompt()
|
||||||
request.prompt = prompt
|
request.prompt = prompt
|
||||||
|
prompt_updated = True
|
||||||
else:
|
else:
|
||||||
prompt = request.prompt
|
prompt = request.prompt
|
||||||
|
|
||||||
condition_inputs = {}
|
condition_inputs = {}
|
||||||
|
if prompt_updated:
|
||||||
|
condition_inputs[LINGBOT_PROMPT_UPDATED_CONDITION] = True
|
||||||
camera_actions = state.sample_camera_actions(chunk_size)
|
camera_actions = state.sample_camera_actions(chunk_size)
|
||||||
if camera_actions is not None:
|
if camera_actions is not None:
|
||||||
condition_inputs["camera_actions"] = camera_actions
|
condition_inputs[LINGBOT_CAMERA_ACTIONS_CONDITION] = camera_actions
|
||||||
return RealtimeChunkInputs(prompt=prompt, condition_inputs=condition_inputs)
|
return RealtimeChunkInputs(prompt=prompt, condition_inputs=condition_inputs)
|
||||||
|
|
||||||
def build_sampling_params(
|
def build_sampling_params(
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ class CausalSelfAttentionKVCache:
|
|||||||
value: torch.Tensor,
|
value: torch.Tensor,
|
||||||
current_chunk_start: int,
|
current_chunk_start: int,
|
||||||
cache_head_start: int | None = None,
|
cache_head_start: int | None = None,
|
||||||
|
recent_window_tokens: int | None = None,
|
||||||
debug_name: str = "causal KV cache",
|
debug_name: str = "causal KV cache",
|
||||||
) -> CausalAttentionKVView:
|
) -> CausalAttentionKVView:
|
||||||
"""write fresh kv into the cache, returns the part of view visible to the current chunk
|
"""write fresh kv into the cache, returns the part of view visible to the current chunk
|
||||||
@@ -118,6 +119,11 @@ class CausalSelfAttentionKVCache:
|
|||||||
current_chunk_start: the global position of the start of the chunk
|
current_chunk_start: the global position of the start of the chunk
|
||||||
cache_head_start: first cache head for key/value when they only
|
cache_head_start: first cache head for key/value when they only
|
||||||
carry a local slice of the cache heads; other heads are left untouched
|
carry a local slice of the cache heads; other heads are left untouched
|
||||||
|
recent_window_tokens: recent-window attention size. ``None``
|
||||||
|
returns the full visible attention window. ``0`` keeps only sink
|
||||||
|
tokens plus the current chunk. A positive value keeps sink tokens,
|
||||||
|
up to that many tokens before the current chunk, and the current
|
||||||
|
chunk. Negative values are invalid.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
num_new_tokens = key.shape[1]
|
num_new_tokens = key.shape[1]
|
||||||
@@ -265,17 +271,22 @@ class CausalSelfAttentionKVCache:
|
|||||||
if cache_head_slice is None:
|
if cache_head_slice is None:
|
||||||
self.k[:, local_start_index:local_end_index] = key
|
self.k[:, local_start_index:local_end_index] = key
|
||||||
self.v[:, local_start_index:local_end_index] = value
|
self.v[:, local_start_index:local_end_index] = value
|
||||||
visible_k = self.k[:, attn_start_index:updated_local_end]
|
visible_k, visible_v = self._visible_attention_kv(
|
||||||
visible_v = self.v[:, attn_start_index:updated_local_end]
|
local_start_index=local_start_index,
|
||||||
|
updated_local_end=updated_local_end,
|
||||||
|
attn_start_index=attn_start_index,
|
||||||
|
recent_window_tokens=recent_window_tokens,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.k[:, local_start_index:local_end_index, cache_head_slice, :] = key
|
self.k[:, local_start_index:local_end_index, cache_head_slice, :] = key
|
||||||
self.v[:, local_start_index:local_end_index, cache_head_slice, :] = value
|
self.v[:, local_start_index:local_end_index, cache_head_slice, :] = value
|
||||||
visible_k = self.k[
|
visible_k, visible_v = self._visible_attention_kv(
|
||||||
:, attn_start_index:updated_local_end, cache_head_slice, :
|
local_start_index=local_start_index,
|
||||||
]
|
updated_local_end=updated_local_end,
|
||||||
visible_v = self.v[
|
attn_start_index=attn_start_index,
|
||||||
:, attn_start_index:updated_local_end, cache_head_slice, :
|
recent_window_tokens=recent_window_tokens,
|
||||||
]
|
cache_head_slice=cache_head_slice,
|
||||||
|
)
|
||||||
|
|
||||||
self._write_indices(
|
self._write_indices(
|
||||||
global_end_index=updated_global_end,
|
global_end_index=updated_global_end,
|
||||||
@@ -290,6 +301,100 @@ class CausalSelfAttentionKVCache:
|
|||||||
visible_global_end=updated_global_end,
|
visible_global_end=updated_global_end,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _visible_attention_kv(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
local_start_index: int,
|
||||||
|
updated_local_end: int,
|
||||||
|
attn_start_index: int,
|
||||||
|
recent_window_tokens: int | None,
|
||||||
|
cache_head_slice: slice | None = None,
|
||||||
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||||
|
"""Return the visible KV slice for the current attention call.
|
||||||
|
|
||||||
|
When ``recent_window_tokens`` is ``None``, the returned token range is
|
||||||
|
the standard sliding window::
|
||||||
|
|
||||||
|
[attn_start_index, updated_local_end)
|
||||||
|
|
||||||
|
When recent-window selection is enabled, ``recent_window_tokens`` must be
|
||||||
|
non-negative and the returned token ranges are::
|
||||||
|
|
||||||
|
sink_end = min(self.sink_tokens, updated_local_end)
|
||||||
|
recent_start = max(sink_end, local_start_index - recent_window_tokens)
|
||||||
|
[0, sink_end) + [recent_start, updated_local_end)
|
||||||
|
|
||||||
|
Thus ``0`` keeps only sink tokens plus the current chunk.
|
||||||
|
``cache_head_slice`` applies the same token ranges to a subset of KV
|
||||||
|
heads.
|
||||||
|
"""
|
||||||
|
if recent_window_tokens is None:
|
||||||
|
if cache_head_slice is None:
|
||||||
|
return (
|
||||||
|
self.k[:, attn_start_index:updated_local_end],
|
||||||
|
self.v[:, attn_start_index:updated_local_end],
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
self.k[:, attn_start_index:updated_local_end, cache_head_slice, :],
|
||||||
|
self.v[:, attn_start_index:updated_local_end, cache_head_slice, :],
|
||||||
|
)
|
||||||
|
if recent_window_tokens < 0:
|
||||||
|
raise ValueError("recent_window_tokens must be non-negative or None")
|
||||||
|
|
||||||
|
sink_end = min(self.sink_tokens, updated_local_end)
|
||||||
|
recent_start = max(sink_end, local_start_index - recent_window_tokens)
|
||||||
|
if recent_start <= sink_end:
|
||||||
|
if cache_head_slice is None:
|
||||||
|
return self.k[:, :updated_local_end], self.v[:, :updated_local_end]
|
||||||
|
return (
|
||||||
|
self.k[:, :updated_local_end, cache_head_slice, :],
|
||||||
|
self.v[:, :updated_local_end, cache_head_slice, :],
|
||||||
|
)
|
||||||
|
if sink_end <= 0:
|
||||||
|
if cache_head_slice is None:
|
||||||
|
return (
|
||||||
|
self.k[:, recent_start:updated_local_end],
|
||||||
|
self.v[:, recent_start:updated_local_end],
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
self.k[:, recent_start:updated_local_end, cache_head_slice, :],
|
||||||
|
self.v[:, recent_start:updated_local_end, cache_head_slice, :],
|
||||||
|
)
|
||||||
|
|
||||||
|
if cache_head_slice is None:
|
||||||
|
return (
|
||||||
|
torch.cat(
|
||||||
|
[
|
||||||
|
self.k[:, :sink_end],
|
||||||
|
self.k[:, recent_start:updated_local_end],
|
||||||
|
],
|
||||||
|
dim=1,
|
||||||
|
),
|
||||||
|
torch.cat(
|
||||||
|
[
|
||||||
|
self.v[:, :sink_end],
|
||||||
|
self.v[:, recent_start:updated_local_end],
|
||||||
|
],
|
||||||
|
dim=1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
torch.cat(
|
||||||
|
[
|
||||||
|
self.k[:, :sink_end, cache_head_slice, :],
|
||||||
|
self.k[:, recent_start:updated_local_end, cache_head_slice, :],
|
||||||
|
],
|
||||||
|
dim=1,
|
||||||
|
),
|
||||||
|
torch.cat(
|
||||||
|
[
|
||||||
|
self.v[:, :sink_end, cache_head_slice, :],
|
||||||
|
self.v[:, recent_start:updated_local_end, cache_head_slice, :],
|
||||||
|
],
|
||||||
|
dim=1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class CrossAttentionKVCache:
|
class CrossAttentionKVCache:
|
||||||
|
|||||||
@@ -79,6 +79,13 @@ from sglang.multimodal_gen.runtime.models.dits.wanvideo import (
|
|||||||
WanTransformer3DModel,
|
WanTransformer3DModel,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.models.utils import _use_aiter
|
from sglang.multimodal_gen.runtime.models.utils import _use_aiter
|
||||||
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.lingbot_world.constants import (
|
||||||
|
LINGBOT_C2WS_PLUCKER_EMB_CACHE,
|
||||||
|
LINGBOT_CAM_CONDITIONER_CACHE,
|
||||||
|
LINGBOT_ROPE_CACHE,
|
||||||
|
LINGBOT_SEQUENCE_SHARD_ROPE_CACHE,
|
||||||
|
LINGBOT_TIME_EMBEDDINGS_CACHE,
|
||||||
|
)
|
||||||
from sglang.multimodal_gen.runtime.platforms import (
|
from sglang.multimodal_gen.runtime.platforms import (
|
||||||
AttentionBackendEnum,
|
AttentionBackendEnum,
|
||||||
current_platform,
|
current_platform,
|
||||||
@@ -299,6 +306,11 @@ class LingBotWorldCausalSelfAttention(CausalWanSelfAttention):
|
|||||||
value=v,
|
value=v,
|
||||||
current_chunk_start=current_start,
|
current_chunk_start=current_start,
|
||||||
cache_head_start=cache_head_start,
|
cache_head_start=cache_head_start,
|
||||||
|
recent_window_tokens=(
|
||||||
|
None
|
||||||
|
if update_cache_only
|
||||||
|
else getattr(forward_batch, "realtime_causal_kv_sample_tokens", None)
|
||||||
|
),
|
||||||
debug_name="LingBot KV cache",
|
debug_name="LingBot KV cache",
|
||||||
)
|
)
|
||||||
if update_cache_only:
|
if update_cache_only:
|
||||||
@@ -998,7 +1010,7 @@ class CausalLingBotWorldTransformerBlock(CausalWanTransformerBlock):
|
|||||||
return self.cam_conditioner.compute_scale_shift(c2ws_plucker_emb)
|
return self.cam_conditioner.compute_scale_shift(c2ws_plucker_emb)
|
||||||
|
|
||||||
cache = CausalLingBotWorldTransformer3DModel._get_request_cache(
|
cache = CausalLingBotWorldTransformer3DModel._get_request_cache(
|
||||||
forward_batch, "lingbot_cam_conditioner"
|
forward_batch, LINGBOT_CAM_CONDITIONER_CACHE
|
||||||
)
|
)
|
||||||
if cache is None:
|
if cache is None:
|
||||||
return self.cam_conditioner.compute_scale_shift(c2ws_plucker_emb)
|
return self.cam_conditioner.compute_scale_shift(c2ws_plucker_emb)
|
||||||
@@ -1211,7 +1223,7 @@ class CausalLingBotWorldTransformer3DModel(CausalWanTransformer3DModel):
|
|||||||
) -> torch.Tensor | None:
|
) -> torch.Tensor | None:
|
||||||
if c2ws_plucker_emb is None:
|
if c2ws_plucker_emb is None:
|
||||||
return None
|
return None
|
||||||
cache = self._get_request_cache(forward_batch, "lingbot_c2ws_plucker_emb")
|
cache = self._get_request_cache(forward_batch, LINGBOT_C2WS_PLUCKER_EMB_CACHE)
|
||||||
cache_key = (
|
cache_key = (
|
||||||
c2ws_plucker_emb.data_ptr(),
|
c2ws_plucker_emb.data_ptr(),
|
||||||
tuple(c2ws_plucker_emb.shape),
|
tuple(c2ws_plucker_emb.shape),
|
||||||
@@ -1274,7 +1286,7 @@ class CausalLingBotWorldTransformer3DModel(CausalWanTransformer3DModel):
|
|||||||
start_frame: int,
|
start_frame: int,
|
||||||
device: torch.device,
|
device: torch.device,
|
||||||
) -> tuple[torch.Tensor, ...]:
|
) -> tuple[torch.Tensor, ...]:
|
||||||
cache = self._get_request_cache(forward_batch, "lingbot_rope")
|
cache = self._get_request_cache(forward_batch, LINGBOT_ROPE_CACHE)
|
||||||
cache_key = (
|
cache_key = (
|
||||||
post_patch_num_frames,
|
post_patch_num_frames,
|
||||||
post_patch_height,
|
post_patch_height,
|
||||||
@@ -1327,7 +1339,9 @@ class CausalLingBotWorldTransformer3DModel(CausalWanTransformer3DModel):
|
|||||||
post_patch_width: int,
|
post_patch_width: int,
|
||||||
device: torch.device,
|
device: torch.device,
|
||||||
) -> tuple[torch.Tensor, ...]:
|
) -> tuple[torch.Tensor, ...]:
|
||||||
cache = self._get_request_cache(forward_batch, "lingbot_sequence_shard_rope")
|
cache = self._get_request_cache(
|
||||||
|
forward_batch, LINGBOT_SEQUENCE_SHARD_ROPE_CACHE
|
||||||
|
)
|
||||||
cache_key = (
|
cache_key = (
|
||||||
local_seq_len,
|
local_seq_len,
|
||||||
token_start,
|
token_start,
|
||||||
@@ -1393,7 +1407,7 @@ class CausalLingBotWorldTransformer3DModel(CausalWanTransformer3DModel):
|
|||||||
timestep: torch.LongTensor,
|
timestep: torch.LongTensor,
|
||||||
forward_batch,
|
forward_batch,
|
||||||
) -> tuple[torch.Tensor, torch.Tensor]:
|
) -> tuple[torch.Tensor, torch.Tensor]:
|
||||||
cache = self._get_request_cache(forward_batch, "lingbot_time_embeddings")
|
cache = self._get_request_cache(forward_batch, LINGBOT_TIME_EMBEDDINGS_CACHE)
|
||||||
current_timestep = get_forward_context().current_timestep
|
current_timestep = get_forward_context().current_timestep
|
||||||
cache_key = (
|
cache_key = (
|
||||||
current_timestep,
|
current_timestep,
|
||||||
@@ -1426,7 +1440,7 @@ class CausalLingBotWorldTransformer3DModel(CausalWanTransformer3DModel):
|
|||||||
if not self._should_cache_cam_conditioner(forward_batch):
|
if not self._should_cache_cam_conditioner(forward_batch):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
cache = self._get_request_cache(forward_batch, "lingbot_cam_conditioner")
|
cache = self._get_request_cache(forward_batch, LINGBOT_CAM_CONDITIONER_CACHE)
|
||||||
if cache is None:
|
if cache is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from sglang.multimodal_gen.runtime.pipelines_core.stages import (
|
|||||||
DMDTimestepPreparationStage,
|
DMDTimestepPreparationStage,
|
||||||
ImageEncodingStage,
|
ImageEncodingStage,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.lingbot_world import (
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.lingbot_world.lingbot_world_causal_denoising import (
|
||||||
LingBotWorldCausalDMDDenoisingStage,
|
LingBotWorldCausalDMDDenoisingStage,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.realtime import (
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.realtime import (
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ class Req:
|
|||||||
realtime_output_pacing: bool = False
|
realtime_output_pacing: bool = False
|
||||||
realtime_causal_sink_size: int | None = None
|
realtime_causal_sink_size: int | None = None
|
||||||
realtime_causal_kv_cache_num_frames: int | None = None
|
realtime_causal_kv_cache_num_frames: int | None = None
|
||||||
|
realtime_causal_kv_sample_tokens: int | None = None
|
||||||
# return websocket-friendly raw RGB frame bytes instead of rwa tensors
|
# return websocket-friendly raw RGB frame bytes instead of rwa tensors
|
||||||
return_raw_frames: bool = False
|
return_raw_frames: bool = False
|
||||||
|
|
||||||
|
|||||||
@@ -1094,6 +1094,7 @@ class CausalDMDDenoisingStage(DenoisingStage):
|
|||||||
device,
|
device,
|
||||||
*,
|
*,
|
||||||
sequence_shard_enabled: bool = False,
|
sequence_shard_enabled: bool = False,
|
||||||
|
kv_cache_size: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize (but not fill) a Per-GPU KV cache aligned with the model assumptions.
|
Initialize (but not fill) a Per-GPU KV cache aligned with the model assumptions.
|
||||||
@@ -1102,9 +1103,10 @@ class CausalDMDDenoisingStage(DenoisingStage):
|
|||||||
sequence_shard_enabled=sequence_shard_enabled
|
sequence_shard_enabled=sequence_shard_enabled
|
||||||
)
|
)
|
||||||
attention_head_dim = self.transformer.attention_head_dim
|
attention_head_dim = self.transformer.attention_head_dim
|
||||||
kv_cache_size = self._get_causal_kv_cache_size(
|
if kv_cache_size is None:
|
||||||
sequence_shard_enabled=sequence_shard_enabled
|
kv_cache_size = self._get_causal_kv_cache_size(
|
||||||
)
|
sequence_shard_enabled=sequence_shard_enabled
|
||||||
|
)
|
||||||
self.causal_kv_cache = self._allocate_causal_kv_cache(
|
self.causal_kv_cache = self._allocate_causal_kv_cache(
|
||||||
batch_size=batch_size,
|
batch_size=batch_size,
|
||||||
kv_cache_size=kv_cache_size,
|
kv_cache_size=kv_cache_size,
|
||||||
|
|||||||
+10
-3
@@ -1,9 +1,16 @@
|
|||||||
# SPDX-License-Identifier: Apache-2.0
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
"""LingBot-World-specific pipeline stages."""
|
"""LingBot-World-specific pipeline stages."""
|
||||||
|
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.lingbot_world.lingbot_world_causal_denoising import (
|
|
||||||
LingBotWorldCausalDMDDenoisingStage,
|
def __getattr__(name: str):
|
||||||
)
|
if name == "LingBotWorldCausalDMDDenoisingStage":
|
||||||
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.lingbot_world.lingbot_world_causal_denoising import (
|
||||||
|
LingBotWorldCausalDMDDenoisingStage,
|
||||||
|
)
|
||||||
|
|
||||||
|
return LingBotWorldCausalDMDDenoisingStage
|
||||||
|
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"LingBotWorldCausalDMDDenoisingStage",
|
"LingBotWorldCausalDMDDenoisingStage",
|
||||||
|
|||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
LINGBOT_CAMERA_ACTIONS_CONDITION = "camera_actions"
|
||||||
|
LINGBOT_PROMPT_UPDATED_CONDITION = "lingbot_prompt_updated"
|
||||||
|
|
||||||
|
LINGBOT_INTERACTIVE_KV_WINDOW_CACHE = "lingbot_interactive_kv_window"
|
||||||
|
LINGBOT_C2WS_PLUCKER_EMB_CACHE = "lingbot_c2ws_plucker_emb"
|
||||||
|
LINGBOT_CAM_CONDITIONER_CACHE = "lingbot_cam_conditioner"
|
||||||
|
LINGBOT_ROPE_CACHE = "lingbot_rope"
|
||||||
|
LINGBOT_SEQUENCE_SHARD_ROPE_CACHE = "lingbot_sequence_shard_rope"
|
||||||
|
LINGBOT_TIME_EMBEDDINGS_CACHE = "lingbot_time_embeddings"
|
||||||
+371
-12
@@ -3,8 +3,11 @@
|
|||||||
|
|
||||||
"""LingBot-World causal DMD denoising stage."""
|
"""LingBot-World causal DMD denoising stage."""
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
from sglang.multimodal_gen import envs
|
||||||
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
|
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
|
||||||
get_ring_parallel_world_size,
|
get_ring_parallel_world_size,
|
||||||
get_ulysses_parallel_world_size,
|
get_ulysses_parallel_world_size,
|
||||||
@@ -14,6 +17,15 @@ from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import Req
|
|||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.causal_denoising import (
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.causal_denoising import (
|
||||||
CausalDMDCachePolicy,
|
CausalDMDCachePolicy,
|
||||||
CausalDMDDenoisingStage,
|
CausalDMDDenoisingStage,
|
||||||
|
CausalDMDForwardContext,
|
||||||
|
CausalDMDRealtimeCacheContext,
|
||||||
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.lingbot_world.constants import (
|
||||||
|
LINGBOT_C2WS_PLUCKER_EMB_CACHE,
|
||||||
|
LINGBOT_CAM_CONDITIONER_CACHE,
|
||||||
|
LINGBOT_CAMERA_ACTIONS_CONDITION,
|
||||||
|
LINGBOT_INTERACTIVE_KV_WINDOW_CACHE,
|
||||||
|
LINGBOT_PROMPT_UPDATED_CONDITION,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.validators import (
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.validators import (
|
||||||
StageValidators as V,
|
StageValidators as V,
|
||||||
@@ -23,6 +35,9 @@ from sglang.multimodal_gen.runtime.pipelines_core.stages.validators import (
|
|||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.platforms import current_platform
|
from sglang.multimodal_gen.runtime.platforms import current_platform
|
||||||
from sglang.multimodal_gen.runtime.server_args import ServerArgs
|
from sglang.multimodal_gen.runtime.server_args import ServerArgs
|
||||||
|
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
|
||||||
|
|
||||||
|
logger = init_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class LingBotWorldCausalDMDDenoisingStage(CausalDMDDenoisingStage):
|
class LingBotWorldCausalDMDDenoisingStage(CausalDMDDenoisingStage):
|
||||||
@@ -76,8 +91,11 @@ class LingBotWorldCausalDMDDenoisingStage(CausalDMDDenoisingStage):
|
|||||||
def _causal_kv_cache_kwargs(
|
def _causal_kv_cache_kwargs(
|
||||||
self,
|
self,
|
||||||
policy: CausalDMDCachePolicy,
|
policy: CausalDMDCachePolicy,
|
||||||
) -> dict[str, bool]:
|
) -> dict[str, Any]:
|
||||||
return {"sequence_shard_enabled": policy.sequence_shard_enabled}
|
return {
|
||||||
|
"sequence_shard_enabled": policy.sequence_shard_enabled,
|
||||||
|
"kv_cache_size": policy.expected_cache_tokens,
|
||||||
|
}
|
||||||
|
|
||||||
def _use_causal_cache_int_indices(
|
def _use_causal_cache_int_indices(
|
||||||
self,
|
self,
|
||||||
@@ -86,6 +104,316 @@ class LingBotWorldCausalDMDDenoisingStage(CausalDMDDenoisingStage):
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _chunk_has_camera_motion(actions) -> bool:
|
||||||
|
if not actions:
|
||||||
|
return False
|
||||||
|
for frame_actions in actions:
|
||||||
|
if frame_actions:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _uses_interactive_kv_window(
|
||||||
|
self,
|
||||||
|
batch: Req,
|
||||||
|
server_args: ServerArgs,
|
||||||
|
) -> bool:
|
||||||
|
if not self._interactive_kv_window_enabled(server_args):
|
||||||
|
return False
|
||||||
|
condition_inputs = getattr(batch, "condition_inputs", None) or {}
|
||||||
|
return LINGBOT_CAMERA_ACTIONS_CONDITION in condition_inputs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _interactive_kv_window_enabled(server_args: ServerArgs) -> bool:
|
||||||
|
config_enabled = bool(
|
||||||
|
getattr(
|
||||||
|
server_args.pipeline_config,
|
||||||
|
"interactive_kv_window_enable",
|
||||||
|
False,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return config_enabled or envs.SGLANG_LINGBOT_ENABLE_INTERACTIVE_KV_WINDOW
|
||||||
|
|
||||||
|
def _apply_causal_cache_overrides(
|
||||||
|
self,
|
||||||
|
batch: Req,
|
||||||
|
server_args: ServerArgs,
|
||||||
|
) -> None:
|
||||||
|
self._reset_causal_cache_config_defaults()
|
||||||
|
super()._apply_causal_cache_overrides(batch, server_args)
|
||||||
|
self._sync_interactive_kv_cache_window(server_args)
|
||||||
|
|
||||||
|
def _reset_causal_cache_config_defaults(self) -> None:
|
||||||
|
arch_config = getattr(
|
||||||
|
getattr(self.transformer, "config", None), "arch_config", None
|
||||||
|
)
|
||||||
|
if arch_config is None:
|
||||||
|
return
|
||||||
|
if hasattr(arch_config, "sink_size"):
|
||||||
|
self.sink_size = int(arch_config.sink_size)
|
||||||
|
if hasattr(arch_config, "sliding_window_num_frames"):
|
||||||
|
self.sliding_window_num_frames = int(arch_config.sliding_window_num_frames)
|
||||||
|
|
||||||
|
def _sync_interactive_kv_cache_window(self, server_args: ServerArgs) -> None:
|
||||||
|
if not self._interactive_kv_window_enabled(server_args):
|
||||||
|
return
|
||||||
|
if self.local_attn_size != -1:
|
||||||
|
return
|
||||||
|
self.sliding_window_num_frames = (
|
||||||
|
self._effective_interactive_kv_cache_num_frames(server_args)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _effective_interactive_kv_cache_num_frames(
|
||||||
|
self,
|
||||||
|
server_args: ServerArgs,
|
||||||
|
) -> int:
|
||||||
|
cache_window = int(self.sliding_window_num_frames)
|
||||||
|
if self.local_attn_size != -1:
|
||||||
|
return cache_window
|
||||||
|
|
||||||
|
moving_window = self._moving_kv_sample_num_frames(server_args) or 0
|
||||||
|
still_window = self._still_kv_sample_num_frames(server_args) or 0
|
||||||
|
return max(
|
||||||
|
cache_window,
|
||||||
|
int(self.sink_size)
|
||||||
|
+ max(moving_window, still_window)
|
||||||
|
+ int(self.num_frames_per_block),
|
||||||
|
)
|
||||||
|
|
||||||
|
def _build_realtime_causal_cache_policy(
|
||||||
|
self,
|
||||||
|
batch: Req,
|
||||||
|
server_args: ServerArgs,
|
||||||
|
) -> CausalDMDCachePolicy:
|
||||||
|
policy = super()._build_realtime_causal_cache_policy(batch, server_args)
|
||||||
|
if self._interactive_kv_window_enabled(server_args):
|
||||||
|
policy.expected_cache_tokens = (
|
||||||
|
self._effective_interactive_kv_cache_num_frames(server_args)
|
||||||
|
* self.num_token_per_frame
|
||||||
|
)
|
||||||
|
return policy
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _should_reset_lingbot_crossattn_cache(batch: Req) -> bool:
|
||||||
|
condition_inputs = getattr(batch, "condition_inputs", None) or {}
|
||||||
|
return bool(condition_inputs.get(LINGBOT_PROMPT_UPDATED_CONDITION))
|
||||||
|
|
||||||
|
def _sync_lingbot_crossattn_cache(
|
||||||
|
self,
|
||||||
|
batch: Req,
|
||||||
|
cache_ctx: CausalDMDRealtimeCacheContext,
|
||||||
|
) -> None:
|
||||||
|
if self._should_reset_lingbot_crossattn_cache(batch):
|
||||||
|
self._reset_crossattn_cache(cache_ctx.crossattn_cache)
|
||||||
|
|
||||||
|
def _prepare_realtime_causal_caches(
|
||||||
|
self,
|
||||||
|
batch: Req,
|
||||||
|
server_args: ServerArgs,
|
||||||
|
ctx: CausalDMDForwardContext,
|
||||||
|
) -> CausalDMDRealtimeCacheContext:
|
||||||
|
cache_ctx = super()._prepare_realtime_causal_caches(batch, server_args, ctx)
|
||||||
|
self._sync_lingbot_crossattn_cache(batch, cache_ctx)
|
||||||
|
return cache_ctx
|
||||||
|
|
||||||
|
def _base_kv_sample_num_frames(self) -> int | None:
|
||||||
|
sample_frames = (
|
||||||
|
int(self.sliding_window_num_frames)
|
||||||
|
- int(self.sink_size)
|
||||||
|
- int(self.num_frames_per_block)
|
||||||
|
)
|
||||||
|
return sample_frames if sample_frames > 0 else None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _optional_non_negative_int(value: Any) -> int | None:
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return max(0, int(value))
|
||||||
|
|
||||||
|
def _moving_kv_sample_num_frames(
|
||||||
|
self,
|
||||||
|
server_args: ServerArgs,
|
||||||
|
) -> int | None:
|
||||||
|
return self._optional_non_negative_int(
|
||||||
|
getattr(
|
||||||
|
server_args.pipeline_config,
|
||||||
|
"interactive_kv_moving_window",
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _still_kv_sample_num_frames(
|
||||||
|
self,
|
||||||
|
server_args: ServerArgs,
|
||||||
|
) -> int | None:
|
||||||
|
return self._optional_non_negative_int(
|
||||||
|
getattr(
|
||||||
|
server_args.pipeline_config,
|
||||||
|
"interactive_kv_still_window",
|
||||||
|
3,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_interactive_kv_sample_num_frames(
|
||||||
|
self,
|
||||||
|
cache_state,
|
||||||
|
batch: Req,
|
||||||
|
server_args: ServerArgs,
|
||||||
|
) -> int | None:
|
||||||
|
pipeline_config = server_args.pipeline_config
|
||||||
|
if not self._interactive_kv_window_enabled(server_args):
|
||||||
|
return None
|
||||||
|
if not self._uses_interactive_kv_window(batch, server_args):
|
||||||
|
return self._base_kv_sample_num_frames()
|
||||||
|
|
||||||
|
dynamic_state = cache_state.runtime_cache.setdefault(
|
||||||
|
LINGBOT_INTERACTIVE_KV_WINDOW_CACHE,
|
||||||
|
{
|
||||||
|
"consecutive_still_chunks": 0,
|
||||||
|
"sample_num_frames": None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if cache_state.chunk_idx == 0:
|
||||||
|
dynamic_state["consecutive_still_chunks"] = 0
|
||||||
|
dynamic_state["sample_num_frames"] = None
|
||||||
|
|
||||||
|
moving_window = self._moving_kv_sample_num_frames(server_args)
|
||||||
|
if moving_window is None:
|
||||||
|
return None
|
||||||
|
still_window = self._still_kv_sample_num_frames(server_args)
|
||||||
|
still_chunks_threshold = max(
|
||||||
|
1, int(getattr(pipeline_config, "interactive_kv_still_chunks", 2))
|
||||||
|
)
|
||||||
|
if dynamic_state["sample_num_frames"] is None:
|
||||||
|
dynamic_state["sample_num_frames"] = moving_window
|
||||||
|
|
||||||
|
condition_inputs = getattr(batch, "condition_inputs", None) or {}
|
||||||
|
if self._chunk_has_camera_motion(
|
||||||
|
condition_inputs.get(LINGBOT_CAMERA_ACTIONS_CONDITION)
|
||||||
|
):
|
||||||
|
dynamic_state["consecutive_still_chunks"] = 0
|
||||||
|
dynamic_state["sample_num_frames"] = moving_window
|
||||||
|
else:
|
||||||
|
dynamic_state["consecutive_still_chunks"] += 1
|
||||||
|
if (
|
||||||
|
still_window is not None
|
||||||
|
and dynamic_state["consecutive_still_chunks"] >= still_chunks_threshold
|
||||||
|
):
|
||||||
|
dynamic_state["sample_num_frames"] = still_window
|
||||||
|
|
||||||
|
return int(dynamic_state["sample_num_frames"])
|
||||||
|
|
||||||
|
def _log_lingbot_kv_window(
|
||||||
|
self,
|
||||||
|
cache_state,
|
||||||
|
batch: Req,
|
||||||
|
server_args: ServerArgs,
|
||||||
|
*,
|
||||||
|
sample_frames: int | None,
|
||||||
|
) -> None:
|
||||||
|
if not self._interactive_kv_window_enabled(server_args):
|
||||||
|
return
|
||||||
|
|
||||||
|
mode = "base"
|
||||||
|
still_chunks = None
|
||||||
|
if self._uses_interactive_kv_window(batch, server_args):
|
||||||
|
dynamic_state = cache_state.runtime_cache.get(
|
||||||
|
LINGBOT_INTERACTIVE_KV_WINDOW_CACHE, {}
|
||||||
|
)
|
||||||
|
still_chunks = dynamic_state.get("consecutive_still_chunks")
|
||||||
|
condition_inputs = getattr(batch, "condition_inputs", None) or {}
|
||||||
|
if self._chunk_has_camera_motion(
|
||||||
|
condition_inputs.get(LINGBOT_CAMERA_ACTIONS_CONDITION)
|
||||||
|
):
|
||||||
|
mode = "moving"
|
||||||
|
else:
|
||||||
|
still_window = self._still_kv_sample_num_frames(server_args)
|
||||||
|
still_chunks_threshold = max(
|
||||||
|
1,
|
||||||
|
int(
|
||||||
|
getattr(
|
||||||
|
server_args.pipeline_config,
|
||||||
|
"interactive_kv_still_chunks",
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
still_window is not None
|
||||||
|
and sample_frames == still_window
|
||||||
|
and still_chunks is not None
|
||||||
|
and still_chunks >= still_chunks_threshold
|
||||||
|
):
|
||||||
|
mode = "still"
|
||||||
|
else:
|
||||||
|
mode = "moving"
|
||||||
|
|
||||||
|
window_frames = (
|
||||||
|
int(self.sliding_window_num_frames)
|
||||||
|
if sample_frames is None
|
||||||
|
else int(self.sink_size)
|
||||||
|
+ int(sample_frames)
|
||||||
|
+ int(self.num_frames_per_block)
|
||||||
|
)
|
||||||
|
sample_tokens = (
|
||||||
|
None
|
||||||
|
if sample_frames is None
|
||||||
|
else int(sample_frames) * int(self.num_token_per_frame)
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
"LingBot interactive KV window: session_id=%s request_id=%s "
|
||||||
|
"chunk_idx=%s mode=%s window_frames=%s sample_frames=%s "
|
||||||
|
"cache_frames=%s sink_frames=%s current_frames=%s sample_tokens=%s "
|
||||||
|
"cache_tokens=%s still_chunks=%s",
|
||||||
|
getattr(batch, "realtime_session_id", None),
|
||||||
|
getattr(batch, "request_id", None),
|
||||||
|
getattr(batch, "block_idx", None),
|
||||||
|
mode,
|
||||||
|
window_frames,
|
||||||
|
sample_frames,
|
||||||
|
int(self.sliding_window_num_frames),
|
||||||
|
int(self.sink_size),
|
||||||
|
int(self.num_frames_per_block),
|
||||||
|
sample_tokens,
|
||||||
|
int(self.sliding_window_num_frames) * int(self.num_token_per_frame),
|
||||||
|
still_chunks,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _set_lingbot_kv_sample_tokens(
|
||||||
|
self,
|
||||||
|
cache_state,
|
||||||
|
batch: Req,
|
||||||
|
server_args: ServerArgs,
|
||||||
|
) -> int | None:
|
||||||
|
self._sync_interactive_kv_cache_window(server_args)
|
||||||
|
sample_frames = self._get_interactive_kv_sample_num_frames(
|
||||||
|
cache_state,
|
||||||
|
batch,
|
||||||
|
server_args,
|
||||||
|
)
|
||||||
|
sample_tokens = (
|
||||||
|
None
|
||||||
|
if sample_frames is None
|
||||||
|
else int(sample_frames) * self.num_token_per_frame
|
||||||
|
)
|
||||||
|
self._log_lingbot_kv_window(
|
||||||
|
cache_state,
|
||||||
|
batch,
|
||||||
|
server_args,
|
||||||
|
sample_frames=sample_frames,
|
||||||
|
)
|
||||||
|
previous = getattr(batch, "realtime_causal_kv_sample_tokens", None)
|
||||||
|
batch.realtime_causal_kv_sample_tokens = sample_tokens
|
||||||
|
return previous
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _clear_lingbot_dynamic_condition_cache(cache_state) -> None:
|
||||||
|
runtime_cache = getattr(cache_state, "runtime_cache", None)
|
||||||
|
if runtime_cache is None:
|
||||||
|
return
|
||||||
|
runtime_cache.pop(LINGBOT_C2WS_PLUCKER_EMB_CACHE, None)
|
||||||
|
runtime_cache.pop(LINGBOT_CAM_CONDITIONER_CACHE, None)
|
||||||
|
|
||||||
def verify_input(self, batch: Req, server_args: ServerArgs) -> VerificationResult:
|
def verify_input(self, batch: Req, server_args: ServerArgs) -> VerificationResult:
|
||||||
result = VerificationResult()
|
result = VerificationResult()
|
||||||
result.add_check(
|
result.add_check(
|
||||||
@@ -97,6 +425,35 @@ class LingBotWorldCausalDMDDenoisingStage(CausalDMDDenoisingStage):
|
|||||||
result.add_check("prompt_embeds", batch.prompt_embeds, V.list_not_empty)
|
result.add_check("prompt_embeds", batch.prompt_embeds, V.list_not_empty)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def _denoise_realtime_causal_chunk(
|
||||||
|
self,
|
||||||
|
batch: Req,
|
||||||
|
server_args: ServerArgs,
|
||||||
|
*,
|
||||||
|
ctx,
|
||||||
|
cache_ctx,
|
||||||
|
chunk_latents: torch.Tensor,
|
||||||
|
prepare_model_input,
|
||||||
|
prepare_context_input,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
previous_sample_tokens = self._set_lingbot_kv_sample_tokens(
|
||||||
|
cache_ctx.cache_state,
|
||||||
|
batch,
|
||||||
|
server_args,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
return super()._denoise_realtime_causal_chunk(
|
||||||
|
batch,
|
||||||
|
server_args,
|
||||||
|
ctx=ctx,
|
||||||
|
cache_ctx=cache_ctx,
|
||||||
|
chunk_latents=chunk_latents,
|
||||||
|
prepare_model_input=prepare_model_input,
|
||||||
|
prepare_context_input=prepare_context_input,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
batch.realtime_causal_kv_sample_tokens = previous_sample_tokens
|
||||||
|
|
||||||
def _get_causal_dmd_latents(self, batch: Req) -> torch.Tensor:
|
def _get_causal_dmd_latents(self, batch: Req) -> torch.Tensor:
|
||||||
latents = batch.latents
|
latents = batch.latents
|
||||||
assert latents is not None, (
|
assert latents is not None, (
|
||||||
@@ -294,16 +651,18 @@ class LingBotWorldCausalDMDDenoisingStage(CausalDMDDenoisingStage):
|
|||||||
device=ctx.device,
|
device=ctx.device,
|
||||||
)
|
)
|
||||||
|
|
||||||
current_latents = self._denoise_realtime_causal_chunk(
|
try:
|
||||||
batch,
|
current_latents = self._denoise_realtime_causal_chunk(
|
||||||
server_args,
|
batch,
|
||||||
ctx=ctx,
|
server_args,
|
||||||
cache_ctx=cache_ctx,
|
ctx=ctx,
|
||||||
chunk_latents=current_latents,
|
cache_ctx=cache_ctx,
|
||||||
prepare_model_input=prepare_model_input,
|
chunk_latents=current_latents,
|
||||||
prepare_context_input=prepare_model_input,
|
prepare_model_input=prepare_model_input,
|
||||||
)
|
prepare_context_input=prepare_model_input,
|
||||||
cache_ctx.cache_state.runtime_cache.pop("lingbot_cam_conditioner", None)
|
)
|
||||||
|
finally:
|
||||||
|
self._clear_lingbot_dynamic_condition_cache(cache_ctx.cache_state)
|
||||||
|
|
||||||
# Advance cumulative frame position
|
# Advance cumulative frame position
|
||||||
self._advance_realtime_causal_cache(cache_ctx, num_frames=ctx.num_frames)
|
self._advance_realtime_causal_cache(cache_ctx, num_frames=ctx.num_frames)
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ from types import SimpleNamespace
|
|||||||
|
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
|
from sglang.multimodal_gen.configs.pipeline_configs.lingbot_world import (
|
||||||
|
LingBotWorldCausalDMDConfig,
|
||||||
|
)
|
||||||
from sglang.multimodal_gen.runtime.layers.kvcache.causal_attention_cache import (
|
from sglang.multimodal_gen.runtime.layers.kvcache.causal_attention_cache import (
|
||||||
|
CausalSelfAttentionKVCache,
|
||||||
CrossAttentionKVCache,
|
CrossAttentionKVCache,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.models.dits import (
|
from sglang.multimodal_gen.runtime.models.dits import (
|
||||||
@@ -15,9 +19,21 @@ from sglang.multimodal_gen.runtime.models.dits.lingbot_world import (
|
|||||||
CausalLingBotWorldTransformerBlock,
|
CausalLingBotWorldTransformerBlock,
|
||||||
LingBotWorldCamConditioner,
|
LingBotWorldCamConditioner,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.lingbot_world import (
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.causal_denoising import (
|
||||||
|
CausalDMDCachePolicy,
|
||||||
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.lingbot_world.constants import (
|
||||||
|
LINGBOT_C2WS_PLUCKER_EMB_CACHE,
|
||||||
|
LINGBOT_CAM_CONDITIONER_CACHE,
|
||||||
|
LINGBOT_PROMPT_UPDATED_CONDITION,
|
||||||
|
LINGBOT_ROPE_CACHE,
|
||||||
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.lingbot_world.lingbot_world_causal_denoising import (
|
||||||
LingBotWorldCausalDMDDenoisingStage,
|
LingBotWorldCausalDMDDenoisingStage,
|
||||||
)
|
)
|
||||||
|
from sglang.multimodal_gen.runtime.realtime.states import RealtimeCausalDiTState
|
||||||
|
|
||||||
|
LINGBOT_INTERACTIVE_KV_WINDOW_ENV = "SGLANG_LINGBOT_ENABLE_INTERACTIVE_KV_WINDOW"
|
||||||
|
|
||||||
|
|
||||||
def test_lingbot_denoising_stage_does_not_own_realtime_cache_refs():
|
def test_lingbot_denoising_stage_does_not_own_realtime_cache_refs():
|
||||||
@@ -181,6 +197,408 @@ def test_lingbot_realtime_attention_cache_rolls_with_sink_window():
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_realtime_attention_cache_samples_sink_and_recent_window():
|
||||||
|
cache = CausalSelfAttentionKVCache(
|
||||||
|
k=torch.zeros(1, 8, 1, 1),
|
||||||
|
v=torch.zeros(1, 8, 1, 1),
|
||||||
|
global_end_index=torch.zeros(1, dtype=torch.long),
|
||||||
|
local_end_index=torch.zeros(1, dtype=torch.long),
|
||||||
|
global_end_index_int=0,
|
||||||
|
local_end_index_int=0,
|
||||||
|
cache_size=8,
|
||||||
|
sink_tokens=2,
|
||||||
|
attention_window_size=8,
|
||||||
|
)
|
||||||
|
|
||||||
|
cache.update_and_get_attention_kv(
|
||||||
|
key=torch.ones(1, 3, 1, 1),
|
||||||
|
value=torch.ones(1, 3, 1, 1),
|
||||||
|
current_chunk_start=0,
|
||||||
|
)
|
||||||
|
cache.update_and_get_attention_kv(
|
||||||
|
key=torch.full((1, 3, 1, 1), 2.0),
|
||||||
|
value=torch.full((1, 3, 1, 1), 2.0),
|
||||||
|
current_chunk_start=3,
|
||||||
|
)
|
||||||
|
sampled_view = cache.update_and_get_attention_kv(
|
||||||
|
key=torch.full((1, 3, 1, 1), 3.0),
|
||||||
|
value=torch.full((1, 3, 1, 1), 3.0),
|
||||||
|
current_chunk_start=6,
|
||||||
|
recent_window_tokens=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert sampled_view.k.flatten().tolist() == [1.0, 1.0, 2.0, 3.0, 3.0, 3.0]
|
||||||
|
assert sampled_view.v.flatten().tolist() == [1.0, 1.0, 2.0, 3.0, 3.0, 3.0]
|
||||||
|
|
||||||
|
current_only_view = cache.update_and_get_attention_kv(
|
||||||
|
key=torch.full((1, 3, 1, 1), 4.0),
|
||||||
|
value=torch.full((1, 3, 1, 1), 4.0),
|
||||||
|
current_chunk_start=6,
|
||||||
|
recent_window_tokens=0,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert current_only_view.k.flatten().tolist() == [1.0, 1.0, 4.0, 4.0, 4.0]
|
||||||
|
assert current_only_view.v.flatten().tolist() == [1.0, 1.0, 4.0, 4.0, 4.0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_interactive_kv_window_config_default_disabled():
|
||||||
|
field = LingBotWorldCausalDMDConfig.__dataclass_fields__[
|
||||||
|
"interactive_kv_window_enable"
|
||||||
|
]
|
||||||
|
|
||||||
|
assert field.default is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_lazy_vae_encode_black_frames_env(monkeypatch):
|
||||||
|
config = LingBotWorldCausalDMDConfig()
|
||||||
|
temporal_ratio = int(config.vae_config.arch_config.temporal_compression_ratio)
|
||||||
|
image = torch.zeros(1, 3, temporal_ratio * 2 + 1, 2, 2)
|
||||||
|
|
||||||
|
monkeypatch.delenv("SGLANG_LINGBOT_LAZY_VAE_ENCODE_BLACK_FRAMES", raising=False)
|
||||||
|
assert config.preprocess_vae_encode(image, vae=None) is image
|
||||||
|
|
||||||
|
monkeypatch.setenv("SGLANG_LINGBOT_LAZY_VAE_ENCODE_BLACK_FRAMES", "0")
|
||||||
|
assert config.preprocess_vae_encode(image, vae=None) is image
|
||||||
|
|
||||||
|
monkeypatch.setenv(
|
||||||
|
"SGLANG_LINGBOT_LAZY_VAE_ENCODE_BLACK_FRAMES", str(temporal_ratio)
|
||||||
|
)
|
||||||
|
encoded = config.preprocess_vae_encode(image, vae=None)
|
||||||
|
assert encoded.shape[2] == temporal_ratio + 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_interactive_kv_window_samples_base_moving_and_still(monkeypatch):
|
||||||
|
monkeypatch.delenv(LINGBOT_INTERACTIVE_KV_WINDOW_ENV, raising=False)
|
||||||
|
stage = LingBotWorldCausalDMDDenoisingStage.__new__(
|
||||||
|
LingBotWorldCausalDMDDenoisingStage
|
||||||
|
)
|
||||||
|
stage.local_attn_size = -1
|
||||||
|
stage.sink_size = 9
|
||||||
|
stage.num_token_per_frame = 10
|
||||||
|
stage.num_frames_per_block = 3
|
||||||
|
stage.sliding_window_num_frames = 18
|
||||||
|
stage.transformer = SimpleNamespace(num_attention_heads=1)
|
||||||
|
server_args = SimpleNamespace(
|
||||||
|
pipeline_config=SimpleNamespace(
|
||||||
|
interactive_kv_window_enable=True,
|
||||||
|
interactive_kv_moving_window=12,
|
||||||
|
interactive_kv_still_window=3,
|
||||||
|
interactive_kv_still_chunks=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cache_state = RealtimeCausalDiTState()
|
||||||
|
|
||||||
|
batch = SimpleNamespace(condition_inputs={})
|
||||||
|
previous = stage._set_lingbot_kv_sample_tokens(cache_state, batch, server_args)
|
||||||
|
assert previous is None
|
||||||
|
assert stage.sliding_window_num_frames == 24
|
||||||
|
assert batch.realtime_causal_kv_sample_tokens == 120
|
||||||
|
policy = stage._build_realtime_causal_cache_policy(batch, server_args)
|
||||||
|
assert policy.expected_cache_tokens == 240
|
||||||
|
|
||||||
|
batch.condition_inputs = {"camera_actions": [["w"], [], []]}
|
||||||
|
cache_state.chunk_idx = 0
|
||||||
|
stage._set_lingbot_kv_sample_tokens(cache_state, batch, server_args)
|
||||||
|
assert batch.realtime_causal_kv_sample_tokens == 120
|
||||||
|
|
||||||
|
batch.condition_inputs = {"camera_actions": [[], [], []]}
|
||||||
|
cache_state.chunk_idx = 1
|
||||||
|
stage._set_lingbot_kv_sample_tokens(cache_state, batch, server_args)
|
||||||
|
assert batch.realtime_causal_kv_sample_tokens == 120
|
||||||
|
|
||||||
|
cache_state.chunk_idx = 2
|
||||||
|
stage._set_lingbot_kv_sample_tokens(cache_state, batch, server_args)
|
||||||
|
assert batch.realtime_causal_kv_sample_tokens == 30
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_interactive_kv_window_none_disables_moving_window(monkeypatch):
|
||||||
|
monkeypatch.delenv(LINGBOT_INTERACTIVE_KV_WINDOW_ENV, raising=False)
|
||||||
|
stage = LingBotWorldCausalDMDDenoisingStage.__new__(
|
||||||
|
LingBotWorldCausalDMDDenoisingStage
|
||||||
|
)
|
||||||
|
stage.local_attn_size = -1
|
||||||
|
stage.sink_size = 9
|
||||||
|
stage.num_token_per_frame = 10
|
||||||
|
stage.num_frames_per_block = 3
|
||||||
|
stage.sliding_window_num_frames = 18
|
||||||
|
stage.transformer = SimpleNamespace(num_attention_heads=1)
|
||||||
|
server_args = SimpleNamespace(
|
||||||
|
pipeline_config=SimpleNamespace(
|
||||||
|
realtime_causal_sink_size=9,
|
||||||
|
realtime_causal_kv_cache_num_frames=18,
|
||||||
|
interactive_kv_window_enable=True,
|
||||||
|
interactive_kv_moving_window=None,
|
||||||
|
interactive_kv_still_window=3,
|
||||||
|
interactive_kv_still_chunks=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cache_state = RealtimeCausalDiTState()
|
||||||
|
batch = SimpleNamespace(condition_inputs={"camera_actions": [["w"], [], []]})
|
||||||
|
|
||||||
|
stage._set_lingbot_kv_sample_tokens(cache_state, batch, server_args)
|
||||||
|
assert batch.realtime_causal_kv_sample_tokens is None
|
||||||
|
policy = stage._build_realtime_causal_cache_policy(batch, server_args)
|
||||||
|
assert policy.expected_cache_tokens == 180
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_interactive_kv_window_zero_is_valid_moving_window(monkeypatch):
|
||||||
|
monkeypatch.delenv(LINGBOT_INTERACTIVE_KV_WINDOW_ENV, raising=False)
|
||||||
|
stage = LingBotWorldCausalDMDDenoisingStage.__new__(
|
||||||
|
LingBotWorldCausalDMDDenoisingStage
|
||||||
|
)
|
||||||
|
stage.local_attn_size = -1
|
||||||
|
stage.sink_size = 9
|
||||||
|
stage.num_token_per_frame = 10
|
||||||
|
stage.num_frames_per_block = 3
|
||||||
|
stage.sliding_window_num_frames = 18
|
||||||
|
stage.transformer = SimpleNamespace(num_attention_heads=1)
|
||||||
|
server_args = SimpleNamespace(
|
||||||
|
pipeline_config=SimpleNamespace(
|
||||||
|
realtime_causal_sink_size=9,
|
||||||
|
realtime_causal_kv_cache_num_frames=18,
|
||||||
|
interactive_kv_window_enable=True,
|
||||||
|
interactive_kv_moving_window=0,
|
||||||
|
interactive_kv_still_window=None,
|
||||||
|
interactive_kv_still_chunks=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cache_state = RealtimeCausalDiTState()
|
||||||
|
batch = SimpleNamespace(condition_inputs={"camera_actions": [["w"], [], []]})
|
||||||
|
|
||||||
|
stage._set_lingbot_kv_sample_tokens(cache_state, batch, server_args)
|
||||||
|
assert batch.realtime_causal_kv_sample_tokens == 0
|
||||||
|
policy = stage._build_realtime_causal_cache_policy(batch, server_args)
|
||||||
|
assert policy.expected_cache_tokens == 180
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_interactive_kv_window_updates_total_window_for_moving_default(
|
||||||
|
monkeypatch,
|
||||||
|
):
|
||||||
|
monkeypatch.delenv(LINGBOT_INTERACTIVE_KV_WINDOW_ENV, raising=False)
|
||||||
|
stage = LingBotWorldCausalDMDDenoisingStage.__new__(
|
||||||
|
LingBotWorldCausalDMDDenoisingStage
|
||||||
|
)
|
||||||
|
stage.local_attn_size = -1
|
||||||
|
stage.sink_size = 9
|
||||||
|
stage.num_token_per_frame = 10
|
||||||
|
stage.num_frames_per_block = 3
|
||||||
|
stage.sliding_window_num_frames = 18
|
||||||
|
stage.transformer = SimpleNamespace(num_attention_heads=1)
|
||||||
|
server_args = SimpleNamespace(
|
||||||
|
pipeline_config=SimpleNamespace(
|
||||||
|
realtime_causal_sink_size=9,
|
||||||
|
realtime_causal_kv_cache_num_frames=18,
|
||||||
|
interactive_kv_window_enable=True,
|
||||||
|
interactive_kv_moving_window=12,
|
||||||
|
interactive_kv_still_window=3,
|
||||||
|
interactive_kv_still_chunks=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
batch = SimpleNamespace(condition_inputs={"camera_actions": [["w"], [], []]})
|
||||||
|
|
||||||
|
policy = stage._build_realtime_causal_cache_policy(batch, server_args)
|
||||||
|
|
||||||
|
assert stage.sliding_window_num_frames == 24
|
||||||
|
assert policy.expected_cache_tokens == 240
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_interactive_kv_window_resets_stage_window_between_requests(
|
||||||
|
monkeypatch,
|
||||||
|
):
|
||||||
|
monkeypatch.delenv(LINGBOT_INTERACTIVE_KV_WINDOW_ENV, raising=False)
|
||||||
|
stage = LingBotWorldCausalDMDDenoisingStage.__new__(
|
||||||
|
LingBotWorldCausalDMDDenoisingStage
|
||||||
|
)
|
||||||
|
stage.local_attn_size = -1
|
||||||
|
stage.sink_size = 9
|
||||||
|
stage.num_token_per_frame = 10
|
||||||
|
stage.num_frames_per_block = 3
|
||||||
|
stage.sliding_window_num_frames = 18
|
||||||
|
stage.transformer = SimpleNamespace(
|
||||||
|
num_attention_heads=1,
|
||||||
|
config=SimpleNamespace(
|
||||||
|
arch_config=SimpleNamespace(
|
||||||
|
sink_size=9,
|
||||||
|
sliding_window_num_frames=18,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
dynamic_args = SimpleNamespace(
|
||||||
|
pipeline_config=SimpleNamespace(
|
||||||
|
realtime_causal_sink_size=9,
|
||||||
|
realtime_causal_kv_cache_num_frames=18,
|
||||||
|
interactive_kv_window_enable=True,
|
||||||
|
interactive_kv_moving_window=12,
|
||||||
|
interactive_kv_still_window=3,
|
||||||
|
interactive_kv_still_chunks=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
disabled_args = SimpleNamespace(
|
||||||
|
pipeline_config=SimpleNamespace(
|
||||||
|
interactive_kv_window_enable=False,
|
||||||
|
interactive_kv_moving_window=12,
|
||||||
|
interactive_kv_still_window=3,
|
||||||
|
interactive_kv_still_chunks=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
dynamic_batch = SimpleNamespace(condition_inputs={"camera_actions": [["w"]]})
|
||||||
|
dynamic_policy = stage._build_realtime_causal_cache_policy(
|
||||||
|
dynamic_batch, dynamic_args
|
||||||
|
)
|
||||||
|
assert stage.sliding_window_num_frames == 24
|
||||||
|
assert dynamic_policy.expected_cache_tokens == 240
|
||||||
|
|
||||||
|
disabled_batch = SimpleNamespace(condition_inputs={})
|
||||||
|
disabled_policy = stage._build_realtime_causal_cache_policy(
|
||||||
|
disabled_batch, disabled_args
|
||||||
|
)
|
||||||
|
assert stage.sliding_window_num_frames == 18
|
||||||
|
assert disabled_policy.expected_cache_tokens == 180
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_interactive_kv_window_default_disabled(monkeypatch):
|
||||||
|
monkeypatch.delenv(LINGBOT_INTERACTIVE_KV_WINDOW_ENV, raising=False)
|
||||||
|
stage = LingBotWorldCausalDMDDenoisingStage.__new__(
|
||||||
|
LingBotWorldCausalDMDDenoisingStage
|
||||||
|
)
|
||||||
|
stage.local_attn_size = -1
|
||||||
|
stage.sink_size = 9
|
||||||
|
stage.num_token_per_frame = 10
|
||||||
|
stage.num_frames_per_block = 3
|
||||||
|
stage.sliding_window_num_frames = 18
|
||||||
|
stage.transformer = SimpleNamespace(num_attention_heads=1)
|
||||||
|
server_args = SimpleNamespace(
|
||||||
|
pipeline_config=SimpleNamespace(
|
||||||
|
realtime_causal_sink_size=9,
|
||||||
|
realtime_causal_kv_cache_num_frames=18,
|
||||||
|
interactive_kv_window_enable=False,
|
||||||
|
interactive_kv_moving_window=12,
|
||||||
|
interactive_kv_still_window=3,
|
||||||
|
interactive_kv_still_chunks=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cache_state = RealtimeCausalDiTState()
|
||||||
|
batch = SimpleNamespace(condition_inputs={"camera_actions": [["w"], [], []]})
|
||||||
|
|
||||||
|
stage._set_lingbot_kv_sample_tokens(cache_state, batch, server_args)
|
||||||
|
policy = stage._build_realtime_causal_cache_policy(batch, server_args)
|
||||||
|
|
||||||
|
assert stage.sliding_window_num_frames == 18
|
||||||
|
assert batch.realtime_causal_kv_sample_tokens is None
|
||||||
|
assert policy.expected_cache_tokens == 180
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_interactive_kv_window_env_can_enable_default(monkeypatch):
|
||||||
|
monkeypatch.setenv(LINGBOT_INTERACTIVE_KV_WINDOW_ENV, "1")
|
||||||
|
stage = LingBotWorldCausalDMDDenoisingStage.__new__(
|
||||||
|
LingBotWorldCausalDMDDenoisingStage
|
||||||
|
)
|
||||||
|
stage.local_attn_size = -1
|
||||||
|
stage.sink_size = 9
|
||||||
|
stage.num_token_per_frame = 10
|
||||||
|
stage.num_frames_per_block = 3
|
||||||
|
stage.sliding_window_num_frames = 18
|
||||||
|
stage.transformer = SimpleNamespace(num_attention_heads=1)
|
||||||
|
server_args = SimpleNamespace(
|
||||||
|
pipeline_config=SimpleNamespace(
|
||||||
|
realtime_causal_sink_size=9,
|
||||||
|
realtime_causal_kv_cache_num_frames=18,
|
||||||
|
interactive_kv_window_enable=False,
|
||||||
|
interactive_kv_moving_window=12,
|
||||||
|
interactive_kv_still_window=3,
|
||||||
|
interactive_kv_still_chunks=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
cache_state = RealtimeCausalDiTState()
|
||||||
|
batch = SimpleNamespace(condition_inputs={"camera_actions": [["w"], [], []]})
|
||||||
|
|
||||||
|
stage._set_lingbot_kv_sample_tokens(cache_state, batch, server_args)
|
||||||
|
policy = stage._build_realtime_causal_cache_policy(batch, server_args)
|
||||||
|
|
||||||
|
assert stage.sliding_window_num_frames == 24
|
||||||
|
assert batch.realtime_causal_kv_sample_tokens == 120
|
||||||
|
assert policy.expected_cache_tokens == 240
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_interactive_kv_window_allocates_expected_cache_size():
|
||||||
|
stage = LingBotWorldCausalDMDDenoisingStage.__new__(
|
||||||
|
LingBotWorldCausalDMDDenoisingStage
|
||||||
|
)
|
||||||
|
stage.num_transformer_blocks = 1
|
||||||
|
stage.local_attn_size = -1
|
||||||
|
stage.sink_size = 9
|
||||||
|
stage.num_token_per_frame = 10
|
||||||
|
stage.num_frames_per_block = 3
|
||||||
|
stage.sliding_window_num_frames = 18
|
||||||
|
stage.transformer = SimpleNamespace(num_attention_heads=1, attention_head_dim=1)
|
||||||
|
policy = CausalDMDCachePolicy(
|
||||||
|
sequence_shard_enabled=False,
|
||||||
|
num_attention_heads=1,
|
||||||
|
expected_cache_tokens=240,
|
||||||
|
expected_sink_tokens=90,
|
||||||
|
kv_cache_kwargs={},
|
||||||
|
)
|
||||||
|
|
||||||
|
stage._initialize_kv_cache(
|
||||||
|
batch_size=1,
|
||||||
|
dtype=torch.float32,
|
||||||
|
device=torch.device("cpu"),
|
||||||
|
**stage._causal_kv_cache_kwargs(policy),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert stage.causal_kv_cache is not None
|
||||||
|
cache = stage.causal_kv_cache[0]
|
||||||
|
assert cache.cache_size == 240
|
||||||
|
assert cache.k.shape[1] == 240
|
||||||
|
assert cache.sink_tokens == 90
|
||||||
|
assert cache.attention_window_size == 240
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_dynamic_condition_cache_clear_removes_chunk_entries():
|
||||||
|
cache_state = SimpleNamespace(
|
||||||
|
runtime_cache={
|
||||||
|
LINGBOT_C2WS_PLUCKER_EMB_CACHE: object(),
|
||||||
|
LINGBOT_CAM_CONDITIONER_CACHE: object(),
|
||||||
|
LINGBOT_ROPE_CACHE: object(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
LingBotWorldCausalDMDDenoisingStage._clear_lingbot_dynamic_condition_cache(
|
||||||
|
cache_state
|
||||||
|
)
|
||||||
|
|
||||||
|
assert LINGBOT_C2WS_PLUCKER_EMB_CACHE not in cache_state.runtime_cache
|
||||||
|
assert LINGBOT_CAM_CONDITIONER_CACHE not in cache_state.runtime_cache
|
||||||
|
assert LINGBOT_ROPE_CACHE in cache_state.runtime_cache
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_crossattn_cache_resets_on_prompt_event():
|
||||||
|
stage = LingBotWorldCausalDMDDenoisingStage.__new__(
|
||||||
|
LingBotWorldCausalDMDDenoisingStage
|
||||||
|
)
|
||||||
|
crossattn_cache = [
|
||||||
|
CrossAttentionKVCache(
|
||||||
|
k=torch.empty(1),
|
||||||
|
v=torch.empty(1),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
crossattn_cache[0].store(torch.ones(1), torch.ones(1))
|
||||||
|
cache_ctx = SimpleNamespace(
|
||||||
|
cache_state=RealtimeCausalDiTState(),
|
||||||
|
crossattn_cache=crossattn_cache,
|
||||||
|
)
|
||||||
|
batch = SimpleNamespace(condition_inputs={})
|
||||||
|
|
||||||
|
stage._sync_lingbot_crossattn_cache(batch, cache_ctx)
|
||||||
|
assert crossattn_cache[0].is_init
|
||||||
|
|
||||||
|
batch.condition_inputs = {LINGBOT_PROMPT_UPDATED_CONDITION: True}
|
||||||
|
stage._sync_lingbot_crossattn_cache(batch, cache_ctx)
|
||||||
|
|
||||||
|
assert not crossattn_cache[0].is_init
|
||||||
|
|
||||||
|
|
||||||
def test_lingbot_i2v_model_input_writer_reuses_buffer():
|
def test_lingbot_i2v_model_input_writer_reuses_buffer():
|
||||||
latents = torch.ones(1, 16, 3, 2, 2)
|
latents = torch.ones(1, 16, 3, 2, 2)
|
||||||
condition = torch.full((1, 20, 3, 2, 2), 2.0)
|
condition = torch.full((1, 20, 3, 2, 2), 2.0)
|
||||||
@@ -255,7 +673,7 @@ def test_lingbot_cam_conditioner_cache_reuses_source_tensor(monkeypatch):
|
|||||||
assert first is second
|
assert first is second
|
||||||
assert third is not first
|
assert third is not first
|
||||||
assert block.cam_conditioner.calls == 2
|
assert block.cam_conditioner.calls == 2
|
||||||
cache = forward_batch.extra["lingbot_cam_conditioner"]
|
cache = forward_batch.extra[LINGBOT_CAM_CONDITIONER_CACHE]
|
||||||
assert cache["source_key"][0] == next_source.data_ptr()
|
assert cache["source_key"][0] == next_source.data_ptr()
|
||||||
assert len(cache["entries"]) == 1
|
assert len(cache["entries"]) == 1
|
||||||
|
|
||||||
@@ -287,7 +705,7 @@ def test_lingbot_cam_conditioner_cache_skips_non_sequence_shard(monkeypatch):
|
|||||||
assert first is not second
|
assert first is not second
|
||||||
assert first[0] is not second[0]
|
assert first[0] is not second[0]
|
||||||
assert block.cam_conditioner.calls == 2
|
assert block.cam_conditioner.calls == 2
|
||||||
assert "lingbot_cam_conditioner" not in forward_batch.extra
|
assert LINGBOT_CAM_CONDITIONER_CACHE not in forward_batch.extra
|
||||||
|
|
||||||
|
|
||||||
def test_lingbot_cam_conditioner_cache_skips_single_ulysses_world(monkeypatch):
|
def test_lingbot_cam_conditioner_cache_skips_single_ulysses_world(monkeypatch):
|
||||||
@@ -319,7 +737,7 @@ def test_lingbot_cam_conditioner_cache_skips_single_ulysses_world(monkeypatch):
|
|||||||
|
|
||||||
assert first is not second
|
assert first is not second
|
||||||
assert block.cam_conditioner.calls == 2
|
assert block.cam_conditioner.calls == 2
|
||||||
assert "lingbot_cam_conditioner" not in forward_batch.extra
|
assert LINGBOT_CAM_CONDITIONER_CACHE not in forward_batch.extra
|
||||||
|
|
||||||
|
|
||||||
def test_lingbot_cam_conditioner_cache_reuses_context_update(monkeypatch):
|
def test_lingbot_cam_conditioner_cache_reuses_context_update(monkeypatch):
|
||||||
@@ -351,7 +769,7 @@ def test_lingbot_cam_conditioner_cache_reuses_context_update(monkeypatch):
|
|||||||
|
|
||||||
assert first is second
|
assert first is second
|
||||||
assert block.cam_conditioner.calls == 1
|
assert block.cam_conditioner.calls == 1
|
||||||
assert "lingbot_cam_conditioner" in forward_batch.extra
|
assert LINGBOT_CAM_CONDITIONER_CACHE in forward_batch.extra
|
||||||
|
|
||||||
|
|
||||||
def test_lingbot_model_prepares_cam_conditioner_scale_shifts(monkeypatch):
|
def test_lingbot_model_prepares_cam_conditioner_scale_shifts(monkeypatch):
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from types import SimpleNamespace
|
|||||||
|
|
||||||
import msgspec.msgpack
|
import msgspec.msgpack
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import pytest
|
||||||
import torch
|
import torch
|
||||||
|
|
||||||
from sglang.multimodal_gen.configs.pipeline_configs.lingbot_world import (
|
from sglang.multimodal_gen.configs.pipeline_configs.lingbot_world import (
|
||||||
@@ -36,7 +37,7 @@ from sglang.multimodal_gen.runtime.entrypoints.openai.realtime.registry import (
|
|||||||
get_realtime_model_adapter,
|
get_realtime_model_adapter,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import OutputBatch
|
from sglang.multimodal_gen.runtime.pipelines_core.schedule_batch import OutputBatch
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.lingbot_world import (
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.model_specific_stages.lingbot_world.lingbot_world_causal_denoising import (
|
||||||
LingBotWorldCausalDMDDenoisingStage,
|
LingBotWorldCausalDMDDenoisingStage,
|
||||||
)
|
)
|
||||||
from sglang.multimodal_gen.runtime.pipelines_core.stages.realtime.base import (
|
from sglang.multimodal_gen.runtime.pipelines_core.stages.realtime.base import (
|
||||||
@@ -296,6 +297,104 @@ def test_lingbot_realtime_adapter_ingests_generic_events():
|
|||||||
assert state.latest_sampled_event_id == 8
|
assert state.latest_sampled_event_id == 8
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_realtime_adapter_ingests_composite_input_event():
|
||||||
|
adapter = lingbot_realtime.LingBotWorldRealtimeAdapter()
|
||||||
|
session = GenerateSession()
|
||||||
|
session.set_adapter(adapter)
|
||||||
|
session.set_request(
|
||||||
|
RealtimeVideoGenerationsRequest(
|
||||||
|
type="init",
|
||||||
|
prompt="walk forward",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
composite_event = RealtimeEvent(
|
||||||
|
type="event",
|
||||||
|
kind="composite_input",
|
||||||
|
payload={
|
||||||
|
"input_types": ["prompt", "camera_actions"],
|
||||||
|
"prompt": "turn left",
|
||||||
|
"camera_actions": [["w"], ["d"]],
|
||||||
|
},
|
||||||
|
event_id=9,
|
||||||
|
)
|
||||||
|
|
||||||
|
event_log = adapter.ingest_event(session, composite_event)
|
||||||
|
|
||||||
|
assert "kind=composite_input" in event_log
|
||||||
|
chunk_inputs = adapter.sample_chunk_inputs(
|
||||||
|
session,
|
||||||
|
server_args=SimpleNamespace(),
|
||||||
|
chunk=SimpleNamespace(index=1),
|
||||||
|
chunk_size=3,
|
||||||
|
)
|
||||||
|
assert chunk_inputs.prompt == "turn left"
|
||||||
|
assert chunk_inputs.condition_inputs[
|
||||||
|
lingbot_realtime.LINGBOT_PROMPT_UPDATED_CONDITION
|
||||||
|
]
|
||||||
|
assert chunk_inputs.condition_inputs[
|
||||||
|
lingbot_realtime.LINGBOT_CAMERA_ACTIONS_CONDITION
|
||||||
|
] == [["w"], ["d"], []]
|
||||||
|
assert adapter.get_realtime_event_id(session) == 9
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_realtime_adapter_rejects_composite_input_atomically():
|
||||||
|
adapter = lingbot_realtime.LingBotWorldRealtimeAdapter()
|
||||||
|
session = GenerateSession()
|
||||||
|
session.set_adapter(adapter)
|
||||||
|
session.set_request(
|
||||||
|
RealtimeVideoGenerationsRequest(
|
||||||
|
type="init",
|
||||||
|
prompt="walk forward",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
composite_event = RealtimeEvent(
|
||||||
|
type="event",
|
||||||
|
kind="composite_input",
|
||||||
|
payload={
|
||||||
|
"input_types": ["prompt", "camera_actions"],
|
||||||
|
"prompt": "turn left",
|
||||||
|
"camera_actions": ["w"],
|
||||||
|
},
|
||||||
|
event_id=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError, match="camera_actions"):
|
||||||
|
adapter.ingest_event(session, composite_event)
|
||||||
|
|
||||||
|
state = adapter._state(session)
|
||||||
|
assert not state.has_prompt()
|
||||||
|
assert state.sample_camera_actions(3) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_lingbot_realtime_prompt_event_marks_crossattn_reset():
|
||||||
|
adapter = lingbot_realtime.LingBotWorldRealtimeAdapter()
|
||||||
|
session = GenerateSession()
|
||||||
|
session.set_adapter(adapter)
|
||||||
|
session.set_request(
|
||||||
|
RealtimeVideoGenerationsRequest(
|
||||||
|
type="init",
|
||||||
|
prompt="walk forward",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
state = adapter._state(session)
|
||||||
|
state.receive_prompt("turn left", event_id=8)
|
||||||
|
|
||||||
|
chunk_inputs = adapter.sample_chunk_inputs(
|
||||||
|
session,
|
||||||
|
server_args=SimpleNamespace(),
|
||||||
|
chunk=SimpleNamespace(index=1),
|
||||||
|
chunk_size=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert chunk_inputs.prompt == "turn left"
|
||||||
|
assert session.request.prompt == "turn left"
|
||||||
|
assert chunk_inputs.condition_inputs[
|
||||||
|
lingbot_realtime.LINGBOT_PROMPT_UPDATED_CONDITION
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_lingbot_realtime_adapter_ingests_state_camera_events():
|
def test_lingbot_realtime_adapter_ingests_state_camera_events():
|
||||||
adapter = lingbot_realtime.LingBotWorldRealtimeAdapter()
|
adapter = lingbot_realtime.LingBotWorldRealtimeAdapter()
|
||||||
session = GenerateSession()
|
session = GenerateSession()
|
||||||
@@ -672,6 +771,7 @@ def test_lingbot_realtime_condition_horizon_repeats_blank_tail_chunk():
|
|||||||
latent_condition = torch.ones(1, latent_channels, latent_frames, 2, 2)
|
latent_condition = torch.ones(1, latent_channels, latent_frames, 2, 2)
|
||||||
batch = SimpleNamespace(
|
batch = SimpleNamespace(
|
||||||
height=2 * spatial_ratio,
|
height=2 * spatial_ratio,
|
||||||
|
num_frames=num_frames,
|
||||||
width=2 * spatial_ratio,
|
width=2 * spatial_ratio,
|
||||||
)
|
)
|
||||||
condition_full = config.postprocess_image_latent(latent_condition, batch)
|
condition_full = config.postprocess_image_latent(latent_condition, batch)
|
||||||
|
|||||||
Reference in New Issue
Block a user