{ "cells": [ { "cell_type": "raw", "id": "b0f94c74", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. _communication-patterns:\n", "\n", "Communication Patterns\n", "======================\n", "\n", "Cicada communicators provide a set of *communication patterns* that are inspired by the `Message Passing Interface (MPI) `_. These communication patterns are used by the other components in Cicada (such as :class:`~cicada.logger.Logger`, :class:`~cicada.additive.AdditiveProtocolSuite`, and :class:`~cicada.shamir.ShamirProtocolSuite`) to implement their functionality, but they're also available for you to use in your application code. Using communication patterns helps make your Cicada programs easier to implement and easier to understand, because they provide well-tested implementations and explicit semantics that would otherwise be buried in for-loops.\n", "\n", "To begin, let's look at the *broadcast* pattern, where one player sends a single piece of information to the other players (including themselves); a *one-to-all* communication:" ] }, { "cell_type": "markdown", "id": "9c8ebe05", "metadata": {}, "source": [ "" ] }, { "cell_type": "code", "execution_count": 1, "id": "834636ae", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:root:Player 0 received 'Hello!'\n", "INFO:root:Player 1 received 'Hello!'\n", "INFO:root:Player 2 received 'Hello!'\n" ] } ], "source": [ "import logging\n", "\n", "from cicada.communicator import SocketCommunicator\n", "from cicada.logger import Logger\n", "\n", "logging.basicConfig(level=logging.INFO)\n", "\n", "def main(communicator):\n", " log = Logger(logging.getLogger(), communicator)\n", " \n", " # Player 0 will broadcast a value to every player\n", " value = \"Hello!\" if communicator.rank == 0 else None\n", " value = communicator.broadcast(src=0, value=value)\n", " log.info(f\"Player {communicator.rank} received {value!r}\")\n", " \n", "SocketCommunicator.run(world_size=3, fn=main);" ] }, { "cell_type": "raw", "id": "7753de74", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "Note that this is an example of a *collective* operation, which *must* be called by every player, with consistent arguments. The arguments to :meth:`~cicada.communicator.interface.Communicator.broadcast` are the rank of the player that is broadcasting, and the value to be broadcast (for every player except the one doing the broadcasting, this value is ignored).\n", "\n", "The complement to broadcasting is *gather*, also a collective operation, where every player sends a single piece of information to one player; an *all-to-one* communication:" ] }, { "cell_type": "markdown", "id": "5246e4b1", "metadata": {}, "source": [ "" ] }, { "cell_type": "code", "execution_count": 2, "id": "2e1fdebd", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:root:Player 0 received None\n", "INFO:root:Player 1 received ['Hello from player 0!', 'Hello from player 1!', 'Hello from player 2!']\n", "INFO:root:Player 2 received None\n" ] } ], "source": [ "def main(communicator):\n", " log = Logger(logging.getLogger(), communicator)\n", " \n", " # Every player will send a value to player 1\n", " value = f\"Hello from player {communicator.rank}!\"\n", " value = communicator.gather(value=value, dst=1)\n", " log.info(f\"Player {communicator.rank} received {value!r}\")\n", " \n", "SocketCommunicator.run(world_size=3, fn=main);" ] }, { "cell_type": "raw", "id": "e69d6621", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "In this case every player passes a value to :meth:`~cicada.communicator.interface.Communicator.gather`, but only the destination player returns a :class:`list` of values (the other players return :any:`None`). The values are returned in rank-order, so `result[0]` is the value sent by player 0, and-so-on.\n", "\n", "Building on :meth:`~cicada.communicator.interface.Communicator.gather`, Cicada also provides *all-to-all* communication using :meth:`~cicada.communicator.interface.Communicator.allgather`, where every player simultaneously broadcasts one piece of information to every other player:" ] }, { "cell_type": "markdown", "id": "4e3d7088", "metadata": {}, "source": [ "" ] }, { "cell_type": "code", "execution_count": 3, "id": "9bcebea3", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:root:Player 0 received ['Hello from player 0!', 'Hello from player 1!', 'Hello from player 2!']\n", "INFO:root:Player 1 received ['Hello from player 0!', 'Hello from player 1!', 'Hello from player 2!']\n", "INFO:root:Player 2 received ['Hello from player 0!', 'Hello from player 1!', 'Hello from player 2!']\n" ] } ], "source": [ "def main(communicator):\n", " log = Logger(logging.getLogger(), communicator)\n", " \n", " # Every player will broadcast a value to every other player\n", " value = f\"Hello from player {communicator.rank}!\"\n", " value = communicator.allgather(value=value)\n", " log.info(f\"Player {communicator.rank} received {value!r}\")\n", " \n", "SocketCommunicator.run(world_size=3, fn=main);" ] }, { "cell_type": "raw", "id": "c73dbd0a", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "In the allgather case, every player returns a :class:`list` of values received from every player, in rank order.\n", "\n", "An alternative form of *one-to-all* communication is :meth:`~cicada.communicator.interface.Communicator.scatter`, where one player with :math:`n` pieces of information sends one piece each to the other players (:math:`n` is the number of players in this case):" ] }, { "cell_type": "markdown", "id": "6ec7f3c0", "metadata": {}, "source": [ "" ] }, { "cell_type": "code", "execution_count": 4, "id": "4eb5cef2", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:root:Player 0 received 'Hello to player 0'\n", "INFO:root:Player 1 received 'Hello to player 1'\n", "INFO:root:Player 2 received 'Hello to player 2'\n" ] } ], "source": [ "def main(communicator):\n", " log = Logger(logging.getLogger(), communicator)\n", " \n", " # Player two will send a unique piece of information to every player.\n", " if communicator.rank == 2:\n", " values = [f\"Hello to player {rank}\" for rank in communicator.ranks]\n", " else:\n", " values = None\n", " \n", " value = communicator.scatter(src=2, values=values)\n", " log.info(f\"Player {communicator.rank} received {value!r}\")\n", " \n", "SocketCommunicator.run(world_size=3, fn=main);" ] }, { "cell_type": "raw", "id": "d1c08de2", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "In this case the sending player provides a sequence of values in rank order, and each player returns the value that corresponds to its rank (including the sending player).\n", "\n", "Not all communications are collective operations, of course - communicators also support *point-to-point* communication between individual players, using :meth:`~cicada.communicator.interface.Communicator.send` and :meth:`~cicada.communicator.interface.Communicator.recv`:" ] }, { "cell_type": "markdown", "id": "5a6e1ebd", "metadata": {}, "source": [ "" ] }, { "cell_type": "code", "execution_count": 5, "id": "0fa88bce", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "INFO:root:Player 2 received 'Hey, 2!'\n" ] } ], "source": [ "def main(communicator):\n", " log = Logger(logging.getLogger(), communicator)\n", " \n", " # Player one will send information to player 2.\n", " if communicator.rank ==1:\n", " communicator.send(value=\"Hey, 2!\", dst=2, tag=42)\n", " if communicator.rank == 2:\n", " value = communicator.recv(src=1, tag=42)\n", " logging.info(f\"Player {communicator.rank} received {value!r}\")\n", " \n", "SocketCommunicator.run(world_size=3, fn=main);" ] }, { "cell_type": "raw", "id": "a405f5f4", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ "See :class:`~cicada.communicator.interface.Communicator` for additional communication patterns that you can use in your programs." ] } ], "metadata": { "celltoolbar": "Raw Cell Format", "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.2" } }, "nbformat": 4, "nbformat_minor": 5 }