Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5263a56
[Model] Add LingBot Ulysses sequence parallelism
wtz2333 Aug 27, 2026
e1a9b81
[Example] Expose LingBot World offline Ulysses option
wtz2333 Sep 9, 2026
5bbf6fa
[Bugfix] Isolate LingBot direct cache updates from torch.compile
wtz2333 Sep 9, 2026
b1a49f4
Revert "[Bugfix] Isolate LingBot direct cache updates from torch.comp…
wtz2333 Sep 10, 2026
b7efbf6
[Perf] Keep LingBot timestep modulation frame-sized
wtz2333 Sep 10, 2026
c5df9ad
[Bugfix] Reject unsupported LingBot advanced Ulysses mode
wtz2333 Sep 10, 2026
0a81c75
[Perf] Expand LingBot modulation only with Ulysses
wtz2333 Sep 10, 2026
dadcaf7
[Bugfix] Own compact LingBot text KV head shards
wtz2333 Sep 10, 2026
91990ed
[Bugfix] Validate LingBot SP input and output boundaries
wtz2333 Sep 10, 2026
ce4b110
[Test] Cover LingBot SP and TP with real collectives
wtz2333 Sep 10, 2026
719c4f1
[Test] Trim redundant LingBot SP coverage
wtz2333 Sep 10, 2026
674f7a0
[Test] Merge LingBot SP regression into transformer tests
wtz2333 Sep 10, 2026
d8db5f1
[Perf] Reduce LingBot SP communication and address review feedback
wtz2333 Sep 10, 2026
1018fca
[Refactor] Keep LingBot SP cache changes model-local
wtz2333 Sep 10, 2026
cee4448
[CI] Scope LingBot SP test routing to explicit commands
wtz2333 Sep 10, 2026
c019972
[CI] Revert LingBot changes to test-ready routing
wtz2333 Sep 10, 2026
4327313
[Bugfix] Reject unsupported LingBot Ulysses permute
wtz2333 Sep 11, 2026
b198b03
[Refactor] Use LingBot runtime KV head geometry
wtz2333 Sep 11, 2026
629a3de
Merge branch 'main' into codex/lingbot-world-sp-vae-parallel
tzhouam Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docs/design/feature/realtime_ar_diffusion.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,24 @@ For LingBot World v2:
- `lingbot_world/pipeline.py` constructs conditioning, owns small non-KV session state, and
produces one block plus the standard metadata envelope.

## Ulysses sequence parallelism

LingBot supports pure Ulysses sequence parallelism for both direct and
AR-Diffusion execution. With Ulysses degree greater than one, hidden tokens,
camera features, token-expanded timestep modulation, and RoPE tables are sharded
together. Without SP, timestep modulation retains the frame-broadcast path.
Self-attention performs the sequence-to-head all-to-all before reading or writing
paged KV, while static text K/V uses the same local head shard. Text K/V shards
own compact storage; cross-attention exchanges query/output layouts to use these
shards. This retains the shared cache geometry and reduces text K/V storage at
the cost of two all-to-all calls per layer.

The output head projects local tokens before gathering the flow values. For
the 14B model this reduces the gathered width from 5120 to 64; frame modulation
uses each shard's global token offset, including shards that split a frame. Only
`ulysses_mode="strict"` is supported; `advanced_uaa`, Ring, and AllGather-KV modes
remain unsupported for this model.

## Non-goals

This contract does not currently provide:
Expand Down
6 changes: 5 additions & 1 deletion examples/offline_inference/diffusion/lingbot_world_v2.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Generate a LingBot-World v2 video from an image and camera trajectory.

The official checkpoint is licensed separately under CC BY-NC-SA and is
Expand Down Expand Up @@ -99,6 +99,7 @@ def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
default=1,
help="Number of GPUs used for tensor parallelism inside the DiT.",
)
parser.add_argument("--ulysses-degree", type=int, default=1, help="Pure Ulysses sequence parallel degree.")
parser.add_argument("--flow-shift", type=float, default=5.0, help="Positive FlowUniPC scheduler shift.")
parser.add_argument("--fps", type=int, default=16, help="Frames per second in the exported MP4.")
parser.add_argument("--output", default="lingbot_world_v2.mp4", help="Output MP4 path.")
Expand Down Expand Up @@ -158,13 +159,16 @@ def build_omni_kwargs(

if args.tensor_parallel_size <= 0:
raise ValueError("--tensor-parallel-size must be a positive integer.")
if args.ulysses_degree <= 0:
raise ValueError("--ulysses-degree must be a positive integer.")
flow_shift = _positive_finite(args.flow_shift, "--flow-shift")
model_path = Path(args.model).expanduser()
model = str(model_path.resolve()) if model_path.exists() else args.model
return {
"model": model,
"flow_shift": flow_shift,
"tensor_parallel_size": args.tensor_parallel_size,
"ulysses_degree": args.ulysses_degree,
"enforce_eager": args.enforce_eager,
"model_config": {"lingbot_action_root": str(paths.action_root)},
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project

from __future__ import annotations

import importlib.util
import math
import sys
from pathlib import Path
from types import ModuleType
from types import ModuleType, SimpleNamespace

import pytest
import torch
Expand All @@ -33,6 +33,10 @@
"vllm_omni.diffusion",
"vllm_omni.diffusion.attention",
"vllm_omni.diffusion.attention.layer",
"vllm_omni.diffusion.distributed",
"vllm_omni.diffusion.distributed.comm",
"vllm_omni.diffusion.distributed.parallel_state",
"vllm_omni.diffusion.distributed.sp_plan",
"vllm_omni.diffusion.layers",
"vllm_omni.diffusion.layers.norm",
"vllm_omni.diffusion.layers.rope",
Expand All @@ -58,6 +62,47 @@ def _install_vllm_stubs() -> None:
distributed.get_tensor_model_parallel_world_size = lambda: 1
distributed.tensor_model_parallel_all_reduce = lambda value: value

class _SeqAllToAll4D:
@staticmethod
def apply(group, value, scatter_idx, gather_idx, use_sync=False):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This stub discards scatter_idx and gather_idx and returns the input unchanged, and get_sp_group() below pins ulysses_world_size=1. Combined, no CPU test executes any new SP code: every if self.ulysses_world_size > 1 branch is dead, shard_kv_heads always early-returns, and the two num_local_heads % ulysses_world_size ValueErrors can never fire.

The identity would also mask a genuine scatter/gather index inversion, which is the single easiest thing to get wrong in a Ulysses implementation.

del group, scatter_idx, gather_idx, use_sync
return value

setattr(
sys.modules["vllm_omni.diffusion.distributed.comm"],
"SeqAllToAll4D",
_SeqAllToAll4D,
)

def get_sp_group():
return SimpleNamespace(
ulysses_world_size=1,
ulysses_rank=0,
ulysses_group=None,
)

setattr(
sys.modules["vllm_omni.diffusion.distributed.parallel_state"],
"get_sp_group",
get_sp_group,
)

class _SequenceParallelInput:
def __init__(self, split_dim, expected_dims=None, split_output=False, auto_pad=False):
self.split_dim = split_dim
self.expected_dims = expected_dims
self.split_output = split_output
self.auto_pad = auto_pad

class _SequenceParallelOutput:
def __init__(self, gather_dim, expected_dims=None):
self.gather_dim = gather_dim
self.expected_dims = expected_dims

sp_plan = sys.modules["vllm_omni.diffusion.distributed.sp_plan"]
setattr(sp_plan, "SequenceParallelInput", _SequenceParallelInput)
setattr(sp_plan, "SequenceParallelOutput", _SequenceParallelOutput)

def set_weight_attrs(weight: torch.Tensor, attrs: dict) -> None:
for name, value in attrs.items():
setattr(weight, name, value)
Expand Down Expand Up @@ -541,3 +586,38 @@ def test_tp_rmsnorm_weight_loader_selects_rank_shard(monkeypatch: pytest.MonkeyP
norm.weight.weight_loader(norm.weight, torch.tensor([10.0, 20.0, 30.0, 40.0]))

torch.testing.assert_close(norm.weight, torch.tensor([30.0, 40.0]))


def test_single_token_text_kv_shard_owns_compact_storage(monkeypatch):
module = _load_module()
monkeypatch.setattr(
module,
"get_sp_group",
lambda: SimpleNamespace(
ulysses_world_size=2,
ulysses_rank=1,
ulysses_group=None,
),
)
attention = module.LingBotCrossAttention(dim=8, num_heads=4)
full = torch.arange(8, dtype=torch.float32).reshape(1, 1, 4, 2)
shard = attention.shard_kv_heads(full)
torch.testing.assert_close(shard, full[:, :, 2:], rtol=0, atol=0)
assert shard.is_contiguous()
assert shard.untyped_storage().nbytes() == shard.numel() * shard.element_size()


@pytest.mark.parametrize("attention_class", ["LingBotSelfAttention", "LingBotCrossAttention"])
def test_ulysses_rejects_non_divisible_head_count(monkeypatch, attention_class):
module = _load_module()
monkeypatch.setattr(
module,
"get_sp_group",
lambda: SimpleNamespace(
ulysses_world_size=3,
ulysses_rank=0,
ulysses_group=None,
),
)
with pytest.raises(ValueError, match="heads must be divisible"):
getattr(module, attention_class)(dim=8, num_heads=4)
Loading
Loading