264 lines
7.7 KiB
Markdown
264 lines
7.7 KiB
Markdown
# Wall-X Model Serving
|
|||
|
|
|
||
|
|
This directory contains scripts for serving Wall-X models via a websocket server, allowing remote clients to connect and get action predictions from observations.
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The serving infrastructure consists of three main components:
|
||
|
|
|
||
|
|
1. **WebsocketPolicyServer** (`wall_x/serving/websocket_policy_server.py`): Generic websocket server that can serve any policy implementing the `BasePolicy` interface
|
||
|
|
2. **WallXPolicy** (`wall_x/serving/policy/wall_x_policy.py`): Policy wrapper that adapts the Wall-X model to the `BasePolicy` interface
|
||
|
|
3. **launch_serving.py**: Main script for starting the server with various configurations
|
||
|
|
|
||
|
|
## Quick Start
|
||
|
|
|
||
|
|
### Basic Usage
|
||
|
|
|
||
|
|
Serve a model with default LIBERO configuration:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
cd /x2robot_v2/vincent/workspace/opensource
|
||
|
|
python -m wall_x.serving.launch_serving \
|
||
|
|
--env libero \
|
||
|
|
--model-config.model-path /path/to/libero_model_stuff \
|
||
|
|
--model-config.action-tokenizer-path /path/to/fast/ \
|
||
|
|
--model-config.train-config-path /path/to/config.yml
|
||
|
|
```
|
||
|
|
|
||
|
|
### Specify Environment
|
||
|
|
|
||
|
|
Serve with a specific environment preset:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# LIBERO (single arm, 7 DOF)
|
||
|
|
python -m wall_x.serving.launch_serving --env libero
|
||
|
|
|
||
|
|
# ALOHA (dual arm, 14 DOF)
|
||
|
|
python -m wall_x.serving.launch_serving --env aloha
|
||
|
|
```
|
||
|
|
|
||
|
|
### Custom Configuration
|
||
|
|
|
||
|
|
Serve with custom model paths and settings:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
python -m wall_x.serving.launch_serving \
|
||
|
|
--model-config.model-path /path/to/model \
|
||
|
|
--model-config.action-tokenizer-path /path/to/tokenizer \
|
||
|
|
--model-config.train-config-path /path/to/train_config.yml \
|
||
|
|
--model-config.action-dim 7 \
|
||
|
|
--model-config.state-dim 8 \
|
||
|
|
--model-config.pred-horizon 32 \
|
||
|
|
--model-config.camera-key front_view left_wrist_view \
|
||
|
|
--port 8000
|
||
|
|
```
|
||
|
|
|
||
|
|
## Command Line Arguments
|
||
|
|
|
||
|
|
### Basic Arguments
|
||
|
|
|
||
|
|
- `--env {libero,aloha}`: Environment mode (default: libero)
|
||
|
|
- `--port PORT`: Port to serve on (default: 8000)
|
||
|
|
- `--host HOST`: Host to bind to (default: 0.0.0.0)
|
||
|
|
- `--default-prompt TEXT`: Default text prompt if not provided in observation
|
||
|
|
- `--debug`: Enable debug logging
|
||
|
|
|
||
|
|
### Model Configuration
|
||
|
|
|
||
|
|
All model configuration arguments use the `--model-config.` prefix:
|
||
|
|
|
||
|
|
- `--model-config.model-path PATH`: Path to pretrained model checkpoint (required)
|
||
|
|
- `--model-config.action-tokenizer-path PATH`: Path to action tokenizer (required)
|
||
|
|
- `--model-config.train-config-path PATH`: Path to train config YAML file (required)
|
||
|
|
- `--model-config.action-dim INT`: Action space dimension (default: 7)
|
||
|
|
- `--model-config.state-dim INT`: Robot state dimension (default: 8)
|
||
|
|
- `--model-config.pred-horizon INT`: Prediction horizon (default: 32)
|
||
|
|
- `--model-config.device {cuda,cpu}`: Device to run on (default: cuda)
|
||
|
|
- `--model-config.dtype {bfloat16,float16,float32}`: Model dtype (default: bfloat16)
|
||
|
|
- `--model-config.predict-mode {fast,diffusion}`: Prediction mode (default: fast)
|
||
|
|
- `--model-config.camera-key KEY1 KEY2 ...`: Camera keys for observation images
|
||
|
|
|
||
|
|
### Camera Keys
|
||
|
|
|
||
|
|
The `camera-key` parameter specifies which camera views are expected in the observation dictionary. This is **critical** for proper operation:
|
||
|
|
|
||
|
|
- Keys must match between server configuration and client observations
|
||
|
|
- Order matters: keys are processed in the order specified
|
||
|
|
- Common keys: `front_view`, `left_wrist_view`, `right_wrist_view`, `face_view`
|
||
|
|
|
||
|
|
Example:
|
||
|
|
```bash
|
||
|
|
--model-config.camera-key front_view left_wrist_view
|
||
|
|
```
|
||
|
|
|
||
|
|
Client must send observations with matching keys:
|
||
|
|
```python
|
||
|
|
obs = {
|
||
|
|
"front_view": image1, # Must match camera-key[0]
|
||
|
|
"left_wrist_view": image2, # Must match camera-key[1]
|
||
|
|
"prompt": "task description",
|
||
|
|
"state": robot_state,
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Default Configurations
|
||
|
|
|
||
|
|
### LIBERO (Single Arm)
|
||
|
|
|
||
|
|
```python
|
||
|
|
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"],
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
### ALOHA (Dual Arm)
|
||
|
|
|
||
|
|
```python
|
||
|
|
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"],
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Server Protocol
|
||
|
|
|
||
|
|
### Connection Flow
|
||
|
|
|
||
|
|
1. Client connects to `ws://host:port`
|
||
|
|
2. Server sends metadata JSON with policy information
|
||
|
|
3. Client sends observation (msgpack-encoded)
|
||
|
|
4. Server responds with action prediction (msgpack-encoded)
|
||
|
|
5. Repeat steps 3-4 for each inference
|
||
|
|
|
||
|
|
### Observation Format
|
||
|
|
|
||
|
|
Observations must be a dictionary with camera keys matching server configuration:
|
||
|
|
|
||
|
|
```python
|
||
|
|
obs = {
|
||
|
|
# Image observations - keys must match server's camera_key configuration
|
||
|
|
"front_view": np.ndarray, # (H, W, 3) uint8 or float
|
||
|
|
"left_wrist_view": np.ndarray, # (H, W, 3) uint8 or float
|
||
|
|
|
||
|
|
# Required fields
|
||
|
|
"prompt": str, # Task description
|
||
|
|
"dataset_names": List[str], # Dataset/robot name, e.g., ["physical-intelligence/libero"]
|
||
|
|
"state": np.ndarray, # Robot proprioception state (state_dim,)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Important**: The image keys (`front_view`, `left_wrist_view`, etc.) must exactly match the `camera_key` parameter configured on the server.
|
||
|
|
|
||
|
|
### Action Response Format
|
||
|
|
|
||
|
|
Actions are returned as a dictionary:
|
||
|
|
|
||
|
|
```python
|
||
|
|
{
|
||
|
|
"action": np.ndarray, # Predicted action [pred_horizon, action_dim]
|
||
|
|
"server_timing": {
|
||
|
|
"infer_ms": float, # Inference time in milliseconds
|
||
|
|
"prev_total_ms": float, # Total time for previous request
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Server Metadata
|
||
|
|
|
||
|
|
When connecting, the server sends metadata:
|
||
|
|
|
||
|
|
```python
|
||
|
|
{
|
||
|
|
"action_dim": int, # Action space dimension
|
||
|
|
"pred_horizon": int, # Number of future actions predicted
|
||
|
|
"device": str, # Device model runs on
|
||
|
|
"predict_mode": str, # Prediction mode (fast/diffusion)
|
||
|
|
"env": str, # Environment name
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Health Check
|
||
|
|
|
||
|
|
HTTP health check endpoint available at:
|
||
|
|
```
|
||
|
|
http://host:port/healthz
|
||
|
|
```
|
||
|
|
|
||
|
|
Returns `200 OK` if the server is running.
|
||
|
|
|
||
|
|
## Client Example
|
||
|
|
|
||
|
|
### Synchronous Python Client
|
||
|
|
|
||
|
|
For synchronous usage, see `wall_x/serving/client.py`:
|
||
|
|
|
||
|
|
```python
|
||
|
|
from wall_x.serving.client import WallXClient
|
||
|
|
|
||
|
|
# Create and connect
|
||
|
|
client = WallXClient(uri="ws://localhost:8000")
|
||
|
|
client.connect_sync()
|
||
|
|
|
||
|
|
# Prepare observation
|
||
|
|
obs = {
|
||
|
|
"front_view": image1,
|
||
|
|
"left_wrist_view": image2,
|
||
|
|
"prompt": "task description",
|
||
|
|
"state": robot_state,
|
||
|
|
"dataset_names": ["physical-intelligence/libero"],
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get prediction
|
||
|
|
response = client.predict_sync(obs)
|
||
|
|
action = response["action"]
|
||
|
|
|
||
|
|
# Close connection
|
||
|
|
client.close_sync()
|
||
|
|
```
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
### WebsocketPolicyServer
|
||
|
|
|
||
|
|
Generic websocket server that:
|
||
|
|
- Handles websocket connections with msgpack serialization
|
||
|
|
- Tracks inference timing and performance metrics
|
||
|
|
- Provides health check endpoint
|
||
|
|
- Handles errors gracefully with proper logging
|
||
|
|
- Supports concurrent client connections
|
||
|
|
|
||
|
|
### WallXPolicy
|
||
|
|
|
||
|
|
Policy wrapper that:
|
||
|
|
- Loads and manages the Wall-X model from pretrained checkpoint
|
||
|
|
- Processes multi-camera observations
|
||
|
|
- Handles image preprocessing (smart resize, normalization)
|
||
|
|
- Manages device placement and dtype conversion
|
||
|
|
- Provides policy metadata to clients
|
||
|
|
- Supports both fast tokenizer and diffusion prediction modes
|
||
|
|
|
||
|
|
### Image Processing Pipeline
|
||
|
|
|
||
|
|
1. **Camera Key Matching**: Extracts images from observation dict using configured camera keys
|
||
|
|
2. **Format Conversion**: Converts numpy arrays to PIL Images
|
||
|
|
3. **Smart Resize**: Applies Qwen's smart resize algorithm based on min/max pixels
|
||
|
|
4. **Vision Token Formatting**: Inserts vision tokens in text prompt
|
||
|
|
5. **Batch Preparation**: Creates model-ready BatchFeature input
|