[diffusion] optimize: optimize lingbot performance (#27023)

This commit is contained in:
Mick
2026-06-02 18:33:06 +08:00
committed by GitHub
parent a777672939
commit 3394931044
4 changed files with 155 additions and 25 deletions
@@ -80,6 +80,7 @@ from sglang.multimodal_gen.runtime.platforms import (
AttentionBackendEnum, AttentionBackendEnum,
current_platform, current_platform,
) )
from sglang.multimodal_gen.runtime.realtime.causal_state import RealtimeCausalDiTState
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
from sglang.srt.utils import add_prefix from sglang.srt.utils import add_prefix
@@ -180,6 +181,7 @@ class LingBotWorldCausalSelfAttention(CausalWanSelfAttention):
kv_cache: CausalSelfAttentionKVCache | None = None, kv_cache: CausalSelfAttentionKVCache | None = None,
current_start: int = 0, current_start: int = 0,
cache_start: int | None = None, cache_start: int | None = None,
update_cache_only: bool = False,
): ):
cos, sin = freqs_cis[:2] cos, sin = freqs_cis[:2]
cos_sin_cache = freqs_cis[2] if len(freqs_cis) > 2 else None cos_sin_cache = freqs_cis[2] if len(freqs_cis) > 2 else None
@@ -243,6 +245,8 @@ class LingBotWorldCausalSelfAttention(CausalWanSelfAttention):
current_chunk_start=current_start, current_chunk_start=current_start,
debug_name="LingBot KV cache", debug_name="LingBot KV cache",
) )
if update_cache_only:
return v
attn_impl = self.ulysses_attn if sequence_shard_enabled else self.attn attn_impl = self.ulysses_attn if sequence_shard_enabled else self.attn
x = attn_impl( x = attn_impl(
roped_query, roped_query,
@@ -926,6 +930,7 @@ class CausalLingBotWorldTransformerBlock(CausalWanTransformerBlock):
current_start: int = 0, current_start: int = 0,
cache_start: int | None = None, cache_start: int | None = None,
c2ws_plucker_emb: torch.Tensor | None = None, c2ws_plucker_emb: torch.Tensor | None = None,
update_cache_only: bool = False,
) -> torch.Tensor: ) -> torch.Tensor:
if hidden_states.dim() == 4: if hidden_states.dim() == 4:
hidden_states = hidden_states.squeeze(1) hidden_states = hidden_states.squeeze(1)
@@ -963,7 +968,10 @@ class CausalLingBotWorldTransformerBlock(CausalWanTransformerBlock):
kv_cache, kv_cache,
current_start, current_start,
cache_start, cache_start,
update_cache_only=update_cache_only,
) )
if update_cache_only:
return hidden_states
attn_output = attn_output.flatten(2) attn_output = attn_output.flatten(2)
attn_output, _ = self.to_out(attn_output) attn_output, _ = self.to_out(attn_output)
attn_output = attn_output.squeeze(1) attn_output = attn_output.squeeze(1)
@@ -1113,6 +1121,10 @@ class CausalLingBotWorldTransformer3DModel(CausalWanTransformer3DModel):
def _get_request_cache(forward_batch, name: str) -> dict | None: def _get_request_cache(forward_batch, name: str) -> dict | None:
if forward_batch is None: if forward_batch is None:
return None return None
session = getattr(forward_batch, "session", None)
if session is not None:
state = session.get_or_create_state(RealtimeCausalDiTState)
return state.runtime_cache.setdefault(name, {})
extra = getattr(forward_batch, "extra", None) extra = getattr(forward_batch, "extra", None)
if extra is None: if extra is None:
return None return None
@@ -1179,6 +1191,49 @@ class CausalLingBotWorldTransformer3DModel(CausalWanTransformer3DModel):
cache[cache_key] = freqs_cis cache[cache_key] = freqs_cis
return freqs_cis return freqs_cis
def _prepare_cached_rope_for_sequence_shard(
self,
*,
forward_batch,
local_seq_len: int,
token_start: int,
frame_stride: int,
post_patch_width: int,
device: torch.device,
) -> tuple[torch.Tensor, ...]:
cache = self._get_request_cache(forward_batch, "lingbot_sequence_shard_rope")
cache_key = (
local_seq_len,
token_start,
frame_stride,
post_patch_width,
device.type,
device.index,
)
if cache is not None and cache_key in cache:
return cache[cache_key]
freqs_cos, freqs_sin = self._compute_rope_for_sequence_shard_with_offset(
local_seq_len,
token_start,
frame_stride,
post_patch_width,
device,
)
freqs_cos = freqs_cos.float()
freqs_sin = freqs_sin.float()
freqs_cis: tuple[torch.Tensor, ...] = (freqs_cos, freqs_sin)
if _is_cuda:
freqs_cis = (
freqs_cos,
freqs_sin,
torch.cat([freqs_cos.contiguous(), freqs_sin.contiguous()], dim=-1),
)
if cache is not None:
cache.clear()
cache[cache_key] = freqs_cis
return freqs_cis
def _prepare_condition_embeddings( def _prepare_condition_embeddings(
self, self,
*, *,
@@ -1187,14 +1242,48 @@ class CausalLingBotWorldTransformer3DModel(CausalWanTransformer3DModel):
encoder_hidden_states_image: torch.Tensor | None, encoder_hidden_states_image: torch.Tensor | None,
crossattn_cache: list[CrossAttentionKVCache] | None, crossattn_cache: list[CrossAttentionKVCache] | None,
): ):
forward_batch = get_forward_context().forward_batch
temb, timestep_proj = self._prepare_cached_time_embeddings(
timestep=timestep,
forward_batch=forward_batch,
)
if self._all_crossattn_caches_initialized(crossattn_cache): if self._all_crossattn_caches_initialized(crossattn_cache):
temb = self.condition_embedder.time_embedder(timestep.flatten())
timestep_proj = self.condition_embedder.time_modulation(temb)
return temb, timestep_proj, encoder_hidden_states, None return temb, timestep_proj, encoder_hidden_states, None
return self.condition_embedder( encoder_hidden_states = self.condition_embedder.text_embedder(
timestep.flatten(), encoder_hidden_states, encoder_hidden_states_image encoder_hidden_states
) )
if encoder_hidden_states_image is not None:
assert self.condition_embedder.image_embedder is not None
encoder_hidden_states_image = self.condition_embedder.image_embedder(
encoder_hidden_states_image
)
return temb, timestep_proj, encoder_hidden_states, encoder_hidden_states_image
def _prepare_cached_time_embeddings(
self,
*,
timestep: torch.LongTensor,
forward_batch,
) -> tuple[torch.Tensor, torch.Tensor]:
cache = self._get_request_cache(forward_batch, "lingbot_time_embeddings")
current_timestep = get_forward_context().current_timestep
cache_key = (
current_timestep,
tuple(timestep.shape),
timestep.dtype,
timestep.device.type,
timestep.device.index,
)
if cache is not None and cache_key in cache:
return cache[cache_key]
temb = self.condition_embedder.time_embedder(timestep.flatten())
timestep_proj = self.condition_embedder.time_modulation(temb)
if cache is not None:
cache[cache_key] = (temb, timestep_proj)
return temb, timestep_proj
def forward( def forward(
self, self,
@@ -1274,22 +1363,14 @@ class CausalLingBotWorldTransformer3DModel(CausalWanTransformer3DModel):
) )
frame_stride = post_patch_height * post_patch_width frame_stride = post_patch_height * post_patch_width
token_start = start_frame * frame_stride + sum(seq_shard_splits[:sp_rank]) token_start = start_frame * frame_stride + sum(seq_shard_splits[:sp_rank])
freqs_cos, freqs_sin = self._compute_rope_for_sequence_shard_with_offset( freqs_cis = self._prepare_cached_rope_for_sequence_shard(
local_seq_len, forward_batch=forward_batch,
token_start, local_seq_len=local_seq_len,
frame_stride, token_start=token_start,
post_patch_width, frame_stride=frame_stride,
hidden_states.device, post_patch_width=post_patch_width,
device=hidden_states.device,
) )
freqs_cos = freqs_cos.float()
freqs_sin = freqs_sin.float()
freqs_cis = (freqs_cos, freqs_sin)
if _is_cuda:
freqs_cis = (
freqs_cos,
freqs_sin,
torch.cat([freqs_cos.contiguous(), freqs_sin.contiguous()], dim=-1),
)
temb, timestep_proj, encoder_hidden_states, encoder_hidden_states_image = ( temb, timestep_proj, encoder_hidden_states, encoder_hidden_states_image = (
self._prepare_condition_embeddings( self._prepare_condition_embeddings(
@@ -1324,22 +1405,23 @@ class CausalLingBotWorldTransformer3DModel(CausalWanTransformer3DModel):
current_start=current_start, current_start=current_start,
cache_start=cache_start, cache_start=cache_start,
c2ws_plucker_emb=c2ws_plucker_emb, c2ws_plucker_emb=c2ws_plucker_emb,
update_cache_only=skip_final_projection
and block_index == len(self.blocks) - 1,
) )
if skip_final_projection: if skip_final_projection:
return hidden_states return hidden_states
temb = temb.unflatten(dim=0, sizes=timestep.shape).unsqueeze(2)
shift, scale = (self.scale_shift_table.unsqueeze(1) + temb).chunk(2, dim=2)
hidden_states = self.norm_out(hidden_states, shift, scale)
hidden_states = self.proj_out(hidden_states)
if sequence_shard_enabled: if sequence_shard_enabled:
hidden_states = _sequence_all_gather_varlen( hidden_states = _sequence_all_gather_varlen(
hidden_states.contiguous(), hidden_states.contiguous(),
list(forward_batch.sequence_shard_splits), list(forward_batch.sequence_shard_splits),
get_sp_group().device_group, get_sp_group().device_group,
) )
temb = temb.unflatten(dim=0, sizes=timestep.shape).unsqueeze(2)
shift, scale = (self.scale_shift_table.unsqueeze(1) + temb).chunk(2, dim=2)
hidden_states = self.norm_out(hidden_states, shift, scale)
hidden_states = self.proj_out(hidden_states)
hidden_states = hidden_states.reshape( hidden_states = hidden_states.reshape(
batch_size, batch_size,
post_patch_num_frames, post_patch_num_frames,
@@ -190,7 +190,7 @@ class LingBotWorldCausalDMDDenoisingStage(CausalDMDDenoisingStage):
enabled=autocast_enabled, enabled=autocast_enabled,
), ),
set_forward_context( set_forward_context(
current_timestep=0, current_timestep=-1,
attn_metadata=attn_metadata, attn_metadata=attn_metadata,
forward_batch=batch, forward_batch=batch,
), ),
@@ -10,11 +10,13 @@ class RealtimeCausalDiTState(BaseRealtimeState):
super().__init__() super().__init__()
self.kv_cache = None self.kv_cache = None
self.crossattn_cache = None self.crossattn_cache = None
self.runtime_cache: dict = {}
self.current_chunk_start_frame: int = 0 self.current_chunk_start_frame: int = 0
self.chunk_idx: int = 0 self.chunk_idx: int = 0
def dispose(self) -> None: def dispose(self) -> None:
self.kv_cache = None self.kv_cache = None
self.crossattn_cache = None self.crossattn_cache = None
self.runtime_cache.clear()
self.current_chunk_start_frame = 0 self.current_chunk_start_frame = 0
self.chunk_idx = 0 self.chunk_idx = 0
@@ -80,6 +80,21 @@ class DiffusionTestCaseVisitor(ast.NodeVisitor):
def __init__(self): def __init__(self):
self.cases: Dict[str, List[str]] = {} # list_name -> [case_id, ...] self.cases: Dict[str, List[str]] = {} # list_name -> [case_id, ...]
self.factory_case_ids: Dict[str, str] = {}
def visit_Module(self, node: ast.Module):
for stmt in node.body:
if not isinstance(stmt, ast.FunctionDef):
continue
case_id = self._extract_factory_case_id(stmt)
if case_id:
self.factory_case_ids[stmt.name] = case_id
for stmt in node.body:
if isinstance(stmt, ast.Expr):
self._process_expr(stmt.value)
self.generic_visit(node)
def visit_Assign(self, node: ast.Assign): def visit_Assign(self, node: ast.Assign):
self._process_assignment(node.targets, node.value) self._process_assignment(node.targets, node.value)
@@ -121,6 +136,26 @@ class DiffusionTestCaseVisitor(ast.NodeVisitor):
lhs_case_ids = self.cases.get(target.id, []) lhs_case_ids = self.cases.get(target.id, [])
self.cases[target.id] = [*lhs_case_ids, *rhs_case_ids] self.cases[target.id] = [*lhs_case_ids, *rhs_case_ids]
def _process_expr(self, node: ast.AST):
"""Process list mutation calls such as `ONE_GPU_CASES.append(...)`."""
if not isinstance(node, ast.Call):
return
if not isinstance(node.func, ast.Attribute):
return
if node.func.attr != "append":
return
if not isinstance(node.func.value, ast.Name):
return
list_name = node.func.value.id
if list_name not in CASE_LIST_TO_SUITE:
return
if len(node.args) != 1:
return
case_id = self._extract_case_id_from_call(node.args[0])
if case_id:
self.cases.setdefault(list_name, []).append(case_id)
def _extract_case_ids(self, node: ast.AST) -> Optional[List[str]]: def _extract_case_ids(self, node: ast.AST) -> Optional[List[str]]:
"""Extract case IDs from a supported expression.""" """Extract case IDs from a supported expression."""
if isinstance(node, ast.List): if isinstance(node, ast.List):
@@ -167,9 +202,20 @@ class DiffusionTestCaseVisitor(ast.NodeVisitor):
}: }:
if node.args and isinstance(node.args[0], ast.Constant): if node.args and isinstance(node.args[0], ast.Constant):
return node.args[0].value return node.args[0].value
if isinstance(node.func, ast.Name) and not node.args:
return self.factory_case_ids.get(node.func.id)
return None return None
def _extract_factory_case_id(self, node: ast.FunctionDef) -> Optional[str]:
for child in ast.walk(node):
if not isinstance(child, ast.Return) or child.value is None:
continue
case_id = self._extract_case_id_from_call(child.value)
if case_id:
return case_id
return None
def resolve_case_config_path(repo_root: Path, run_suite_path: Path) -> Path: def resolve_case_config_path(repo_root: Path, run_suite_path: Path) -> Path:
""" """