Skip to content

Commit fed9ee4

Browse files
committed
Merge pull request #1847 from rapidsai/branch-21.10
2 parents e03e008 + 54b8573 commit fed9ee4

551 files changed

Lines changed: 20627 additions & 12246 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ datasets/*
7676
cpp/doxygen/html
7777

7878
# Raft symlink
79-
python/cugraph/raft
79+
python/cugraph/cugraph/raft
80+
python/pylibcugraph/pylibcugraph/raft
8081
python/_external_repositories/
8182

8283
# created by Dask tests
8384
python/dask-worker-space
85+
86+
# Sphinx docs & build artifacts
87+
docs/cugraph/source/api_docs/api/*

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# cuGraph 21.10.00 (Date TBD)
2+
3+
Please see https://github.com/rapidsai/cugraph/releases/tag/v21.10.00a for the latest changes to this development branch.
4+
15
# cuGraph 21.08.00 (4 Aug 2021)
26

37
## 🚨 Breaking Changes

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ As of Release 21.08 - including 21.08 nightly
8383
| Traversal | | | |
8484
| | Breadth First Search (BFS) | Multi-GPU | with cutoff support <br/> [C++ README](cpp/src/traversal/README.md#BFS) |
8585
| | Single Source Shortest Path (SSSP) | Multi-GPU | [C++ README](cpp/src/traversal/README.md#SSSP) |
86-
| | Traveling Salesperson Problem (TSP) | Single-GPU | |
8786
| Tree | | | |
8887
| | Minimum Spanning Tree | Single-GPU | |
8988
| | Maximum Spanning Tree | Single-GPU | |

SOURCEBUILD.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ conda env create --name cugraph_dev --file conda/environments/cugraph_dev_cuda11
4949
# for CUDA 11.2
5050
conda env create --name cugraph_dev --file conda/environments/cugraph_dev_cuda11.2.yml
5151

52+
# for CUDA 11.4
53+
conda env create --name cugraph_dev --file conda/environments/cugraph_dev_cuda11.4.yml
54+
55+
5256
# activate the environment
5357
conda activate cugraph_dev
5458

benchmarks/python_e2e/README.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# cuGraph benchmarks
2+
3+
## Overview
4+
5+
The sources are currently intended to benchmark `cuGraph` via the python API,
6+
but future updates may include benchmarks written in C++ or other languages.
7+
8+
The benchmarks here use datasets generated by the RMAT graph generator but also
9+
support csv files as input.
10+
11+
## Prerequisites
12+
* cugraph built and installed (or `cugraph` sources and built C++ extensions
13+
available on `PYTHONPATH`)
14+
15+
* Multi-node multi-GPU (MNMG) runs require a cluster environment set up and a
16+
dask scheduler .json file be made accessible to all workers on every node.
17+
18+
* While not a strict prerequisite, the RAPIDS
19+
[multi-gpu-tools](https://github.com/rapidsai/multi-gpu-tools) package can be
20+
used to automate setting up a cluster for MNMG runs and will be referenced in
21+
the examples below.
22+
23+
## Single-node multi-GPU runs (SNMG)
24+
* Set the `CUDA_VISIBLE_DEVICES` environment variable to the number of GPUs to
25+
use for the benchmarks. If the env var is unset, it is assumed to be a
26+
single-GPU run. _Note: this is a slightly different usage for
27+
`CUDA_VISIBLE_DEVICES` compared to the standard documented use of this env
28+
var, where having it unset means "no restricted GPUs, use all of them"._
29+
30+
* Run `python main.py --help` for a list of all available benchmark options
31+
32+
* Run `python main.py` to run individual algo benchmarks with specific
33+
options. For example, to benchmark a 2-GPU run of BFS and SSSP with a
34+
generated graph of size scale 23:
35+
```
36+
(rapids) user@machine:/cugraph/benchmarks/python_e2e> export CUDA_VISIBLE_DEVICES=0,1
37+
38+
(rapids) user@machine:/cugraph/benchmarks/python_e2e> python main.py --scale=23 --algo=bfs --algo=sssp
39+
calling setup...distributed.preloading - INFO - Import preload module: dask_cuda.initialize
40+
distributed.preloading - INFO - Import preload module: dask_cuda.initialize
41+
done.
42+
running generate_edgelist (RMAT)...done.
43+
running from_dask_cudf_edgelist...done.
44+
running compute_renumber_edge_list...done.
45+
running compute_renumber_edge_list...done.
46+
running bfs (warmup)...done.
47+
running bfs...done.
48+
running sssp (warmup)...done.
49+
running sssp...done.
50+
from_dask_cudf_edgelist() 0.0133009
51+
------------------------------------------------------------
52+
bfs(start:73496080) 0.569328
53+
------------------------------------------------------------
54+
sssp(start:73496080) 1.48114
55+
56+
calling teardown...done.
57+
```
58+
59+
* See [run_all_nightly_benches.sh](run_all_nightly_benches.sh) for an example of
60+
multiple SNMG runs over different scales, gpu configurations and edgefactors
61+
62+
## Multi-node multi-GPU runs (MNMG)
63+
64+
* MNMG runs require a cluster of multi-GPU machines (nodes) that have access to
65+
the same files. Each node must use the same cugraph environment (using a
66+
shared conda environment is recommended).
67+
68+
* A dask scheduler must be running on one node in the cluster. The dask
69+
scheduler generates a JSON file (typically named `dask-scheduler.json`) which
70+
is intended to be read by the individual worker processes (see below) and the
71+
dask client, and is therefore recommended to be hosted on a shared file system
72+
that all other nodes in the cluster can access.
73+
74+
* dask workers must be running on each node in the cluster and passed the JSON
75+
file (likely named `dask-scheduler.json`, see above) generated by the
76+
scheduler.
77+
78+
* The dask client - the object created in the test/application process itself -
79+
must be configured to match the configuration of the scheduler and
80+
workers. This includes matching settings for UCX, NVLink, InfiniBand, etc. The
81+
client will also read the scheduler JSON file for information on how to
82+
communicate with the scheduler.
83+
84+
* The scheduler JSON file must be passed to the `main.py` script used to run the
85+
individual benchmarks by passing the `--dask-scheduler-file` option. See
86+
`python main.py --help` for more information.
87+
88+
* The [multi-gpu-tools](https://github.com/rapidsai/multi-gpu-tools) package can
89+
make setting up the cluster easier by providing the following scripts:
90+
91+
* `run-dask-process.sh` - starts either a dask scheduler, worker, or both on
92+
the machine it was run on. This script is typically launched using cluster
93+
job management software (eg. Slurm, LSF) to start the required dask
94+
processes on the nodes in the cluster. See the `--help` output for more
95+
information.
96+
97+
* `wait_for_workers.py` - this script is run on the node running the dask
98+
client, prior to the start of the test or benchmark script. This script does
99+
not return until the requested number of workers are up and running. See the
100+
`--help` output for more information.
101+
102+
* `default-config.sh` - not a runnable script, but a file that contains the
103+
default values for various dask-related configuration options (among other
104+
settings which may not be relevant and should be safe to ignore).
105+
106+
## Other Example Runs:
107+
_**NOTE: Some algos require the graph to be symmetrized (Louvain, WCC) or unweighted.**_
108+
* Run all the benchmarks with a generated dataset of scale=23
109+
```
110+
(rapids) user@machine:/cugraph/benchmarks/python_e2e> python main.py --scale=23
111+
```
112+
113+
* Run all the benchmarks with a generated unweighted dataset of scale=23
114+
```
115+
(rapids) user@machine:/cugraph/benchmarks/python_e2e> python main.py --scale=23 --unweighted
116+
```
117+
118+
* Symmetrize the generated dataset of scale=23 and run all the benchmarks
119+
```
120+
(rapids) user@machine:/cugraph/benchmarks/python_e2e> python main.py --scale=23 --symmetric-graph
121+
```
122+
123+
* Create a graph from a csv file an run all the benchmarks
124+
```
125+
(rapids) user@machine:/cugraph/benchmarks/python_e2e> python main.py --csv='karate.csv'
126+
```

benchmarks/python_e2e/benchmark.py

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Copyright (c) 2021, NVIDIA CORPORATION.
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
import sys
15+
import time
16+
from functools import wraps
17+
18+
19+
class BenchmarkedResult:
20+
"""
21+
Class to hold results (the return value of the callable being benchmarked
22+
and meta-data about the benchmarked function) of a benchmarked function run.
23+
"""
24+
def __init__(self, name, retval, runtime, params=None):
25+
self.name = name
26+
self.retval = retval
27+
self.runtime = runtime
28+
self.params = params or {}
29+
self.validator_result = True
30+
31+
32+
def benchmark(func):
33+
"""
34+
Returns a callable/closure that wraps func with code to time the func call
35+
and return a BenchmarkedResult. The resulting callable takes the same
36+
args/kwargs as func.
37+
38+
The BenchmarkedResult will have its params value assigned from the kwargs
39+
dictionary, but the func positional args are not captured. If a user needs
40+
the params captured for reporting purposes, they must use kwargs. This is
41+
useful since positional args can be used for args that would not be
42+
meaningful in a benchmark result as a param to the benchmark.
43+
44+
This can be used as a function decorator or a standalone function to wrap
45+
functions to benchmark.
46+
"""
47+
benchmark_name = getattr(func, "benchmark_name", func.__name__)
48+
@wraps(func)
49+
def benchmark_wrapper(*func_args, **func_kwargs):
50+
t1 = time.perf_counter()
51+
retval = func(*func_args, **func_kwargs)
52+
t2 = time.perf_counter()
53+
return BenchmarkedResult(name=benchmark_name,
54+
retval=retval,
55+
runtime=(t2-t1),
56+
params=func_kwargs,
57+
)
58+
59+
# Assign the name to the returned callable as well for use in debug prints,
60+
# etc.
61+
benchmark_wrapper.name = benchmark_name
62+
return benchmark_wrapper
63+
64+
65+
class BenchmarkRun:
66+
"""
67+
Represents a benchmark "run", which can be executed by calling the run()
68+
method, and results are saved as BenchmarkedResult instances in the results
69+
list member.
70+
"""
71+
def __init__(self,
72+
input_dataframe,
73+
construct_graph_func,
74+
algo_func_param_list,
75+
algo_validator_list=None
76+
):
77+
self.input_dataframe = input_dataframe
78+
79+
if type(construct_graph_func) is tuple:
80+
(construct_graph_func,
81+
self.construct_graph_func_args) = construct_graph_func
82+
else:
83+
self.construct_graph_func_args = None
84+
85+
# Create benchmark instances for each algo/func to be timed.
86+
# FIXME: need to accept and save individual algo args
87+
self.construct_graph = benchmark(construct_graph_func)
88+
89+
#add starting node to algos: BFS and SSSP
90+
for i, algo in enumerate (algo_func_param_list):
91+
if benchmark(algo).name in ["bfs", "sssp"]:
92+
param={}
93+
param["start"]=self.input_dataframe['src'].head()[0]
94+
algo_func_param_list[i]=(algo,)+(param,)
95+
96+
self.algos = []
97+
for item in algo_func_param_list:
98+
if type(item) is tuple:
99+
(algo, params) = item
100+
else:
101+
(algo, params) = (item, {})
102+
self.algos.append((benchmark(algo), params))
103+
104+
self.validators = algo_validator_list or [None] * len(self.algos)
105+
self.results = []
106+
107+
108+
@staticmethod
109+
def __log(s, end="\n"):
110+
print(s, end=end)
111+
sys.stdout.flush()
112+
113+
114+
def run(self):
115+
"""
116+
Run and time the graph construction step, then run and time each algo.
117+
"""
118+
self.results = []
119+
120+
self.__log(f"running {self.construct_graph.name}...", end="")
121+
result = self.construct_graph(self.input_dataframe,
122+
*self.construct_graph_func_args)
123+
self.__log("done.")
124+
G = result.retval
125+
self.results.append(result)
126+
127+
#algos with transposed=True : PageRank, Katz
128+
#algos with transposed=False: BFS, SSSP, Louvain
129+
for i in range(len(self.algos)):
130+
if self.algos[i][0].name in ["pagerank", "katz"]: #set transpose=True when renumbering
131+
if self.algos[i][0].name == "katz" and self.construct_graph.name == "from_dask_cudf_edgelist":
132+
largest_out_degree = G.out_degree().compute().\
133+
nlargest(n=1, columns="degree") #compute outdegree before renumbering because outdegree has transpose=False
134+
largest_out_degree = largest_out_degree["degree"].iloc[0]
135+
katz_alpha = 1 / (largest_out_degree + 1)
136+
self.algos[i][1]["alpha"] = katz_alpha
137+
elif self.algos[i][0].name == "katz" and self.construct_graph.name == "from_cudf_edgelist":
138+
largest_out_degree = G.out_degree().nlargest(n=1, columns="degree")
139+
largest_out_degree = largest_out_degree["degree"].iloc[0]
140+
katz_alpha = 1 / (largest_out_degree + 1)
141+
self.algos[i][1]["alpha"] = katz_alpha
142+
if hasattr(G, "compute_renumber_edge_list"):
143+
G.compute_renumber_edge_list(transposed=True)
144+
else: #set transpose=False when renumbering
145+
self.__log("running compute_renumber_edge_list...", end="")
146+
if hasattr(G, "compute_renumber_edge_list"):
147+
G.compute_renumber_edge_list(transposed=False)
148+
self.__log("done.")
149+
# FIXME: need to handle individual algo args
150+
for ((algo, params), validator) in zip(self.algos, self.validators):
151+
self.__log(f"running {algo.name} (warmup)...", end="")
152+
algo(G, **params)
153+
self.__log("done.")
154+
self.__log(f"running {algo.name}...", end="")
155+
result = algo(G, **params)
156+
self.__log("done.")
157+
158+
if validator:
159+
result.validator_result = validator(result.retval, G)
160+
161+
self.results.append(result)
162+
# Reclaim memory since computed algo result is no longer needed
163+
result.retval = None
164+
165+
return False not in [r.validator_result for r in self.results]

0 commit comments

Comments
 (0)