multiproc_gpu_executor.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. import asyncio
  2. import os
  3. from functools import partial
  4. from typing import Any, List, Optional
  5. from aphrodite.common.sequence import ExecuteModelRequest, SamplerOutput
  6. from aphrodite.common.utils import (cuda_device_count_stateless,
  7. error_on_invalid_device_count_status,
  8. get_aphrodite_instance_id,
  9. get_distributed_init_method, get_open_port,
  10. make_async, update_environment_variables)
  11. from aphrodite.executor.distributed_gpu_executor import ( # yapf: disable
  12. DistributedGPUExecutor, DistributedGPUExecutorAsync)
  13. from aphrodite.executor.multiproc_worker_utils import (ProcessWorkerWrapper,
  14. ResultHandler,
  15. WorkerMonitor)
  16. class MultiprocessingGPUExecutor(DistributedGPUExecutor):
  17. """Python multiprocessing-based multi-GPU executor"""
  18. def _init_executor(self) -> None:
  19. # Create the parallel GPU workers.
  20. world_size = self.parallel_config.tensor_parallel_size
  21. # Set CUDA_VISIBLE_DEVICES for the driver, inherited by workers
  22. if "CUDA_VISIBLE_DEVICES" not in os.environ:
  23. update_environment_variables({
  24. "CUDA_VISIBLE_DEVICES": (",".join(map(str, range(world_size))))
  25. })
  26. # Ensure that APHRODITE_INSTANCE_ID is set, to be inherited by workers
  27. os.environ["APHRODITE_INSTANCE_ID"] = get_aphrodite_instance_id()
  28. # Disable torch async compiling which won't work with daemonic processes
  29. os.environ["TORCHINDUCTOR_COMPILE_THREADS"] = "1"
  30. # Set OMP_NUM_THREADS to 1 if it is not set explicitly, avoids CPU
  31. # contention amongst the shards
  32. if "OMP_NUM_THREADS" not in os.environ:
  33. os.environ["OMP_NUM_THREADS"] = "1"
  34. assert world_size <= cuda_device_count_stateless(), (
  35. "please set tensor_parallel_size to less than max local gpu count")
  36. error_on_invalid_device_count_status()
  37. # Multiprocessing-based executor does not support multi-node setting.
  38. # Since it only works for single node, we can use the loopback address
  39. # 127.0.0.1 for communication.
  40. distributed_init_method = get_distributed_init_method(
  41. "127.0.0.1", get_open_port())
  42. if world_size == 1:
  43. self.workers = []
  44. self.worker_monitor = None
  45. else:
  46. result_handler = ResultHandler()
  47. self.workers = [
  48. ProcessWorkerWrapper(
  49. result_handler,
  50. partial(
  51. self._create_worker,
  52. rank=rank,
  53. local_rank=rank,
  54. distributed_init_method=distributed_init_method,
  55. )) for rank in range(1, world_size)
  56. ]
  57. self.worker_monitor = WorkerMonitor(self.workers, result_handler)
  58. result_handler.start()
  59. self.worker_monitor.start()
  60. self.driver_worker = self._create_worker(
  61. distributed_init_method=distributed_init_method)
  62. self._run_workers("init_device")
  63. self._run_workers("load_model",
  64. max_concurrent_workers=self.parallel_config.
  65. max_parallel_loading_workers)
  66. def shutdown(self):
  67. if (worker_monitor := getattr(self, "worker_monitor",
  68. None)) is not None:
  69. worker_monitor.close()
  70. def _driver_execute_model(
  71. self, execute_model_req: Optional[ExecuteModelRequest]
  72. ) -> Optional[List[SamplerOutput]]:
  73. """Run execute_model in the driver worker.
  74. Passing None will cause the driver to stop the model execution
  75. loop running in each of the remote workers.
  76. """
  77. return self.driver_worker.execute_model(execute_model_req)
  78. def _run_workers(
  79. self,
  80. method: str,
  81. *args,
  82. async_run_tensor_parallel_workers_only: bool = False,
  83. max_concurrent_workers: Optional[int] = None,
  84. **kwargs,
  85. ) -> Any:
  86. """Runs the given method on all workers.
  87. Args:
  88. async_run_tensor_parallel_workers_only: If True the method will be
  89. run only in the remote TP workers, not the driver worker.
  90. It will also be run asynchronously and return a list of futures
  91. rather than blocking on the results.
  92. """
  93. if max_concurrent_workers:
  94. raise NotImplementedError(
  95. "max_concurrent_workers is not supported yet.")
  96. # Start the workers first.
  97. worker_outputs = [
  98. worker.execute_method(method, *args, **kwargs)
  99. for worker in self.workers
  100. ]
  101. if async_run_tensor_parallel_workers_only:
  102. # Just return futures
  103. return worker_outputs
  104. driver_worker_method = getattr(self.driver_worker, method)
  105. driver_worker_output = driver_worker_method(*args, **kwargs)
  106. # Get the results of the workers.
  107. return [driver_worker_output
  108. ] + [output.get() for output in worker_outputs]
  109. def check_health(self) -> None:
  110. """Raises an error if engine is unhealthy."""
  111. if self.worker_monitor is not None and not self.worker_monitor.is_alive(
  112. ):
  113. raise RuntimeError("Worker processes are not running")
  114. def _wait_for_tasks_completion(self, parallel_worker_tasks: Any) -> None:
  115. """Wait for futures returned from _run_workers() with
  116. async_run_remote_workers_only to complete."""
  117. for result in parallel_worker_tasks:
  118. result.get()
  119. class MultiprocessingGPUExecutorAsync(MultiprocessingGPUExecutor,
  120. DistributedGPUExecutorAsync):
  121. def __init__(self, *args, **kwargs):
  122. super().__init__(*args, **kwargs)
  123. self.driver_exec_model = make_async(self.driver_worker.execute_model)
  124. async def _driver_execute_model_async(
  125. self,
  126. execute_model_req: Optional[ExecuteModelRequest] = None
  127. ) -> List[SamplerOutput]:
  128. return await self.driver_exec_model(execute_model_req)
  129. async def _start_worker_execution_loop(self):
  130. coros = [
  131. worker.execute_method_async("start_worker_execution_loop")
  132. for worker in self.workers
  133. ]
  134. return await asyncio.gather(*coros)