221 lines
6.2 KiB
Python
221 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Server script for Wall-X model.
|
|
|
|
This script serves a Wall-X model using a websocket server, allowing
|
|
clients to connect and get action predictions from observations.
|
|
|
|
Based on the OpenPI serve_policy.py script structure.
|
|
"""
|
|
|
|
import dataclasses
|
|
from dataclasses import field
|
|
import enum
|
|
import logging
|
|
import socket
|
|
import sys
|
|
import yaml
|
|
from pathlib import Path
|
|
from typing import List
|
|
|
|
import tyro
|
|
|
|
from wall_x.serving.policy.wall_x_policy import WallXPolicy
|
|
from wall_x.serving.websocket_policy_server import WebsocketPolicyServer
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class EnvMode(enum.Enum):
|
|
"""Supported environments/datasets."""
|
|
|
|
LIBERO = "libero"
|
|
ALOHA = "aloha"
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class ModelConfig:
|
|
"""Configuration for loading a Wall-X model."""
|
|
|
|
# Path to the pretrained model checkpoint
|
|
model_path: str
|
|
# Path to the action tokenizer
|
|
action_tokenizer_path: str
|
|
# Path to train config yaml
|
|
train_config_path: str
|
|
# Action dimension for the environment
|
|
action_dim: int = 7
|
|
# State dimension for the environment
|
|
state_dim: int = 8
|
|
# Prediction horizon (number of future actions to predict)
|
|
pred_horizon: int = 32
|
|
# Device to run model on
|
|
device: str = "cuda"
|
|
# Model dtype (bfloat16, float16, float32)
|
|
dtype: str = "bfloat16"
|
|
# Prediction mode (fast or slow)
|
|
predict_mode: str = "fast"
|
|
# Camera key for the environment
|
|
camera_key: List[str] = field(
|
|
default_factory=lambda: ["front_view", "left_wrist_view", "right_wrist_view"]
|
|
)
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class Args:
|
|
"""Arguments for the serve_wall_x script."""
|
|
|
|
# Environment mode (used for default configurations)
|
|
env: EnvMode = EnvMode.LIBERO
|
|
|
|
# Model configuration. If not provided, uses default config for the environment
|
|
model_config: ModelConfig | None = None
|
|
|
|
# Default text prompt to use if not provided in observation
|
|
default_prompt: str | None = None
|
|
|
|
# Port to serve the policy on
|
|
port: int = 8000
|
|
|
|
# Host to bind the server to
|
|
host: str = "0.0.0.0"
|
|
|
|
# Enable debug logging
|
|
debug: bool = False
|
|
|
|
|
|
# Default model configurations for each environment
|
|
DEFAULT_CONFIGS: dict[EnvMode, ModelConfig] = {
|
|
EnvMode.LIBERO: ModelConfig(
|
|
model_path="/path/to/model",
|
|
action_tokenizer_path="/path/to/action_tokenizer",
|
|
train_config_path="/path/to/train_config",
|
|
state_dim=8,
|
|
action_dim=7,
|
|
pred_horizon=32,
|
|
device="cuda",
|
|
dtype="bfloat16",
|
|
predict_mode="fast",
|
|
camera_key=["front_view", "left_wrist_view"],
|
|
),
|
|
EnvMode.ALOHA: ModelConfig(
|
|
model_path="/path/to/model",
|
|
action_tokenizer_path="/path/to/action_tokenizer",
|
|
train_config_path="/path/to/train_config",
|
|
state_dim=14,
|
|
action_dim=14,
|
|
pred_horizon=32,
|
|
device="cuda",
|
|
dtype="bfloat16",
|
|
predict_mode="fast",
|
|
camera_key=["face_view", "left_wrist_view", "right_wrist_view"],
|
|
),
|
|
}
|
|
|
|
|
|
def get_model_config(args: Args) -> ModelConfig:
|
|
"""Get model configuration from args or defaults."""
|
|
if args.model_config is not None:
|
|
return args.model_config
|
|
|
|
if config := DEFAULT_CONFIGS.get(args.env):
|
|
logger.info(f"Using default configuration for {args.env.value}")
|
|
return config
|
|
|
|
raise ValueError(
|
|
f"No default configuration for {args.env.value}. "
|
|
f"Please provide --model-config with model_path and action_tokenizer_path."
|
|
)
|
|
|
|
|
|
def create_policy(args: Args) -> WallXPolicy:
|
|
"""Create a Wall-X policy from the given arguments."""
|
|
config = get_model_config(args)
|
|
logger.info(f"Creating Wall-X policy with config: {config}")
|
|
|
|
# Validate paths
|
|
if not Path(config.model_path).exists():
|
|
logger.warning(f"Model path does not exist: {config.model_path}")
|
|
|
|
if not Path(config.action_tokenizer_path).exists():
|
|
logger.warning(
|
|
f"Action tokenizer path does not exist: {config.action_tokenizer_path}"
|
|
)
|
|
|
|
with open(config.train_config_path, "r") as f:
|
|
train_config = yaml.load(f, Loader=yaml.FullLoader)
|
|
|
|
policy = WallXPolicy(
|
|
model_path=config.model_path,
|
|
train_config=train_config,
|
|
action_tokenizer_path=config.action_tokenizer_path,
|
|
action_dim=config.action_dim,
|
|
agent_pos_dim=config.state_dim,
|
|
pred_horizon=config.pred_horizon,
|
|
device=config.device,
|
|
dtype=config.dtype,
|
|
predict_mode=config.predict_mode,
|
|
default_prompt=args.default_prompt,
|
|
camera_key=config.camera_key,
|
|
)
|
|
|
|
return policy
|
|
|
|
|
|
def main(args: Args) -> None:
|
|
"""Main function to start the Wall-X model server."""
|
|
log_level = logging.DEBUG if args.debug else logging.INFO
|
|
logging.basicConfig(
|
|
level=log_level,
|
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
)
|
|
|
|
logger.info("Starting Wall-X model server")
|
|
logger.info(f"Environment: {args.env.value}")
|
|
logger.info(f"Port: {args.port}")
|
|
logger.info(f"Host: {args.host}")
|
|
|
|
# Create policy
|
|
try:
|
|
policy = create_policy(args)
|
|
except Exception as e:
|
|
logger.error(f"Failed to create policy: {e}")
|
|
sys.exit(1)
|
|
|
|
# Get policy metadata
|
|
policy_metadata = policy.metadata
|
|
policy_metadata["env"] = args.env.value
|
|
|
|
# Get network info
|
|
hostname = socket.gethostname()
|
|
try:
|
|
local_ip = socket.gethostbyname(hostname)
|
|
except Exception:
|
|
local_ip = "unknown"
|
|
|
|
logger.info(f"Server hostname: {hostname}")
|
|
logger.info(f"Server IP: {local_ip}")
|
|
logger.info(f"Server will be available at: ws://{args.host}:{args.port}")
|
|
logger.info(f"Health check endpoint: http://{args.host}:{args.port}/healthz")
|
|
|
|
# Create and start server
|
|
server = WebsocketPolicyServer(
|
|
policy=policy,
|
|
host=args.host,
|
|
port=args.port,
|
|
metadata=policy_metadata,
|
|
)
|
|
|
|
logger.info("Starting server...")
|
|
try:
|
|
server.serve_forever()
|
|
except KeyboardInterrupt:
|
|
logger.info("Server stopped by user")
|
|
except Exception as e:
|
|
logger.error(f"Server error: {e}")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(tyro.cli(Args))
|