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/backends/actioncodec2/training.py from ZibinDong/ActionCodec2: direct link, hf CLI and curl.
- Browser
- Download file 25.2 kB
-
https://huggingface.co/ZibinDong/ActionCodec2/resolve/main/runtime/backends/actioncodec2/training.py
- Command line
-
hf download hf://ZibinDong/ActionCodec2/runtime/backends/actioncodec2/training.py
-
curl -L -o training.py https://huggingface.co/ZibinDong/ActionCodec2/resolve/main/runtime/backends/actioncodec2/training.py
25.2 kB
| 'Whole-trajectory primitive construction and BPE fitting.' | |
| from __future__ import annotations | |
| import math | |
| import time | |
| from bisect import bisect_right | |
| from collections import defaultdict | |
| from collections.abc import Iterable, Sequence | |
| import numpy as np | |
| from ._validation import broadcast_vector | |
| from .bpe import BPESerializer, SetBPESerializer | |
| from .bpe_training import train_bpe | |
| from .configuration import ActionCodec2Config, ConfigError | |
| from .physical_codec import PhysicalCodec | |
| from .primitives import PrimitiveStore | |
| from .processor import ActionCodec2Processor | |
| from .setbpe.spatial import aggregate_records | |
| from .setbpe.spatial_training import train_spatial | |
| from .setbpe.temporal_training import train_temporal | |
| from .trajectory import TrajectoryLike, prepare_trajectory, trajectory_boundary | |
| def _zero_anchored_grid(lower: np.float32, upper: np.float32, spacing: np.float32) -> np.ndarray: | |
| below = int(np.ceil(-lower / spacing)) | |
| above = int(np.ceil(upper / spacing)) | |
| negative = np.linspace(lower, np.float32(0.0), below + 1, dtype=np.float32)[:-1] | |
| positive = np.linspace(np.float32(0.0), upper, above + 1, dtype=np.float32) | |
| return np.concatenate((negative, positive)) | |
| def _additive_axis( | |
| lower: np.float32, | |
| upper: np.float32, | |
| distortion_limit: np.float32, | |
| *, | |
| zero_primitive: bool, | |
| ) -> np.ndarray: | |
| width = np.float32(upper - lower) | |
| if width == 0.0: | |
| return np.asarray([lower], dtype=np.float32) | |
| spacing = np.float32(2.0) * distortion_limit | |
| if zero_primitive and lower < 0.0 < upper: | |
| return _zero_anchored_grid(lower, upper, spacing) | |
| count = 1 + int(np.ceil(width / spacing)) | |
| return np.linspace(lower, upper, count, dtype=np.float32) | |
| def build_primitives(config: ActionCodec2Config) -> PrimitiveStore: | |
| 'Derive primitive arrays and force an exact zero on every axis.' | |
| if config.train is None: | |
| raise ConfigError("config.train is required to build primitives") | |
| output: list[np.ndarray | None] = [None] * config.physical.input_dim | |
| for component in config.physical.components.values(): | |
| width = len(component.dims) | |
| if component.space == "additive": | |
| domain = component.constraints[f"order_{component.primitive_order}"] | |
| lower = broadcast_vector( | |
| domain.minimum, width, dtype=np.dtype(np.float32), name="minimum" | |
| ) | |
| upper = broadcast_vector( | |
| domain.maximum, width, dtype=np.dtype(np.float32), name="maximum" | |
| ) | |
| epsilon = broadcast_vector( | |
| component.distortion_limit, | |
| width, | |
| dtype=np.dtype(np.float32), | |
| name="distortion_limit", | |
| ) | |
| if component.primitive_order == 2: | |
| lower = lower - 2 * epsilon | |
| upper = upper + 2 * epsilon | |
| axes = [ | |
| _additive_axis( | |
| lower[index], | |
| upper[index], | |
| epsilon[index], | |
| zero_primitive=True, | |
| ) | |
| for index in range(width) | |
| ] | |
| else: | |
| domain = component.constraints[f"order_{component.primitive_order}"] | |
| radius_dtype = ( | |
| np.float64 if component.representation == "rotation_vector" else np.float32 | |
| ) | |
| lower = broadcast_vector( | |
| domain.minimum, width, dtype=np.dtype(radius_dtype), name="minimum" | |
| ) | |
| upper = broadcast_vector( | |
| domain.maximum, width, dtype=np.dtype(radius_dtype), name="maximum" | |
| ) | |
| corner = np.maximum(np.abs(lower), np.abs(upper)) | |
| source_radius = ( | |
| float(np.linalg.norm(corner)) | |
| if component.representation == "rotation_vector" | |
| else float(np.sum(corner, dtype=np.float32)) | |
| ) | |
| source_radius = min(math.pi, source_radius) | |
| epsilon = float(component.distortion_limit) | |
| allowance = (3 if component.primitive_order == 2 else 1) * epsilon | |
| cover_radius = float(min(math.pi, source_radius + allowance)) | |
| maximum_spacing = 2.0 * epsilon / math.sqrt(3.0) | |
| count = int(math.ceil(2.0 * cover_radius / maximum_spacing)) + 1 | |
| if count % 2 == 0: | |
| count += 1 | |
| axis = np.linspace(-cover_radius, cover_radius, count, dtype=np.float64) | |
| if count % 2 == 1: | |
| axis[count // 2] = 0.0 | |
| axes = [axis.copy() for _ in range(width)] | |
| for dimension, axis in zip(component.dims, axes, strict=True): | |
| output[dimension] = axis | |
| if any(axis is None for axis in output): | |
| raise RuntimeError("internal error: primitive construction left an unassigned dimension") | |
| store = PrimitiveStore([axis for axis in output if axis is not None]) | |
| missing_zero = [ | |
| dimension | |
| for dimension, axis in enumerate(store.values) | |
| if not np.any(np.asarray(axis) == 0) | |
| ] | |
| if missing_zero: | |
| raise ConfigError( | |
| "every fitted primitive axis must contain exact zero; missing dimension(s) " | |
| f"{missing_zero}. Ensure each configured domain contains zero." | |
| ) | |
| return store | |
| def _validate_component_support( | |
| active_dims: tuple[int, ...], config: ActionCodec2Config, *, index: int | |
| ) -> None: | |
| active = set(active_dims) | |
| for name, component in config.physical.components.items(): | |
| present = active.intersection(component.dims) | |
| if component.space == "so3" and present and present != set(component.dims): | |
| raise ValueError(f"trajectory {index} activates only part of SO(3) component {name!r}") | |
| def _length_buckets( | |
| pending: Sequence[tuple[np.ndarray, tuple[int, ...], np.ndarray | None]], | |
| *, | |
| maximum_ratio: float = 1.25, | |
| ) -> list[list[int]]: | |
| ordered = sorted(range(len(pending)), key=lambda index: len(pending[index][0])) | |
| buckets: list[list[int]] = [] | |
| current: list[int] = [] | |
| shortest = 0 | |
| for index in ordered: | |
| length = len(pending[index][0]) | |
| if current and length > shortest * maximum_ratio: | |
| buckets.append(current) | |
| current = [] | |
| if not current: | |
| shortest = length | |
| current.append(index) | |
| if current: | |
| buckets.append(current) | |
| return buckets | |
| class _CellCorpus(Sequence[np.ndarray]): | |
| _actioncodec2_validated = True | |
| def __init__(self, chunks: list[np.ndarray], offsets: list[np.ndarray]) -> None: | |
| self.chunks: list[np.ndarray | None] = list(chunks) | |
| self.offsets = tuple(offsets) | |
| counts = np.asarray([len(value) - 1 for value in offsets], dtype=np.int64) | |
| self._episode_ends = np.cumsum(counts) | |
| def __len__(self) -> int: | |
| return int(self._episode_ends[-1]) if len(self._episode_ends) else 0 | |
| def __getitem__(self, index: int) -> np.ndarray: | |
| if not isinstance(index, int): | |
| raise TypeError("cell corpus indices must be integers") | |
| if index < 0: | |
| index += len(self) | |
| if not 0 <= index < len(self): | |
| raise IndexError(index) | |
| chunk = bisect_right(self._episode_ends, index) | |
| previous = 0 if chunk == 0 else int(self._episode_ends[chunk - 1]) | |
| local = index - previous | |
| start, stop = self.offsets[chunk][local : local + 2] | |
| values = self.chunks[chunk] | |
| if values is None: | |
| raise RuntimeError("cell corpus chunk has already been consumed") | |
| return values[int(start) : int(stop)] | |
| def __iter__(self): | |
| for chunk, offsets in zip(self.chunks, self.offsets, strict=True): | |
| if chunk is None: | |
| raise RuntimeError("cell corpus chunk has already been consumed") | |
| for start, stop in zip(offsets[:-1], offsets[1:], strict=True): | |
| yield chunk[int(start) : int(stop)] | |
| def release_chunk(self, index: int) -> None: | |
| """Release a replayed chunk while retaining compact episode metadata.""" | |
| self.chunks[index] = None | |
| def _quantize_pending_batch( | |
| pending: Sequence[tuple[np.ndarray, tuple[int, ...], np.ndarray | None]], | |
| physical: PhysicalCodec, | |
| cell_dtype: type[np.signedinteger], | |
| ) -> list[np.ndarray]: | |
| result: list[np.ndarray | None] = [None] * len(pending) | |
| dimension = physical.config.physical.input_dim | |
| for indices in _length_buckets(pending): | |
| length = max(len(pending[index][0]) for index in indices) | |
| batch = np.zeros((len(indices), length, dimension), dtype=np.float32) | |
| active_by_item: list[tuple[int, ...]] = [] | |
| for local, index in enumerate(indices): | |
| trajectory, active_dims, _ = pending[index] | |
| item = batch[local, : len(trajectory)] | |
| item[:, active_dims] = trajectory | |
| active_by_item.append(active_dims) | |
| boundaries = [pending[index][2] for index in indices] | |
| previous = ( | |
| None | |
| if all(value is None for value in boundaries) | |
| else np.stack( | |
| [ | |
| np.zeros(dimension, dtype=np.float32) if value is None else value | |
| for value in boundaries | |
| ] | |
| ) | |
| ) | |
| encoded = physical.encode_batch(batch, previous_first_order=previous).astype( | |
| cell_dtype, copy=False | |
| ) | |
| for local, index in enumerate(indices): | |
| item_length = len(pending[index][0]) | |
| dense = np.full((item_length, dimension), -1, dtype=cell_dtype) | |
| dense[:, active_by_item[local]] = encoded[local][:item_length, active_by_item[local]] | |
| result[index] = dense | |
| if any(value is None for value in result): | |
| raise RuntimeError("internal error: trajectory batch was not fully quantized") | |
| return [value for value in result if value is not None] | |
| def _quantize_trajectories( | |
| trajectories: Iterable[TrajectoryLike], | |
| physical: PhysicalCodec, | |
| *, | |
| batch_size: int, | |
| max_cell_bytes: int | None, | |
| ) -> tuple[_CellCorpus, int, int, dict[int, int]]: | |
| if batch_size < 1: | |
| raise ValueError("batch_size must be positive") | |
| cell_chunks: list[np.ndarray] = [] | |
| offset_chunks: list[np.ndarray] = [] | |
| cell_parts: list[np.ndarray] = [] | |
| cell_lengths: list[int] = [] | |
| chunk_actions = 0 | |
| action_count = 0 | |
| active_value_count = 0 | |
| arity_counts: dict[int, int] = defaultdict(int) | |
| source_index = 0 | |
| retained_cell_bytes = 0 | |
| cell_dtype = np.int16 if max(physical.bins) <= np.iinfo(np.int16).max else np.int32 | |
| iterator = iter(trajectories) | |
| def flush_cells() -> None: | |
| nonlocal chunk_actions, retained_cell_bytes | |
| if not cell_parts: | |
| return | |
| chunk = np.concatenate(cell_parts, axis=0) | |
| projected = retained_cell_bytes + chunk.nbytes | |
| if max_cell_bytes is not None and projected > max_cell_bytes: | |
| raise MemoryError( | |
| "quantized ActionCodec2 corpus would retain at least " | |
| f"{projected / 2**30:.2f} GiB, exceeding the configured " | |
| f"{max_cell_bytes / 2**30:.2f} GiB limit; use stratified fitting, " | |
| "lower coverage, or explicitly raise --max-cell-memory-gib" | |
| ) | |
| cell_chunks.append(chunk) | |
| retained_cell_bytes = projected | |
| offset_chunks.append(np.concatenate(([0], np.cumsum(cell_lengths, dtype=np.int64)))) | |
| cell_parts.clear() | |
| cell_lengths.clear() | |
| chunk_actions = 0 | |
| while True: | |
| pending: list[tuple[np.ndarray, tuple[int, ...], np.ndarray | None]] = [] | |
| for _ in range(batch_size): | |
| try: | |
| raw = next(iterator) | |
| except StopIteration: | |
| break | |
| trajectory, active_dims = prepare_trajectory( | |
| raw, | |
| index=source_index, | |
| dimension=physical.config.physical.input_dim, | |
| dtype=np.float32, | |
| ) | |
| _validate_component_support(active_dims, physical.config, index=source_index) | |
| source_index += 1 | |
| action_count += len(trajectory) | |
| active_value_count += len(trajectory) * len(active_dims) | |
| arity_counts[len(active_dims)] += 1 | |
| pending.append( | |
| (trajectory, active_dims, trajectory_boundary(raw, physical.config, active_dims)) | |
| ) | |
| if not pending: | |
| break | |
| for value in _quantize_pending_batch(pending, physical, cell_dtype): | |
| cell_parts.append(value) | |
| cell_lengths.append(len(value)) | |
| chunk_actions += len(value) | |
| if chunk_actions >= 1_000_000: | |
| flush_cells() | |
| flush_cells() | |
| cells = _CellCorpus(cell_chunks, offset_chunks) | |
| if not cells: | |
| raise ValueError("training requires at least one complete trajectory") | |
| return cells, action_count, active_value_count, dict(sorted(arity_counts.items())) | |
| def _aggregate_trajectory_records( | |
| cells: Sequence[np.ndarray], bins: Sequence[int], action_count: int | |
| ) -> tuple[np.ndarray, np.ndarray]: | |
| del action_count | |
| dimension = len(tuple(bins)) | |
| widths = tuple(int(count).bit_length() for count in bins) | |
| if sum(widths) <= 64: | |
| return _aggregate_packed_records(cells, bins, widths) | |
| counts_by_row: dict[bytes, int] = defaultdict(int) | |
| pending: list[np.ndarray] = [] | |
| pending_rows = 0 | |
| chunk_rows = 1_000_000 | |
| def flush() -> None: | |
| nonlocal pending_rows | |
| if not pending: | |
| return | |
| block = ( | |
| np.asarray(pending[0], dtype=np.int32) | |
| if len(pending) == 1 | |
| else np.concatenate(pending, axis=0).astype(np.int32, copy=False) | |
| ) | |
| unique, counts = aggregate_records(block, bins) | |
| for row, count in zip(unique.astype(np.int32, copy=False), counts, strict=True): | |
| counts_by_row[row.tobytes()] += int(count) | |
| pending.clear() | |
| pending_rows = 0 | |
| for trajectory in cells: | |
| pending.append(np.asarray(trajectory, dtype=np.int32)) | |
| pending_rows += len(trajectory) | |
| if pending_rows >= chunk_rows: | |
| flush() | |
| flush() | |
| keys = sorted(counts_by_row) | |
| records = np.frombuffer(b"".join(keys), dtype=np.int32).reshape(-1, dimension) | |
| weights = np.fromiter((counts_by_row[key] for key in keys), dtype=np.int64, count=len(keys)) | |
| return records, weights | |
| def _aggregate_packed_records( | |
| cells: Sequence[np.ndarray], bins: Sequence[int], widths: Sequence[int] | |
| ) -> tuple[np.ndarray, np.ndarray]: | |
| counts_by_key: dict[int, int] = defaultdict(int) | |
| pending: list[np.ndarray] = [] | |
| pending_rows = 0 | |
| chunk_rows = 1_000_000 | |
| def flush() -> None: | |
| nonlocal pending_rows | |
| if not pending: | |
| return | |
| block = pending[0] if len(pending) == 1 else np.concatenate(pending, axis=0) | |
| keys = np.zeros(len(block), dtype=np.uint64) | |
| shift = 0 | |
| for dimension, width in enumerate(widths): | |
| encoded = np.asarray(block[:, dimension], dtype=np.int64) + 1 | |
| keys |= encoded.astype(np.uint64) << np.uint64(shift) | |
| shift += width | |
| unique, counts = np.unique(keys, return_counts=True) | |
| for key, count in zip(unique, counts, strict=True): | |
| counts_by_key[int(key)] += int(count) | |
| pending.clear() | |
| pending_rows = 0 | |
| for trajectory in cells: | |
| pending.append(trajectory) | |
| pending_rows += len(trajectory) | |
| if pending_rows >= chunk_rows: | |
| flush() | |
| flush() | |
| keys = np.fromiter(sorted(counts_by_key), dtype=np.uint64, count=len(counts_by_key)) | |
| records = np.empty((len(keys), len(tuple(bins))), dtype=np.int32) | |
| shift = 0 | |
| for dimension, width in enumerate(widths): | |
| mask = np.uint64((1 << width) - 1) | |
| records[:, dimension] = ((keys >> np.uint64(shift)) & mask).astype(np.int32) - 1 | |
| shift += width | |
| weights = np.fromiter( | |
| (counts_by_key[int(key)] for key in keys), dtype=np.int64, count=len(keys) | |
| ) | |
| return records, weights | |
| def _fit_report( | |
| config: ActionCodec2Config, | |
| primitives: PrimitiveStore, | |
| codec: ActionCodec2Processor, | |
| *, | |
| trajectory_count: int, | |
| actions: int, | |
| active_values: int, | |
| arity_counts: dict[int, int], | |
| cell_dtype: str, | |
| unique_records: int, | |
| spatial_report: dict[str, object], | |
| temporal_report: dict[str, object], | |
| timestamps: tuple[float, float, float, float, float, float], | |
| ) -> dict[str, object]: | |
| started, primitive_done, quantized, aggregated, spatial_done, finished = timestamps | |
| scheme = config.set_bpe.scheme | |
| stages: dict[str, object] = ( | |
| {"bpe": temporal_report} | |
| if scheme == "bpe" | |
| else {"spatial": spatial_report, "temporal": temporal_report} | |
| ) | |
| return { | |
| "trajectories": trajectory_count, | |
| "actions": actions, | |
| "active_values": active_values, | |
| "trajectory_arities": {str(key): value for key, value in arity_counts.items()}, | |
| "bpe_boundary": "complete_component_trajectory", | |
| "bpe_training_domain": "quantized_primitive_cells", | |
| "physical_boundary": ( | |
| "explicit_previous_first_order" | |
| if codec.requires_previous_first_order | |
| else "zero_position_identity_orientation" | |
| ), | |
| "component_primitive_orders": { | |
| name: component.primitive_order | |
| for name, component in config.physical.components.items() | |
| }, | |
| "allow_cross_component_merge": config.set_bpe.allow_cross_component_merge, | |
| "lookahead": config.set_bpe.lookahead, | |
| "physical_bins": list(primitives.bins), | |
| "cell_dtype": cell_dtype, | |
| "unique_spatial_records": unique_records, | |
| "primitive_atoms": sum(primitives.bins), | |
| "tokenization_scheme": scheme, | |
| **stages, | |
| "vocab_size": codec.vocab_size, | |
| "tokens_per_action": float(temporal_report["tokens_per_action"]), | |
| "seconds": { | |
| "primitives": primitive_done - started, | |
| "quantize": quantized - primitive_done, | |
| "aggregate": aggregated - quantized, | |
| "spatial_train": spatial_done - aggregated, | |
| "spatial": spatial_done - quantized, | |
| "temporal": finished - spatial_done, | |
| "total": finished - started, | |
| }, | |
| } | |
| def _fit_set_bpe( | |
| cells: Sequence[np.ndarray], | |
| config: ActionCodec2Config, | |
| primitives: PrimitiveStore, | |
| actions: int, | |
| *, | |
| backend: str, | |
| threads: int, | |
| progress: bool, | |
| ) -> tuple[SetBPESerializer, dict[str, object], dict[str, object], int, float, float]: | |
| records, weights = _aggregate_trajectory_records(cells, primitives.bins, actions) | |
| aggregated = time.perf_counter() | |
| bpe = config.train.bpe if config.train is not None else None | |
| assert bpe is not None | |
| spatial, spatial_report = train_spatial( | |
| records, | |
| weights, | |
| primitives.bins, | |
| vocab_size=bpe.spatial_vocab_size, | |
| backend=backend, | |
| threads=threads, | |
| progress=progress, | |
| ) | |
| spatial_done = time.perf_counter() | |
| vocabulary, temporal_report = train_temporal( | |
| cells, | |
| spatial, | |
| vocab_size=bpe.vocab_size, | |
| lookahead=config.set_bpe.lookahead, | |
| backend=backend, | |
| threads=threads, | |
| progress=progress, | |
| ) | |
| serializer = SetBPESerializer( | |
| bins=primitives.bins, | |
| lookahead=config.set_bpe.lookahead, | |
| spatial_merges=spatial.merges, | |
| temporal_merges=vocabulary.merges, | |
| ) | |
| return serializer, spatial_report, temporal_report, len(records), aggregated, spatial_done | |
| def _fit_bpe( | |
| cells: Sequence[np.ndarray], | |
| config: ActionCodec2Config, | |
| primitives: PrimitiveStore, | |
| *, | |
| backend: str, | |
| threads: int, | |
| progress: bool, | |
| ) -> tuple[BPESerializer, dict[str, object], dict[str, object], int, float, float]: | |
| started = time.perf_counter() | |
| bpe = config.train.bpe if config.train is not None else None | |
| assert bpe is not None | |
| vocabulary, report = train_bpe( | |
| cells, | |
| primitives.bins, | |
| vocab_size=bpe.vocab_size, | |
| max_token_cells=config.set_bpe.lookahead * config.physical.input_dim, | |
| backend=backend, | |
| threads=threads, | |
| progress=progress, | |
| ) | |
| serializer = BPESerializer( | |
| bins=primitives.bins, | |
| lookahead=config.set_bpe.lookahead, | |
| merges=vocabulary.merges, | |
| ) | |
| return serializer, {}, report, 0, started, started | |
| def _validate_fit_options(config: ActionCodec2Config, max_cell_bytes: int | None) -> None: | |
| if config.train is None: | |
| raise ConfigError("config.train is required for fitting") | |
| if config.set_bpe.scheme == "sparse_set_bpe": | |
| raise ConfigError("sparse_set_bpe profiles are fitted by ActionCodec2Sparse") | |
| if max_cell_bytes is not None and ( | |
| isinstance(max_cell_bytes, bool) | |
| or not isinstance(max_cell_bytes, int) | |
| or max_cell_bytes < 1 | |
| ): | |
| raise ValueError("max_cell_bytes must be a positive integer or None") | |
| if config.set_bpe.allow_cross_component_merge: | |
| raise ConfigError( | |
| "allow_cross_component_merge=true requires the future multi-component " | |
| "corpus representation; v1 fitting supports the default isolated boundary only" | |
| ) | |
| if config.set_bpe.scheme == "bpe" and config.train.bpe.spatial_vocab_size != ( | |
| config.train.bpe.vocab_size | |
| ): | |
| raise ConfigError( | |
| "set_bpe.scheme=bpe has a single merge stage; set " | |
| "train.bpe.spatial_vocab_size equal to train.bpe.vocab_size to make the " | |
| "unused split explicit" | |
| ) | |
| class ProfileTrainer: | |
| 'Internal whole-trajectory fitter for one physical profile.' | |
| def __init__(self, config: ActionCodec2Config) -> None: | |
| self.config = config | |
| def fit( | |
| self, | |
| trajectories: Iterable[TrajectoryLike], | |
| *, | |
| backend: str = "auto", | |
| threads: int = 0, | |
| batch_size: int = 256, | |
| progress: bool = False, | |
| max_cell_bytes: int | None = None, | |
| ) -> tuple[ActionCodec2Processor, dict[str, object]]: | |
| 'Fit primitives and the configured tokenization scheme on whole trajectories.' | |
| config = self.config | |
| _validate_fit_options(config, max_cell_bytes) | |
| started = time.perf_counter() | |
| primitives = build_primitives(config) | |
| primitive_done = time.perf_counter() | |
| atoms = sum(primitives.bins) | |
| bpe = config.train.bpe | |
| if bpe.spatial_vocab_size < atoms: | |
| raise ConfigError( | |
| f"train.bpe.spatial_vocab_size must be at least the {atoms} primitive atoms" | |
| ) | |
| physical = PhysicalCodec(config, primitives) | |
| cells, actions, active_values, arity_counts = _quantize_trajectories( | |
| trajectories, | |
| physical, | |
| batch_size=batch_size, | |
| max_cell_bytes=max_cell_bytes, | |
| ) | |
| trajectory_count = len(cells) | |
| first_cell_dtype = str(cells[0].dtype) | |
| quantized = time.perf_counter() | |
| if config.set_bpe.scheme == "unified_set_bpe": | |
| from .unified_set_bpe import fit_unified_set_bpe | |
| serializer, unified_report = fit_unified_set_bpe( | |
| config, cells, threads=threads, backend=backend, progress=progress | |
| ) | |
| temporal_report = { | |
| **unified_report["temporal"], | |
| "tokens_per_action": unified_report["temporal"]["final_instances"] / actions, | |
| } | |
| fitted = serializer, {}, temporal_report, 0, quantized, quantized | |
| elif config.set_bpe.scheme == "bpe": | |
| fitted = _fit_bpe( | |
| cells, config, primitives, backend=backend, threads=threads, progress=progress | |
| ) | |
| else: | |
| fitted = _fit_set_bpe( | |
| cells, | |
| config, | |
| primitives, | |
| actions, | |
| backend=backend, | |
| threads=threads, | |
| progress=progress, | |
| ) | |
| serializer, spatial_report, temporal_report, unique_records, aggregated, spatial_done = ( | |
| fitted | |
| ) | |
| codec = ActionCodec2Processor(config, primitives, serializer) | |
| finished = time.perf_counter() | |
| report = _fit_report( | |
| config, | |
| primitives, | |
| codec, | |
| trajectory_count=trajectory_count, | |
| actions=actions, | |
| active_values=active_values, | |
| arity_counts=arity_counts, | |
| cell_dtype=first_cell_dtype, | |
| unique_records=unique_records, | |
| spatial_report=spatial_report, | |
| temporal_report=temporal_report, | |
| timestamps=( | |
| started, | |
| primitive_done, | |
| quantized, | |
| aggregated, | |
| spatial_done, | |
| finished, | |
| ), | |
| ) | |
| return codec, report | |