base_loader.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. # SPDX-License-Identifier: Apache-2.0
  2. # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
  3. import os
  4. from abc import ABC, abstractmethod
  5. import torch
  6. import torch.nn as nn
  7. import vllm.envs as envs
  8. from vllm.config import ModelConfig, VllmConfig
  9. from vllm.config.load import LoadConfig
  10. from vllm.logger import init_logger
  11. from vllm.model_executor.model_loader.reload import finalize_layerwise_processing
  12. from vllm.model_executor.model_loader.utils import (
  13. initialize_model,
  14. process_weights_after_loading,
  15. )
  16. from vllm.platforms import current_platform
  17. from vllm.tracing import instrument
  18. from vllm.utils.mem_utils import format_gib
  19. from vllm.utils.torch_utils import set_default_torch_dtype
  20. logger = init_logger(__name__)
  21. def _optional(module: str, *names: str):
  22. """`module`, or a stand-in whose `names` do nothing when it is not installed."""
  23. import importlib
  24. from types import SimpleNamespace
  25. try:
  26. return importlib.import_module(module)
  27. except ModuleNotFoundError as e:
  28. if e.name != module:
  29. raise
  30. return SimpleNamespace(**{n: (lambda *args, **kwargs: None) for n in names})
  31. class BaseModelLoader(ABC):
  32. """Base class for model loaders."""
  33. def __init__(self, load_config: LoadConfig):
  34. self.load_config = load_config
  35. @abstractmethod
  36. def download_model(self, model_config: ModelConfig) -> None:
  37. """Download a model so that it can be immediately loaded."""
  38. raise NotImplementedError
  39. @abstractmethod
  40. def load_weights(self, model: nn.Module, model_config: ModelConfig) -> None:
  41. """Load weights into a model. This standalone API allows
  42. inplace weights loading for an already-initialized model"""
  43. raise NotImplementedError
  44. def create_model(
  45. self, vllm_config: VllmConfig, model_config: ModelConfig, prefix: str = ""
  46. ) -> nn.Module:
  47. """Create a model with the given configurations."""
  48. model = initialize_model(
  49. vllm_config=vllm_config,
  50. model_config=model_config,
  51. prefix=prefix,
  52. )
  53. log_online_quantization(vllm_config)
  54. log_model_inspection(model)
  55. return model
  56. @instrument(span_name="Load model")
  57. def load_model(
  58. self, vllm_config: VllmConfig, model_config: ModelConfig, prefix: str = ""
  59. ) -> nn.Module:
  60. """Load a model with the given configurations."""
  61. # Local weight snapshot (weight_snapshot.py): rebuild from it when
  62. # this rank has one, otherwise load normally and write one.
  63. # fp8.yaml and megamoe.yaml mount these; without them their steps do nothing.
  64. dense_fp8 = _optional("vllm.model_executor.model_loader.dense_fp8", "convert", "simulate_nvfp4")
  65. megamoe_vllm = _optional("vllm.model_executor.layers.fused_moe.megamoe_vllm", "install")
  66. from vllm.model_executor.model_loader.weight_snapshot import Snapshot
  67. snapshot = Snapshot.for_model(vllm_config, model_config)
  68. normal = os.environ.get("VLLM_WEIGHT_SNAPSHOT_NORMAL") == "1" # diagnostic: load, dump, no save
  69. if snapshot is not None and snapshot.complete() and not normal:
  70. # The normal path on dummy weights, then every tensor overwritten
  71. # in place: the object graph is exactly a real load's.
  72. from vllm.model_executor.model_loader.dummy_loader import DummyModelLoader
  73. model = self._load_from_checkpoint(
  74. vllm_config, model_config, prefix,
  75. weights_from=DummyModelLoader(self.load_config).load_weights,
  76. )
  77. dense_fp8.convert(model)
  78. snapshot.restore(model)
  79. # What a model's own load_weights does after loading, which the
  80. # dummy loader skips: the DFlash drafter fuses its KV weights.
  81. for m in model.modules():
  82. if hasattr(m, "_build_fused_kv_buffers"):
  83. m._build_fused_kv_buffers()
  84. if os.environ.get("VLLM_WEIGHT_SNAPSHOT_VERIFY") == "1":
  85. from vllm.model_executor.model_loader.weight_snapshot import verify
  86. # The reference registers the same layer names; keep the
  87. # snapshot model's registrations and discard its.
  88. cc = vllm_config.compilation_config
  89. kept = {k: (dict(getattr(cc, k)) if isinstance(getattr(cc, k), dict) else list(getattr(cc, k)))
  90. for k in ("static_forward_context", "static_all_moe_layers") if hasattr(cc, k)}
  91. for k in kept:
  92. getattr(cc, k).clear()
  93. reference = self._load_from_checkpoint(vllm_config, model_config, prefix)
  94. for k, v in kept.items():
  95. getattr(cc, k).clear()
  96. getattr(cc, k).update(v) if isinstance(v, dict) else getattr(cc, k).extend(v)
  97. verify(model, reference)
  98. # Diagnostic only: never serve the model under test.
  99. logger.warning("Weight snapshot verify done; this worker now idles. Take the stack down.")
  100. import time
  101. while True:
  102. time.sleep(3600)
  103. self._dump_state(model, vllm_config, snapshot, "snapshot")
  104. dense_fp8.simulate_nvfp4(model)
  105. megamoe_vllm.install(model)
  106. return model
  107. model = self._load_from_checkpoint(vllm_config, model_config, prefix)
  108. dense_fp8.convert(model)
  109. for m in model.modules(): # the drafter fuses its KV weights from whatever format they are now in
  110. if hasattr(m, "_build_fused_kv_buffers"):
  111. m._build_fused_kv_buffers()
  112. if snapshot is not None:
  113. self._dump_state(model, vllm_config, snapshot, "normal")
  114. if not normal:
  115. snapshot.save(model)
  116. dense_fp8.simulate_nvfp4(model)
  117. megamoe_vllm.install(model)
  118. return model
  119. @staticmethod
  120. def _dump_state(model, vllm_config, snapshot, mode):
  121. out = os.environ.get("VLLM_WEIGHT_SNAPSHOT_DUMP")
  122. if out and snapshot is not None:
  123. from vllm.model_executor.model_loader.weight_snapshot import dump_state
  124. name = os.path.basename(snapshot.path).rsplit("-", 1)[0]
  125. dump_state(model, vllm_config, os.path.join(out, f"{mode}-{name}.txt"))
  126. def _load_from_checkpoint(
  127. self, vllm_config: VllmConfig, model_config: ModelConfig, prefix: str = "", weights_from=None
  128. ) -> nn.Module:
  129. device_config = vllm_config.device_config
  130. load_config = vllm_config.load_config
  131. load_device = (
  132. device_config.device if load_config.device is None else load_config.device
  133. )
  134. target_device = torch.device(load_device)
  135. with set_default_torch_dtype(model_config.dtype):
  136. with target_device:
  137. model = self.create_model(
  138. vllm_config=vllm_config,
  139. model_config=model_config,
  140. prefix=prefix,
  141. )
  142. logger.debug("Loading weights on %s ...", load_device)
  143. (weights_from or self.load_weights)(model, model_config)
  144. # Log peak GPU memory after loading weights. This is needed
  145. # to have test coverage on peak memory for online quantization.
  146. if current_platform.is_cuda_alike() or current_platform.is_xpu():
  147. peak_memory = torch.accelerator.max_memory_allocated()
  148. logger.debug_once(
  149. "Peak GPU memory after loading weights: %s GiB",
  150. format_gib(peak_memory),
  151. )
  152. # Process weights into kernel format. Note that when using online
  153. # quantization, weights are (typically) quantized as they are loaded.
  154. if _has_online_quant(model):
  155. finalize_layerwise_processing(model, model_config)
  156. process_weights_after_loading(model, model_config, target_device)
  157. return model.eval()
  158. def log_model_inspection(model: nn.Module) -> None:
  159. """Log model structure if VLLM_LOG_MODEL_INSPECTION=1."""
  160. if not envs.VLLM_LOG_MODEL_INSPECTION:
  161. return
  162. from vllm.model_inspection import format_model_inspection
  163. logger.info("vLLM model structure:\n%s", format_model_inspection(model))
  164. def log_online_quantization(vllm_config: VllmConfig) -> None:
  165. """Log the online-quantized layer count and types, when applicable."""
  166. from vllm.model_executor.layers.quantization.online.base import (
  167. OnlineQuantizationConfig,
  168. )
  169. quant_config = vllm_config.quant_config
  170. online_quantization_config = getattr(
  171. quant_config, "online_quantization_config", None
  172. )
  173. if isinstance(online_quantization_config, OnlineQuantizationConfig):
  174. quant_config = online_quantization_config
  175. if not isinstance(quant_config, OnlineQuantizationConfig):
  176. return
  177. logger.info(
  178. "Quantized %d layers of types: %s",
  179. len(quant_config.quantized_layers),
  180. "; ".join(quant_config.quantized_layer_summaries),
  181. )
  182. def _has_online_quant(model: nn.Module):
  183. for module in model.modules():
  184. quant_method = getattr(module, "quant_method", None)
  185. if getattr(quant_method, "uses_meta_device", False):
  186. return True
  187. return False