Download deployment/model_server/policy_wrapper.py from mmmmmcyaaa/ReTime: direct link, hf CLI and curl.
- Browser
- Download file 7.55 kB
-
https://huggingface.co/mmmmmcyaaa/ReTime/resolve/main/deployment/model_server/policy_wrapper.py
- Command line
-
hf download hf://mmmmmcyaaa/ReTime/deployment/model_server/policy_wrapper.py
-
curl -L -o policy_wrapper.py https://huggingface.co/mmmmmcyaaa/ReTime/resolve/main/deployment/model_server/policy_wrapper.py
7.55 kB
| # Copyright 2025 starVLA community. All rights reserved. | |
| # Licensed under the MIT License. | |
| """Policy server wrapper. | |
| Encapsulates a `baseframework` instance plus a :class:`PolicyNormProcessor` | |
| that reuses the *training-time* :class:`ComposedModalityTransform` for action | |
| un-normalization (no hand-rolled math). The websocket server returns | |
| already-unnormalized actions. | |
| Client-side responsibilities that REMAIN on the client: | |
| - environment-specific adapters (image_history, gripper sticky, action | |
| ensembling) | |
| - chunk-cache scheduling (`step % chunk_size == 0` triggers a new infer) | |
| Exposed API: | |
| - ``metadata`` (dict, sent at handshake): ``action_chunk_size``, | |
| ``available_unnorm_keys``, ``action_keys``, ``state_keys``. | |
| - ``predict_action(examples, unnorm_key=None, **kwargs)`` returns | |
| ``{"actions": np.ndarray[B, T, action_dim]}``. | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| from typing import Any, Dict, List, Optional | |
| import numpy as np | |
| import torch | |
| from starVLA.model.framework.base_framework import baseframework | |
| from starVLA.model.framework.share_tools import read_mode_config | |
| from deployment.model_server.policy_norm_processor import PolicyNormProcessor | |
| class PolicyServerWrapper: | |
| """Wraps a `baseframework` for use as a websocket-server policy.""" | |
| def __init__( | |
| self, | |
| ckpt_path: str, | |
| device: str = "cuda", | |
| use_bf16: bool = False, | |
| unnorm_key: Optional[str] = None, | |
| normalized_action_clip: Optional[float] = None, | |
| ) -> None: | |
| self._ckpt_path = str(ckpt_path) | |
| self._normalized_action_clip = normalized_action_clip | |
| logging.info("PolicyServerWrapper: loading framework from %s", self._ckpt_path) | |
| framework = baseframework.from_pretrained(self._ckpt_path) | |
| if use_bf16: | |
| framework = framework.to(torch.bfloat16) | |
| framework = framework.to(device).eval() | |
| self._framework = framework | |
| # Co-located metadata. | |
| model_cfg, _ = read_mode_config(self._ckpt_path) | |
| self._model_cfg = model_cfg | |
| vla_data_cfg = model_cfg.get("datasets", {}).get("vla_data", {}) | |
| include_state = vla_data_cfg.get("include_state", False) | |
| self._uses_state = include_state not in (False, None, 0, "False", "false", "0") | |
| # action_chunk_size = future_action_window_size + 1 (matches old client). | |
| action_model_cfg = model_cfg["framework"]["action_model"] | |
| if "action_horizon" in action_model_cfg: | |
| self._action_chunk_size = int(action_model_cfg["action_horizon"]) | |
| elif "future_action_window_size" in action_model_cfg: | |
| self._action_chunk_size = int(action_model_cfg["future_action_window_size"]) + 1 | |
| else: | |
| raise ValueError( | |
| f"PolicyServerWrapper: no action_horizon or future_action_window_size found in model config for {self._ckpt_path}" | |
| ) | |
| # Cache of PolicyNormProcessor instances per unnorm_key. | |
| # For single-dataset ckpts unnorm_key is auto-selected; for multi-dataset | |
| # ckpts clients must pass unnorm_key per request. | |
| self._default_unnorm_key = unnorm_key | |
| self._norm_processors: Dict[str, PolicyNormProcessor] = {} | |
| # Peek at available keys without building a full processor. | |
| _, _ns = read_mode_config(self._ckpt_path) | |
| self._available_unnorm_keys: List[str] = list(_ns.keys()) | |
| # Eagerly build when unambiguous; defer for multi-key / no explicit key. | |
| if unnorm_key is not None or len(self._available_unnorm_keys) == 1: | |
| default_proc = self._get_processor(unnorm_key) | |
| self._default_unnorm_key = default_proc.unnorm_key | |
| logging.info( | |
| "PolicyServerWrapper ready: action_chunk_size=%d, default_unnorm_key=%s, " | |
| "available_unnorm_keys=%s, action_keys=%s, state_keys=%s", | |
| self._action_chunk_size, | |
| default_proc.unnorm_key, | |
| default_proc.available_unnorm_keys, | |
| default_proc.action_keys, | |
| default_proc.state_keys, | |
| ) | |
| else: | |
| logging.info( | |
| "PolicyServerWrapper ready (multi-key): action_chunk_size=%d, " | |
| "available_unnorm_keys=%s — clients must pass unnorm_key per request.", | |
| self._action_chunk_size, | |
| self._available_unnorm_keys, | |
| ) | |
| def _get_processor(self, unnorm_key: Optional[str]) -> PolicyNormProcessor: | |
| cache_key = unnorm_key if unnorm_key is not None else "__default__" | |
| if cache_key not in self._norm_processors: | |
| self._norm_processors[cache_key] = PolicyNormProcessor( | |
| self._ckpt_path, unnorm_key=unnorm_key | |
| ) | |
| return self._norm_processors[cache_key] | |
| def metadata(self) -> Dict[str, Any]: | |
| """Model-invariant metadata; sent to client at websocket handshake.""" | |
| base = { | |
| "env": "starvla_policy_server", | |
| "ckpt_path": self._ckpt_path, | |
| "action_chunk_size": self._action_chunk_size, | |
| "uses_state": self._uses_state, | |
| "available_unnorm_keys": self._available_unnorm_keys, | |
| "default_unnorm_key": self._default_unnorm_key, | |
| "normalized_action_clip": self._normalized_action_clip, | |
| "action_head_attention_mask": False, | |
| "inference_rng": "continuous_server_stream", | |
| } | |
| # Enrich with per-embodiment keys when a default processor already exists. | |
| if self._default_unnorm_key is not None: | |
| proc = self._get_processor(self._default_unnorm_key) | |
| base["action_keys"] = proc.action_keys | |
| base["state_keys"] = proc.state_keys | |
| return base | |
| def predict_action( | |
| self, | |
| examples: List[dict], | |
| unnorm_key: Optional[str] = None, | |
| **kwargs, | |
| ) -> Dict[str, np.ndarray]: | |
| """Run the framework, then un-normalize via training-time transforms. | |
| Args: | |
| examples: list of dicts (each with ``image`` / ``lang`` / optional ``state``). | |
| unnorm_key: dataset key for un-normalization stats. ``None`` --> | |
| use the wrapper's default (auto-picked at startup). | |
| **kwargs: forwarded to the framework's ``predict_action`` | |
| (``do_sample``, ``use_ddim``, ``num_ddim_steps``, ...). | |
| Returns: | |
| ``{"actions": np.ndarray[B, T, D]}`` -- un-normalized. | |
| """ | |
| effective_key = unnorm_key if unnorm_key is not None else self._default_unnorm_key | |
| if effective_key is None: | |
| if len(self._available_unnorm_keys) == 1: | |
| effective_key = self._available_unnorm_keys[0] | |
| else: | |
| raise ValueError( | |
| f"predict_action: unnorm_key not specified and no default set. " | |
| f"Pass one of {self._available_unnorm_keys}." | |
| ) | |
| proc = self._get_processor(effective_key) | |
| out = self._framework.predict_action(examples=examples, **kwargs) | |
| normalized = np.asarray(out["normalized_actions"]) # (B, T, D) | |
| if self._normalized_action_clip is not None: | |
| limit = float(self._normalized_action_clip) | |
| normalized = np.clip(normalized, -limit, limit) | |
| unnorm = np.stack( | |
| [proc.unapply_actions(normalized[b]) for b in range(normalized.shape[0])], | |
| axis=0, | |
| ) | |
| return {"actions": unnorm} | |