mirror of
https://github.com/hpcaitech/ColossalAI.git
synced 2026-07-12 19:06:17 +00:00
155 lines
5.8 KiB
Python
155 lines
5.8 KiB
Python
import copy
|
|
from typing import Any, Dict
|
|
|
|
import ray
|
|
import ray.util.collective as cc
|
|
import torch
|
|
import torch.distributed.distributed_c10d as c10d
|
|
from packaging.version import Version
|
|
|
|
|
|
def ray_broadcast_object(obj: Any, src: int = 0, device=None, group_name: str = "default") -> Any:
|
|
rank = cc.get_rank(group_name)
|
|
if rank == src:
|
|
if Version(torch.__version__) >= Version("2.3.0"):
|
|
obj_tensor, size_tensor = c10d._object_to_tensor(obj, device=device, group=None)
|
|
elif Version(torch.__version__) >= Version("1.13.0"):
|
|
obj_tensor, size_tensor = c10d._object_to_tensor(obj, device=device)
|
|
else:
|
|
obj_tensor, size_tensor = c10d._object_to_tensor(obj)
|
|
obj_tensor = obj_tensor.to(device)
|
|
size_tensor = size_tensor.to(device)
|
|
else:
|
|
size_tensor = torch.empty(1, dtype=torch.int64, device=device)
|
|
cc.broadcast(size_tensor, src, group_name)
|
|
if rank != src:
|
|
obj_tensor = torch.empty(size_tensor.item(), dtype=torch.uint8, device=device)
|
|
cc.broadcast(obj_tensor, src, group_name)
|
|
if rank != src:
|
|
if Version(torch.__version__) >= Version("2.3.0"):
|
|
obj = c10d._tensor_to_object(obj_tensor, size_tensor.item(), group=None)
|
|
else:
|
|
obj = c10d._tensor_to_object(obj, size_tensor.item())
|
|
return obj
|
|
|
|
|
|
def ray_broadcast_tensor_dict(
|
|
tensor_dict: Dict[str, torch.Tensor],
|
|
src: int = 0,
|
|
device=None,
|
|
group_name: str = "default",
|
|
backend: str = "nccl",
|
|
offload_to_cpu: bool = False,
|
|
pin_memory: bool = False,
|
|
) -> Dict[str, torch.Tensor]:
|
|
rank = cc.get_rank(group_name)
|
|
if tensor_dict is None:
|
|
tensor_dict = {}
|
|
if rank == src:
|
|
metadata = []
|
|
for k, v in tensor_dict.items():
|
|
metadata.append((k, v.shape, v.dtype))
|
|
else:
|
|
metadata = None
|
|
metadata = ray_broadcast_object(metadata, src, device, group_name)
|
|
for k, shape, dtype in metadata:
|
|
if rank == src:
|
|
if offload_to_cpu:
|
|
tensor = tensor_dict[k].to(device)
|
|
else:
|
|
tensor = tensor_dict[k]
|
|
else:
|
|
tensor = tensor_dict.get(k, torch.zeros(shape, dtype=dtype, device=device, pin_memory=pin_memory))
|
|
if backend == "gloo" and dtype == torch.bfloat16:
|
|
# Gloo does not support bfloat16, convert to float16
|
|
tensor = tensor.view(torch.float16)
|
|
cc.broadcast(tensor, src, group_name)
|
|
if backend == "gloo" and dtype == torch.bfloat16:
|
|
# Convert back to bfloat16 if it was converted to float16
|
|
tensor = tensor.view(torch.bfloat16)
|
|
if rank != src:
|
|
if offload_to_cpu:
|
|
tensor_dict[k] = tensor.cpu()
|
|
else:
|
|
tensor_dict[k] = tensor
|
|
return tensor_dict
|
|
|
|
|
|
@ray.remote
|
|
class SharedVariableActor:
|
|
def __init__(self, number_of_readers: int = 0, buffer_size_limit: int = 1000):
|
|
self.data_queue = []
|
|
self.data_uid = 0
|
|
self.number_of_readers = number_of_readers
|
|
self.queue_size = 0
|
|
self.signals = {}
|
|
self.process_locks = {}
|
|
self.signal_procs_meet_count = {}
|
|
self.buffer_size_limit = buffer_size_limit
|
|
|
|
def pickup_rollout_task(self, num_tasks: int):
|
|
"""
|
|
use queue size to control whether producers should generating new rollouts or wait
|
|
for consumer to consumer more data. if queue size is less than threshold,
|
|
it means consumer is consuming data fast enough, so producers can generate new rollouts.
|
|
if queue size is greater than threshold, it means consumer is consuming data slowly,
|
|
so producers should wait for consumer to consume more data.
|
|
|
|
Any free producer can pick up the task to generate rollout then increase the queued_data_size
|
|
to prevent other producer to pick up the task redundantly, Note it is not the real
|
|
queue length as data may still be generating
|
|
"""
|
|
ret = False
|
|
if self.queue_size < (self.buffer_size_limit / max(0.1, self.signals.get("sample_utilization", 1.0))):
|
|
ret = True
|
|
self.queue_size += num_tasks
|
|
return ret
|
|
|
|
def append_data(self, data):
|
|
self.data_queue.append([self.data_uid, data, 0]) # [data_uid, data, access_count]
|
|
self.data_uid += 1
|
|
return True
|
|
|
|
def get_data(self, data_uid: int):
|
|
# for multi-process data reading
|
|
if not self.data_queue:
|
|
# no data in the queue, return None
|
|
return None
|
|
to_pop_index = None
|
|
ret = None
|
|
for i, (uid, data, access_count) in enumerate(self.data_queue):
|
|
if uid == data_uid:
|
|
# found the data with the given uid
|
|
self.data_queue[i][2] += 1
|
|
ret = copy.deepcopy(data)
|
|
if self.data_queue[i][2] == self.number_of_readers:
|
|
to_pop_index = i
|
|
break
|
|
if to_pop_index is not None:
|
|
# remove the data from the queue if it has been accessed by all readers
|
|
self.data_queue.pop(to_pop_index)
|
|
self.queue_size -= data["input_ids"].size(0)
|
|
return ret
|
|
|
|
def acquire_process_lock(self, key: str):
|
|
# atomic lock for process
|
|
if key not in self.process_locks:
|
|
self.process_locks[key] = 1 # locked
|
|
return 0
|
|
if self.process_locks[key] == 0:
|
|
self.process_locks[key] = 1 # lock the process
|
|
return 0
|
|
else:
|
|
return 1
|
|
|
|
def release_process_lock(self, key: str):
|
|
# atomic unlock for process
|
|
assert self.process_locks.get(key, 0) == 1, f"Releasing a process lock {key} that is not locked."
|
|
self.process_locks[key] = 0
|
|
|
|
def set_signal(self, key: str, signal: str):
|
|
self.signals[key] = signal
|
|
|
|
def get_signal(self):
|
|
return self.signals
|