Files
dmlc--dgl/python/dgl/distributed/role.py
T
2026-07-13 13:35:51 +08:00

248 lines
7.0 KiB
Python

"""Manage the roles in different clients.
Right now, the clients have different roles. Some clients work as samplers and
some work as trainers.
"""
import os
import numpy as np
from . import rpc
REGISTER_ROLE = 700001
REG_ROLE_MSG = "Register_Role"
class RegisterRoleResponse(rpc.Response):
"""Send a confirmation signal (just a short string message)
of RegisterRoleRequest to client.
"""
def __init__(self, msg):
self.msg = msg
def __getstate__(self):
return self.msg
def __setstate__(self, state):
self.msg = state
class RegisterRoleRequest(rpc.Request):
"""Send client id and role to server
Parameters
----------
client_id : int
ID of client
role : str
role of client
"""
def __init__(self, client_id, machine_id, role):
self.client_id = client_id
self.machine_id = machine_id
self.role = role
self.group_id = rpc.get_group_id()
def __getstate__(self):
return self.client_id, self.machine_id, self.role, self.group_id
def __setstate__(self, state):
self.client_id, self.machine_id, self.role, self.group_id = state
def process_request(self, server_state):
kv_store = server_state.kv_store
role = server_state.roles.setdefault(self.group_id, {})
if self.role not in role:
role[self.role] = set()
if kv_store is not None:
barrier_count = kv_store.barrier_count.setdefault(
self.group_id, {}
)
barrier_count[self.role] = 0
role[self.role].add((self.client_id, self.machine_id))
total_count = 0
for key in role:
total_count += len(role[key])
# Clients are blocked util all clients register their roles.
if total_count == rpc.get_num_client():
res_list = []
for target_id in range(rpc.get_num_client()):
res_list.append((target_id, RegisterRoleResponse(REG_ROLE_MSG)))
return res_list
return None
GET_ROLE = 700002
GET_ROLE_MSG = "Get_Role"
class GetRoleResponse(rpc.Response):
"""Send the roles of all client processes"""
def __init__(self, role):
self.role = role
self.msg = GET_ROLE_MSG
def __getstate__(self):
return self.role, self.msg
def __setstate__(self, state):
self.role, self.msg = state
class GetRoleRequest(rpc.Request):
"""Send a request to get the roles of all client processes."""
def __init__(self):
self.msg = GET_ROLE_MSG
self.group_id = rpc.get_group_id()
def __getstate__(self):
return self.msg, self.group_id
def __setstate__(self, state):
self.msg, self.group_id = state
def process_request(self, server_state):
return GetRoleResponse(server_state.roles[self.group_id])
# The key is role, the value is a dict of mapping RPC rank to a rank within the role.
PER_ROLE_RANK = {}
# The global rank of a client process. The client processes of the same role have
# global ranks that fall in a contiguous range.
GLOBAL_RANK = {}
# The role of the current process
CUR_ROLE = None
IS_STANDALONE = False
def init_role(role):
"""Initialize the role of the current process.
Each process is associated with a role so that we can determine what
function can be invoked in a process. For example, we do not allow some
functions in sampler processes.
The initialization includes registeration the role of the current process and
get the roles of all client processes. It also computes the rank of all client
processes in a deterministic way so that all clients will have the same rank for
the same client process.
"""
global CUR_ROLE
CUR_ROLE = role
global PER_ROLE_RANK
global GLOBAL_RANK
global IS_STANDALONE
if os.environ.get("DGL_DIST_MODE", "standalone") == "standalone":
if role == "default":
GLOBAL_RANK[0] = 0
PER_ROLE_RANK["default"] = {0: 0}
IS_STANDALONE = True
return
PER_ROLE_RANK = {}
GLOBAL_RANK = {}
# Register the current role. This blocks until all clients register themselves.
client_id = rpc.get_rank()
machine_id = rpc.get_machine_id()
request = RegisterRoleRequest(client_id, machine_id, role)
rpc.send_request(0, request)
response = rpc.recv_response()
assert response.msg == REG_ROLE_MSG
# Get all clients on all machines.
request = GetRoleRequest()
rpc.send_request(0, request)
response = rpc.recv_response()
assert response.msg == GET_ROLE_MSG
# Here we want to compute a new rank for each client.
# We compute the per-role rank as well as global rank.
# For per-role rank, we ensure that all ranks within a machine is contiguous.
# For global rank, we also ensure that all ranks within a machine are contiguous,
# and all ranks within a role are contiguous.
global_rank = 0
# We want to ensure that the global rank of the trainer process starts from 0.
role_names = ["default"]
for role_name in response.role:
if role_name not in role_names:
role_names.append(role_name)
for role_name in role_names:
# Let's collect the ranks of this role in all machines.
machines = {}
for client_id, machine_id in response.role[role_name]:
if machine_id not in machines:
machines[machine_id] = []
machines[machine_id].append(client_id)
num_machines = len(machines)
PER_ROLE_RANK[role_name] = {}
per_role_rank = 0
for i in range(num_machines):
clients = machines[i]
clients = np.sort(clients)
for client_id in clients:
GLOBAL_RANK[client_id] = global_rank
global_rank += 1
PER_ROLE_RANK[role_name][client_id] = per_role_rank
per_role_rank += 1
def get_global_rank():
"""Get the global rank
The rank can globally identify the client process. For the client processes
of the same role, their ranks are in a contiguous range.
"""
if IS_STANDALONE:
return 0
else:
return GLOBAL_RANK[rpc.get_rank()]
def get_rank(role):
"""Get the role-specific rank"""
if IS_STANDALONE:
return 0
else:
return PER_ROLE_RANK[role][rpc.get_rank()]
def get_trainer_rank():
"""Get the rank of the current trainer process.
This function can only be called in the trainer process. It will result in
an error if it's called in the process of other roles.
"""
assert CUR_ROLE == "default"
if IS_STANDALONE:
return 0
else:
return PER_ROLE_RANK["default"][rpc.get_rank()]
def get_role():
"""Get the role of the current process"""
return CUR_ROLE
def get_num_trainers():
"""Get the number of trainer processes"""
return len(PER_ROLE_RANK["default"])
rpc.register_service(REGISTER_ROLE, RegisterRoleRequest, RegisterRoleResponse)
rpc.register_service(GET_ROLE, GetRoleRequest, GetRoleResponse)