tensorizer.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. import argparse
  2. import dataclasses
  3. import io
  4. import os
  5. import time
  6. import typing
  7. from dataclasses import dataclass
  8. from functools import partial
  9. from typing import Generator, Optional, Tuple, Type, Union
  10. import torch
  11. from loguru import logger
  12. from torch import nn
  13. from transformers import PretrainedConfig
  14. from aphrodite.common.config import ModelConfig, ParallelConfig
  15. from aphrodite.engine.aphrodite_engine import AphroditeEngine
  16. from aphrodite.modeling.layers.vocab_parallel_embedding import \
  17. VocabParallelEmbedding
  18. from aphrodite.quantization.base_config import QuantizationConfig
  19. tensorizer_error_msg = None
  20. try:
  21. from tensorizer import (DecryptionParams, EncryptionParams,
  22. TensorDeserializer, TensorSerializer)
  23. from tensorizer.stream_io import open_stream
  24. from tensorizer.utils import (convert_bytes, get_mem_usage,
  25. no_init_or_tensor)
  26. _read_stream, _write_stream = (partial(
  27. open_stream,
  28. mode=mode,
  29. ) for mode in ("rb", "wb+"))
  30. except ImportError as e:
  31. tensorizer_error_msg = e
  32. __all__ = [
  33. 'EncryptionParams', 'DecryptionParams', 'TensorDeserializer',
  34. 'TensorSerializer', 'open_stream', 'convert_bytes', 'get_mem_usage',
  35. 'no_init_or_tensor', 'TensorizerConfig'
  36. ]
  37. @dataclass
  38. class TensorizerConfig:
  39. tensorizer_uri: Union[io.BufferedIOBase, io.RawIOBase, typing.BinaryIO,
  40. str, bytes, os.PathLike, int]
  41. aphrodite_tensorized: Optional[bool] = False
  42. verify_hash: Optional[bool] = False
  43. num_readers: Optional[int] = None
  44. encryption_keyfile: Optional[str] = None
  45. s3_access_key_id: Optional[str] = None
  46. s3_secret_access_key: Optional[str] = None
  47. s3_endpoint: Optional[str] = None
  48. model_class: Optional[Type[torch.nn.Module]] = None
  49. hf_config: Optional[PretrainedConfig] = None
  50. dtype: Optional[Union[str, torch.dtype]] = None
  51. def _construct_tensorizer_args(self) -> "TensorizerArgs":
  52. tensorizer_args = {
  53. "tensorizer_uri": self.tensorizer_uri,
  54. "aphrodite_tensorized": self.aphrodite_tensorized,
  55. "verify_hash": self.verify_hash,
  56. "num_readers": self.num_readers,
  57. "encryption_keyfile": self.encryption_keyfile,
  58. "s3_access_key_id": self.s3_access_key_id,
  59. "s3_secret_access_key": self.s3_secret_access_key,
  60. "s3_endpoint": self.s3_endpoint,
  61. }
  62. return TensorizerArgs(**tensorizer_args)
  63. def verify_with_parallel_config(
  64. self,
  65. parallel_config: "ParallelConfig",
  66. ) -> None:
  67. if (parallel_config.tensor_parallel_size > 1
  68. and self.tensorizer_uri is not None):
  69. raise ValueError(
  70. "Loading to multiple GPUs is not currently supported with "
  71. "aphrodite-serialized models. Please set "
  72. "tensor_parallel_size=1. or use a non-aphrodite-serialized "
  73. "model, such as a serialized Hugging Face `PretrainedModel`.")
  74. def verify_with_model_config(self, model_config: "ModelConfig") -> None:
  75. if (model_config.quantization is not None
  76. and self.tensorizer_uri is not None):
  77. logger.warning(
  78. "Loading a model using Tensorizer with quantization on "
  79. "aphrodite is unstable and may lead to errors.")
  80. def load_with_tensorizer(tensorizer_config: TensorizerConfig,
  81. **extra_kwargs) -> nn.Module:
  82. tensorizer = TensorizerAgent(tensorizer_config, **extra_kwargs)
  83. return tensorizer.deserialize()
  84. @dataclass
  85. class TensorizerArgs:
  86. tensorizer_uri: Union[io.BufferedIOBase, io.RawIOBase, typing.BinaryIO,
  87. str, bytes, os.PathLike, int]
  88. aphrodite_tensorized: Optional[bool] = False
  89. verify_hash: Optional[bool] = False
  90. num_readers: Optional[int] = None
  91. encryption_keyfile: Optional[str] = None
  92. s3_access_key_id: Optional[str] = None
  93. s3_secret_access_key: Optional[str] = None
  94. s3_endpoint: Optional[str] = None
  95. """
  96. Args for the TensorizerAgent class. These are used to configure the behavior
  97. of the TensorDeserializer when loading tensors from a serialized model.
  98. Args:
  99. tensorizer_uri: Path to serialized model tensors. Can be a local file
  100. path or a S3 URI.
  101. aphrodite_tensorized: If True, indicates that the serialized model is a
  102. aphrodite model. This is used to determine the behavior of the
  103. TensorDeserializer when loading tensors from a serialized model.
  104. It is far faster to deserialize a aphrodite model as it utilizes
  105. ttensorizer's optimized GPU loading. Note that this is now
  106. deprecated, as serialized Aphrodite models are now automatically
  107. inferred as Aphrodite models.
  108. verify_hash: If True, the hashes of each tensor will be verified against
  109. the hashes stored in the metadata. A `HashMismatchError` will be
  110. raised if any of the hashes do not match.
  111. num_readers: Controls how many threads are allowed to read concurrently
  112. from the source file. Default is `None`, which will dynamically set
  113. the number of readers based on the number of available
  114. resources and model size. This greatly increases performance.
  115. encryption_keyfile: File path to a binary file containing a
  116. binary key to use for decryption. `None` (the default) means
  117. no decryption. See the example script in
  118. examples/tensorize_aphrodite_model.py.
  119. s3_access_key_id: The access key for the S3 bucket. Can also be set via
  120. the S3_ACCESS_KEY_ID environment variable.
  121. s3_secret_access_key: The secret access key for the S3 bucket. Can also
  122. be set via the S3_SECRET_ACCESS_KEY environment variable.
  123. s3_endpoint: The endpoint for the S3 bucket. Can also be set via the
  124. S3_ENDPOINT_URL environment variable.
  125. """
  126. def __post_init__(self):
  127. self.file_obj = self.tensorizer_uri
  128. self.s3_access_key_id = (self.s3_access_key_id
  129. or os.environ.get("S3_ACCESS_KEY_ID")) or None
  130. self.s3_secret_access_key = (
  131. self.s3_secret_access_key
  132. or os.environ.get("S3_SECRET_ACCESS_KEY")) or None
  133. self.s3_endpoint = (self.s3_endpoint
  134. or os.environ.get("S3_ENDPOINT_URL")) or None
  135. self.stream_params = {
  136. "s3_access_key_id": self.s3_access_key_id,
  137. "s3_secret_access_key": self.s3_secret_access_key,
  138. "s3_endpoint": self.s3_endpoint,
  139. }
  140. self.deserializer_params = {
  141. "verify_hash": self.verify_hash,
  142. "encryption": self.encryption_keyfile,
  143. "num_readers": self.num_readers
  144. }
  145. if self.encryption_keyfile:
  146. with open_stream(
  147. self.encryption_keyfile,
  148. **self.stream_params,
  149. ) as stream:
  150. key = stream.read()
  151. decryption_params = DecryptionParams.from_key(key)
  152. self.deserializer_params['encryption'] = decryption_params
  153. @staticmethod
  154. def add_cli_args(
  155. parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
  156. """Tensorizer CLI arguments"""
  157. # Tensorizer options arg group
  158. group = parser.add_argument_group(
  159. 'tensorizer options',
  160. description=('Options for configuring the behavior of the'
  161. ' tensorizer deserializer when '
  162. 'load_format=tensorizer is specified when '
  163. 'initializing an AphroditeEngine, either via the CLI '
  164. 'when running the Aphrodite OpenAI inference server '
  165. 'with a JSON string passed to '
  166. '--model-loader-extra-config or as arguments given '
  167. 'to TensorizerConfig when passed to '
  168. 'model_loader_extra_config in the constructor '
  169. 'for AphroditeEngine.'))
  170. group.add_argument(
  171. "--tensorizer-uri",
  172. help="Path to serialized model tensors. Can be a local file path,"
  173. " or an HTTP(S) or S3 URI.",
  174. )
  175. group.add_argument(
  176. "--verify-hash",
  177. action="store_true",
  178. help="If enabled, the hashes of each tensor will be verified"
  179. " against the hashes stored in the file metadata. An exception"
  180. " will be raised if any of the hashes do not match.",
  181. )
  182. group.add_argument(
  183. "--encryption-keyfile",
  184. default=None,
  185. help="The file path to a binary file containing a binary key to "
  186. "use for decryption. Can be a file path or S3 network URI.")
  187. group.add_argument(
  188. "--num-readers",
  189. default=None,
  190. type=int,
  191. help="Controls how many threads are allowed to read concurrently "
  192. "from the source file. Default is `None`, which will dynamically "
  193. "set the number of readers based on the available resources "
  194. "and model size. This greatly increases performance.")
  195. group.add_argument(
  196. "--s3-access-key-id",
  197. default=None,
  198. help="The access key for the S3 bucket. Can also be set via the "
  199. "S3_ACCESS_KEY_ID environment variable.",
  200. )
  201. group.add_argument(
  202. "--s3-secret-access-key",
  203. default=None,
  204. help="The secret access key for the S3 bucket. Can also be set via "
  205. "the S3_SECRET_ACCESS_KEY environment variable.",
  206. )
  207. group.add_argument(
  208. "--s3-endpoint",
  209. default=None,
  210. help="The endpoint for the S3 bucket. Can also be set via the "
  211. "S3_ENDPOINT_URL environment variable.",
  212. )
  213. return parser
  214. @classmethod
  215. def from_cli_args(cls, args: argparse.Namespace) -> "TensorizerArgs":
  216. attrs = [attr.name for attr in dataclasses.fields(cls)]
  217. tensorizer_args = cls(**{
  218. attr: getattr(args, attr)
  219. for attr in attrs if hasattr(args, attr)
  220. })
  221. return tensorizer_args
  222. class TensorizerAgent:
  223. """
  224. A class for performing tensorizer deserializations specifically for
  225. aphrodite models using plaid_mode. Uses TensorizerArgs to configure the
  226. behavior of the TensorDeserializer when loading tensors from a serialized
  227. model. For deserializations of HuggingFace models, TensorDeserializer is
  228. instead used as an iterator directly in the func hf_model_weights_iterator
  229. in aphrodite/modeling/model_loader/weight_utils.py
  230. """
  231. def __init__(self, tensorizer_config: TensorizerConfig,
  232. quant_config: QuantizationConfig, **extra_kwargs):
  233. if tensorizer_error_msg is not None:
  234. raise ImportError(
  235. "Tensorizer is not installed. Please install tensorizer "
  236. "to use this feature with "
  237. "`pip install aphrodite-engine[tensorizer]`. "
  238. "Error message: {}".format(tensorizer_error_msg))
  239. self.tensorizer_config = tensorizer_config
  240. self.tensorizer_args = (
  241. self.tensorizer_config._construct_tensorizer_args())
  242. self.extra_kwargs = extra_kwargs
  243. if extra_kwargs.get("quant_config", None) is not None:
  244. self.quant_config = extra_kwargs["quant_config"]
  245. else:
  246. self.quant_config = quant_config
  247. self.model = self._init_model()
  248. def _init_model(self):
  249. model_args = self.tensorizer_config.hf_config
  250. model_args.torch_dtype = self.tensorizer_config.dtype
  251. with no_init_or_tensor():
  252. return self.tensorizer_config.model_class(
  253. config=model_args,
  254. quant_config=self.quant_config,
  255. **self.extra_kwargs)
  256. def _resize_lora_embeddings(self):
  257. """Modify LoRA embedding layers to use bigger tensors
  258. to allow for adapter added tokens."""
  259. for child in self.model.modules():
  260. if (isinstance(child, VocabParallelEmbedding)
  261. and child.weight.shape[0] <
  262. child.num_embeddings_per_partition):
  263. new_weight = torch.empty(child.num_embeddings_per_partition,
  264. child.embedding_dim,
  265. dtype=child.weight.dtype,
  266. device=child.weight.device)
  267. new_weight[:child.weight.shape[0]].copy_(child.weight.data)
  268. new_weight[child.weight.shape[0]:].fill_(0)
  269. child.weight.data = new_weight
  270. def _check_tensors_on_meta_device(self):
  271. for tensor in self.model.state_dict().values():
  272. if tensor.device.type == 'meta':
  273. raise ValueError(
  274. "The serialized model contains tensors on the meta device,"
  275. " indicating that some tensors were not loaded properly."
  276. " Please check that the parameters of the model being"
  277. " specified match that of the serialized model, such as"
  278. " its quantization.")
  279. def deserialize(self):
  280. """
  281. Deserialize the model using the TensorDeserializer. This method is
  282. specifically for Aphrodite models using tensorizer's plaid_mode.
  283. The deserializer makes use of tensorizer_args.stream_params
  284. to configure the behavior of the stream when loading tensors from a
  285. serialized model. The deserializer_params are used to configure the
  286. behavior of the TensorDeserializer when loading tensors themselves.
  287. Documentation on these params can be found in TensorizerArgs
  288. Returns:
  289. nn.Module: The deserialized model.
  290. """
  291. before_mem = get_mem_usage()
  292. start = time.perf_counter()
  293. with _read_stream(
  294. self.tensorizer_config.tensorizer_uri,
  295. **self.tensorizer_args.stream_params
  296. ) as stream, TensorDeserializer(
  297. stream,
  298. dtype=self.tensorizer_config.dtype,
  299. **self.tensorizer_args.deserializer_params) as deserializer:
  300. deserializer.load_into_module(self.model)
  301. end = time.perf_counter()
  302. total_bytes_str = convert_bytes(deserializer.total_tensor_bytes)
  303. duration = end - start
  304. per_second = convert_bytes(deserializer.total_tensor_bytes / duration)
  305. after_mem = get_mem_usage()
  306. deserializer.close()
  307. logger.info(f"Deserialized {total_bytes_str} in "
  308. f"{end - start:0.2f}s, {per_second}/s")
  309. logger.info(f"Memory usage before: {before_mem}")
  310. logger.info(f"Memory usage after: {after_mem}")
  311. self._check_tensors_on_meta_device()
  312. self._resize_lora_embeddings()
  313. del self.model.aphrodite_tensorized_marker
  314. return self.model.eval()
  315. def tensorizer_weights_iterator(
  316. tensorizer_args: "TensorizerArgs"
  317. ) -> Generator[Tuple[str, torch.Tensor], None, None]:
  318. logger.warning(
  319. "Deserializing HuggingFace models is not optimized for "
  320. "loading on Aphrodite, as tensorizer is forced to load to CPU. "
  321. "Consider deserializing a Aphrodite model instead for faster "
  322. "load times. See the examples/tensorize_aphrodite_model.py example "
  323. "script for serializing Aphrodite models.")
  324. deserializer_args = tensorizer_args.deserializer_params
  325. stream_params = tensorizer_args.stream_params
  326. stream = open_stream(tensorizer_args.tensorizer_uri, **stream_params)
  327. with TensorDeserializer(stream, **deserializer_args,
  328. device="cpu") as state:
  329. for name, param in state.items():
  330. yield name, param
  331. del state
  332. def is_aphrodite_tensorized(tensorizer_config: "TensorizerConfig") -> bool:
  333. """
  334. Infer if the model is a Aphrodite model by checking the weights for
  335. a Aphrodite tensorized marker.
  336. Args:
  337. tensorizer_config: The TensorizerConfig object containing the
  338. tensorizer_uri to the serialized model.
  339. Returns:
  340. bool: True if the model is a Aphrodite model, False otherwise.
  341. """
  342. tensorizer_args = tensorizer_config._construct_tensorizer_args()
  343. deserializer = TensorDeserializer(open_stream(
  344. tensorizer_args.tensorizer_uri, **tensorizer_args.stream_params),
  345. **tensorizer_args.deserializer_params,
  346. lazy_load=True)
  347. if tensorizer_config.aphrodite_tensorized:
  348. logger.warning(
  349. "Please note that newly serialized Aphrodite models are "
  350. "automatically inferred as Aphrodite models, so setting "
  351. "aphrodite_tensorized=True is only necessary for models serialized "
  352. "prior to this change.")
  353. return True
  354. if (".aphrodite_tensorized_marker" in deserializer):
  355. return True
  356. return False
  357. def get_pretensorized_aphrodite_model(engine: "AphroditeEngine") -> nn.Module:
  358. model = (engine.model_executor.driver_worker.model_runner.model)
  359. model.register_parameter(
  360. "aphrodite_tensorized_marker",
  361. nn.Parameter(torch.tensor((1, ), device="meta"), requires_grad=False))
  362. return model
  363. def serialize_aphrodite_model(engine: "AphroditeEngine",
  364. tensorizer_config : TensorizerConfig,
  365. encryption_key_path: Optional[str] = None) \
  366. -> nn.Module:
  367. model = get_pretensorized_aphrodite_model(engine)
  368. tensorizer_args = tensorizer_config._construct_tensorizer_args()
  369. encryption_params = None
  370. if encryption_key_path is not None:
  371. encryption_params = EncryptionParams.random()
  372. with _write_stream(encryption_key_path,
  373. **tensorizer_args.stream_params) as stream:
  374. stream.write(encryption_params.key)
  375. with _write_stream(tensorizer_args.tensorizer_uri,
  376. **tensorizer_args.stream_params) as stream:
  377. serializer = TensorSerializer(stream, encryption=encryption_params)
  378. serializer.write_module(model)
  379. serializer.close()
  380. logger.info("Successfully serialized model to "
  381. f"{str(tensorizer_args.tensorizer_uri)}")
  382. return model