123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- import asyncio
- import os
- from functools import partial
- from typing import Any, List, Optional
- from aphrodite.common.sequence import ExecuteModelRequest, SamplerOutput
- from aphrodite.common.utils import (cuda_device_count_stateless,
- error_on_invalid_device_count_status,
- get_aphrodite_instance_id,
- get_distributed_init_method, get_open_port,
- make_async, update_environment_variables)
- from aphrodite.executor.distributed_gpu_executor import ( # yapf: disable
- DistributedGPUExecutor, DistributedGPUExecutorAsync)
- from aphrodite.executor.multiproc_worker_utils import (ProcessWorkerWrapper,
- ResultHandler,
- WorkerMonitor)
- class MultiprocessingGPUExecutor(DistributedGPUExecutor):
- """Python multiprocessing-based multi-GPU executor"""
- def _init_executor(self) -> None:
- # Create the parallel GPU workers.
- world_size = self.parallel_config.tensor_parallel_size
- # Set CUDA_VISIBLE_DEVICES for the driver, inherited by workers
- if "CUDA_VISIBLE_DEVICES" not in os.environ:
- update_environment_variables({
- "CUDA_VISIBLE_DEVICES": (",".join(map(str, range(world_size))))
- })
- # Ensure that APHRODITE_INSTANCE_ID is set, to be inherited by workers
- os.environ["APHRODITE_INSTANCE_ID"] = get_aphrodite_instance_id()
- # Disable torch async compiling which won't work with daemonic processes
- os.environ["TORCHINDUCTOR_COMPILE_THREADS"] = "1"
- # Set OMP_NUM_THREADS to 1 if it is not set explicitly, avoids CPU
- # contention amongst the shards
- if "OMP_NUM_THREADS" not in os.environ:
- os.environ["OMP_NUM_THREADS"] = "1"
- assert world_size <= cuda_device_count_stateless(), (
- "please set tensor_parallel_size to less than max local gpu count")
- error_on_invalid_device_count_status()
- # Multiprocessing-based executor does not support multi-node setting.
- # Since it only works for single node, we can use the loopback address
- # 127.0.0.1 for communication.
- distributed_init_method = get_distributed_init_method(
- "127.0.0.1", get_open_port())
- if world_size == 1:
- self.workers = []
- self.worker_monitor = None
- else:
- result_handler = ResultHandler()
- self.workers = [
- ProcessWorkerWrapper(
- result_handler,
- partial(
- self._create_worker,
- rank=rank,
- local_rank=rank,
- distributed_init_method=distributed_init_method,
- )) for rank in range(1, world_size)
- ]
- self.worker_monitor = WorkerMonitor(self.workers, result_handler)
- result_handler.start()
- self.worker_monitor.start()
- self.driver_worker = self._create_worker(
- distributed_init_method=distributed_init_method)
- self._run_workers("init_device")
- self._run_workers("load_model",
- max_concurrent_workers=self.parallel_config.
- max_parallel_loading_workers)
- def shutdown(self):
- if (worker_monitor := getattr(self, "worker_monitor",
- None)) is not None:
- worker_monitor.close()
- def _driver_execute_model(
- self, execute_model_req: Optional[ExecuteModelRequest]
- ) -> Optional[List[SamplerOutput]]:
- """Run execute_model in the driver worker.
- Passing None will cause the driver to stop the model execution
- loop running in each of the remote workers.
- """
- return self.driver_worker.execute_model(execute_model_req)
- def _run_workers(
- self,
- method: str,
- *args,
- async_run_tensor_parallel_workers_only: bool = False,
- max_concurrent_workers: Optional[int] = None,
- **kwargs,
- ) -> Any:
- """Runs the given method on all workers.
- Args:
- async_run_tensor_parallel_workers_only: If True the method will be
- run only in the remote TP workers, not the driver worker.
- It will also be run asynchronously and return a list of futures
- rather than blocking on the results.
- """
- if max_concurrent_workers:
- raise NotImplementedError(
- "max_concurrent_workers is not supported yet.")
- # Start the workers first.
- worker_outputs = [
- worker.execute_method(method, *args, **kwargs)
- for worker in self.workers
- ]
- if async_run_tensor_parallel_workers_only:
- # Just return futures
- return worker_outputs
- driver_worker_method = getattr(self.driver_worker, method)
- driver_worker_output = driver_worker_method(*args, **kwargs)
- # Get the results of the workers.
- return [driver_worker_output
- ] + [output.get() for output in worker_outputs]
- def check_health(self) -> None:
- """Raises an error if engine is unhealthy."""
- if self.worker_monitor is not None and not self.worker_monitor.is_alive(
- ):
- raise RuntimeError("Worker processes are not running")
- def _wait_for_tasks_completion(self, parallel_worker_tasks: Any) -> None:
- """Wait for futures returned from _run_workers() with
- async_run_remote_workers_only to complete."""
- for result in parallel_worker_tasks:
- result.get()
- class MultiprocessingGPUExecutorAsync(MultiprocessingGPUExecutor,
- DistributedGPUExecutorAsync):
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.driver_exec_model = make_async(self.driver_worker.execute_model)
- async def _driver_execute_model_async(
- self,
- execute_model_req: Optional[ExecuteModelRequest] = None
- ) -> List[SamplerOutput]:
- return await self.driver_exec_model(execute_model_req)
- async def _start_worker_execution_loop(self):
- coros = [
- worker.execute_method_async("start_worker_execution_loop")
- for worker in self.workers
- ]
- return await asyncio.gather(*coros)
|