Skip to content
Draft
Show file tree
Hide file tree
Changes from 15 commits
Commits
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
48 changes: 45 additions & 3 deletions .azure-pipelines/multi-nodes-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,54 @@ jobs:

- template: templates/run-remote-task.yml
parameters:
name: RunMscclppTest
displayName: Run multi-nodes mscclpp-test
name: RunBenchCollectiveAllreduceTest
displayName: Run multi-nodes bench_collective DSL allreduce
continueOnError: true
runRemoteArgs: '--hostfile $(System.DefaultWorkingDirectory)/test/deploy/hostfile --host ${{ parameters.vmssName }}000000 --user azureuser'
remoteScript: |
bash /root/mscclpp/test/deploy/run_tests.sh mscclpp-test
set -e
mpirun --allow-run-as-root --bind-to numa -hostfile /root/mscclpp/test/deploy/hostfile_mpi \
-mca btl_tcp_if_include eth0 -np 16 -npernode 8 \
-x MSCCLPP_DEBUG=WARN -x MSCCLPP_SOCKET_IFNAME=eth0 -x MSCCLPP_HOME=/root/mscclpp \
-x LD_LIBRARY_PATH=/root/mscclpp/build/lib:$LD_LIBRARY_PATH -x PATH=$PATH \
-x PYTHONPATH=/root/mscclpp/python \
python3 -m mscclpp_benchmark.bench_collective \
--collective allreduce --autotune --enable-dsl --buffer-mode in-place \
--d-model 5120 --batch-sizes 8,16,32 --dsl-tbg 1,2 --dsl-tpb 512,1024

- template: templates/run-remote-task.yml
parameters:
name: RunBenchCollectiveAllgatherTest
displayName: Run multi-nodes bench_collective DSL allgather
continueOnError: true
runRemoteArgs: '--hostfile $(System.DefaultWorkingDirectory)/test/deploy/hostfile --host ${{ parameters.vmssName }}000000 --user azureuser'
remoteScript: |
set -e
mpirun --allow-run-as-root --bind-to numa -hostfile /root/mscclpp/test/deploy/hostfile_mpi \
-mca btl_tcp_if_include eth0 -np 16 -npernode 8 \
-x MSCCLPP_DEBUG=WARN -x MSCCLPP_SOCKET_IFNAME=eth0 -x MSCCLPP_HOME=/root/mscclpp \
-x LD_LIBRARY_PATH=/root/mscclpp/build/lib:$LD_LIBRARY_PATH -x PATH=$PATH \
-x PYTHONPATH=/root/mscclpp/python \
python3 -m mscclpp_benchmark.bench_collective \
--collective allgather --autotune --enable-dsl --buffer-mode in-place \
--d-model 5120 --batch-sizes 8,16,32 --dsl-tbg 1,2 --dsl-tpb 512,1024

- template: templates/run-remote-task.yml
parameters:
name: RunBenchCollectiveReduceScatterTest
displayName: Run multi-nodes bench_collective DSL reducescatter
continueOnError: true
runRemoteArgs: '--hostfile $(System.DefaultWorkingDirectory)/test/deploy/hostfile --host ${{ parameters.vmssName }}000000 --user azureuser'
remoteScript: |
set -e
mpirun --allow-run-as-root --bind-to numa -hostfile /root/mscclpp/test/deploy/hostfile_mpi \
-mca btl_tcp_if_include eth0 -np 16 -npernode 8 \
-x MSCCLPP_DEBUG=WARN -x MSCCLPP_SOCKET_IFNAME=eth0 -x MSCCLPP_HOME=/root/mscclpp \
-x LD_LIBRARY_PATH=/root/mscclpp/build/lib:$LD_LIBRARY_PATH -x PATH=$PATH \
-x PYTHONPATH=/root/mscclpp/python \
python3 -m mscclpp_benchmark.bench_collective \
--collective reducescatter --autotune --enable-dsl --buffer-mode in-place \
--d-model 5120 --batch-sizes 8,16,32 --dsl-tbg 1,2 --dsl-tpb 512,1024

- template: templates/run-remote-task.yml
parameters:
Expand Down
34 changes: 34 additions & 0 deletions python/mscclpp/default_algos/reducescatter_multi_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,37 @@ def reducescatter_multi_nodes(
)

return prog


if __name__ == "__main__":
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--name", type=str, help="name of the program")
parser.add_argument("--num_gpus", type=int, help="total number of gpus")
parser.add_argument("--gpus_per_node", type=int, help="number of gpus per node")
parser.add_argument("--tbg", type=int, default=1, help="thread block group size")
parser.add_argument("--num_threads_per_block", type=int, default=1024, help="number of threads per block")
parser.add_argument("--min_message_size", type=int, default=0, help="minimum message size")
parser.add_argument("--max_message_size", type=int, default=2**64 - 1, help="maximum message size")

args = parser.parse_args()

spec = AlgoSpec(
name=args.name,
collective=ReduceScatter(args.num_gpus, 1, True),
nranks_per_node=args.gpus_per_node,
world_size=args.num_gpus,
in_place=True,
instances=1,
protocol="LL",
auto_sync=False,
num_threads_per_block=args.num_threads_per_block,
reuse_resources=True,
use_double_scratch_buffer=True,
min_message_size=args.min_message_size,
max_message_size=args.max_message_size,
)

prog = reducescatter_multi_nodes(spec, args.tbg)
print(prog.to_json())
85 changes: 80 additions & 5 deletions python/mscclpp_benchmark/bench_collective.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

_mscclpp_module = None

from mscclpp_benchmark.comm import Comm
from mscclpp_benchmark.comm import DEFAULT_DSL_TBG, DEFAULT_DSL_TPB, Comm
from mscclpp_benchmark.correctness import (
CorrectnessStats,
check_correctness as _check_correctness,
Expand All @@ -24,6 +24,7 @@

_ALLREDUCE = "allreduce"
_ALLGATHER = "allgather"
_REDUCESCATTER = "reducescatter"
_DEFAULT_BATCH_SIZES = (
1,
2,
Expand Down Expand Up @@ -81,6 +82,14 @@ class CandidateSpec:
supported_skus: tuple[str, ...] | None = None
requires_nvls: bool = False
requires_symmetric_memory: bool = False
# Native algorithms use all-pairs CUDA-IPC and only work within a single node; they hang if
# tuned on a multi-node job. Only algorithms that explicitly opt in are considered when the
# world spans more than one node.
supports_multi_node: bool = False
# None means "use the tuner's global sweep"; an explicit tuple overrides it, which DSL
# algorithms need since they bake their launch geometry into the plan and ignore nblocks/nthreads.
candidate_nblocks: tuple[int, ...] | None = None
candidate_nthreads: tuple[int, ...] | None = None


@dataclass
Expand Down Expand Up @@ -255,14 +264,52 @@ def _candidate_specs(collective: str, *, symmetric_memory: bool = False) -> tupl
return candidates


def _dsl_candidate_specs(comm: Comm, collective: str) -> tuple[CandidateSpec, ...]:
"""Synthesize candidate specs for the DSL algorithms compiled by Comm.

Unlike the native algorithms, DSL variant names are only known at runtime, so their specs cannot
be listed statically. Each variant reports its own applicable message size range, which the usual
message size filter then applies, and pins the tuner to a single launch config because the plan
already bakes in its launch geometry.
"""
available = comm.algorithms.get(collective, {})
specs: list[CandidateSpec] = []
for name in sorted(getattr(comm, "dsl_algorithms", ())):
algorithm = available.get(name)
if algorithm is None:
continue
min_message_size, max_message_size = algorithm.message_size_range
Comment thread
Empyreus marked this conversation as resolved.
specs.append(
CandidateSpec(
name,
min_message_size=min_message_size,
max_message_size=max_message_size,
candidate_nblocks=(0,),
candidate_nthreads=(0,),
supports_multi_node=True,
)
)
return tuple(specs)


def _candidate_algorithms(comm: Comm, case: BenchmarkCase) -> list[tuple[Any, CandidateSpec]]:
available = comm.algorithms.get(case.collective, {})
candidates: list[tuple[Any, CandidateSpec]] = []
seen: set[str] = set()
symmetric_memory = case.symmetric_memory
profile = getattr(comm, "hardware_profile", None)
comm_group = comm.comm_group
nranks = getattr(comm_group, "nranks", 1) or 1
nranks_per_node = getattr(comm_group, "nranks_per_node", nranks) or nranks
n_nodes = nranks // nranks_per_node if nranks_per_node else 1
Comment thread
Empyreus marked this conversation as resolved.
filtered_out = False
for candidate in _candidate_specs(case.collective, symmetric_memory=symmetric_memory):
for candidate in (
*_candidate_specs(case.collective, symmetric_memory=symmetric_memory),
*_dsl_candidate_specs(comm, case.collective),
):
Comment thread
Empyreus marked this conversation as resolved.
if n_nodes > 1 and not candidate.supports_multi_node:
filtered_out = True
continue
Comment on lines +315 to +317
if not _candidate_supports_profile(candidate, profile):
filtered_out = True
continue
Expand Down Expand Up @@ -334,9 +381,24 @@ def _make_case(
symmetric_memory=symmetric_memory,
)

if collective == _REDUCESCATTER:
# The DSL reducescatter is compiled in-place, so the per-rank output chunk always aliases the
# matching slice of the full input buffer (mirrors python/test/executor_test.py build_bufs).
input_buffer = _mscclpp().GpuBuffer(nelems * comm_group.nranks, dtype=dtype_spec.cupy_dtype)
start = comm_group.my_rank * nelems
output = input_buffer[start : start + nelems]
Comment thread
Empyreus marked this conversation as resolved.
return BenchmarkCase(
collective=collective,
message_size=output.nbytes,
total_size=input_buffer.nbytes,
Comment on lines +408 to +411
input=input_buffer,
output=output,
dtype_spec=dtype_spec,
symmetric_memory=symmetric_memory,
)

if collective != _ALLGATHER:
raise ValueError(f"Unsupported collective: {collective}")

if buffer_mode == "in-place":
output = _mscclpp().GpuBuffer(nelems * comm_group.nranks, dtype=dtype_spec.cupy_dtype)
start = comm_group.my_rank * nelems
Expand Down Expand Up @@ -441,7 +503,7 @@ def _busbw_factor(collective: str, nranks: int) -> float:
return 1.0
if collective == _ALLREDUCE:
return 2 * (nranks - 1) / nranks
if collective == _ALLGATHER:
if collective in (_ALLGATHER, _REDUCESCATTER):
return (nranks - 1) / nranks
raise ValueError(f"Unsupported collective: {collective}")

Expand Down Expand Up @@ -470,7 +532,7 @@ def _format_mismatches(stats: CorrectnessStats | None) -> str:

def _build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Benchmark MSCCL++ collectives without PyTorch dependencies")
parser.add_argument("--collective", choices=(_ALLREDUCE, _ALLGATHER), default=_ALLREDUCE)
parser.add_argument("--collective", choices=(_ALLREDUCE, _ALLGATHER, _REDUCESCATTER), default=_ALLREDUCE)
parser.add_argument("--d-model", type=int, default=5120)
parser.add_argument("--dtype", default="float16")
parser.add_argument("--accum-type", help="Accumulation type for reductions: native, float16, or float32")
Expand All @@ -495,6 +557,13 @@ def _build_parser() -> argparse.ArgumentParser:
parser.add_argument("--tune-iterations", type=int, default=20)
parser.add_argument("--candidate-nblocks", help="Comma-separated nblocks tuning candidates")
parser.add_argument("--candidate-nthreads", help="Comma-separated nthreads tuning candidates")
parser.add_argument(
"--enable-dsl",
action="store_true",
help="Compile DSL algorithm variants and tune them alongside the native algorithms",
)
parser.add_argument("--dsl-tbg", help="Comma-separated DSL thread_block_group_size candidates")
parser.add_argument("--dsl-tpb", help="Comma-separated DSL num_threads_per_block candidates")
parser.add_argument("--symmetric-memory", action="store_true")
return parser

Expand Down Expand Up @@ -533,6 +602,8 @@ def main(argv: list[str] | None = None) -> None:
batch_sizes = _parse_int_list(args.batch_sizes, _DEFAULT_BATCH_SIZES)
candidate_nblocks = _parse_int_list(args.candidate_nblocks, _DEFAULT_CANDIDATE_NBLOCKS)
candidate_nthreads = _parse_int_list(args.candidate_nthreads, _DEFAULT_CANDIDATE_NTHREADS)
dsl_tbg = _parse_int_list(args.dsl_tbg, DEFAULT_DSL_TBG)
dsl_tpb = _parse_int_list(args.dsl_tpb, DEFAULT_DSL_TPB)

comm_group = _mscclpp().CommGroup(MPI.COMM_WORLD)
setattr(comm_group, "_mpi_comm", MPI.COMM_WORLD)
Expand All @@ -543,6 +614,10 @@ def main(argv: list[str] | None = None) -> None:
config_store=config_store,
hardware_profile=hardware_profile,
scratch_buffer_size=args.scratch_buffer_size,
collective=args.collective,
enable_dsl=args.enable_dsl,
dsl_tbg=dsl_tbg,
dsl_tpb=dsl_tpb,
)
tuner = OfflineTuner(
comm,
Expand Down
Loading
Loading