Skip to content
This repository was archived by the owner on Sep 18, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
3c0a12a
Add multiprocess test to perform all-to-all ep creation
pentschev Mar 8, 2021
35664da
Add support for persistent endpoints in multiprocess all_to_all test
pentschev Mar 10, 2021
65f1870
Improve closing of endpoints in multiproc all_to_all test
pentschev Mar 10, 2021
ba66c92
Do more transfers between worker pairs
pentschev Mar 10, 2021
b712ce9
Support multiple endpoints per worker in multiproc all_to_all test
pentschev Mar 10, 2021
e8fee54
Merge remote-tracking branch 'upstream/branch-0.21' into all-to-all-test
pentschev Jun 9, 2021
aa06731
Remove monitor process in favor of multiprocessing shared memory
pentschev Jun 9, 2021
ce98f43
Merge remote-tracking branch 'upstream/branch-0.21' into all-to-all-test
pentschev Jul 1, 2021
68c07a6
Mark some multiple processes all-to-all tests as slow
pentschev Jul 1, 2021
c91ec3c
Store remote EP port in dictionary
pentschev Jul 1, 2021
33b630d
Mark more tests as slow
pentschev Jul 1, 2021
4f41ebe
Benchmark for multiprocess all-to-all
pentschev Jul 1, 2021
b6508ed
Add Tornado-based all-to-all benchmark
pentschev Jul 5, 2021
ac88060
Change all-to-all worker function into class
pentschev Jul 6, 2021
4c485c2
Use BaseWorker class to create TornadoWorker all-to-all benchmark
pentschev Jul 6, 2021
6ddcf04
Add asyncio all-to-all benchmark
pentschev Jul 6, 2021
9675844
Remove non-persistent endpoints
pentschev Jul 6, 2021
c8fb2a7
Split UCX transfers in frames to make it similar to Tornado/Asyncio
pentschev Jul 7, 2021
bf48ebd
Move serialization to comms send method
pentschev Jul 7, 2021
7d2eaff
Add monitor process to allow syncing workers over network
pentschev Jul 8, 2021
8ae422a
Split `run` into multiple methods
pentschev Jul 8, 2021
da2320e
Parametrize communication and monitor enable/disable
pentschev Jul 8, 2021
e21a973
Remove worker_num argument
pentschev Jul 8, 2021
6c7de45
Make shm synchronization arguments optional
pentschev Jul 8, 2021
1bdc4d2
Add listener_address argument
pentschev Jul 8, 2021
da75bc1
Separate benchmark into multiple files
pentschev Jul 9, 2021
9202310
Improve benchmark result formatting
pentschev Jul 9, 2021
dba6dbf
Reorganize run_worker and run_monitor
pentschev Jul 9, 2021
26c3712
Fix single-node benchmark
pentschev Jul 9, 2021
199f960
Add more all-to-all benchmark CLI arguments
pentschev Jul 9, 2021
49b194f
Remove hardcoded enp1s0f0 interface
pentschev Jul 9, 2021
796880f
Remove redundant --multi-node argument
pentschev Jul 9, 2021
cc7961b
Improve documentation for all-to-all benchmark CLI arguments
pentschev Jul 9, 2021
18a4557
Fix all-to-all benchmark test
pentschev Jul 9, 2021
58a30f8
Change worker address formatting in benchmark output
pentschev Jul 9, 2021
5a509e3
Add --port argument to specify monitor port
pentschev Jul 9, 2021
75fca3d
Fix issues with all-to-all pytest
pentschev Jul 9, 2021
b1b0b1b
Remove usage of partial
pentschev Jul 12, 2021
d3e835d
Add all-to-all benchmark with uvloop
pentschev Jul 13, 2021
bd86b11
Add uvloop support for multi-node benchmark
pentschev Jul 13, 2021
a96358c
Fix bandwidth calculation
pentschev Jul 13, 2021
b3bdfb0
Parse bytes with --size
pentschev Jul 13, 2021
29468b4
Skip uvloop test when not installed
pentschev Jul 13, 2021
14010ac
Raise when number of workers don't match
pentschev Aug 6, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 240 additions & 0 deletions tests/benchmark_multiple_processes_all_to_all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import argparse

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want this in tests/ or would it make sense to include in benchmarks/?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still thinking about it. I don't want it in tests, but I want a test there (for the UCX part only). However, a lot of the code is going to be shared and we don't have a good place currently where that common code would be visible to both. I'm not even sure the non-UCX code should live in this repo as we'll soon upstream it to OpenUCX, so it doesn't really make sense to have non-UCX code there. I'm still thinking of an appropriate place for this, if you have any suggestions, please let me know.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that makes sense. Will think about it as well.

import multiprocessing

from utils_all_to_all import (
asyncio_process,
tornado_process,
ucx_process,
uvloop_process,
)

from dask.utils import parse_bytes

import ucp


def parse_args():
parser = argparse.ArgumentParser(description="All-to-all benchmark")
parser.add_argument(
"--monitor",
default=False,
action="store_true",
help="Start a monitor process only. Requires --num-worker processes "
"started with --worker to connect to monitor. Default: disabled.",
)
parser.add_argument(
"--worker",
default=False,
action="store_true",
help="Start a worker process only. Requires a --monitor process "
"to connect to, with an address specified with --monitor-address. "
"Default: disabled.",
)
parser.add_argument(
"--enable-monitor",
default=False,
action="store_true",
help="Use a monitor process to synchronize workers when in single-node "
"mode (when neither --monitor or --worker are requested), otherwise "
"synchronize processes via multiprocessing shared memory. Default: "
"disabled.",
)
parser.add_argument(
"--listen-interface",
default=None,
help="Interface where monitor (if --monitor), or worker (if --worker), "
"or all (in single-node mode, i.e., no --monitor or --worker are "
"specified) will listen for connections.",
)
parser.add_argument(
"--monitor-address",
metavar="IP:PORT",
default=None,
help="Address where --monitor process is listening to in the HOST:PORT "
"format.",
)
parser.add_argument(
"--port",
default=None,
type=int,
help="Port where --monitor will listen. Only applies to --monitor "
"process, --worker process should still use --monitor-address to "
"specify the monitor address. Default: random port.",
)
parser.add_argument(
"--num-workers",
default=2,
type=int,
help="Number of workers to start in single-node mode, or number of "
"workers that --monitor process will wait to connect before starting "
"transfers between workers. Default: 2.",
)
parser.add_argument(
"--endpoints-per-worker",
default=1,
type=int,
help="Number of simultaneous endpoints between each worker pair that "
"will send and receive data. In a case where --num-workers=2, this "
"translates to Worker1 creating two endpoints connecting to the "
"listener on Worker2, with Worker2 creating another two endpoints "
"connecting to the listener on Worker1, totalling four endpoint pairs "
"sending and receiving benchmark data simultaneously. Default: 1.",
)
parser.add_argument(
"--communication-lib",
default="ucx",
type=str,
help="Communication library to benchmark. Options are "
"'ucx', 'asyncio', 'tornado', 'uvloop'. Default: 'ucx'.",
)
parser.add_argument(
"--size",
metavar="BYTES",
default="1 MiB",
type=parse_bytes,
help="Size to be passed for data generation function. Default: '1 Mb'.",
)
parser.add_argument(
"--iterations",
default=15,
type=int,
help="Number of iterations of data transfers per worker pair. "
"Each iteration consists in sending and receiving the same "
"data amount. Default: 15.",
)
parser.add_argument(
"--gather-send-recv",
default=False,
action="store_true",
help="If disabled (default), send and receive operations will "
"be awaited individually, otherwise they are launched "
"simultaneously via an asyncio.gather or gen.multi operation. "
"Default: disabled.",
)

args = parser.parse_args()
if args.worker and args.monitor:
raise RuntimeError("--monitor and --worker can't be defined together.")
return args


def main():
args = parse_args()

num_workers = args.num_workers
endpoints_per_worker = args.endpoints_per_worker
listener_address = ucp.get_address(ifname=args.listen_interface)
if args.monitor_address is not None:
monitor_address, monitor_port = args.monitor_address.split(":")
monitor_port = int(monitor_port)

if args.communication_lib == "ucx":
communication_func = ucx_process
elif args.communication_lib == "asyncio":
communication_func = asyncio_process
elif args.communication_lib == "uvloop":
communication_func = uvloop_process
elif args.communication_lib == "tornado":
communication_func = tornado_process
else:
raise ValueError(
f"Communication library {args.communication_lib} not supported"
)

if args.monitor is True:
communication_func(
listener_address,
num_workers,
endpoints_per_worker,
True,
args.port,
args.size,
args.iterations,
args.gather_send_recv,
shm_sync=False,
)
elif args.worker is True:
communication_func(
listener_address,
num_workers,
endpoints_per_worker,
False,
monitor_port,
args.size,
args.iterations,
args.gather_send_recv,
shm_sync=False,
)
else:
ctx = multiprocessing.get_context("spawn")

signal = ctx.Array("i", [0, 0])
ports = ctx.Array("i", range(num_workers))
lock = ctx.Lock()

monitor_port = 0

if args.enable_monitor:
monitor_process = ctx.Process(
name="worker",
target=communication_func,
args=[
listener_address,
num_workers,
endpoints_per_worker,
True,
0,
args.size,
args.iterations,
args.gather_send_recv,
],
kwargs={
"shm_sync": True,
"signal": signal,
"ports": ports,
"lock": lock,
},
)
monitor_process.start()

while signal[0] == 0:
pass

monitor_port = signal[0]

worker_processes = []
for worker_num in range(num_workers):
worker_process = ctx.Process(
name="worker",
target=communication_func,
args=[
listener_address,
num_workers,
endpoints_per_worker,
False,
monitor_port,
args.size,
args.iterations,
args.gather_send_recv,
],
kwargs={
"shm_sync": not args.enable_monitor,
"signal": signal,
"ports": ports,
"lock": lock,
},
)
worker_process.start()
worker_processes.append(worker_process)

for worker_process in worker_processes:
worker_process.join()

if args.enable_monitor:
monitor_process.join()

assert worker_process.exitcode == 0


if __name__ == "__main__":
main()
104 changes: 104 additions & 0 deletions tests/test_multiple_processes_all_to_all.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import multiprocessing

import pytest
from utils_all_to_all import (
asyncio_process,
tornado_process,
ucx_process,
uvloop_process,
)

import ucp


def _test_send_recv_cu(
num_workers, endpoints_per_worker, enable_monitor, size, iterations, communication
):
ctx = multiprocessing.get_context("spawn")

listener_address = ucp.get_address()
monitor_port = 0

signal = ctx.Array("i", [0, 0])
ports = ctx.Array("i", range(num_workers))
lock = ctx.Lock()

if enable_monitor:
monitor_process = ctx.Process(
name="worker",
target=communication,
args=[
listener_address,
num_workers,
endpoints_per_worker,
True,
0,
size,
iterations,
False,
],
kwargs={"shm_sync": True, "signal": signal, "ports": ports, "lock": lock},
)
monitor_process.start()

while signal[0] == 0:
pass

monitor_port = signal[0]

worker_processes = []
for worker_num in range(num_workers):
worker_process = ctx.Process(
name="worker",
target=communication,
args=[
listener_address,
num_workers,
endpoints_per_worker,
False,
monitor_port,
size,
iterations,
False,
],
kwargs={
"shm_sync": not enable_monitor,
"signal": signal,
"ports": ports,
"lock": lock,
},
)
worker_process.start()
worker_processes.append(worker_process)

for worker_process in worker_processes:
worker_process.join()

if enable_monitor:
monitor_process.join()

assert worker_process.exitcode == 0


@pytest.mark.parametrize("num_workers", [2, 4, 8])
@pytest.mark.parametrize("endpoints_per_worker", [1])
@pytest.mark.parametrize("enable_monitor", [True, False])
@pytest.mark.parametrize("size", [2 ** 20])
@pytest.mark.parametrize("iterations", [5])
@pytest.mark.parametrize(
"communication", [ucx_process, asyncio_process, uvloop_process, tornado_process]
)
def test_send_recv_cu(
num_workers, endpoints_per_worker, enable_monitor, size, iterations, communication
):
if communication == uvloop_process:
pytest.importorskip("uvloop", reason="uvloop not installed")

_test_send_recv_cu(
num_workers,
endpoints_per_worker,
enable_monitor,
size,
iterations,
communication,
)
Loading