registry.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import functools
  2. from collections import UserDict
  3. from dataclasses import dataclass
  4. from typing import (TYPE_CHECKING, Any, Callable, Dict, Mapping, Optional,
  5. Protocol, Tuple, Type)
  6. from loguru import logger
  7. from torch import nn
  8. from transformers import PretrainedConfig
  9. from typing_extensions import TypeVar
  10. from .data import LLMInputs
  11. if TYPE_CHECKING:
  12. from aphrodite.common.config import ModelConfig
  13. from aphrodite.common.sequence import SequenceData
  14. from aphrodite.multimodal import MultiModalDataDict, MultiModalRegistry
  15. C = TypeVar("C", bound=PretrainedConfig)
  16. @dataclass(frozen=True)
  17. class InputContext:
  18. """
  19. Contains information about the model which may be used to
  20. modify the inputs.
  21. """
  22. model_config: "ModelConfig"
  23. """The configuration of the model."""
  24. def get_hf_config(self, hf_config_type: Type[C] = PretrainedConfig) -> C:
  25. """
  26. Get the HuggingFace configuration
  27. (:class:`transformers.PretrainedConfig`) of the model,
  28. additionally checking its type.
  29. Raises:
  30. ValueError: If the model is not of the specified type.
  31. """
  32. hf_config = self.model_config.hf_config
  33. if not isinstance(hf_config, hf_config_type):
  34. raise TypeError("Invalid type of HuggingFace config. "
  35. f"Expected type: {hf_config_type}, but "
  36. f"found type: {type(hf_config)}")
  37. return hf_config
  38. def get_hf_image_processor_config(self) -> Dict[str, Any]:
  39. """
  40. Get the HuggingFace image processor configuration of the model.
  41. """
  42. return self.model_config.hf_image_processor_config
  43. N = TypeVar("N", bound=Type[nn.Module])
  44. class DummyDataFactory(Protocol):
  45. def __call__(
  46. self,
  47. ctx: InputContext,
  48. seq_len: int,
  49. mm_counts: Mapping[str, int],
  50. ) -> Tuple["SequenceData", Optional["MultiModalDataDict"]]:
  51. """
  52. Create dummy data to be inputted into the model.
  53. Note:
  54. :data:`InputProcessor` is not applied to the dummy data.
  55. """
  56. ...
  57. class _MultiModalCounts(UserDict):
  58. """
  59. Wraps `mm_counts` for a more informative error message
  60. when attempting to access a plugin that does not exist.
  61. """
  62. def __getitem__(self, key: str) -> int:
  63. try:
  64. return super().__getitem__(key)
  65. except KeyError as exc:
  66. msg = (f"There is no multi-modal plugin with the key: {key}. "
  67. f"Available keys: {set(self.keys())}")
  68. raise KeyError(msg) from exc
  69. InputProcessor = Callable[[InputContext, LLMInputs], LLMInputs]
  70. """Preprocess the inputs to the model."""
  71. class InputRegistry:
  72. """
  73. A registry to dispatch data processing
  74. according to the target model.
  75. """
  76. def __init__(self) -> None:
  77. self._dummy_factories_by_model_type: Dict[Type[nn.Module],
  78. DummyDataFactory] = {}
  79. self._input_processors_by_model_type: Dict[Type[nn.Module],
  80. InputProcessor] = {}
  81. def _default_dummy_data_factory(
  82. self,
  83. ctx: InputContext,
  84. seq_len: int,
  85. mm_counts: Mapping[str, int],
  86. ) -> Tuple["SequenceData", Optional["MultiModalDataDict"]]:
  87. """
  88. The default dummy data factory represents the longest possible text
  89. that can be inputted to the model.
  90. Note:
  91. :data:`InputProcessor` is not applied to the dummy data.
  92. """
  93. # Avoid circular import
  94. from aphrodite.common.sequence import SequenceData
  95. dummy_seq_data = SequenceData.from_counts({0: seq_len})
  96. dummy_multi_modal_data = None
  97. return dummy_seq_data, dummy_multi_modal_data
  98. def register_dummy_data(self, factory: DummyDataFactory):
  99. """
  100. Register a dummy data factory to a model class.
  101. During memory profiling, the provided function is invoked to create
  102. dummy data to be inputted into the model. The resulting memory usage
  103. should be an upper bound of what the model would use at inference time.
  104. """
  105. def wrapper(model_cls: N) -> N:
  106. if model_cls in self._dummy_factories_by_model_type:
  107. logger.warning(
  108. f"Model class {model_cls} already has dummy data "
  109. f"registered to {self}. It is overwritten by the new one.")
  110. self._dummy_factories_by_model_type[model_cls] = factory
  111. return model_cls
  112. return wrapper
  113. def dummy_data_for_profiling(
  114. self,
  115. model_config: "ModelConfig",
  116. seq_len: int,
  117. mm_registry: "MultiModalRegistry",
  118. ) -> Tuple["SequenceData", Optional["MultiModalDataDict"]]:
  119. """
  120. Create dummy data for profiling the memory usage of a model.
  121. The model is identified by ``model_config``.
  122. See also:
  123. :ref:`enabling_multimodal_inputs`
  124. Note:
  125. This should be called after
  126. :meth:`~MultiModalRegistry.init_mm_limits_per_prompt`.
  127. """
  128. # Avoid circular import
  129. from aphrodite.modeling.model_loader import get_model_architecture
  130. model_cls, _ = get_model_architecture(model_config)
  131. dummy_factory = self._dummy_factories_by_model_type \
  132. .get(model_cls, self._default_dummy_data_factory)
  133. mm_counts = mm_registry.get_mm_limits_per_prompt(model_config)
  134. seq_data, mm_data = dummy_factory(
  135. InputContext(model_config),
  136. seq_len,
  137. _MultiModalCounts(mm_counts),
  138. )
  139. # Having more tokens is over-conservative but otherwise fine
  140. num_tokens = seq_data.prompt_token_ids
  141. assert len(num_tokens) >= seq_len, (
  142. f"Expected at least {seq_len} dummy tokens for profiling, "
  143. f"but found {len(num_tokens)} tokens instead.")
  144. if mm_data is not None:
  145. for k, v in mm_data.items():
  146. num_items = len(v) if isinstance(v, list) else 1
  147. num_expected = mm_counts[k]
  148. assert num_items >= num_expected, (
  149. f"Expected at least {num_expected} dummy '{k}' instances "
  150. f"for profiling, but found {num_items} instances instead.")
  151. return seq_data, mm_data
  152. def _default_input_processor(self, ctx: InputContext,
  153. inputs: LLMInputs) -> LLMInputs:
  154. """The default input processor is a no-op."""
  155. return inputs
  156. def register_input_processor(self, processor: InputProcessor):
  157. """
  158. Register an input processor to a model class.
  159. The provided function is invoked on each input to the model. This
  160. happens before
  161. :meth:`~aphrodite.multimodal.MultiModalRegistry.map_input`.
  162. See also:
  163. :ref:`input_processing_pipeline`
  164. """
  165. def wrapper(model_cls: N) -> N:
  166. if model_cls in self._input_processors_by_model_type:
  167. logger.warning(
  168. f"Model class {model_cls} already has input processor "
  169. f"registered to {self}. It is overwritten by the new one.")
  170. self._input_processors_by_model_type[model_cls] = processor
  171. return model_cls
  172. return wrapper
  173. def process_input(self, model_config: "ModelConfig",
  174. inputs: LLMInputs) -> LLMInputs:
  175. """
  176. Apply an input processor to an instance of model inputs.
  177. The model is identified by ``model_config``.
  178. See also:
  179. :ref:`input_processing_pipeline`
  180. """
  181. # Avoid circular import
  182. from aphrodite.modeling.model_loader import get_model_architecture
  183. model_cls, _ = get_model_architecture(model_config)
  184. processor = self._input_processors_by_model_type \
  185. .get(model_cls, self._default_input_processor)
  186. return processor(InputContext(model_config), inputs)
  187. def create_input_processor(self, model_config: "ModelConfig"):
  188. """
  189. Create an input processor (see :meth:`process_input`) for a
  190. specific model.
  191. """
  192. return functools.partial(self.process_input, model_config)