Instructions to use ZibinDong/ActionCodec2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ZibinDong/ActionCodec2 with Transformers:
# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("ZibinDong/ActionCodec2", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download runtime/frontend/actioncodec2/processor.py from ZibinDong/ActionCodec2: direct link, hf CLI and curl.
- Browser
- Download file 16.1 kB
-
https://huggingface.co/ZibinDong/ActionCodec2/resolve/main/runtime/frontend/actioncodec2/processor.py
- Command line
-
hf download hf://ZibinDong/ActionCodec2/runtime/frontend/actioncodec2/processor.py
-
curl -L -o processor.py https://huggingface.co/ZibinDong/ActionCodec2/resolve/main/runtime/frontend/actioncodec2/processor.py
16.1 kB
| """Hugging Face processor over the shared ActionCodec2 frontend implementation.""" | |
| from __future__ import annotations | |
| import json | |
| from collections.abc import Mapping | |
| from pathlib import Path | |
| from typing import Any | |
| import numpy as np | |
| import torch | |
| from transformers.feature_extraction_utils import BatchFeature | |
| from transformers.processing_utils import ProcessorMixin | |
| from ...backends.actioncodec2 import ActionCodec2Router, PresetConfig | |
| from ...backends.actioncodec2.defaults import ( | |
| CodecBuilder, | |
| ProfileBundleBuilder, | |
| builtin_action_spaces, | |
| ) | |
| from ...backends.actioncodec2.hf import ( | |
| PROCESSOR_CONFIG_NAME, | |
| ROUTER_ALLOW_PATTERNS, | |
| ROUTER_MANIFEST_PATTERNS, | |
| ActionCodec2FrontendHFConfig, | |
| resolve_pretrained_directory, | |
| save_huggingface_metadata, | |
| save_model_card, | |
| upload_pretrained_directory, | |
| ) | |
| from ...backends.actioncodec2.preprocessing import PreprocessingSpec | |
| from ...backends.actioncodec2.release import MANIFEST_NAME | |
| from ...exporting import save_runtime | |
| from .loading import describe_release, load_release_router | |
| from .tokenizer import ActionCodec2Tokenizer | |
| from ..training import TrainingMixin | |
| from ...backends.actioncodec2.training import ProfileTrainer | |
| def _saved_metadata(directory: Path) -> dict: | |
| metadata = directory / PROCESSOR_CONFIG_NAME | |
| return json.loads(metadata.read_text(encoding="utf-8")) if metadata.is_file() else {} | |
| def _fitted_status(profiles) -> str: | |
| status = ", ".join(sorted(profiles)) or "none; call codec.fit(episodes)" | |
| return f"Fitted profiles: {status}\n" | |
| class ActionCodec2(TrainingMixin, ActionCodec2Tokenizer, ProcessorMixin): | |
| 'Action-space-bound processor with lossless token serialization.' | |
| trainer_class = ProfileTrainer | |
| model_type = ActionCodec2FrontendHFConfig.model_type | |
| model_input_names = ["input_ids"] | |
| attributes = [] | |
| def __init__( | |
| self, | |
| backend: ActionCodec2Router | None = None, | |
| *, | |
| action_space: str, | |
| primitive_order: int = 1, | |
| bpe_scheme: str | None = None, | |
| profile_configs: Mapping[str, object] | None = None, | |
| ) -> None: | |
| fresh = backend is None | |
| if fresh: | |
| backend = CodecBuilder( | |
| primitive_order=primitive_order, | |
| bpe_scheme=bpe_scheme, | |
| profile_configs=profile_configs, | |
| ).build() | |
| elif primitive_order != 1 or bpe_scheme is not None or profile_configs is not None: | |
| raise ValueError("profile construction options cannot accompany an existing backend") | |
| super().__init__(backend, action_space=action_space) | |
| ProcessorMixin.__init__(self) | |
| self._fitted_profiles = frozenset() if fresh else frozenset(backend.profiles) | |
| self.training_report = None | |
| self.preprocessing: dict[str, PreprocessingSpec] = {} | |
| self.release_metadata: dict[str, Any] | None = None | |
| def __dir__(self): | |
| inapplicable = { | |
| "audio_tokenizer", | |
| "chat_template", | |
| "feature_extractor_class", | |
| "apply_chat_template", | |
| "post_process_image_text_to_text", | |
| "pad_action_id", | |
| "bos_blk_id", | |
| "parts_meta", | |
| "BAR_SENTINEL_TOKENS", | |
| } | |
| return [name for name in super().__dir__() if name not in inapplicable] | |
| def __repr__(self) -> str: | |
| return ( | |
| f"ActionCodec2(action_space={self.action_space.name!r}, " | |
| f"D={self.action_dim}, vocab={self.action_tokenizer.vocab_size})" | |
| ) | |
| def builtin_action_spaces(): | |
| """Return the immutable named layout definitions shipped in the package.""" | |
| return builtin_action_spaces() | |
| def from_profiles(cls, joint, eef, *, action_space: str, token_budgets=None): | |
| 'Bind an artifact assembled from existing joint/EEF profile directories.' | |
| router = ProfileBundleBuilder(joint, eef, token_budgets=token_budgets).build() | |
| return cls(router, action_space=action_space) | |
| def logits_processor( | |
| self, | |
| horizon: int, | |
| *, | |
| fps: float, | |
| prompt_length: int, | |
| eos_token_id: int, | |
| token_offset: int = 0, | |
| pad_token_id=None, | |
| ): | |
| 'Create a Transformers LogitsProcessor for a fixed action horizon.' | |
| from .generation import ActionCodec2LogitsProcessor | |
| return ActionCodec2LogitsProcessor( | |
| self, | |
| horizon, | |
| fps=fps, | |
| prompt_length=prompt_length, | |
| eos_token_id=eos_token_id, | |
| token_offset=token_offset, | |
| pad_token_id=pad_token_id, | |
| ) | |
| def batch_decode( | |
| self, token_rows, *, fps=None, current_state=None, previous_first_order=None, errors="raise" | |
| ): | |
| 'Decode independent token rows, including different valid horizons.' | |
| self._require_fitted() | |
| if errors not in ("raise", "return"): | |
| raise ValueError("errors must be 'raise' or 'return'") | |
| rows = list(token_rows) | |
| current = ( | |
| None | |
| if current_state is None | |
| else np.asarray( | |
| current_state.detach().cpu() | |
| if isinstance(current_state, torch.Tensor) | |
| else current_state | |
| ) | |
| ) | |
| if current is not None and current.shape != (len(rows), self.action_dim): | |
| raise ValueError(f"current_state must have shape {(len(rows), self.action_dim)}") | |
| previous = self._numpy_boundary(previous_first_order) | |
| if previous is not None and any( | |
| np.ndim(v) != 2 or len(v) != len(rows) for v in previous.values() | |
| ): | |
| raise ValueError( | |
| "previous_first_order arrays must have batch dimension equal to token rows" | |
| ) | |
| result = [] | |
| for index, row in enumerate(rows): | |
| try: | |
| decoded = self.decode( | |
| [row], | |
| fps=fps, | |
| current_state=None if current is None else current[index : index + 1], | |
| previous_first_order=None | |
| if previous is None | |
| else {k: v[index : index + 1] for k, v in previous.items()}, | |
| ) | |
| except ValueError as error: | |
| error = ValueError(f"token row {index}: {error}") | |
| if errors == "raise": | |
| raise error from None | |
| decoded = error | |
| result.append(decoded) | |
| return result | |
| def apply_chat_template(self, *args, **kwargs): | |
| raise NotImplementedError( | |
| "ActionCodec2 accepts action tensors; use codec.encode(actions, fps=...) or codec(actions, fps=...)" | |
| ) | |
| def post_process_image_text_to_text(self, *args, **kwargs): | |
| raise NotImplementedError( | |
| "ActionCodec2 returns actions; use codec.decode(token_rows, fps=...)" | |
| ) | |
| def fitted_profiles(self) -> frozenset[str]: | |
| """Names of profiles fitted on data; persisted by save_pretrained().""" | |
| return self._fitted_profiles | |
| def is_fitted(self) -> bool: | |
| """Whether every profile used by the selected action space is fitted.""" | |
| return all(c.space in self.fitted_profiles for c in self.action_space.components) | |
| def _require_fitted(self): | |
| missing = {c.space for c in self.action_space.components} - self.fitted_profiles | |
| if missing: | |
| raise RuntimeError( | |
| f"Unfitted profiles {sorted(missing)}; call codec.fit(episodes) first" | |
| ) | |
| def _describe_action_spaces(self, action_dim: int | None = None) -> str: | |
| return _fitted_status(self.fitted_profiles) + super()._describe_action_spaces(action_dim) | |
| def for_action_space(self, name: str) -> ActionCodec2: | |
| """Share current weights with a new binding; subsequent fit replaces only that instance.""" | |
| codec = super().for_action_space(name) | |
| codec._fitted_profiles = self.fitted_profiles | |
| codec.training_report = self.training_report | |
| codec.preprocessing = dict(self.preprocessing) | |
| codec.release_metadata = self.release_metadata | |
| return codec | |
| def _encode_action_indices(self, action, encode_kwargs=None): | |
| self._require_fitted() | |
| return super()._encode_action_indices(action, encode_kwargs) | |
| def _decode_action_indices(self, action_indices, **kwargs): | |
| self._require_fitted() | |
| return super()._decode_action_indices(action_indices, **kwargs) | |
| def decode_prefix(self, action_token_ids, **kwargs): | |
| self._require_fitted() | |
| return super().decode_prefix(action_token_ids, **kwargs) | |
| def grammar(self, horizon: int, *, fps: float | None = None): | |
| self._require_fitted() | |
| return super().grammar(horizon, fps=fps) | |
| def final_first_order(self, action_token_ids, *, previous_first_order, executed_steps=None): | |
| self._require_fitted() | |
| return super().final_first_order( | |
| action_token_ids, | |
| previous_first_order=previous_first_order, | |
| executed_steps=executed_steps, | |
| ) | |
| def describe_pretrained( | |
| cls, | |
| pretrained_model_name_or_path: str | Path, | |
| *, | |
| action_dim: int | None = None, | |
| gripper_mode: str = "2bin", | |
| primitive_order: int = 1, | |
| **kwargs: Any, | |
| ) -> str: | |
| 'Inspect a local/Hub artifact before choosing an action space.' | |
| directory = resolve_pretrained_directory( | |
| pretrained_model_name_or_path, allow_patterns=ROUTER_MANIFEST_PATTERNS, **kwargs | |
| ) | |
| if (directory / MANIFEST_NAME).is_file(): | |
| return describe_release( | |
| directory, | |
| action_dim=action_dim, | |
| gripper_mode=gripper_mode, | |
| primitive_order=primitive_order, | |
| ) | |
| specs, _ = ActionCodec2Router.read_manifest(directory) | |
| metadata = _saved_metadata(directory) | |
| fitted = metadata.get("fitted_profiles", [spec.name for spec in specs]) | |
| return _fitted_status(fitted) + ActionCodec2Router.describe_pretrained( | |
| directory, selected=metadata.get("action_space"), action_dim=action_dim | |
| ) | |
| def from_pretrained( | |
| cls, | |
| pretrained_model_name_or_path: str | Path, | |
| *, | |
| action_space: str | Path | PresetConfig | Mapping[str, object] | None = None, | |
| gripper_mode: str | None = None, | |
| primitive_order: int | None = None, | |
| verify_gates: bool = False, | |
| **kwargs: Any, | |
| ) -> ActionCodec2: | |
| 'Load a local/Hub artifact and restore or override its action space.' | |
| if cls is ActionCodec2 and ("variant" in kwargs or (Path(pretrained_model_name_or_path) / "variants.json").is_file() | |
| or (Path(pretrained_model_name_or_path) / "artifact_manifest.json").is_file()): | |
| from ...catalog import ActionCodec2 as Catalog | |
| return Catalog.from_pretrained(pretrained_model_name_or_path, action_space=action_space, **kwargs) | |
| directory = resolve_pretrained_directory( | |
| pretrained_model_name_or_path, allow_patterns=ROUTER_ALLOW_PATTERNS, **kwargs | |
| ) | |
| if action_space is None: | |
| action_space = _saved_metadata(directory).get("action_space") | |
| if action_space is None: | |
| raise ValueError( | |
| "action_space is required when loading an unbound router artifact. " | |
| "First inspect its layouts with print(ActionCodec2.describe_pretrained(path)), " | |
| "then pass action_space='NAME'; shape alone cannot identify physical meaning." | |
| ) | |
| if (directory / MANIFEST_NAME).is_file(): | |
| router, preprocessing, metadata = load_release_router( | |
| directory, | |
| gripper_mode="2bin" if gripper_mode is None else gripper_mode, | |
| primitive_order=1 if primitive_order is None else primitive_order, | |
| verify_gates=verify_gates, | |
| ) | |
| codec = cls._from_router(router, action_space=action_space) | |
| codec.preprocessing, codec.release_metadata = preprocessing, metadata | |
| return codec | |
| if gripper_mode is not None or primitive_order is not None or verify_gates: | |
| raise ValueError( | |
| "gripper_mode, primitive_order and verify_gates select release variants only" | |
| ) | |
| codec = super().from_pretrained(directory, action_space=action_space) | |
| metadata = _saved_metadata(directory) | |
| fitted = metadata.get("fitted_profiles", list(codec.action_tokenizer.profiles)) | |
| if not isinstance(fitted, list) or any( | |
| not isinstance(name, str) or name not in codec.action_tokenizer.profiles | |
| for name in fitted | |
| ): | |
| raise ValueError("invalid fitted_profiles in processor_config.json") | |
| codec._fitted_profiles = frozenset(fitted) | |
| policies = metadata.get("preprocessing", {}) | |
| if ( | |
| not isinstance(policies, dict) | |
| or policies.keys() - codec.action_tokenizer.profiles.keys() | |
| ): | |
| raise ValueError("invalid preprocessing profiles in processor_config.json") | |
| codec.preprocessing = { | |
| name: PreprocessingSpec.from_dict(spec) for name, spec in policies.items() | |
| } | |
| codec.release_metadata = metadata.get("release") | |
| report_path = directory / "fit_report.json" | |
| if report_path.is_file(): | |
| codec.training_report = json.loads(report_path.read_text(encoding="utf-8")) | |
| return codec | |
| def to_dict(self, legacy_serialization: bool = True) -> dict[str, object]: | |
| 'Return lightweight discovery metadata.' | |
| del legacy_serialization | |
| payload = { | |
| "processor_class": type(self).__name__, | |
| "model_type": self.model_type, | |
| "action_space": self.action_space.name, | |
| "fitted_profiles": sorted(self.fitted_profiles), | |
| } | |
| if self.preprocessing: | |
| payload["preprocessing"] = { | |
| name: spec.to_dict() for name, spec in self.preprocessing.items() | |
| } | |
| return payload | |
| def save_pretrained( | |
| self, save_directory: str | Path, push_to_hub: bool = False, *, include_training_report=False, **kwargs: Any | |
| ) -> list[str]: | |
| """Save the complete router and the selected action-space binding.""" | |
| if not push_to_hub and kwargs: | |
| raise TypeError(f"unused save_pretrained keyword(s): {', '.join(sorted(kwargs))}") | |
| directory = Path(save_directory) | |
| files = self.action_tokenizer.save_pretrained(directory) | |
| save_model_card(directory, str(self)) | |
| save_huggingface_metadata( | |
| directory, | |
| **self.to_dict(), | |
| ) | |
| report_path = directory / "fit_report.json" | |
| if include_training_report and self.training_report is not None: | |
| report_path.write_text( | |
| json.dumps(self.training_report, indent=2) + "\n", encoding="utf-8" | |
| ) | |
| files.append(str(report_path)) | |
| elif report_path.exists(): | |
| report_path.unlink() | |
| files.extend(save_runtime(directory)) | |
| if push_to_hub: | |
| upload_pretrained_directory(directory, **kwargs) | |
| return files | |
| def __call__( | |
| self, | |
| action: torch.Tensor | np.ndarray, | |
| *, | |
| fps: float | None = None, | |
| current_state: torch.Tensor | np.ndarray | None = None, | |
| previous_first_order: Mapping[str, torch.Tensor | np.ndarray] | None = None, | |
| encode_kwargs: dict | None = None, | |
| return_tensors: str | None = None, | |
| ) -> BatchFeature: | |
| rows = self.encode( | |
| action, | |
| fps=fps, | |
| current_state=current_state, | |
| previous_first_order=previous_first_order, | |
| encode_kwargs=encode_kwargs, | |
| ) | |
| return BatchFeature({"input_ids": rows}, tensor_type=return_tensors) | |