Source code for mmirage.core.process.base
"""Base classes and registry for processors in MMIRAGE."""
import abc
from dataclasses import dataclass
from importlib import import_module
from typing import Callable, Generic, List, Type, TypeVar
from mmirage.core.process.variables import OutputVar, VariableEnvironment
[docs]
@dataclass
class BaseProcessorConfig:
"""Base configuration class for processors.
All processor configurations must inherit from this class.
Attributes:
type: Registry key of the processor. Concrete configs narrow this to
their own literal (e.g. ``Literal["llm"] = "llm"``).
"""
type: str
C = TypeVar("C", bound=OutputVar)
[docs]
@dataclass
class TokenCounts:
"""Cumulative token counts from LLM processors."""
input_tokens: int
output_tokens: int
[docs]
class BaseProcessor(abc.ABC, Generic[C]):
"""Abstract base class for data processors.
Processors are responsible for transforming data by generating
new output variables from existing variables.
Type Parameters:
C: The output variable type this processor works with.
Attributes:
config: Configuration object for this processor.
"""
[docs]
def __init__(
self, config: BaseProcessorConfig, shard_id: int = 0, **kwargs
) -> None:
"""Initialize the processor with configuration.
Args:
config: Configuration object for this processor.
shard_id: Optional shard identifier accepted for compatibility
with callers that forward it during processor construction.
**kwargs: Additional keyword arguments. Any unexpected keyword
arguments will raise ``TypeError``.
Raises:
TypeError: If unexpected keyword arguments are provided.
"""
if kwargs:
unexpected_args = ", ".join(sorted(kwargs))
raise TypeError(
f"Unexpected keyword argument(s) for "
f"{self.__class__.__name__}: {unexpected_args}"
)
super().__init__()
self.config = config
self.shard_id = shard_id
[docs]
@abc.abstractmethod
def batch_process_sample(
self, batch: List[VariableEnvironment], output_var: C
) -> List[VariableEnvironment]:
"""Process a batch of variable environments.
Args:
batch: List of variable environments to process.
output_var: Output variable definition to generate.
Returns:
List of updated variable environments with the new output variable.
Raises:
NotImplementedError: If not implemented by subclass.
"""
raise NotImplementedError()
[docs]
def finalize(self) -> None:
"""Optional lifecycle hook; override when a processor buffers state."""
pass
[docs]
@abc.abstractmethod
def get_token_counts(self) -> TokenCounts:
"""Get cumulative token counts from this processor.
Returns:
TokenCounts object containing input and output token counts.
Raises:
NotImplementedError: If not implemented by subclass.
"""
raise NotImplementedError()
[docs]
@abc.abstractmethod
def get_load_time(self) -> float:
"""Get the time taken to load any necessary resources (e.g., models).
Returns:
Time in seconds taken to load resources.
Raises:
NotImplementedError: If not implemented by subclass.
"""
raise NotImplementedError()
[docs]
def shutdown(self) -> None:
"""Release any resources held by this processor.
Override in subclasses that hold GPU memory, open file handles, or
network connections. The default implementation is a no-op.
"""
[docs]
class ProcessorRegistry:
"""Registry for managing and accessing available processors.
Provides a centralized registry for processor classes, their
configuration classes, and their output variable classes.
Attributes:
_registry: Mapping from processor name to registered processor class.
_config_registry: Mapping from processor name to its configuration class.
_output_var_registry: Mapping from processor name to its output variable class.
"""
_registry = dict()
_config_registry = dict()
_output_var_registry = dict()
# Import processor implementations lazily because they may depend on heavy
# libraries (torch/transformers). Config/output-var types are registered via
# mmirage.config.utils importing the relevant config modules.
_lazy_processor_imports = {
"llm": "mmirage.core.process.processors.llm.llm_processor",
"image_gen": "mmirage.core.process.processors.image_gen.image_gen_processor",
"custom": "mmirage.core.process.processors.custom.custom_processor",
"batch_api": "mmirage.core.process.processors.batch_api.batch_api_processor",
}
[docs]
@classmethod
def register_types(
cls,
name: str,
config_cls: Type[BaseProcessorConfig],
output_var_cls: Type[OutputVar],
) -> None:
"""Register config/output-var types without importing processor implementations."""
cls._config_registry[name] = config_cls
cls._output_var_registry[name] = output_var_cls
@classmethod
def _maybe_import_processor(cls, name: str) -> None:
module = cls._lazy_processor_imports.get(name)
if module:
import_module(module)
[docs]
@classmethod
def register(
cls,
name: str,
config_cls: Type[BaseProcessorConfig],
output_var_cls: Type[OutputVar],
) -> Callable:
"""Register a processor class with its associated classes.
Args:
name: String identifier for the processor.
config_cls: Configuration class associated with this processor.
output_var_cls: Output variable class associated with this processor.
Returns:
Decorator function to register the processor class.
"""
def inner_register(clazz):
cls._registry[name] = clazz
cls._config_registry[name] = config_cls
cls._output_var_registry[name] = output_var_cls
return clazz
return inner_register
[docs]
@classmethod
def get_processor(cls, name: str) -> Type[BaseProcessor]:
"""Get a registered processor class by name.
Args:
name: String identifier of the processor.
Returns:
The registered processor class.
Raises:
ValueError: If no processor is registered under the given name.
"""
if name not in cls._registry:
cls._maybe_import_processor(name)
if name not in cls._registry:
raise ValueError(
f"Processor {name} not registered. Available processors are {list(cls._registry.keys())}"
)
return cls._registry[name]
[docs]
@classmethod
def get_config_cls(cls, name: str) -> Type[BaseProcessorConfig]:
"""Get a registered configuration class by processor name.
Args:
name: String identifier of the processor.
Returns:
The registered configuration class.
Raises:
ValueError: If no processor is registered under the given name.
"""
if name not in cls._config_registry:
cls._maybe_import_processor(name)
if name not in cls._config_registry:
raise ValueError(
f"Processor {name} not registered. Available processors are {list(cls._config_registry.keys())}"
)
return cls._config_registry[name]
[docs]
@classmethod
def get_output_var_cls(cls, name: str) -> Type[OutputVar]:
"""Get a registered output variable class by processor name.
Args:
name: String identifier of the processor.
Returns:
The registered output variable class.
Raises:
ValueError: If no processor is registered under the given name.
"""
if name not in cls._output_var_registry:
cls._maybe_import_processor(name)
if name not in cls._output_var_registry:
raise ValueError(
f"Processor {name} not registered. Available processors are {list(cls._output_var_registry.keys())}"
)
return cls._output_var_registry[name]
[docs]
class AutoProcessor:
"""Factory class for instantiating processors by name."""
[docs]
@classmethod
def from_name(cls, name: str) -> Type[BaseProcessor]:
"""Retrieve a processor class by its registered name.
Args:
name: The registry name of the processor.
Returns:
The registered processor class.
Raises:
ValueError: If no processor is registered under the given name.
"""
return ProcessorRegistry.get_processor(name)