def master(group_name: str, is_immediate: bool = False): """ The main master logic includes initialize proxy and allocate jobs to workers. Args: group_name (str): Identifier for the group of all communication components, is_immediate (bool): If True, it will be an async mode; otherwise, it will be an sync mode. Async Mode: The proxy only returns the session id for sending messages. Based on the local task priority, you can do something with high priority before receiving replied messages from peers. Sync Mode: It will block until the proxy returns all the replied messages. """ proxy = Proxy(group_name=group_name, component_type="master", expected_peers={"worker": 1}) random_integer_list = np.random.randint(0, 100, 5) print(f"generate random integer list: {random_integer_list}.") for peer in proxy.peers_name["worker"]: message = SessionMessage(tag="sum", source=proxy.name, destination=peer, payload=random_integer_list, session_type=SessionType.TASK) if is_immediate: session_id = proxy.isend(message) # Do some tasks with higher priority here. replied_msgs = proxy.receive_by_id(session_id, timeout=-1) else: replied_msgs = proxy.send(message, timeout=-1) for msg in replied_msgs: print( f"{proxy.name} receive {msg.source}, replied payload is {msg.payload}." )
class ActorProxy(object): def __init__(self, proxy_params): self._proxy = Proxy(component_type="actor", **proxy_params) def roll_out(self, model_dict: dict = None, epsilon_dict: dict = None, done: bool = False, return_details: bool = True): if done: self._proxy.ibroadcast(tag=MessageTag.ROLLOUT, session_type=SessionType.NOTIFICATION, payload={PayloadKey.DONE: True}) return None, None else: performance, exp_by_agent = {}, {} payloads = [(peer, {PayloadKey.MODEL: model_dict, PayloadKey.EPSILON: epsilon_dict, PayloadKey.RETURN_DETAILS: return_details}) for peer in self._proxy.peers["actor_worker"]] # TODO: double check when ack enable replies = self._proxy.scatter(tag=MessageTag.ROLLOUT, session_type=SessionType.TASK, destination_payload_list=payloads) for msg in replies: performance[msg.source] = msg.payload[PayloadKey.PERFORMANCE] if msg.payload[PayloadKey.EXPERIENCE] is not None: for agent_id, exp_set in msg.payload[PayloadKey.EXPERIENCE].items(): if agent_id not in exp_by_agent: exp_by_agent[agent_id] = defaultdict(list) for k, v in exp_set.items(): exp_by_agent[agent_id][k].extend(v) return performance, exp_by_agent
def master(group_name: str, worker_num: int, is_immediate: bool = False): """ The main master logic includes initialize proxy and allocate jobs to workers. Args: group_name (str): Identifier for the group of all communication components, worker_num (int): The number of workers, is_immediate (bool): If True, it will be an async mode; otherwise, it will be an sync mode. Async Mode: The proxy only returns the session id for sending messages. Based on the local task priority, you can do something with high priority before receiving replied messages from peers. Sync Mode: It will block until the proxy returns all the replied messages. """ proxy = Proxy(group_name=group_name, component_type="master", expected_peers={"worker": worker_num}) if is_immediate: session_ids = proxy.ibroadcast(tag="INC", session_type=SessionType.NOTIFICATION) # do some tasks with higher priority here. replied_msgs = proxy.receive_by_id(session_ids) else: replied_msgs = proxy.broadcast(tag="INC", session_type=SessionType.NOTIFICATION) for msg in replied_msgs: print( f"{proxy.component_name} get receive notification from {msg.source} with message session stage " + f"{msg.session_stage}.")
class ActorWorker(object): def __init__(self, local_actor: AbsActor, proxy_params): self._local_actor = local_actor self._proxy = Proxy(component_type="actor_worker", **proxy_params) self._registry_table = RegisterTable(self._proxy.get_peers) self._registry_table.register_event_handler("actor:rollout:1", self.on_rollout_request) def on_rollout_request(self, message): data = message.payload if data.get(PayloadKey.DONE, False): sys.exit(0) performance, experiences = self._local_actor.roll_out(model_dict=data[PayloadKey.MODEL], epsilon_dict=data[PayloadKey.EPSILON], return_details=data[PayloadKey.RETURN_DETAILS]) self._proxy.reply(received_message=message, tag=MessageTag.UPDATE, payload={PayloadKey.PERFORMANCE: performance, PayloadKey.EXPERIENCE: experiences} ) def launch(self): """ This launches an ActorWorker instance. """ for msg in self._proxy.receive(): self._registry_table.push(msg) triggered_events = self._registry_table.get() for handler_fn, cached_messages in triggered_events: handler_fn(cached_messages)
class ActorProxy(object): """A simple proxy wrapper for sending roll-out requests to remote actors. Args: proxy_params: Parameters for instantiating a ``Proxy`` instance. """ def __init__(self, proxy_params): self._proxy = Proxy(component_type="actor", **proxy_params) def roll_out(self, model_dict: dict = None, epsilon_dict: dict = None, done: bool = False, return_details: bool = True): """Send roll-out requests to remote actors. This method has exactly the same signature as ``SimpleActor``'s ``roll_out`` method but instead of doing the roll-out itself, sends roll-out requests to remote actors and returns the results sent back. The ``SimpleLearner`` simply calls the actor's ``roll_out`` method without knowing whether its performed locally or remotely. Args: model_dict (dict): If not None, the agents will load the models from model_dict and use these models to perform roll-out. epsilon_dict (dict): Exploration rate by agent. done (bool): If True, the current call is the last call, i.e., no more roll-outs will be performed. This flag is used to signal remote actor workers to exit. return_details (bool): If True, return experiences as well as performance metrics provided by the env. Returns: Performance and per-agent experiences from the remote actor. """ if done: self._proxy.ibroadcast(tag=MessageTag.ROLLOUT, session_type=SessionType.NOTIFICATION, payload={PayloadKey.DONE: True}) return None, None else: performance, exp_by_agent = {}, {} payloads = [(peer, { PayloadKey.MODEL: model_dict, PayloadKey.EPSILON: epsilon_dict, PayloadKey.RETURN_DETAILS: return_details }) for peer in self._proxy.peers["actor_worker"]] # TODO: double check when ack enable replies = self._proxy.scatter(tag=MessageTag.ROLLOUT, session_type=SessionType.TASK, destination_payload_list=payloads) for msg in replies: performance[msg.source] = msg.payload[PayloadKey.PERFORMANCE] if msg.payload[PayloadKey.EXPERIENCE] is not None: for agent_id, exp_set in msg.payload[ PayloadKey.EXPERIENCE].items(): if agent_id not in exp_by_agent: exp_by_agent[agent_id] = defaultdict(list) for k, v in exp_set.items(): exp_by_agent[agent_id][k].extend(v) return performance, exp_by_agent
def proxy_generator(component_type): if component_type == "master": proxy = Proxy(group_name="proxy_unit_test", component_type="master", expected_peers={"worker": 5}, log_enable=False) elif component_type == "worker": proxy = Proxy(group_name="proxy_unit_test", component_type="worker", expected_peers={"master": 1}, log_enable=False) return proxy
def proxy_generator(component_type, redis_port): proxy_parameters = { "group_name": "communication_unit_test", "redis_address": ("localhost", redis_port), "log_enable": False } component_type_expected_peers_map = { "receiver": { "sender": 1 }, "sender": { "receiver": 1 }, "master": { "worker": 5 }, "worker": { "master": 1 } } proxy = Proxy( component_type=component_type, expected_peers=component_type_expected_peers_map[component_type], **proxy_parameters) return proxy
def setUpClass(cls): print(f"The proxy unit test start!") # Initialize the Redis. redis_port = get_random_port() cls.redis_process = subprocess.Popen(["redis-server", "--port", str(redis_port), "--daemonize yes"]) cls.redis_process.wait() # Initialize the proxies. cls.peers_number = 3 q = multiprocessing.Queue() actor_process_list = [] for i in range(cls.peers_number): actor_process_list.append(multiprocessing.Process(target=actor_init, args=(q, redis_port))) process_rejoin = multiprocessing.Process(target=fake_rejoin, args=(q, redis_port)) for i in range(cls.peers_number): actor_process_list[i].start() process_rejoin.start() cls.master_proxy = Proxy( component_type="master", expected_peers={"actor": 3}, redis_address=("localhost", redis_port), **PROXY_PARAMETER ) cls.peers = cls.master_proxy.peers_name["actor"]
class ActorWorker(object): """A ``AbsActor`` wrapper that accepts roll-out requests and performs roll-out tasks. Args: local_actor: An ``AbsActor`` instance. proxy_params: Parameters for instantiating a ``Proxy`` instance. """ def __init__(self, local_actor: AbsActor, proxy_params): self._local_actor = local_actor self._proxy = Proxy(component_type="actor_worker", **proxy_params) self._registry_table = RegisterTable(self._proxy.get_peers) self._registry_table.register_event_handler("actor:rollout:1", self.on_rollout_request) def on_rollout_request(self, message): """Perform local roll-out and send the results back to the request sender. Args: message: Message containing roll-out parameters and options. """ data = message.payload if data.get(PayloadKey.DONE, False): sys.exit(0) performance, experiences = self._local_actor.roll_out( model_dict=data[PayloadKey.MODEL], epsilon_dict=data[PayloadKey.EPSILON], return_details=data[PayloadKey.RETURN_DETAILS]) self._proxy.reply(received_message=message, tag=MessageTag.UPDATE, payload={ PayloadKey.PERFORMANCE: performance, PayloadKey.EXPERIENCE: experiences }) def launch(self): """Entry point method. This enters the actor into an infinite loop of listening to requests and handling them according to the register table. In this case, the only type of requests the actor needs to handle is roll-out requests. """ for msg in self._proxy.receive(): self._registry_table.push(msg) triggered_events = self._registry_table.get() for handler_fn, cached_messages in triggered_events: handler_fn(cached_messages)
def worker(group_name): """ The main worker logic includes initialize proxy and handle jobs from the master. Args: group_name (str): Identifier for the group of all communication components """ proxy = Proxy(group_name=group_name, component_type="worker", expected_peers={"master": 1}) # Nonrecurring receive the message from the proxy. for msg in proxy.receive(is_continuous=False): print(f"{proxy.component_name} receive message from {msg.source}. the payload is {msg.payload}.") if msg.tag == "sum": replied_payload = sum(msg.payload) proxy.reply(received_message=msg, tag="sum", payload=replied_payload)
def __init__(self, group_name: str, num_actors: int, update_trigger: str = None, proxy_options: dict = None, log_dir: str = getcwd()): self.agent = None peers = {"actor": num_actors} if proxy_options is None: proxy_options = {} self._proxy = Proxy(group_name, "learner", peers, **proxy_options) self._actors = self._proxy.peers_name["actor"] # remote actor ID's self._registry_table = RegisterTable(self._proxy.peers_name) if update_trigger is None: update_trigger = len(self._actors) self._registry_table.register_event_handler( f"actor:{MessageTag.FINISHED.value}:{update_trigger}", self._on_rollout_finish) self.logger = Logger("ACTOR_PROXY", dump_folder=log_dir)
def as_worker(self, group: str, proxy_options=None): """Executes an event loop where roll-outs are performed on demand from a remote learner. Args: group (str): Identifier of the group to which the actor belongs. It must be the same group name assigned to the learner (and decision clients, if any). proxy_options (dict): Keyword parameters for the internal ``Proxy`` instance. See ``Proxy`` class for details. Defaults to None. """ if proxy_options is None: proxy_options = {} proxy = Proxy(group, "actor", {"learner": 1}, **proxy_options) logger = InternalLogger(proxy.name) for msg in proxy.receive(): if msg.tag == MessageTag.EXIT: logger.info("Exiting...") sys.exit(0) elif msg.tag == MessageTag.ROLLOUT: ep = msg.payload[PayloadKey.ROLLOUT_INDEX] logger.info(f"Rolling out ({ep})...") metrics, rollout_data = self.roll_out( ep, training=msg.payload[PayloadKey.TRAINING], model_by_agent=msg.payload[PayloadKey.MODEL], exploration_params=msg.payload[ PayloadKey.EXPLORATION_PARAMS]) if rollout_data is None: logger.info(f"Roll-out {ep} aborted") else: logger.info(f"Roll-out {ep} finished") rollout_finish_msg = Message( MessageTag.FINISHED, proxy.name, proxy.peers_name["learner"][0], payload={ PayloadKey.ROLLOUT_INDEX: ep, PayloadKey.METRICS: metrics, PayloadKey.DETAILS: rollout_data }) proxy.isend(rollout_finish_msg) self.env.reset()
class ActorProxy(object): def __init__(self, proxy_params): self._proxy = Proxy(component_type="actor_proxy", **proxy_params) def roll_out(self, mode: RolloutMode, models: dict = None, epsilon_dict: dict = None, seed: int = None): if mode == RolloutMode.EXIT: # TODO: session type: notification self._proxy.broadcast(tag=MessageType.ROLLOUT, session_type=SessionType.TASK, payload={PayloadKey.RolloutMode: mode}) return None, None else: performance, exp_by_agent = {}, {} payloads = [(peer, { PayloadKey.MODEL: models, PayloadKey.RolloutMode: mode, PayloadKey.EPSILON: epsilon_dict, PayloadKey.SEED: (seed + i) if seed is not None else None }) for i, peer in enumerate(self._proxy.get_peers("actor_worker"))] # TODO: double check when ack enable replies = self._proxy.scatter(tag=MessageType.ROLLOUT, session_type=SessionType.TASK, destination_payload_list=payloads) for msg in replies: performance[msg.source] = msg.payload[PayloadKey.PERFORMANCE] if msg.payload[PayloadKey.EXPERIENCE] is not None: for agent_id, exp_set in msg.payload[ PayloadKey.EXPERIENCE].items(): if agent_id not in exp_by_agent: exp_by_agent[agent_id] = defaultdict(list) for k, v in exp_set.items(): exp_by_agent[agent_id][k].extend(v) return performance, exp_by_agent
def worker(group_name): """ The main worker logic includes initialize proxy and handle jobs from the master. Args: group_name (str): Identifier for the group of all communication components. """ proxy = Proxy(group_name=group_name, component_type="worker", expected_peers={"master": 1}) counter = 0 print(f"{proxy.component_name}'s counter is {counter}.") # nonrecurring receive the message from the proxy. for msg in proxy.receive(is_continuous=False): print(f"{proxy.component_name} receive message from {msg.source}.") if msg.tag == "INC": counter += 1 print( f"{proxy.component_name} receive INC request, {proxy.component_name}'s count is {counter}." ) proxy.reply(received_message=msg, tag="done")
class ActorWorker(object): def __init__(self, local_actor, proxy_params): self._local_actor = local_actor self._proxy = Proxy(component_type="actor_worker", **proxy_params) self._registry_table = RegisterTable(self._proxy.get_peers) self._registry_table.register_event_handler("actor_proxy:rollout:1", self.on_rollout_request) def on_rollout_request(self, message): if message.payload[PayloadKey.RolloutMode] == RolloutMode.EXIT: sys.exit(0) data = message.payload performance, exp_by_agent = self._local_actor.roll_out( mode=data[PayloadKey.RolloutMode], models=data[PayloadKey.MODEL], epsilon_dict=data[PayloadKey.EPSILON], seed=data[PayloadKey.SEED]) self._proxy.reply(received_message=message, tag=MessageType.UPDATE, payload={ PayloadKey.PERFORMANCE: performance["local"], PayloadKey.EXPERIENCE: exp_by_agent }) def launch(self): # TODO """ Launches the an ActorWorker instance """ for msg in self._proxy.receive(): self._registry_table.push(msg) triggered_events = self._registry_table.get() for handler_fn, cached_messages in triggered_events: handler_fn(cached_messages)
def __init__(self, local_actor: AbsActor, proxy_params): self._local_actor = local_actor self._proxy = Proxy(component_type="actor_worker", **proxy_params) self._registry_table = RegisterTable(self._proxy.get_peers) self._registry_table.register_event_handler("actor:rollout:1", self.on_rollout_request)
class ActorProxy(object): """A simple proxy wrapper for sending roll-out requests to remote actors. Args: proxy_params: Parameters for instantiating a ``Proxy`` instance. experience_collecting_func (Callable): A function responsible for collecting experiences from multiple sources. """ def __init__(self, proxy_params, experience_collecting_func: Callable): self._proxy = Proxy(component_type="learner", **proxy_params) self._experience_collecting_func = experience_collecting_func def roll_out(self, model_dict: dict = None, exploration_params=None, done: bool = False, return_details: bool = True): """Send roll-out requests to remote actors. This method has exactly the same signature as ``SimpleActor``'s ``roll_out`` method but instead of doing the roll-out itself, sends roll-out requests to remote actors and returns the results sent back. The ``SimpleLearner`` simply calls the actor's ``roll_out`` method without knowing whether its performed locally or remotely. Args: model_dict (dict): If not None, the agents will load the models from model_dict and use these models to perform roll-out. exploration_params: Exploration parameters. done (bool): If True, the current call is the last call, i.e., no more roll-outs will be performed. This flag is used to signal remote actor workers to exit. return_details (bool): If True, return experiences as well as performance metrics provided by the env. Returns: Performance and per-agent experiences from the remote actor. """ if done: self._proxy.ibroadcast(component_type="actor", tag=MessageTag.ROLLOUT, session_type=SessionType.NOTIFICATION, payload={PayloadKey.DONE: True}) return None, None payloads = [(peer, { PayloadKey.MODEL: model_dict, PayloadKey.EXPLORATION_PARAMS: exploration_params, PayloadKey.RETURN_DETAILS: return_details }) for peer in self._proxy.peers_name["actor"]] # TODO: double check when ack enable replies = self._proxy.scatter(tag=MessageTag.ROLLOUT, session_type=SessionType.TASK, destination_payload_list=payloads) performance = [(msg.source, msg.payload[PayloadKey.PERFORMANCE]) for msg in replies] details_by_source = { msg.source: msg.payload[PayloadKey.DETAILS] for msg in replies } details = self._experience_collecting_func( details_by_source) if return_details else None return performance, details
def __init__(self, proxy_params): self._proxy = Proxy(component_type="actor", **proxy_params)
def __init__(self, proxy_params, experience_collecting_func: Callable): self._proxy = Proxy(component_type="learner", **proxy_params) self._experience_collecting_func = experience_collecting_func
class ActorProxy(object): """Actor proxy that manages a set of remote actors. Args: group_name (str): Identifier of the group to which the actor belongs. It must be the same group name assigned to the actors (and roll-out clients, if any). num_actors (int): Expected number of actors in the group identified by ``group_name``. update_trigger (str): Number or percentage of ``MessageTag.FINISHED`` messages required to trigger learner updates, i.e., model training. proxy_options (dict): Keyword parameters for the internal ``Proxy`` instance. See ``Proxy`` class for details. Defaults to None. """ def __init__( self, group_name: str, num_actors: int, update_trigger: str = None, proxy_options: dict = None ): self.agent = None peers = {"actor": num_actors} if proxy_options is None: proxy_options = {} self._proxy = Proxy(group_name, "learner", peers, **proxy_options) self._actors = self._proxy.peers_name["actor"] # remote actor ID's self._registry_table = RegisterTable(self._proxy.peers_name) if update_trigger is None: update_trigger = len(self._actors) self._registry_table.register_event_handler( f"actor:{MessageTag.FINISHED.value}:{update_trigger}", self._on_rollout_finish ) self.logger = InternalLogger("ACTOR_PROXY") def roll_out(self, index: int, training: bool = True, model_by_agent: dict = None, exploration_params=None): """Collect roll-out data from remote actors. Args: index (int): Index of roll-out requests. training (bool): If true, the roll-out request is for training purposes. model_by_agent (dict): Models to be broadcast to remote actors for inference. Defaults to None. exploration_params: Exploration parameters to be used by the remote roll-out actors. Defaults to None. """ payload = { PayloadKey.ROLLOUT_INDEX: index, PayloadKey.TRAINING: training, PayloadKey.MODEL: model_by_agent, PayloadKey.EXPLORATION_PARAMS: exploration_params } self._proxy.iscatter(MessageTag.ROLLOUT, SessionType.TASK, [(actor, payload) for actor in self._actors]) self.logger.info(f"Sent roll-out requests to {self._actors} for ep-{index}") # Receive roll-out results from remote actors for msg in self._proxy.receive(): if msg.payload[PayloadKey.ROLLOUT_INDEX] != index: self.logger.info( f"Ignore a message of type {msg.tag} with ep {msg.payload[PayloadKey.ROLLOUT_INDEX]} " f"(expected {index} or greater)" ) continue if msg.tag == MessageTag.FINISHED: # If enough update messages have been received, call update() and break out of the loop to start # the next episode. result = self._registry_table.push(msg) if result: env_metrics, details = result[0] break return env_metrics, details def _on_rollout_finish(self, messages: List[Message]): metrics = {msg.source: msg.payload[PayloadKey.METRICS] for msg in messages} details = {msg.source: msg.payload[PayloadKey.DETAILS] for msg in messages} return metrics, details def terminate(self): """Tell the remote actors to exit.""" self._proxy.ibroadcast( component_type="actor", tag=MessageTag.EXIT, session_type=SessionType.NOTIFICATION ) self.logger.info("Exiting...")
def master(group_name: str, sum_worker_number: int, multiply_worker_number: int, is_immediate: bool = False): """ The main master logic includes initialize proxy and allocate jobs to workers. Args: group_name (str): Identifier for the group of all communication components, sum_worker_number (int): The number of sum workers, multiply_worker_number (int): The number of multiply workers, is_immediate (bool): If True, it will be an async mode; otherwise, it will be an sync mode. Async Mode: The proxy only returns the session id for sending messages. Based on the local task priority, you can do something with high priority before receiving replied messages from peers. Sync Mode: It will block until the proxy returns all the replied messages. """ proxy = Proxy(group_name=group_name, component_type="master", expected_peers={ "sum_worker": sum_worker_number, "multiply_worker": multiply_worker_number }) sum_list = np.random.randint(0, 10, 100) multiple_list = np.random.randint(1, 10, 20) print("Generate random sum/multiple list with length 100.") # Assign sum tasks for summation workers. destination_payload_list = [] for idx, peer in enumerate(proxy.peers_name["sum_worker"]): data_length_per_peer = int( len(sum_list) / len(proxy.peers_name["sum_worker"])) destination_payload_list.append( (peer, sum_list[idx * data_length_per_peer:(idx + 1) * data_length_per_peer])) # Assign multiply tasks for multiplication workers. for idx, peer in enumerate(proxy.peers_name["multiply_worker"]): data_length_per_peer = int( len(multiple_list) / len(proxy.peers_name["multiply_worker"])) destination_payload_list.append( (peer, multiple_list[idx * data_length_per_peer:(idx + 1) * data_length_per_peer])) if is_immediate: session_ids = proxy.iscatter( tag="job", session_type=SessionType.TASK, destination_payload_list=destination_payload_list) # Do some tasks with higher priority here. replied_msgs = proxy.receive_by_id(session_ids, timeout=-1) else: replied_msgs = proxy.scatter( tag="job", session_type=SessionType.TASK, destination_payload_list=destination_payload_list, timeout=-1) sum_result, multiply_result = 0, 1 for msg in replied_msgs: if msg.tag == "sum": print( f"{proxy.name} receive message from {msg.source} with the sum result {msg.payload}." ) sum_result += msg.payload elif msg.tag == "multiply": print( f"{proxy.name} receive message from {msg.source} with the multiply result {msg.payload}." ) multiply_result *= msg.payload # Check task result correction. assert (sum(sum_list) == sum_result) assert (np.prod(multiple_list) == multiply_result)
def actor_init(queue, redis_port): proxy = Proxy( component_type="actor", expected_peers={"master": 1}, redis_address=("localhost", redis_port), **PROXY_PARAMETER ) # Continuously receive messages from proxy. for msg in proxy.receive(is_continuous=True): print(f"receive message from master. {msg.tag}") if msg.tag == "cont": proxy.reply(received_message=msg, tag="recv", payload="successful receive!") elif msg.tag == "stop": proxy.reply(received_message=msg, tag="recv", payload=f"{proxy.component_name} exited!") queue.put(proxy.component_name) break elif msg.tag == "finish": proxy.reply(received_message=msg, tag="recv", payload=f"{proxy.component_name} finish!") sys.exit(0) proxy.__del__() sys.exit(1)