Files
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

1108 lines
41 KiB
Python

# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import json
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Iterator
import torch
from safetensors import safe_open
from torch import nn
from torch.distributed.fsdp import MixedPrecisionPolicy
from sglang.multimodal_gen.configs.pipeline_configs.pi05 import Pi05PipelineConfig
from sglang.multimodal_gen.runtime.distributed.communication_op import (
sequence_model_parallel_all_gather,
tensor_model_parallel_all_gather,
)
from sglang.multimodal_gen.runtime.distributed.parallel_state import (
get_ring_parallel_world_size,
get_sequence_parallel_world_size,
get_sp_parallel_rank,
get_tp_world_size,
get_ulysses_parallel_world_size,
model_parallel_is_initialized,
)
from sglang.multimodal_gen.runtime.loader.utils import (
set_default_torch_dtype,
skip_init_modules,
)
from sglang.multimodal_gen.runtime.loader.weight_load_plan import WeightLoadPlan
from sglang.multimodal_gen.runtime.loader.weight_utils import (
safetensors_weights_iterator,
)
from sglang.multimodal_gen.runtime.models.vlas.pi05_core import Pi05CoreModel
from sglang.multimodal_gen.runtime.platforms import current_platform
from sglang.multimodal_gen.runtime.utils.hf_diffusers_utils import maybe_download_model
from sglang.multimodal_gen.runtime.utils.logging_utils import init_logger
from sglang.multimodal_gen.runtime.vla.denoise_cuda_graph import (
VLADenoiseGraphRunner,
VLADenoiseGraphSignature,
)
from sglang.multimodal_gen.runtime.vla.observation import (
VLAObservationBatch,
tensor_fingerprint,
)
from sglang.multimodal_gen.runtime.vla.parallel import (
broadcast_tensor_from_rank,
get_vla_split_group,
)
from sglang.multimodal_gen.runtime.vla.prefix_cache import (
PrefixContext,
VLADensePrefixCache,
VLAPrefixCacheManager,
)
from sglang.multimodal_gen.utils import set_mixed_precision_policy
logger = init_logger(__name__)
@dataclass
class Pi05CheckpointManifest:
model_path: str
safetensor_files: list[str] = field(default_factory=list)
component_keys: dict[str, list[str]] = field(default_factory=dict)
skipped_lm_head_keys: list[str] = field(default_factory=list)
class Pi05ActionExpert(nn.Module):
def __init__(self, config: Pi05PipelineConfig, core_model: Pi05CoreModel):
super().__init__()
self.config = config
self.core_model = core_model
def forward(
self,
prefix_context: PrefixContext,
x_t: torch.Tensor,
timestep: torch.Tensor,
*,
action_position_offset: int = 0,
) -> torch.Tensor:
return self.core_model.denoise_step(
prefix_context.prefix_pad_masks,
prefix_context.past_key_values,
x_t,
timestep,
bool(prefix_context.layout.get("full_attention", False)),
action_position_offset=action_position_offset,
)
class Pi05PolicyModel(nn.Module):
_ROLE_COMPONENTS = {
"all": None,
"prefix": {"vision_tower", "paligemma", "multi_modal_projector"},
"action": {"action_expert", "action_heads"},
"idle": set(),
}
_FUSED_WEIGHT_MAPPINGS = (
(".self_attn.qkv_proj.", ".self_attn.q_proj.", "q"),
(".self_attn.qkv_proj.", ".self_attn.k_proj.", "k"),
(".self_attn.qkv_proj.", ".self_attn.v_proj.", "v"),
(".mlp.gate_up_proj.", ".mlp.gate_proj.", 0),
(".mlp.gate_up_proj.", ".mlp.up_proj.", 1),
)
def __init__(
self,
config: Pi05PipelineConfig,
*,
model_path: str,
device: torch.device,
dtype: torch.dtype,
manifest: Pi05CheckpointManifest,
):
super().__init__()
self.config = config
self.model_path = model_path
self.device = device
self.dtype = dtype
self.manifest = manifest
self.runtime_role = self._resolve_runtime_role()
mp_policy = MixedPrecisionPolicy(
dtype,
dtype,
dtype,
cast_forward_inputs=False,
)
set_mixed_precision_policy(
param_dtype=dtype,
reduce_dtype=dtype,
output_dtype=dtype,
mp_policy=mp_policy,
)
prefix_tensor_parallel = self._should_use_prefix_tensor_parallel()
with set_default_torch_dtype(dtype), skip_init_modules():
self.core_model = Pi05CoreModel(
config,
runtime_role=self.runtime_role,
prefix_tensor_parallel=prefix_tensor_parallel,
)
self.core_model.eval()
if device.type == "cuda":
if self._use_componentwise_empty_init():
self._componentwise_empty_init()
else:
self._to_empty_preserve_buffers(self.core_model, device=device)
self._set_prefix_output_device()
self._move_offloaded_prefix_modules_to_empty_cpu()
if torch.cuda.is_available():
torch.cuda.empty_cache()
self._load_weights()
torch.cuda.empty_cache()
else:
self._load_weights()
self.core_model.to(device)
self._set_prefix_output_device()
if self.runtime_role != "all":
logger.info("Pi05 split runtime role on rank: %s", self.runtime_role)
self.action_expert = Pi05ActionExpert(config, self.core_model)
self.graph_runner = VLADenoiseGraphRunner(
enabled=config.enable_action_cuda_graph
)
def _should_use_prefix_tensor_parallel(self) -> bool:
if self.runtime_role not in ("all", "prefix"):
return False
if self.config.prefix_parallel_strategy != "tp":
return False
if get_vla_split_group() is not None:
return False
if not model_parallel_is_initialized():
return False
return get_tp_world_size() > 1
@staticmethod
def _to_empty_preserve_buffers(module: nn.Module, *, device: torch.device) -> None:
buffers = {
name: buffer.detach().clone().to(device=device)
for name, buffer in module.named_buffers(recurse=True)
}
module.to_empty(device=device)
for name, buffer in buffers.items():
if "." in name:
parent_name, buffer_name = name.rsplit(".", 1)
parent = module.get_submodule(parent_name)
else:
parent = module
buffer_name = name
parent._buffers[buffer_name] = buffer
def _use_componentwise_empty_init(self) -> bool:
prefix_offload = (
self.config.offload_prefix_image_encoder
or self.config.offload_prefix_image_encoder_after_embed
or self.config.offload_prefix_token_embedding
or self.config.offload_prefix_language_layers
or self.config.offload_prefix_language_layers_after_prefix
or self.config.offload_prefix_language_layer_count_after_prefix > 0
)
return (
self.runtime_role in ("all", "prefix") and prefix_offload
) or self._offload_action_expert_between_requests()
def _prefix_language_phase_offload_layer_count(self, layer_count: int) -> int:
if self.config.offload_prefix_language_layers_after_prefix:
return layer_count
return min(
max(self.config.offload_prefix_language_layer_count_after_prefix, 0),
layer_count,
)
def _componentwise_empty_init(self) -> None:
logger.info(
"Pi05 componentwise empty init enabled for runtime role %s",
self.runtime_role,
)
self._to_empty_preserve_buffers(self.core_model, device=torch.device("cpu"))
paligemma = self.core_model.paligemma_with_expert.paligemma
if paligemma is not None:
language_model = paligemma.model.language_model
self._to_empty_preserve_buffers(
language_model.rotary_emb,
device=self.device,
)
self._to_empty_preserve_buffers(language_model.norm, device=self.device)
if (
self.config.offload_prefix_image_encoder
or self.config.offload_prefix_image_encoder_after_embed
):
self._to_empty_preserve_buffers(
paligemma.model.vision_tower,
device=torch.device("cpu"),
)
self._to_empty_preserve_buffers(
paligemma.model.multi_modal_projector,
device=torch.device("cpu"),
)
else:
self._to_empty_preserve_buffers(
paligemma.model.vision_tower,
device=self.device,
)
self._to_empty_preserve_buffers(
paligemma.model.multi_modal_projector,
device=self.device,
)
if self.config.offload_prefix_token_embedding:
self._to_empty_preserve_buffers(
language_model.embed_tokens,
device=torch.device("cpu"),
)
else:
self._to_empty_preserve_buffers(
language_model.embed_tokens,
device=self.device,
)
phase_layer_count = self._prefix_language_phase_offload_layer_count(
len(language_model.layers)
)
if self.config.offload_prefix_language_layers:
self._to_empty_preserve_buffers(
language_model.layers,
device=torch.device("cpu"),
)
language_model.configure_layerwise_cpu_offload(
compute_device=self.device,
empty_cache=self.config.offload_prefix_language_layers_empty_cache,
)
elif phase_layer_count:
for i, layer in enumerate(language_model.layers):
self._to_empty_preserve_buffers(
layer,
device=(
torch.device("cpu")
if i < phase_layer_count
else self.device
),
)
else:
self._to_empty_preserve_buffers(
language_model.layers,
device=self.device,
)
self._set_prefix_output_device()
action_device = (
torch.device("cpu")
if self._offload_action_expert_between_requests()
else self.device
)
self._move_action_modules_to_empty_device(action_device)
if torch.cuda.is_available():
torch.cuda.empty_cache()
def _move_action_modules_to_empty_device(self, device: torch.device) -> None:
gemma_expert = self.core_model.paligemma_with_expert.gemma_expert
if gemma_expert is not None:
self._to_empty_preserve_buffers(gemma_expert, device=device)
for module in (
self.core_model.action_in_proj,
self.core_model.action_out_proj,
self.core_model.time_mlp_in,
self.core_model.time_mlp_out,
):
if module is not None:
self._to_empty_preserve_buffers(module, device=device)
def _offload_action_expert_between_requests(self) -> bool:
return (
self.runtime_role == "all"
and self.device.type == "cuda"
and self.config.offload_action_expert_after_denoise
)
def _move_action_modules_to_device(self, device: torch.device) -> None:
gemma_expert = self.core_model.paligemma_with_expert.gemma_expert
if gemma_expert is not None:
gemma_expert.to(device)
for module in (
self.core_model.action_in_proj,
self.core_model.action_out_proj,
self.core_model.time_mlp_in,
self.core_model.time_mlp_out,
):
if module is not None:
module.to(device)
def _set_prefix_output_device(self) -> None:
paligemma_with_expert = self.core_model.paligemma_with_expert
if paligemma_with_expert.paligemma is not None:
paligemma_with_expert.set_prefix_output_device(self.device)
def _move_offloaded_prefix_modules_to_empty_cpu(self) -> None:
paligemma = self.core_model.paligemma_with_expert.paligemma
if paligemma is None:
return
cpu = torch.device("cpu")
if (
self.config.offload_prefix_image_encoder
or self.config.offload_prefix_image_encoder_after_embed
):
self._to_empty_preserve_buffers(
paligemma.model.vision_tower,
device=cpu,
)
self._to_empty_preserve_buffers(
paligemma.model.multi_modal_projector,
device=cpu,
)
if self.config.offload_prefix_token_embedding:
self._to_empty_preserve_buffers(
paligemma.model.language_model.embed_tokens,
device=cpu,
)
language_model = paligemma.model.language_model
phase_layer_count = self._prefix_language_phase_offload_layer_count(
len(language_model.layers)
)
if self.config.offload_prefix_language_layers:
self._to_empty_preserve_buffers(language_model.layers, device=cpu)
language_model.configure_layerwise_cpu_offload(
compute_device=self.device,
empty_cache=self.config.offload_prefix_language_layers_empty_cache,
)
elif phase_layer_count:
for layer in language_model.layers[:phase_layer_count]:
self._to_empty_preserve_buffers(layer, device=cpu)
@staticmethod
def _resolve_runtime_role() -> str:
split = get_vla_split_group()
if split is None:
return "all"
if split.is_prefix_rank and split.is_action_rank:
return "all"
if split.is_prefix_rank:
return "prefix"
if split.is_action_rank:
return "action"
return "idle"
@classmethod
def from_pretrained(
cls,
model_path: str,
config: Pi05PipelineConfig,
*,
dtype: torch.dtype | None = None,
) -> Pi05PolicyModel:
local_path = maybe_download_model(
model_path,
force_diffusers_model=False,
allow_patterns=["*.json", "*.model", "*.safetensors", "*.txt"],
)
cls._apply_checkpoint_config(local_path, config)
device = torch.device(current_platform.device_type)
dtype = dtype or cls._dtype_from_config(config.materialize_dtype)
manifest = cls._inspect_checkpoint(local_path, config)
logger.info(
"Loaded Pi05 checkpoint manifest from %s (%d safetensors, %d skipped lm_head tensors)",
local_path,
len(manifest.safetensor_files),
len(manifest.skipped_lm_head_keys),
)
return cls(
config,
model_path=local_path,
device=device,
dtype=dtype,
manifest=manifest,
)
@staticmethod
def _dtype_from_config(dtype_name: str) -> torch.dtype:
name = (dtype_name or "bf16").lower()
if name in ("bf16", "bfloat16"):
return torch.bfloat16
if name in ("fp16", "float16", "half"):
return torch.float16
if name in ("fp32", "float32"):
return torch.float32
raise ValueError(f"Unsupported Pi05 dtype: {dtype_name}")
@staticmethod
def _apply_checkpoint_config(
model_path: str,
config: Pi05PipelineConfig,
) -> None:
config_path = Path(model_path) / "config.json"
if not config_path.exists():
return
with open(config_path, encoding="utf-8") as f:
payload = json.load(f)
config.paligemma_variant = payload.get(
"paligemma_variant", config.paligemma_variant
)
config.action_expert_variant = payload.get(
"action_expert_variant", config.action_expert_variant
)
config.action_horizon = int(payload.get("chunk_size", config.action_horizon))
config.n_action_steps = int(
payload.get("n_action_steps", config.n_action_steps)
)
config.action_dim = int(payload.get("max_action_dim", config.action_dim))
config.state_dim = int(payload.get("max_state_dim", config.state_dim))
config.default_num_inference_steps = int(
payload.get("num_inference_steps", config.default_num_inference_steps)
)
config.max_token_len = int(
payload.get("tokenizer_max_length", config.max_token_len)
)
config.time_embedding_min_period = float(
payload.get("min_period", config.time_embedding_min_period)
)
config.time_embedding_max_period = float(
payload.get("max_period", config.time_embedding_max_period)
)
if "image_resolution" in payload:
resolution = tuple(payload["image_resolution"])
config.image_size = (int(resolution[0]), int(resolution[1]))
input_features = payload.get("input_features") or {}
image_keys = []
for key, feature in input_features.items():
if feature.get("type") == "VISUAL":
image_keys.append(key.rsplit(".", 1)[-1])
elif feature.get("type") == "STATE":
shape = feature.get("shape") or []
if shape:
config.state_dim = int(shape[0])
config.empty_cameras = int(payload.get("empty_cameras", 0) or 0)
image_keys.extend(f"empty_camera_{i}" for i in range(config.empty_cameras))
if image_keys:
config.image_keys = tuple(image_keys)
output_features = payload.get("output_features") or {}
action_feature = output_features.get("action") or {}
action_shape = action_feature.get("shape") or []
if action_shape:
config.output_action_dim = int(action_shape[0])
@staticmethod
def _inspect_checkpoint(
model_path: str,
config: Pi05PipelineConfig,
) -> Pi05CheckpointManifest:
path = Path(model_path)
safetensor_files = sorted(str(p) for p in path.glob("*.safetensors"))
component_keys = {name: [] for name in config.loader_component_map}
skipped_lm_head_keys: list[str] = []
try:
from safetensors import safe_open
except ImportError:
return Pi05CheckpointManifest(
model_path=model_path,
safetensor_files=safetensor_files,
component_keys=component_keys,
)
for filename in safetensor_files:
with safe_open(filename, framework="pt", device="cpu") as f:
for key in f.keys():
if (
config.skip_unused_lm_head
and key == "paligemma_with_expert.gemma_expert.lm_head.weight"
):
skipped_lm_head_keys.append(key)
continue
for component, prefixes in config.loader_component_map.items():
if any(key.startswith(prefix) for prefix in prefixes):
component_keys[component].append(key)
break
return Pi05CheckpointManifest(
model_path=model_path,
safetensor_files=safetensor_files,
component_keys=component_keys,
skipped_lm_head_keys=skipped_lm_head_keys,
)
@staticmethod
def _candidate_weight_keys(key: str) -> list[str]:
if key.startswith("model."):
key = key[len("model.") :]
if key.startswith("PaligemmaWithExpert."):
key = key.replace("PaligemmaWithExpert.", "paligemma_with_expert.", 1)
if key.startswith("action_time_mlp_in."):
key = key.replace("action_time_mlp_in.", "time_mlp_in.", 1)
elif key.startswith("action_time_mlp_out."):
key = key.replace("action_time_mlp_out.", "time_mlp_out.", 1)
if key.startswith("state_proj."):
return []
if key == "paligemma_with_expert.gemma_expert.lm_head.weight":
return []
candidates = [key]
replacements = {
".vision_tower.vision_model.": ".vision_tower.",
".paligemma.language_model.": ".paligemma.model.language_model.",
".paligemma.vision_tower.": ".paligemma.model.vision_tower.",
".paligemma.multi_modal_projector.": (
".paligemma.model.multi_modal_projector."
),
}
for old, new in replacements.items():
if old in key:
candidates.append(key.replace(old, new))
if key in {
"paligemma_with_expert.paligemma.lm_head.weight",
"paligemma_with_expert.paligemma.model.lm_head.weight",
}:
candidates.append(
"paligemma_with_expert.paligemma.model.language_model."
"embed_tokens.weight"
)
return list(dict.fromkeys(candidates))
def _component_for_source_key(self, key: str) -> str | None:
if key in {
"paligemma_with_expert.paligemma.lm_head.weight",
"paligemma_with_expert.paligemma.model.lm_head.weight",
}:
return "paligemma"
candidates = self._candidate_weight_keys(key)
if not candidates:
return None
for component, prefixes in self.config.loader_component_map.items():
if any(
candidate.startswith(prefix)
for candidate in candidates
for prefix in prefixes
):
return component
return None
def _should_load_source_key(self, key: str) -> bool:
components = self._ROLE_COMPONENTS[self.runtime_role]
if components is None:
return True
component = self._component_for_source_key(key)
return component in components
def _should_read_source_key(self, key: str) -> bool:
return self._should_load_source_key(key) and bool(
self._candidate_weight_keys(key)
)
@classmethod
def _candidate_target_weights(cls, source_key: str) -> list[tuple[str, Any | None]]:
candidates = []
for candidate in cls._candidate_weight_keys(source_key):
candidates.append((candidate, None))
for target_name, weight_name, shard_id in cls._FUSED_WEIGHT_MAPPINGS:
if weight_name in candidate:
candidates.append(
(candidate.replace(weight_name, target_name), shard_id)
)
return list(dict.fromkeys(candidates))
def _resolve_target_weight(
self,
source_key: str,
target_state: dict[str, torch.Tensor],
target_params: dict[str, nn.Parameter],
) -> tuple[str, Any | None] | None:
candidates = self._candidate_target_weights(source_key)
if not candidates:
return None
for candidate, shard_id in candidates:
if candidate in target_params or candidate in target_state:
return candidate, shard_id
return None
@staticmethod
def _target_tensor_for_key(
target_key: str,
target_state: dict[str, torch.Tensor],
target_params: dict[str, nn.Parameter],
) -> torch.Tensor:
target = target_params.get(target_key)
if target is not None:
return target
return target_state[target_key]
@staticmethod
def _load_tensor_to_target(
target: torch.Tensor,
tensor: torch.Tensor,
shard_id: Any | None,
) -> bool:
if tensor.dtype != target.dtype:
tensor = tensor.to(dtype=target.dtype)
weight_loader = getattr(target, "weight_loader", None)
if weight_loader is not None:
if shard_id is None:
weight_loader(target, tensor)
else:
weight_loader(target, tensor, shard_id)
return True
if tuple(target.shape) != tuple(tensor.shape):
return False
target.copy_(tensor, non_blocking=target.device.type == "cuda")
return True
def _should_stream_weights_to_gpu(
self,
target_state: dict[str, torch.Tensor],
target_params: dict[str, nn.Parameter],
) -> bool:
if self.device.type != "cuda":
return False
has_weight = False
for filename in self.manifest.safetensor_files:
with safe_open(filename, framework="pt", device="cpu") as f:
for source_key in f.keys():
if not self._should_read_source_key(source_key):
continue
target_weight = self._resolve_target_weight(
source_key,
target_state,
target_params,
)
if target_weight is None:
continue
target_key, _ = target_weight
has_weight = True
target = self._target_tensor_for_key(
target_key,
target_state,
target_params,
)
if target.device.type != "cuda":
return False
return has_weight
def _cpu_weights_iterator(self) -> Iterator[tuple[str, torch.Tensor]]:
for filename in self.manifest.safetensor_files:
with safe_open(filename, framework="pt", device="cpu") as f:
for source_key in f.keys():
if self._should_read_source_key(source_key):
yield source_key, f.get_tensor(source_key)
def _load_weights(self) -> None:
target_state = self.core_model.state_dict()
target_params = dict(self.core_model.named_parameters())
loaded_keys: set[str] = set()
unexpected = 0
mismatched = 0
stream_to_gpu = self._should_stream_weights_to_gpu(
target_state,
target_params,
)
checkpoint_load_device = self.device if stream_to_gpu else torch.device("cpu")
weight_load_plan = WeightLoadPlan(checkpoint_load_device=checkpoint_load_device)
if stream_to_gpu:
logger.info(
"Pi05 weight load streams safetensors directly to %s",
weight_load_plan.checkpoint_load_device,
)
with torch.no_grad():
if stream_to_gpu:
weights = safetensors_weights_iterator(
self.manifest.safetensor_files,
weight_load_plan=weight_load_plan,
key_filter=self._should_read_source_key,
clone_streamed_tensors=False,
)
else:
weights = self._cpu_weights_iterator()
for source_key, tensor in weights:
target_weight = self._resolve_target_weight(
source_key,
target_state,
target_params,
)
if target_weight is None:
unexpected += 1
continue
target_key, shard_id = target_weight
target = self._target_tensor_for_key(
target_key,
target_state,
target_params,
)
if not self._load_tensor_to_target(target, tensor, shard_id):
mismatched += 1
continue
loaded_keys.add(target_key)
missing = [key for key in target_state if key not in loaded_keys]
if missing or mismatched:
raise RuntimeError(
f"Pi05 weight load failed: {len(missing)} missing weights, "
f"{mismatched} mismatched weights. Running a robot policy with "
"uninitialized or partially loaded weights is unsafe."
)
if unexpected:
logger.warning(
"Pi05 weight load: %d loaded, %d unexpected",
len(loaded_keys),
unexpected,
)
else:
logger.info("Pi05 weights loaded successfully")
def build_prefix_cache_key(
self,
observation: VLAObservationBatch,
) -> str:
camera_order = tuple(observation.metadata.get("camera_order", ()))
image_hashes = {
name: tensor_fingerprint(observation.images[name]) for name in camera_order
}
masks = {
name: bool(mask.item()) for name, mask in observation.image_masks.items()
}
token_len = int(observation.token_masks.sum(dim=1).max().item())
tokens = (
observation.tokens[:, :token_len] if token_len > 0 else observation.tokens
)
token_masks = (
observation.token_masks[:, :token_len]
if token_len > 0
else observation.token_masks
)
model_revision = os.path.basename(os.path.normpath(self.model_path))
return VLAPrefixCacheManager.make_key(
model_revision=model_revision,
tokenizer_id=f"{self.config.paligemma_variant}:{self.config.max_token_len}",
camera_order=camera_order,
image_hashes=image_hashes,
token_digest=tensor_fingerprint(tokens),
token_mask_digest=tensor_fingerprint(token_masks),
masks=masks,
positions_version=self.config.prefix_cache_layout_version,
dtype=str(self.dtype).replace("torch.", ""),
parallel_layout_version=self.config.parallel_layout_version,
cache_namespace="pi05",
)
def _prefix_language_model(self) -> nn.Module | None:
paligemma = self.core_model.paligemma_with_expert.paligemma
if paligemma is None:
return None
return paligemma.model.language_model
def _prefix_kv_requires_tp_gather(self) -> bool:
language_model = self._prefix_language_model()
if language_model is None or not language_model.tensor_parallel:
return False
if not language_model.layers:
return False
attn = language_model.layers[0].self_attn
return attn.total_num_key_value_heads > attn.num_key_value_heads
def _materialize_prefix_kv_for_action(
self,
past_key_values: VLADensePrefixCache,
) -> VLADensePrefixCache:
if not self._prefix_kv_requires_tp_gather():
return past_key_values
return VLADensePrefixCache(
tuple(
(
tensor_model_parallel_all_gather(keys.contiguous(), dim=1),
tensor_model_parallel_all_gather(values.contiguous(), dim=1),
sliding_window,
)
for keys, values, sliding_window in past_key_values
)
)
def encode_prefix(self, observation: VLAObservationBatch) -> PrefixContext:
camera_order = tuple(observation.metadata.get("camera_order", ()))
images = [
observation.images[name].to(self.device, dtype=torch.float32)
for name in camera_order
]
image_masks = [
observation.image_masks[name].to(self.device) for name in camera_order
]
token_len = int(observation.token_masks.sum(dim=1).max().item())
tokens_trimmed = token_len > 0
if tokens_trimmed and token_len < observation.tokens.shape[1]:
tokens_cpu = observation.tokens[:, :token_len]
token_masks_cpu = observation.token_masks[:, :token_len]
else:
tokens_cpu = observation.tokens
token_masks_cpu = observation.token_masks
tokens = tokens_cpu.to(self.device)
token_masks = token_masks_cpu.to(self.device)
prefix_full_attention_hint = all(
bool(observation.image_masks[name].all().item()) for name in camera_order
) and bool(token_masks_cpu.all().item())
past_key_values, prefix_pad_masks, full_attention = (
self.core_model.encode_prefix(
images,
image_masks,
tokens,
token_masks,
prefix_full_attention_hint=prefix_full_attention_hint,
tokens_trimmed=tokens_trimmed,
)
)
past_key_values = self._materialize_prefix_kv_for_action(past_key_values)
return PrefixContext(
past_key_values=past_key_values,
prefix_pad_masks=prefix_pad_masks,
prefix_len=prefix_pad_masks.shape[1],
layout={"full_attention": full_attention},
)
def sample_noise(
self,
batch_size: int,
*,
generator: torch.Generator | None = None,
) -> torch.Tensor:
return torch.randn(
batch_size,
self.config.action_horizon,
self.config.action_dim,
generator=generator,
device=self.device,
dtype=torch.float32,
)
def denoise_step(
self,
prefix_context: PrefixContext,
x_t: torch.Tensor,
timestep: torch.Tensor,
*,
use_cuda_graph: bool = True,
action_position_offset: int = 0,
action_sp_enabled: bool = False,
) -> torch.Tensor:
if not bool(prefix_context.layout.get("full_attention", False)):
use_cuda_graph = False
if not use_cuda_graph:
return self.action_expert(
prefix_context,
x_t,
timestep,
action_position_offset=action_position_offset,
)
parallel_layout = self.config.parallel_layout_version
if action_sp_enabled:
parallel_layout = (
f"{parallel_layout}:action_sp"
f":rank{get_sp_parallel_rank()}:offset{action_position_offset}"
)
signature = VLADenoiseGraphSignature(
batch_size=x_t.shape[0],
prefix_len=prefix_context.prefix_len,
action_horizon=x_t.shape[1],
action_dim=x_t.shape[2],
dtype=str(x_t.dtype).replace("torch.", ""),
parallel_layout=parallel_layout,
)
def step_fn(
current_prefix_context: PrefixContext,
current_x_t: torch.Tensor,
current_timestep: torch.Tensor,
) -> torch.Tensor:
return self.action_expert(
current_prefix_context,
current_x_t,
current_timestep,
action_position_offset=action_position_offset,
)
return self.graph_runner.capture_or_run(
signature,
step_fn,
prefix_context,
x_t,
timestep,
)
def _can_use_action_sequence_parallel(
self,
prefix_context: PrefixContext | None,
action_horizon: int,
) -> bool:
split = get_vla_split_group()
if split is None or not split.uses_action_sp:
return False
if self.runtime_role not in ("all", "action"):
return False
if self._offload_action_expert_between_requests():
return False
if prefix_context is None or not bool(
prefix_context.layout.get("full_attention", False)
):
return False
if not model_parallel_is_initialized():
return False
try:
sp_world_size = get_sequence_parallel_world_size()
ulysses_world_size = get_ulysses_parallel_world_size()
ring_world_size = get_ring_parallel_world_size()
except AssertionError:
return False
if sp_world_size <= 1 or ulysses_world_size <= 1 or ring_world_size != 1:
return False
action_expert = self.core_model.paligemma_with_expert.gemma_expert
if action_expert is None:
return False
num_heads = action_expert.model.config.num_attention_heads
return action_horizon % sp_world_size == 0 and num_heads % sp_world_size == 0
def should_run_action_denoise(
self,
prefix_context: PrefixContext | None,
) -> bool:
split = get_vla_split_group()
if split is None:
return True
if self._can_use_action_sequence_parallel(
prefix_context,
self.config.action_horizon,
):
return split.is_action_rank
return split.rank == split.action_root
def action_parallel_info(
self,
prefix_context: PrefixContext | None,
) -> dict[str, Any]:
split = get_vla_split_group()
if split is None:
return {
"split_group": False,
"runtime_role": self.runtime_role,
"action_sequence_parallel": False,
}
return {
"split_group": True,
"runtime_role": self.runtime_role,
"world_size": split.group.world_size,
"prefix_root": split.prefix_root,
"action_root": split.action_root,
"action_ranks": list(split.action_ranks),
"action_sequence_parallel": self._can_use_action_sequence_parallel(
prefix_context,
self.config.action_horizon,
),
}
def _broadcast_initial_action_state(
self,
x_t: torch.Tensor | None,
) -> torch.Tensor:
split = get_vla_split_group()
if split is None:
if x_t is None:
raise RuntimeError("Pi05 action state is missing on single-rank run")
return x_t
x_t = broadcast_tensor_from_rank(
x_t,
split,
src=split.action_root,
device=self.device,
)
if x_t is None:
raise RuntimeError("Pi05 action state broadcast returned None")
return x_t
def _shard_action_sequence(self, x_t: torch.Tensor) -> tuple[torch.Tensor, int]:
sp_world_size = get_sequence_parallel_world_size()
sp_rank = get_sp_parallel_rank()
local_len = x_t.shape[1] // sp_world_size
start = sp_rank * local_len
end = start + local_len
return x_t[:, start:end].contiguous(), start
def sample_actions(
self,
observation: VLAObservationBatch,
prefix_context: PrefixContext,
*,
noise: torch.Tensor | None,
num_steps: int,
use_cuda_graph: bool = True,
generator: torch.Generator | None = None,
) -> torch.Tensor:
offload_action = self._offload_action_expert_between_requests()
if offload_action:
self._move_action_modules_to_device(self.device)
use_cuda_graph = False
split = get_vla_split_group()
action_sp_enabled = self._can_use_action_sequence_parallel(
prefix_context,
self.config.action_horizon,
)
if split is None or split.rank == split.action_root:
x_t = noise
if x_t is None:
x_t = self.sample_noise(observation.batch_size, generator=generator)
else:
x_t = x_t.to(device=self.device, dtype=torch.float32).clone()
else:
x_t = None
if action_sp_enabled:
x_t = self._broadcast_initial_action_state(x_t)
elif x_t is None:
raise RuntimeError("Pi05 action fallback must run on the action root")
action_position_offset = 0
if action_sp_enabled:
x_t, action_position_offset = self._shard_action_sequence(x_t)
dt = -1.0 / num_steps
timesteps = torch.linspace(
1.0,
1.0 / num_steps,
num_steps,
dtype=torch.float32,
device=self.device,
)
for timestep_value in timesteps:
timestep = timestep_value.expand(observation.batch_size)
velocity = self.denoise_step(
prefix_context,
x_t,
timestep,
use_cuda_graph=use_cuda_graph,
action_position_offset=action_position_offset,
action_sp_enabled=action_sp_enabled,
)
x_t.add_(velocity, alpha=dt)
if action_sp_enabled:
x_t = sequence_model_parallel_all_gather(x_t.contiguous(), dim=1)
if offload_action:
self._move_action_modules_to_device(torch.device("cpu"))
torch.cuda.empty_cache()
return x_t
def warmup_actions(self, batch_size: int = 1) -> torch.Tensor:
return torch.zeros(
batch_size,
self.config.action_horizon,
self.config.action_dim,
device=self.device,
dtype=torch.float32,
)
__all__ = [
"Pi05ActionExpert",
"Pi05CheckpointManifest",
"Pi05PolicyModel",
]