mirror of
https://github.com/huggingface/lerobot.git
synced 2026-06-02 11:51:25 +00:00
* feat(tokenizer): Introduce TokenizerProcessor for text tokenization - Added TokenizerProcessor class to handle tokenization of task strings using Hugging Face's AutoTokenizer. - Supports both string and list inputs, with customizable parameters for task key, output key, and tokenization settings. - Implemented comprehensive unit tests to validate functionality, including handling of various input scenarios and integration with RobotProcessor. - Updated types.py to include LANGUAGE feature type and modified __init__.py to register the new processor. * feat(language): Enhance language processing in TokenizerProcessor - Added OBS_LANGUAGE constant to define the observation language key. - Updated TokenizerProcessor to store tokenized task data in the observation dictionary, ensuring compatibility with the new language feature. - Introduced Pi0NewLineProcessor to append newlines to tasks for proper tokenization. - Modified tests to validate the integration of language tokens and attention masks in the observation structure. * feat(tokenizer): Add padding configuration to TokenizerProcessor - Introduced `padding_side` parameter to the TokenizerProcessor for customizable padding direction. - Updated the `make_pi0_processor` function to include the new padding configuration. - Enhanced unit tests to validate the functionality of the `padding_side` parameter in various scenarios. * feat(processor): Add state management methods to Pi0NewLineProcessor * feat(normalization): Track normalization and unnormalization info in complementary data - Updated NormalizerProcessor and UnnormalizerProcessor to accept additional parameters for tracking normalization modes. - Enhanced the __call__ methods to store normalization and unnormalization information in the complementary data of transitions. - Added unit tests to verify the correct tracking of normalization info, including scenarios with missing stats and selective normalization keys. * feat(factory): Add preprocessor and postprocessor overrides to ProcessorConfigKwargs - Updated ProcessorConfigKwargs to include optional overrides for preprocessor and postprocessor configurations. - Enhanced the make_processor function to utilize the new overrides, allowing for more flexible processor initialization. * feat(processors): Integrate RenameProcessor into various processor configurations - Added RenameProcessor to the input steps of multiple processor functions, including make_act_processor, make_diffusion_processor, make_pi0_processor, make_sac_processor, make_tdmpc_processor, make_vqbet_processor, and make_smolvla_processor. - Consolidated normalization features from input and output into a single NormalizerProcessor for improved efficiency. - Updated the input steps to ensure compatibility with the new RenameProcessor integration. * feat(smolvla): Refactor language processing and introduce new line processor (#1658) - Removed the prepare_language method and directly accessed language tokens and masks from the batch using the OBS_LANGUAGE constant. - Added SmolVLANewLineProcessor to ensure tasks end with a newline, enhancing tokenization compatibility. - Updated the make_smolvla_processor function to include the new line processor and tokenizer processor for improved input handling. * feture(policies): add device processor (#1659) * feat(processors): Integrate DeviceProcessor into multiple processor configurations - Added DeviceProcessor to the input and output steps of various processor functions, including make_act_processor, make_diffusion_processor, make_pi0_processor, make_pi0fast_processor, make_sac_processor, make_tdmpc_processor, make_vqbet_processor, and make_smolvla_processor. - Enhanced the DeviceProcessor class with state management methods and ensured compatibility with existing processor pipelines. - Introduced unit tests for DeviceProcessor to validate functionality across different scenarios, including CPU and CUDA operations. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * refactor(pipeline): Remove to() method for device management - Eliminated the to() method from RobotProcessor, which was responsible for moving tensor states to specified devices. - Removed associated unit tests that validated the functionality of the to() method across various scenarios. - Streamlined the pipeline code by focusing on other device management strategies. * feat(processor): Enhance DeviceProcessor with float dtype conversion - Added support for optional float dtype conversion in DeviceProcessor, allowing tensors to be converted to specified floating-point types while preserving non-float types. - Implemented validation for float dtype input and updated the processor's configuration methods to include float dtype. - Refactored tensor processing logic to streamline device movement and dtype conversion. - Introduced comprehensive unit tests to validate the new float dtype functionality across various scenarios. * feat(policies): Add new line processors and update module exports * feat(processor): Enhance batch and device processors to handle index and task_index fields - Added logic to ToBatchProcessor for unsqueezing 0D tensors for index and task_index fields, ensuring they are processed as 1D tensors. - Updated DeviceProcessor to process index and task_index fields in complementary data, preserving their tensor types and ensuring non-tensor fields remain unchanged. - Enhanced unit tests to validate the correct handling of index and task_index fields across various scenarios, including device compatibility and dtype preservation.
211 lines
7.7 KiB
Python
211 lines
7.7 KiB
Python
"""
|
|
Tokenizer processor for handling text tokenization in robot transitions.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
import torch
|
|
from transformers import AutoTokenizer
|
|
|
|
from lerobot.configs.types import FeatureType, PolicyFeature
|
|
from lerobot.constants import OBS_LANGUAGE
|
|
from lerobot.processor.pipeline import EnvTransition, ProcessorStepRegistry, TransitionKey
|
|
|
|
|
|
@dataclass
|
|
@ProcessorStepRegistry.register(name="tokenizer_processor")
|
|
class TokenizerProcessor:
|
|
"""Tokenizes text tasks in complementary data using a huggingface tokenizer.
|
|
|
|
This processor handles tokenization of task strings found in the complementary_data
|
|
using a specified pretrained tokenizer from Hugging Face. It adds tokenized versions
|
|
to the observation data for model processing while preserving the original task string.
|
|
|
|
The processor supports both single strings and lists of strings as task inputs.
|
|
|
|
Args:
|
|
tokenizer_name: Name of the pretrained tokenizer to load from Hugging Face Hub
|
|
(e.g., "bert-base-uncased", "microsoft/DialoGPT-medium"). This will be used
|
|
with AutoTokenizer.from_pretrained(). If tokenizer is provided, this is ignored.
|
|
tokenizer: A tokenizer object (e.g., from transformers library) that implements
|
|
the __call__ method. If provided, tokenizer_name is ignored. This parameter
|
|
is not serialized and must be provided via overrides when loading.
|
|
max_length: Maximum sequence length for tokenization. Defaults to 512.
|
|
task_key: Key in complementary_data containing the task text. Defaults to "task".
|
|
padding: Padding strategy for tokenization. Defaults to "max_length".
|
|
truncation: Whether to truncate sequences longer than max_length. Defaults to True.
|
|
|
|
Examples:
|
|
Using tokenizer name (auto-loaded):
|
|
```python
|
|
processor = TokenizerProcessor(tokenizer_name="bert-base-uncased", max_length=128)
|
|
```
|
|
|
|
Using custom tokenizer object:
|
|
```python
|
|
from transformers import AutoTokenizer
|
|
|
|
custom_tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
|
|
processor = TokenizerProcessor(tokenizer=custom_tokenizer, max_length=128)
|
|
```
|
|
"""
|
|
|
|
tokenizer_name: str | None = None
|
|
tokenizer: AutoTokenizer | None = None
|
|
max_length: int = 512
|
|
task_key: str = "task"
|
|
padding_side: str = "right"
|
|
padding: str = "max_length"
|
|
truncation: bool = True
|
|
|
|
# Internal tokenizer instance (not serialized)
|
|
_tokenizer: Any = field(default=None, init=False, repr=False)
|
|
|
|
def __post_init__(self):
|
|
"""Initialize the tokenizer from the provided tokenizer or tokenizer name."""
|
|
if self.tokenizer is not None:
|
|
# Use provided tokenizer object directly
|
|
self._tokenizer = self.tokenizer
|
|
elif self.tokenizer_name is not None:
|
|
self._tokenizer = AutoTokenizer.from_pretrained(self.tokenizer_name)
|
|
else:
|
|
raise ValueError(
|
|
"Either 'tokenizer' or 'tokenizer_name' must be provided. "
|
|
"Pass a tokenizer object directly or a tokenizer name to auto-load."
|
|
)
|
|
|
|
def get_task(self, transition: EnvTransition) -> list[str] | None:
|
|
"""Extract and normalize task from complementary data.
|
|
|
|
Args:
|
|
transition: Input transition containing complementary_data.
|
|
|
|
Returns:
|
|
List of task strings if task is present, None otherwise.
|
|
"""
|
|
complementary_data = transition.get(TransitionKey.COMPLEMENTARY_DATA)
|
|
if complementary_data is None:
|
|
return None
|
|
|
|
if self.task_key not in complementary_data:
|
|
return None
|
|
|
|
task = complementary_data[self.task_key]
|
|
if task is None:
|
|
return None
|
|
|
|
# Convert to list of strings
|
|
if isinstance(task, str):
|
|
return [task]
|
|
elif isinstance(task, list) and all(isinstance(t, str) for t in task):
|
|
return task
|
|
|
|
return None
|
|
|
|
def __call__(self, transition: EnvTransition) -> EnvTransition:
|
|
"""Process the transition by tokenizing the task text.
|
|
|
|
Args:
|
|
transition: Input transition containing complementary_data with task text.
|
|
|
|
Returns:
|
|
Modified transition with tokenized task added to observation.
|
|
|
|
Raises:
|
|
ValueError: If tokenizer initialization failed.
|
|
"""
|
|
task = self.get_task(transition)
|
|
if task is None:
|
|
return transition
|
|
|
|
# Tokenize the task
|
|
tokenized_prompt = self._tokenize_text(task)
|
|
|
|
# Get or create observation dict
|
|
if TransitionKey.OBSERVATION not in transition or transition[TransitionKey.OBSERVATION] is None:
|
|
transition[TransitionKey.OBSERVATION] = {}
|
|
observation = transition[TransitionKey.OBSERVATION]
|
|
|
|
# Add tokenized data to observation
|
|
observation[f"{OBS_LANGUAGE}.tokens"] = tokenized_prompt["input_ids"]
|
|
observation[f"{OBS_LANGUAGE}.attention_mask"] = tokenized_prompt["attention_mask"].to(
|
|
dtype=torch.bool
|
|
)
|
|
|
|
return transition
|
|
|
|
def _tokenize_text(self, text: str | list[str]) -> dict[str, torch.Tensor]:
|
|
"""Tokenize text using the configured tokenizer.
|
|
|
|
Args:
|
|
text: Text string or list of strings to tokenize.
|
|
|
|
Returns:
|
|
Dictionary containing tokenized output with keys like 'input_ids', 'attention_mask'.
|
|
"""
|
|
return self._tokenizer(
|
|
text,
|
|
max_length=self.max_length,
|
|
truncation=self.truncation,
|
|
padding=self.padding,
|
|
padding_side=self.padding_side,
|
|
return_tensors="pt",
|
|
)
|
|
|
|
def get_config(self) -> dict[str, Any]:
|
|
"""Return configuration for serialization.
|
|
|
|
Note: Only tokenizer_name is saved, not the tokenizer object itself.
|
|
When loading, provide the tokenizer via overrides if needed.
|
|
"""
|
|
config = {
|
|
"max_length": self.max_length,
|
|
"task_key": self.task_key,
|
|
"padding_side": self.padding_side,
|
|
"padding": self.padding,
|
|
"truncation": self.truncation,
|
|
}
|
|
|
|
# Only include tokenizer_name if it was used (not when tokenizer object was provided)
|
|
if self.tokenizer_name is not None:
|
|
config["tokenizer_name"] = self.tokenizer_name
|
|
|
|
return config
|
|
|
|
def state_dict(self) -> dict[str, torch.Tensor]:
|
|
"""Return state dictionary (empty for this processor)."""
|
|
return {}
|
|
|
|
def load_state_dict(self, state: dict[str, torch.Tensor]) -> None:
|
|
"""Load state dictionary (no-op for this processor)."""
|
|
pass
|
|
|
|
def reset(self) -> None:
|
|
"""Reset processor state (no-op for this processor)."""
|
|
pass
|
|
|
|
def feature_contract(self, features: dict[str, PolicyFeature]) -> dict[str, PolicyFeature]:
|
|
"""Add tokenized task features to the feature contract.
|
|
|
|
Args:
|
|
features: Input feature dictionary.
|
|
|
|
Returns:
|
|
Updated feature dictionary with tokenized task features added.
|
|
"""
|
|
# Add features for tokenized output if they don't exist
|
|
# Standard tokenizer output includes tokens and attention_mask
|
|
tokens_key = f"{OBS_LANGUAGE}.tokens"
|
|
attention_mask_key = f"{OBS_LANGUAGE}.attention_mask"
|
|
|
|
if tokens_key not in features:
|
|
features[tokens_key] = PolicyFeature(type=FeatureType.LANGUAGE, shape=(self.max_length,))
|
|
|
|
if attention_mask_key not in features:
|
|
features[attention_mask_key] = PolicyFeature(type=FeatureType.LANGUAGE, shape=(self.max_length,))
|
|
|
|
return features
|