Files
2026-07-13 13:17:40 +08:00

96 lines
2.9 KiB
Python

import logging
import threading
import cupy
from ray.util.collective.collective_group import nccl_util
from ray.util.collective.const import ENV
NCCL_STREAM_POOL_SIZE = 32
MAX_GPU_PER_ACTOR = 16
logger = logging.getLogger(__name__)
class StreamPool:
"""The class that represents a stream pool associated with a GPU.
When multistream is enabled, we will allocate a pool of streams for each
GPU, and get available stream from this pool when a collective kernel is
initialized. This enables overlapping computation/communication kernels
using multiple CUDA streams, given that the streams a appropriately
synchronized. The class is thread-safe.
Args:
device_idx: the absolute index of the device for this pool.
"""
def __init__(self, device_idx: int):
self.device_idx = device_idx
self._initialized = False
self._initialized_lock = threading.Lock()
self._pool = [None] * NCCL_STREAM_POOL_SIZE
self._counter = 0
self._pool_lock = threading.Lock()
def get_stream(self):
"""Get an available stream from the pool.
The function locks the stream pool and releases the lock before
returning.
Returns:
stream (cupy.cuda.Stream): the returned stream from pool.
"""
# check the flag
self._initialized_lock.acquire()
if not self._initialized:
self._init_once()
self._initialized_lock.release()
# Get the stream from the pool.
self._pool_lock.acquire()
stream = self._pool[self._counter]
self._counter = (self._counter + 1) % NCCL_STREAM_POOL_SIZE
self._pool_lock.release()
return stream
def _init_once(self):
"""Initialize the stream pool only for once."""
with nccl_util.Device(self.device_idx):
for i in range(NCCL_STREAM_POOL_SIZE):
# this is the only place where self._pool will be written.
if ENV.NCCL_USE_MULTISTREAM.val:
logger.debug("NCCL multistream enabled.")
self._pool[i] = cupy.cuda.Stream(null=False, non_blocking=False)
else:
logger.debug("NCCL multistream disabled.")
self._pool[i] = cupy.cuda.Stream.null
self._init_flag = True
# This is a map from GPU index to its stream pool.
# It is supposed to be READ-ONLY out of this file
_device_stream_pool_map = dict()
def _init_stream_pool():
global _device_stream_pool_map
for i in range(MAX_GPU_PER_ACTOR):
_device_stream_pool_map[i] = StreamPool(i)
def get_stream_pool(device_idx):
"""Get the CUDA stream pool of a GPU device."""
# In case there will be multiple threads writing to the pool.
lock = threading.Lock()
lock.acquire()
if not _device_stream_pool_map:
_init_stream_pool()
lock.release()
return _device_stream_pool_map[device_idx]