diff --git a/.gitignore b/.gitignore
index 2fea1022910..9480c2618bf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -78,9 +78,6 @@ datasets/*
!datasets/karate-disjoint.csv
!datasets/netscience.csv
-# nx-cugraph side effects
-python/nx-cugraph/objects.inv
-
.pydevproject
# Jupyter Notebooks
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b5fbcf9ad42..4bb037b5fda 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -19,7 +19,6 @@ repos:
language_version: python3
args: [--target-version=py310]
files: ^(python/.*|benchmarks/.*)$
- exclude: ^python/nx-cugraph/
- repo: https://github.com/PyCQA/flake8
rev: 7.1.1
hooks:
@@ -59,23 +58,3 @@ repos:
hooks:
- id: rapids-dependency-file-generator
args: ["--clean"]
- - repo: local
- hooks:
- - id: nx-cugraph-meta-data-update
- name: nx-cugraph meta-data updater
- entry: bash -c "PYTHONPATH=./python/nx-cugraph python ./python/nx-cugraph/_nx_cugraph/__init__.py"
- files: ^python/nx-cugraph/
- types: [python]
- language: python
- pass_filenames: false
- additional_dependencies: ["networkx>=3.4"]
- - repo: local
- hooks:
- - id: nx-cugraph-readme-update
- name: nx-cugraph README updater
- entry: bash -c "PYTHONPATH=./python/nx-cugraph python ./python/nx-cugraph/scripts/update_readme.py ./python/nx-cugraph/README.md"
- files: ^python/nx-cugraph/
- types_or: [python, markdown]
- language: python
- pass_filenames: false
- additional_dependencies: ["networkx>=3.4"]
diff --git a/README.md b/README.md
index 8026e4feb64..e41caec17b0 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@
-----
## News
-___NEW!___ _[nx-cugraph](./python/nx-cugraph/README.md)_, a NetworkX backend that provides GPU acceleration to NetworkX with zero code change.
+___NEW!___ _[nx-cugraph](https://rapids.ai/nx-cugraph/)_, a NetworkX backend that provides GPU acceleration to NetworkX with zero code change.
```
> pip install nx-cugraph-cu11 --extra-index-url https://pypi.nvidia.com
> export NETWORKX_AUTOMATIC_BACKENDS=cugraph
@@ -62,7 +62,7 @@ That's it. NetworkX now leverages cuGraph for accelerated graph algorithms.
- [External Data Types](./readme_pages/data_types.md)
- [pylibcugraph](./readme_pages/pylibcugraph.md)
- [libcugraph (C/C++/CUDA)](./readme_pages/libcugraph.md)
- - [nx-cugraph](./python/nx-cugraph/README.md)
+ - [nx-cugraph](https://rapids.ai/nx-cugraph/)
- [cugraph-service](./readme_pages/cugraph_service.md)
- [cugraph-dgl](./readme_pages/cugraph_dgl.md)
- [cugraph-ops](./readme_pages/cugraph_ops.md)
@@ -127,7 +127,7 @@ df_page.sort_values('pagerank', ascending=False).head(10)
* ArangoDB - a free and open-source native multi-model database system - https://www.arangodb.com/
* CuPy - "NumPy/SciPy-compatible Array Library for GPU-accelerated Computing with Python" - https://cupy.dev/
* Memgraph - In-memory Graph database - https://memgraph.com/
-* NetworkX (via [nx-cugraph](./python/nx-cugraph/README.md) backend) - an extremely popular, free and open-source package for the creation, manipulation, and study of the structure, dynamics, and functions of complex networks - https://networkx.org/
+* NetworkX (via [nx-cugraph](https://rapids.ai/nx-cugraph/) backend) - an extremely popular, free and open-source package for the creation, manipulation, and study of the structure, dynamics, and functions of complex networks - https://networkx.org/
* PyGraphistry - free and open-source GPU graph ETL, AI, and visualization, including native RAPIDS & cuGraph support - http://github.com/graphistry/pygraphistry
* ScanPy - a scalable toolkit for analyzing single-cell gene expression data - https://scanpy.readthedocs.io/en/stable/
diff --git a/benchmarks/nx-cugraph/pytest-based/README.md b/benchmarks/nx-cugraph/pytest-based/README.md
deleted file mode 100644
index 414a22171a0..00000000000
--- a/benchmarks/nx-cugraph/pytest-based/README.md
+++ /dev/null
@@ -1,49 +0,0 @@
-## `nx-cugraph` Benchmarks
-
-### Overview
-
-This directory contains a set of scripts designed to benchmark NetworkX with the `nx-cugraph` backend and deliver a report that summarizes the speed-up and runtime deltas over default NetworkX.
-
-Our current benchmarks provide the following datasets:
-
-| Dataset | Nodes | Edges | Directed |
-| -------- | ------- | ------- | ------- |
-| netscience | 1,461 | 5,484 | Yes |
-| email-Eu-core | 1,005 | 25,571 | Yes |
-| amazon0302 | 262,111 | 1,234,877 | Yes |
-| cit-Patents | 3,774,768 | 16,518,948 | Yes |
-| hollywood | 1,139,905 | 57,515,616 | No |
-| soc-LiveJournal1 | 4,847,571 | 68,993,773 | Yes |
-
-
-
-### Scripts
-
-#### 1. `run-main-benchmarks.sh`
-This script allows users to run a small set of commonly-used algorithms across multiple datasets and backends. All results are stored inside a sub-directory (`logs/`) and output files are named based on the combination of parameters for that benchmark.
-
-NOTE:
- - If running with all algorithms and datasets using NetworkX without an accelerated backend, this script may take a few hours to finish running.
- - The `betweenness_centrality` benchmark will run with values `[10, 20, 50, 100, 500, 1000]` by default. You can specify only specific k-values to be run by editing `bc_k_values` (line 46) to be passed as a [pytest keyword object](https://docs.pytest.org/en/6.2.x/usage.html#specifying-tests-selecting-tests).
-
-**Usage:**
- - Run with `--cpu-only`:
- ```bash
- ./run-main-benchmarks.sh --cpu-only
- ```
- - Run with `--gpu-only`:
- ```bash
- ./run-main-benchmarks.sh --gpu-only
- ```
- - Run without any arguments (all backends):
- ```bash
- ./run-main-benchmarks.sh
- ```
-
-#### 2. `create_results_summary_page.py`
-This script is designed to be run after `run-main-benchmarks.sh` in order to generate an HTML page displaying a results table comparing default NetworkX to nx-cugraph. The script also provides information about the current system, so it should be run on the machine on which benchmarks were run.
-
-**Usage:**
- ```bash
- python create_results_summary_page.py > report.html
- ```
diff --git a/benchmarks/nx-cugraph/pytest-based/bench_algos.py b/benchmarks/nx-cugraph/pytest-based/bench_algos.py
deleted file mode 100644
index 8852ed2a875..00000000000
--- a/benchmarks/nx-cugraph/pytest-based/bench_algos.py
+++ /dev/null
@@ -1,985 +0,0 @@
-# Copyright (c) 2023-2024, NVIDIA CORPORATION.
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import random
-
-import networkx as nx
-import pandas as pd
-import pytest
-from cugraph import datasets
-import nx_cugraph as nxcg
-
-# Attempt to import the NetworkX dispatching module, which is only needed when
-# testing with NX <3.2 in order to dynamically switch backends. NX >=3.2 allows
-# the backend to be specified directly in the API call.
-try:
- from networkx.classes import backends # NX <3.2
-except ImportError:
- backends = None
-
-
-################################################################################
-# Fixtures and params
-
-# See https://pytest-benchmark.readthedocs.io/en/latest/glossary.html for how
-# these variables are used.
-rounds = 1
-iterations = 1
-warmup_rounds = 1
-
-# FIXME: Add this to cugraph.datasets. This is done here so these benchmarks
-# can be run without requiring an updated cugraph install. This temporarily
-# adds a dataset based on an Amazon product co-purchasing network.
-amazon0302_metadata = """
-name: amazon0302
-description:
- Network was collected by crawling Amazon website. It is based on Customers Who Bought This Item Also Bought feature of the Amazon website. If a product i is frequently co-purchased with product j, the graph contains a directed edge from i to j. The data was collected in March 02 2003.
-author: J. Leskovec, L. Adamic and B. Adamic
-refs: J. Leskovec, L. Adamic and B. Adamic. The Dynamics of Viral Marketing. ACM Transactions on the Web (ACM TWEB), 1(1), 2007.
-delim: "\t"
-header: 3
-col_names:
- - FromNodeId
- - ToNodeId
-col_types:
- - int32
- - int32
-has_loop: false
-is_directed: true
-is_multigraph: false
-is_symmetric: false
-number_of_edges: 1234877
-number_of_nodes: 262111
-url: https://snap.stanford.edu/data/amazon0302.txt.gz
-"""
-amazon0302_metadata_file_name = datasets.default_download_dir.path / "amazon0302.yaml"
-if not amazon0302_metadata_file_name.exists():
- amazon0302_metadata_file_name.parent.mkdir(parents=True, exist_ok=True)
- with open(amazon0302_metadata_file_name, "w") as f:
- f.write(amazon0302_metadata)
-
-amazon0302_dataset = datasets.Dataset(amazon0302_metadata_file_name)
-amazon0302_dataset.metadata["file_type"] = ".gz"
-
-dataset_param_values = [
- # name: karate, nodes: 34, edges: 156
- pytest.param(datasets.karate, marks=[pytest.mark.small, pytest.mark.undirected]),
- # name: netscience, nodes: 1461, edges: 5484
- pytest.param(datasets.netscience, marks=[pytest.mark.small, pytest.mark.directed]),
- # name: email-Eu-core, nodes: 1005, edges: 25571
- pytest.param(
- datasets.email_Eu_core, marks=[pytest.mark.small, pytest.mark.directed]
- ),
- # name: amazon0302, nodes: 262111, edges: 1234877
- pytest.param(amazon0302_dataset, marks=[pytest.mark.medium, pytest.mark.directed]),
- # name: cit-Patents, nodes: 3774768, edges: 16518948
- pytest.param(
- datasets.cit_patents, marks=[pytest.mark.medium, pytest.mark.directed]
- ),
- # name: hollywood, nodes: 1139905, edges: 57515616
- pytest.param(
- datasets.hollywood, marks=[pytest.mark.medium, pytest.mark.undirected]
- ),
- # name: soc-LiveJournal1, nodes: 4847571, edges: 68993773
- pytest.param(
- datasets.soc_livejournal, marks=[pytest.mark.medium, pytest.mark.directed]
- ),
- # name: europe_osm, nodes: 50912018, edges: 54054660
- pytest.param(
- datasets.europe_osm, marks=[pytest.mark.large, pytest.mark.undirected]
- ),
-]
-
-backend_param_values = ["cugraph", "cugraph-preconverted", None]
-
-
-def setup_module(module):
- """
- Trivial conversion call to force various one-time CUDA initialization
- operations to happen outside of benchmarks.
- """
- G = nx.karate_club_graph()
- nxcg.from_networkx(G)
-
-
-# Test IDs are generated using the lambda assigned to the ids arg to provide an
-# easier-to-read name. This is especially helpful for Dataset objs (see
-# https://docs.pytest.org/en/stable/reference/reference.html#pytest-fixture)
-@pytest.fixture(
- scope="module", params=dataset_param_values, ids=lambda ds: f"ds={str(ds)}"
-)
-def graph_obj(request):
- """
- Returns a NX Graph or DiGraph obj from the dataset instance parameter.
- """
- dataset = request.param
- return nx_graph_from_dataset(dataset)
-
-
-@pytest.fixture(
- scope="module",
- params=backend_param_values,
- ids=lambda backend: f"backend={backend}",
-)
-def backend(request):
- """
- Returns the backend name to use. This is done as a fixture for consistency
- and simplicity when creating benchmarks (no need to mark the benchmark as
- parametrized).
- """
- return request.param
-
-
-################################################################################
-# Helpers
-def nx_graph_from_dataset(dataset_obj):
- """
- Read the dataset specified by the dataset_obj and create and return a
- nx.Graph or nx.DiGraph instance based on the dataset is_directed metadata.
- """
- create_using = nx.DiGraph if dataset_obj.metadata["is_directed"] else nx.Graph
- names = dataset_obj.metadata["col_names"]
- pandas_edgelist = dataset_obj.get_edgelist(download=True, reader="pandas")
- G = nx.from_pandas_edgelist(
- pandas_edgelist, source=names[0], target=names[1], create_using=create_using
- )
- return G
-
-
-def get_legacy_backend_wrapper(backend_name):
- """
- Returns a callable that wraps an algo function with either the default
- dispatcher (which dispatches based on input graph type), or the "testing"
- dispatcher (which autoconverts and unconditionally dispatches).
- This is only supported for NetworkX <3.2
- """
- backends.plugin_name = "cugraph"
- orig_dispatch = backends._dispatch
- testing_dispatch = backends.test_override_dispatch
-
- if backend_name == "cugraph":
- dispatch = testing_dispatch
- else:
- dispatch = orig_dispatch
-
- def wrap_callable_for_dispatch(func, exhaust_returned_iterator=False):
- # Networkx <3.2 registers functions when the dispatch decorator is
- # applied (called) and errors if re-registered, so clear bookkeeping to
- # allow it to be called repeatedly.
- backends._registered_algorithms = {}
- actual_func = dispatch(func) # returns the func the dispatcher picks
-
- def wrapper(*args, **kwargs):
- retval = actual_func(*args, **kwargs)
- if exhaust_returned_iterator:
- retval = list(retval)
- return retval
-
- return wrapper
-
- return wrap_callable_for_dispatch
-
-
-def get_backend_wrapper(backend_name):
- """
- Returns a callable that wraps an algo function in order to set the
- "backend" kwarg on it.
- This is only supported for NetworkX >= 3.2
- """
-
- def wrap_callable_for_dispatch(func, exhaust_returned_iterator=False):
- def wrapper(*args, **kwargs):
- kwargs["backend"] = backend_name
- retval = func(*args, **kwargs)
- if exhaust_returned_iterator:
- retval = list(retval)
- return retval
-
- return wrapper
-
- return wrap_callable_for_dispatch
-
-
-@pytest.fixture(
- scope="module",
- params=backend_param_values,
- ids=lambda backend: f"backend={backend}",
-)
-def backend_wrapper(request):
- """
- Returns a callable that takes a function algo and wraps it in another
- function that calls the algo using the appropriate backend.
-
- For example: if the backend to test is "cugraph", this will return a
- function that calls nx.pagerank(..., backend='cugraph')
- """
- backend_name = request.param
- actual_backend_name = backend_name
-
- # Special case: cugraph-preconverted may be specified as a backend but this
- # name is reserved to indicate a cugraph backend is to be used with a
- # preconverted graph obj (rather than having the backend do the
- # conversion).
- if backend_name == "cugraph-preconverted":
- actual_backend_name = "cugraph"
-
- # NX <3.2 does not support the backends= kwarg, so the backend must be
- # enabled differently
- if backends is not None:
- wrapper = get_legacy_backend_wrapper(actual_backend_name)
- else:
- wrapper = get_backend_wrapper(actual_backend_name)
-
- wrapper.backend_name = backend_name
- return wrapper
-
-
-def get_graph_obj_for_benchmark(graph_obj, backend_wrapper):
- """
- Given a Graph object and a backend name, return a converted Graph or the
- original Graph object based on the backend to use.
-
- This is needed because some backend names are actually used as descriptions
- for combinations of backends and converted/non-converted graphs. For
- example, a benchmark may specify the "cugraph-preconverted" backend, which
- is not an installed backend but instead refers to the "cugraph" backend
- passed a NX Graph that has been converted to a nx-cugraph Graph object.
- """
- G = graph_obj
- if backend_wrapper.backend_name == "cugraph-preconverted":
- G = nxcg.from_networkx(G, preserve_all_attrs=True)
- return G
-
-
-def get_highest_degree_node(graph_obj):
- degrees = graph_obj.degree() # list of tuples of (node, degree)
- return max(degrees, key=lambda t: t[1])[0]
-
-
-def build_personalization_dict(pagerank_dict):
- """
- Returns a dictionary that can be used as the personalization value for a
- call to nx.pagerank(). The pagerank_dict passed in is used as the initial
- source of values for each node, and this function simply treats the list of
- dict values as two halves (halves A and B) and swaps them so (most if not
- all) nodes/keys are assigned a different value from the dictionary.
- """
- num_half = len(pagerank_dict) // 2
- A_half_items = list(pagerank_dict.items())[:num_half]
- B_half_items = list(pagerank_dict.items())[num_half:]
-
- # Support an odd number of items by initializing with B_half_items, which
- # will always be one bigger if the number of items is odd. This will leave
- # the one remainder (in the case of an odd number) unchanged.
- pers_dict = dict(B_half_items)
- pers_dict.update({A_half_items[i][0]: B_half_items[i][1] for i in range(num_half)})
- pers_dict.update({B_half_items[i][0]: A_half_items[i][1] for i in range(num_half)})
-
- return pers_dict
-
-
-################################################################################
-# Benchmarks
-def bench_from_networkx(benchmark, graph_obj):
- benchmark(nxcg.from_networkx, graph_obj)
-
-
-# normalized_param_values = [True, False]
-normalized_param_values = [True]
-k_param_values = [10, 20, 50, 100, 500, 1000]
-
-
-@pytest.mark.parametrize(
- "normalized", normalized_param_values, ids=lambda norm: f"{norm=}"
-)
-@pytest.mark.parametrize("k", k_param_values, ids=lambda k: f"{k=}")
-def bench_betweenness_centrality(benchmark, graph_obj, backend_wrapper, normalized, k):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- if k > G.number_of_nodes():
- pytest.skip(reason=f"{k=} > {G.number_of_nodes()=}")
-
- result = benchmark.pedantic(
- target=backend_wrapper(nx.betweenness_centrality),
- args=(G,),
- kwargs=dict(
- weight=None,
- normalized=normalized,
- k=k,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-@pytest.mark.parametrize(
- "normalized", normalized_param_values, ids=lambda norm: f"{norm=}"
-)
-@pytest.mark.parametrize("k", k_param_values, ids=lambda k: f"{k=}")
-def bench_edge_betweenness_centrality(
- benchmark, graph_obj, backend_wrapper, normalized, k
-):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
-
- if k > G.number_of_nodes():
- pytest.skip(reason=f"{k=} > {G.number_of_nodes()=}")
-
- result = benchmark.pedantic(
- target=backend_wrapper(nx.edge_betweenness_centrality),
- args=(G,),
- kwargs=dict(
- weight=None,
- normalized=normalized,
- k=k,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-def bench_louvain_communities(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- # DiGraphs are not supported
- if G.is_directed():
- G = G.to_undirected()
- result = benchmark.pedantic(
- target=backend_wrapper(nx.community.louvain_communities),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is list
-
-
-def bench_degree_centrality(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.degree_centrality),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-def bench_eigenvector_centrality(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.eigenvector_centrality),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-@pytest.mark.parametrize(
- "normalized", normalized_param_values, ids=lambda norm: f"{norm=}"
-)
-def bench_hits(benchmark, graph_obj, backend_wrapper, normalized):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.hits),
- args=(G,),
- kwargs=dict(
- normalized=normalized,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is tuple
- assert len(result) == 2
- assert type(result[0]) is dict
- assert type(result[1]) is dict
-
-
-def bench_in_degree_centrality(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.in_degree_centrality),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-@pytest.mark.parametrize(
- "normalized", normalized_param_values, ids=lambda norm: f"{norm=}"
-)
-def bench_katz_centrality(benchmark, graph_obj, backend_wrapper, normalized):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.katz_centrality),
- args=(G,),
- kwargs=dict(
- normalized=normalized,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-def bench_k_truss(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- # DiGraphs are not supported
- if G.is_directed():
- G = G.to_undirected()
- result = benchmark.pedantic(
- target=backend_wrapper(nx.k_truss),
- args=(G,),
- kwargs=dict(
- k=2,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- # Check that this at least appears to be some kind of NX-like Graph
- assert hasattr(result, "has_node")
-
-
-def bench_out_degree_centrality(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.out_degree_centrality),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-def bench_pagerank(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.pagerank),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-def bench_pagerank_personalized(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
-
- # FIXME: This will run for every combination of inputs, even if the
- # graph/dataset does not change. Ideally this is run once per
- # graph/dataset.
- pagerank_dict = nx.pagerank(G)
- personalization_dict = build_personalization_dict(pagerank_dict)
-
- result = benchmark.pedantic(
- target=backend_wrapper(nx.pagerank),
- args=(G,),
- kwargs={"personalization": personalization_dict},
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-def bench_shortest_path(benchmark, graph_obj, backend_wrapper):
- """
- This passes in the source node with the highest degree, but no target.
- """
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
-
- result = benchmark.pedantic(
- target=backend_wrapper(nx.shortest_path),
- args=(G,),
- kwargs=dict(
- source=node,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-def bench_single_source_shortest_path_length(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
-
- result = benchmark.pedantic(
- target=backend_wrapper(nx.single_source_shortest_path_length),
- args=(G,),
- kwargs=dict(
- source=node,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-def bench_single_target_shortest_path_length(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
- result = benchmark.pedantic(
- target=backend_wrapper(
- nx.single_target_shortest_path_length, exhaust_returned_iterator=True
- ),
- args=(G,),
- kwargs=dict(
- target=node,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- # exhaust_returned_iterator=True forces the result to a list, but is not
- # needed for this algo in NX 3.3+ which returns a dict instead of an
- # iterator. Forcing to a list does not change the benchmark timing.
- assert type(result) is list
-
-
-def bench_ancestors(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.ancestors),
- args=(G,),
- kwargs=dict(
- source=node,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is set
-
-
-def bench_average_clustering(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- # DiGraphs are not supported by nx-cugraph
- if G.is_directed():
- G = G.to_undirected()
- result = benchmark.pedantic(
- target=backend_wrapper(nx.average_clustering),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is float
-
-
-def bench_generic_bfs_edges(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.generic_bfs_edges, exhaust_returned_iterator=True),
- args=(G,),
- kwargs=dict(
- source=node,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is list
-
-
-def bench_bfs_edges(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.bfs_edges, exhaust_returned_iterator=True),
- args=(G,),
- kwargs=dict(
- source=node,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is list
-
-
-def bench_bfs_layers(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.bfs_layers, exhaust_returned_iterator=True),
- args=(G,),
- kwargs=dict(
- sources=node,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is list
-
-
-def bench_bfs_predecessors(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.bfs_predecessors, exhaust_returned_iterator=True),
- args=(G,),
- kwargs=dict(
- source=node,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is list
-
-
-def bench_bfs_successors(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.bfs_successors, exhaust_returned_iterator=True),
- args=(G,),
- kwargs=dict(
- source=node,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is list
-
-
-def bench_bfs_tree(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.bfs_tree),
- args=(G,),
- kwargs=dict(
- source=node,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- # Check that this at least appears to be some kind of NX-like Graph
- assert hasattr(result, "has_node")
-
-
-def bench_clustering(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- # DiGraphs are not supported by nx-cugraph
- if G.is_directed():
- G = G.to_undirected()
- result = benchmark.pedantic(
- target=backend_wrapper(nx.clustering),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-def bench_core_number(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- # DiGraphs are not supported by nx-cugraph
- if G.is_directed():
- G = G.to_undirected()
- result = benchmark.pedantic(
- target=backend_wrapper(nx.core_number),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-def bench_descendants(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.descendants),
- args=(G,),
- kwargs=dict(
- source=node,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is set
-
-
-def bench_descendants_at_distance(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.descendants_at_distance),
- args=(G,),
- kwargs=dict(
- source=node,
- distance=1,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is set
-
-
-def bench_is_bipartite(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.is_bipartite),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is bool
-
-
-def bench_is_strongly_connected(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.is_strongly_connected),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is bool
-
-
-def bench_is_weakly_connected(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.is_weakly_connected),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is bool
-
-
-def bench_number_strongly_connected_components(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.number_strongly_connected_components),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is int
-
-
-def bench_number_weakly_connected_components(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.number_weakly_connected_components),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is int
-
-
-def bench_overall_reciprocity(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.overall_reciprocity),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is float
-
-
-def bench_reciprocity(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.reciprocity),
- args=(G,),
- kwargs=dict(
- nodes=node,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is float
-
-
-def bench_strongly_connected_components(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(
- nx.strongly_connected_components, exhaust_returned_iterator=True
- ),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is list
-
-
-def bench_transitivity(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- # DiGraphs are not supported by nx-cugraph
- if G.is_directed():
- G = G.to_undirected()
- result = benchmark.pedantic(
- target=backend_wrapper(nx.transitivity),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is float
-
-
-def bench_triangles(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- # DiGraphs are not supported
- if G.is_directed():
- G = G.to_undirected()
- result = benchmark.pedantic(
- target=backend_wrapper(nx.triangles),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is dict
-
-
-def bench_weakly_connected_components(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- result = benchmark.pedantic(
- target=backend_wrapper(
- nx.weakly_connected_components, exhaust_returned_iterator=True
- ),
- args=(G,),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert type(result) is list
-
-
-def bench_ego_graph(benchmark, graph_obj, backend_wrapper):
- G = get_graph_obj_for_benchmark(graph_obj, backend_wrapper)
- node = get_highest_degree_node(graph_obj)
- result = benchmark.pedantic(
- target=backend_wrapper(nx.ego_graph),
- args=(G,),
- kwargs=dict(
- n=node,
- radius=100,
- ),
- rounds=rounds,
- iterations=iterations,
- warmup_rounds=warmup_rounds,
- )
- assert isinstance(result, (nx.Graph, nxcg.Graph))
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_complete_bipartite_graph(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_connected_components(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_is_connected(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_node_connected_component(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_number_connected_components(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_is_isolate(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_isolates(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_number_of_isolates(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_complement(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_reverse(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_is_arborescence(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_is_branching(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_is_forest(benchmark, graph_obj, backend_wrapper):
- pass
-
-
-@pytest.mark.skip(reason="benchmark not implemented")
-def bench_is_tree(benchmark, graph_obj, backend_wrapper):
- pass
diff --git a/benchmarks/nx-cugraph/pytest-based/create_results_summary_page.py b/benchmarks/nx-cugraph/pytest-based/create_results_summary_page.py
deleted file mode 100644
index e4aff10f0a5..00000000000
--- a/benchmarks/nx-cugraph/pytest-based/create_results_summary_page.py
+++ /dev/null
@@ -1,293 +0,0 @@
-# Copyright (c) 2024, NVIDIA CORPORATION.
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-import re
-import pathlib
-import json
-import platform
-import psutil
-import socket
-import subprocess
-
-
-def get_formatted_time_value(time):
- res = ""
- if time < 1:
- if time < 0.001:
- units = "us"
- time *= 1e6
- else:
- units = "ms"
- time *= 1e3
- else:
- units = "s"
- return f"{time:.3f}{units}"
-
-
-def get_all_benchmark_info():
- benchmarks = {}
- # Populate benchmarks dir from .json files
- for json_file in logs_dir.glob("*.json"):
- try:
- data = json.loads(open(json_file).read())
- except json.decoder.JSONDecodeError:
- continue
-
- for benchmark_run in data["benchmarks"]:
- # example name: "bench_triangles[ds=netscience-backend=cugraph-preconverted]"
- name = benchmark_run["name"]
-
- algo_name = name.split("[")[0]
- if algo_name.startswith("bench_"):
- algo_name = algo_name[6:]
- # special case for betweenness_centrality
- match = k_patt.match(name)
- if match is not None:
- algo_name += f", k={match.group(1)}"
-
- match = dataset_patt.match(name)
- if match is None:
- raise RuntimeError(
- f"benchmark name {name} in file {json_file} has an unexpected format"
- )
- dataset = match.group(1)
- if dataset.endswith("-backend"):
- dataset = dataset[:-8]
-
- match = backend_patt.match(name)
- if match is None:
- raise RuntimeError(
- f"benchmark name {name} in file {json_file} has an unexpected format"
- )
- backend = match.group(1)
- if backend == "None":
- backend = "networkx"
-
- runtime = benchmark_run["stats"]["mean"]
- benchmarks.setdefault(algo_name, {}).setdefault(backend, {})[
- dataset
- ] = runtime
- return benchmarks
-
-
-def compute_perf_vals(cugraph_runtime, networkx_runtime):
- speedup_string = f"{networkx_runtime / cugraph_runtime:.3f}X"
- delta = networkx_runtime - cugraph_runtime
- if abs(delta) < 1:
- if abs(delta) < 0.001:
- units = "us"
- delta *= 1e6
- else:
- units = "ms"
- delta *= 1e3
- else:
- units = "s"
- delta_string = f"{delta:.3f}{units}"
-
- return (speedup_string, delta_string)
-
-
-def get_mem_info():
- return round(psutil.virtual_memory().total / (1024**3), 2)
-
-
-def get_cuda_version():
- output = subprocess.check_output("nvidia-smi", shell=True).decode()
- try:
- return next(
- line.split("CUDA Version: ")[1].split()[0]
- for line in output.splitlines()
- if "CUDA Version" in line
- )
- except subprocess.CalledProcessError:
- return "Failed to get CUDA version."
-
-
-def get_first_gpu_info():
- try:
- gpu_info = (
- subprocess.check_output(
- "nvidia-smi --query-gpu=name,memory.total,memory.free,memory.used --format=csv,noheader",
- shell=True,
- )
- .decode()
- .strip()
- )
- if gpu_info:
- gpus = gpu_info.split("\n")
- num_gpus = len(gpus)
- first_gpu = gpus[0] # Get the information for the first GPU
- gpu_name, mem_total, _, _ = first_gpu.split(",")
- return f"{num_gpus} x {gpu_name.strip()} ({round(int(mem_total.strip().split()[0]) / (1024), 2)} GB)"
- else:
- print("No GPU found or unable to query GPU details.")
- except subprocess.CalledProcessError:
- print("Failed to execute nvidia-smi. No GPU information available.")
-
-
-def get_system_info():
- print('
')
- print(f"
Hostname: {socket.gethostname()}
")
- print(
- f'
Operating System: {platform.system()} {platform.release()}
'
- )
- print(f'
Kernel Version : {platform.version()}
')
- with open("/proc/cpuinfo") as f:
- print(
- f'
CPU: {next(line.strip().split(": ")[1] for line in f if "model name" in line)} ({psutil.cpu_count(logical=False)} cores)
'
- )
- print(f'
Memory: {get_mem_info()} GB
')
- print(f"
GPU: {get_first_gpu_info()}
")
- print(f"
CUDA Version: {get_cuda_version()}
")
-
-
-if __name__ == "__main__":
- logs_dir = pathlib.Path("logs")
-
- dataset_patt = re.compile(".*ds=([\w-]+).*")
- backend_patt = re.compile(".*backend=(\w+).*")
- k_patt = re.compile(".*k=(\d+).*")
-
- # Organize all benchmark runs by the following hierarchy: algo -> backend -> dataset
- benchmarks = get_all_benchmark_info()
-
- # dump HTML table
- ordered_datasets = [
- "netscience",
- "email_Eu_core",
- "amazon0302",
- "cit-patents",
- "hollywood",
- "soc-livejournal1",
- ]
- # dataset, # Node, # Edge, Directed info
- dataset_meta = {
- "netscience": ["1,461", "5,484", "Yes"],
- "email_Eu_core": ["1,005", "25,571", "Yes"],
- "amazon0302": ["262,111", "1,234,877", "Yes"],
- "cit-patents": ["3,774,768", "16,518,948", "Yes"],
- "hollywood": ["1,139,905", "57,515,616", "No"],
- "soc-livejournal1": ["4,847,571", "68,993,773", "Yes"],
- }
-
- print(
- """
-
-
-
-
-
-
-
- Dataset Nodes Edges Directed | """
- )
- for ds in ordered_datasets:
- print(
- f" {ds} {dataset_meta[ds][0]} {dataset_meta[ds][1]} {dataset_meta[ds][2]}
| "
- )
- print(
- """
-
-
- """
- )
- for algo_name in sorted(benchmarks):
- algo_runs = benchmarks[algo_name]
- print(" ")
- print(f" {algo_name} | ")
- # Proceed only if any results are present for both cugraph and NX
- if "cugraph" in algo_runs and "networkx" in algo_runs:
- cugraph_algo_runs = algo_runs["cugraph"]
- networkx_algo_runs = algo_runs["networkx"]
- datasets_in_both = set(cugraph_algo_runs).intersection(networkx_algo_runs)
-
- # populate the table with speedup results for each dataset in the order
- # specified in ordered_datasets. If results for a run using a dataset
- # are not present for both cugraph and NX, output an empty cell.
- for dataset in ordered_datasets:
- if dataset in datasets_in_both:
- cugraph_runtime = cugraph_algo_runs[dataset]
- networkx_runtime = networkx_algo_runs[dataset]
- (speedup, runtime_delta) = compute_perf_vals(
- cugraph_runtime=cugraph_runtime,
- networkx_runtime=networkx_runtime,
- )
- nx_formatted = get_formatted_time_value(networkx_runtime)
- cg_formatted = get_formatted_time_value(cugraph_runtime)
- print(
- f" {nx_formatted} / {cg_formatted} {speedup} {runtime_delta} | "
- )
- else:
- print(f" | ")
-
- # If a comparison between cugraph and NX cannot be made, output empty cells
- # for each dataset
- else:
- for _ in range(len(ordered_datasets)):
- print(" | ")
- print("
")
- print(
- """
- \n
-
-
-
Table Format:
-
- - NetworkX time / nx-cugraph time
- - Speed-up of using nx-cugraph
- - Time-delta
-
-
"""
- )
- get_system_info()
- print("""
\n
\n""")
diff --git a/benchmarks/nx-cugraph/pytest-based/run-2402.sh b/benchmarks/nx-cugraph/pytest-based/run-2402.sh
deleted file mode 100755
index 44ed0bda43a..00000000000
--- a/benchmarks/nx-cugraph/pytest-based/run-2402.sh
+++ /dev/null
@@ -1,46 +0,0 @@
-#!/bin/bash
-#
-# Copyright (c) 2024, NVIDIA CORPORATION.
-#
-# Runs benchmarks for the 24.02 algos.
-# Pass either a or b or both. This is useful for separating batches of runs on different GPUs:
-# CUDA_VISIBLE_DEVICES=1 run-2402.sh b
-
-mkdir -p logs
-
-# benches="$benches ..." pattern is easy to comment out individual runs
-benches=
-
-while [[ $1 != "" ]]; do
- if [[ $1 == "a" ]]; then
- benches="$benches bench_ancestors"
- benches="$benches bench_average_clustering"
- benches="$benches bench_generic_bfs_edges"
- benches="$benches bench_bfs_edges"
- benches="$benches bench_bfs_layers"
- benches="$benches bench_bfs_predecessors"
- benches="$benches bench_bfs_successors"
- benches="$benches bench_bfs_tree"
- benches="$benches bench_clustering"
- benches="$benches bench_core_number"
- benches="$benches bench_descendants"
- elif [[ $1 == "b" ]]; then
- benches="$benches bench_descendants_at_distance"
- benches="$benches bench_is_bipartite"
- benches="$benches bench_is_strongly_connected"
- benches="$benches bench_is_weakly_connected"
- benches="$benches bench_number_strongly_connected_components"
- benches="$benches bench_number_weakly_connected_components"
- benches="$benches bench_overall_reciprocity"
- benches="$benches bench_reciprocity"
- benches="$benches bench_strongly_connected_components"
- benches="$benches bench_transitivity"
- benches="$benches bench_triangles"
- benches="$benches bench_weakly_connected_components"
- fi
- shift
-done
-
-for bench in $benches; do
- pytest -sv -k "soc-livejournal1" "bench_algos.py::$bench" 2>&1 | tee "logs/${bench}.log"
-done
diff --git a/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh b/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh
deleted file mode 100755
index 73c85000b0f..00000000000
--- a/benchmarks/nx-cugraph/pytest-based/run-main-benchmarks.sh
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/bin/bash
-# Copyright (c) 2024, NVIDIA CORPORATION.
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-
-# location to store datasets used for benchmarking
-export RAPIDS_DATASET_ROOT_DIR=${RAPIDS_DATASET_ROOT_DIR:-/datasets/cugraph}
-mkdir -p logs
-
-# list of algos, datasets, and back-ends to use in combinations
-algos="
- pagerank
- betweenness_centrality
- louvain
- shortest_path
- weakly_connected_components
- triangles
- bfs_predecessors
-"
-datasets="
- netscience
- email_Eu_core
- amazon0302
- cit-patents
- hollywood
- soc-livejournal
-"
-# None backend is default networkx
-# cugraph-preconvert backend is nx-cugraph
-backends="
- None
- cugraph-preconverted
-"
-
-# edit this directly to for pytest
-# e.g. -k "and not 100 and not 1000"
-bc_k_values=""
-
-# check for --cpu-only or --gpu-only args
-if [[ "$#" -eq 1 ]]; then
- case $1 in
- --cpu-only)
- backends="None"
- ;;
- --gpu-only)
- backends="cugraph-preconverted"
- ;;
- *)
- echo "Unknown option: $1"
- exit 1
- ;;
- esac
-fi
-
-for algo in $algos; do
- for dataset in $datasets; do
- for backend in $backends; do
- name="${backend}__${algo}__${dataset}"
- echo "Running: $backend, $dataset, bench_$algo"
-
- # uncomment to get command for reproducing test
- # echo "RUNNING: \"pytest -sv -k \"$backend and $dataset and bench_$algo $bc_k_values\" --benchmark-json=\"logs/${name}.json\" bench_algos.py"
-
- pytest -sv \
- -k "$backend and $dataset and bench_$algo $bc_k_values" \
- --benchmark-json="logs/${name}.json" \
- bench_algos.py 2>&1 | tee "logs/${name}.out"
- done
- done
-done
diff --git a/build.sh b/build.sh
index 29abd48166a..1ab98fe4378 100755
--- a/build.sh
+++ b/build.sh
@@ -32,7 +32,6 @@ VALIDARGS="
cugraph-pyg
cugraph-dgl
cugraph-equivariant
- nx-cugraph
cpp-mgtests
cpp-mtmgtests
docs
@@ -61,7 +60,6 @@ HELP="$0 [ ...] [ ...]
cugraph-pyg - build the cugraph-pyg Python package
cugraph-dgl - build the cugraph-dgl extensions for DGL
cugraph-equivariant - build the cugraph-equivariant Python package
- nx-cugraph - build the nx-cugraph Python package
cpp-mgtests - build libcugraph and libcugraph_etl MG tests. Builds MPI communicator, adding MPI as a dependency.
cpp-mtmgtests - build libcugraph MTMG tests. Adds UCX as a dependency (temporary).
docs - build the docs
@@ -212,7 +210,7 @@ if hasArg uninstall; then
# removes the latest one and leaves the others installed. build.sh uninstall
# can be run multiple times to remove all of them, but that is not obvious.
pip uninstall -y pylibcugraph cugraph cugraph-service-client cugraph-service-server \
- cugraph-dgl cugraph-pyg cugraph-equivariant nx-cugraph
+ cugraph-dgl cugraph-pyg cugraph-equivariant
fi
if hasArg clean; then
@@ -357,15 +355,6 @@ if hasArg cugraph-equivariant || hasArg all; then
fi
fi
-# Build and install the nx-cugraph Python package
-if hasArg nx-cugraph || hasArg all; then
- if hasArg --clean; then
- cleanPythonDir ${REPODIR}/python/nx-cugraph
- else
- python ${PYTHON_ARGS_FOR_INSTALL} ${REPODIR}/python/nx-cugraph
- fi
-fi
-
# Build the docs
if hasArg docs || hasArg all; then
if [ ! -d ${LIBCUGRAPH_BUILD_DIR} ]; then
diff --git a/conda/environments/all_cuda-118_arch-x86_64.yaml b/conda/environments/all_cuda-118_arch-x86_64.yaml
index ec3f61d383f..e4269707168 100644
--- a/conda/environments/all_cuda-118_arch-x86_64.yaml
+++ b/conda/environments/all_cuda-118_arch-x86_64.yaml
@@ -34,7 +34,6 @@ dependencies:
- nbsphinx
- nccl>=2.19
- networkx>=2.5.1
-- networkx>=3.0
- ninja
- notebook>=0.5.0
- numba>=0.57
@@ -53,7 +52,6 @@ dependencies:
- pytest
- pytest-benchmark
- pytest-cov
-- pytest-mpl
- pytest-xdist
- python-louvain
- pytorch>=2.3,<2.4.0a0
diff --git a/conda/environments/all_cuda-125_arch-x86_64.yaml b/conda/environments/all_cuda-125_arch-x86_64.yaml
index ff42bbbc365..eb2625b9d50 100644
--- a/conda/environments/all_cuda-125_arch-x86_64.yaml
+++ b/conda/environments/all_cuda-125_arch-x86_64.yaml
@@ -40,7 +40,6 @@ dependencies:
- nbsphinx
- nccl>=2.19
- networkx>=2.5.1
-- networkx>=3.0
- ninja
- notebook>=0.5.0
- numba>=0.57
@@ -58,7 +57,6 @@ dependencies:
- pytest
- pytest-benchmark
- pytest-cov
-- pytest-mpl
- pytest-xdist
- python-louvain
- pytorch>=2.3,<2.4.0a0
diff --git a/conda/recipes/nx-cugraph/build.sh b/conda/recipes/nx-cugraph/build.sh
deleted file mode 100644
index 26665c1e76a..00000000000
--- a/conda/recipes/nx-cugraph/build.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-
-# Copyright (c) 2023, NVIDIA CORPORATION.
-
-# This assumes the script is executed from the root of the repo directory
-
-./build.sh nx-cugraph
diff --git a/conda/recipes/nx-cugraph/meta.yaml b/conda/recipes/nx-cugraph/meta.yaml
deleted file mode 100644
index 263f53d9a8f..00000000000
--- a/conda/recipes/nx-cugraph/meta.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright (c) 2023-2024, NVIDIA CORPORATION.
-
-{% set version = environ['RAPIDS_PACKAGE_VERSION'].lstrip('v') + environ.get('VERSION_SUFFIX', '') %}
-{% set minor_version = version.split('.')[0] + '.' + version.split('.')[1] %}
-{% set py_version = environ['CONDA_PY'] %}
-{% set date_string = environ['RAPIDS_DATE_STRING'] %}
-
-package:
- name: nx-cugraph
- version: {{ version }}
-
-source:
- path: ../../..
-
-build:
- number: {{ GIT_DESCRIBE_NUMBER }}
- string: py{{ py_version }}_{{ date_string }}_{{ GIT_DESCRIBE_HASH }}_{{ GIT_DESCRIBE_NUMBER }}
-
-requirements:
- host:
- - python
- - rapids-build-backend>=0.3.1,<0.4.0.dev0
- - setuptools>=61.0.0
- run:
- - pylibcugraph ={{ version }}
- - networkx >=3.0
- - cupy >=12.0.0
- - python
-
-tests:
- imports:
- - nx_cugraph
- commands:
- - pip check
- requires:
- - pip
-
-about:
- home: https://rapids.ai/
- dev_url: https://github.com/rapidsai/cugraph
- license: Apache-2.0
- license_file: ../../../LICENSE
- summary: cuGraph backend for GPU-accelerated NetworkX
diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt
index 2cea2e504ab..27e1999cb75 100644
--- a/cpp/CMakeLists.txt
+++ b/cpp/CMakeLists.txt
@@ -167,6 +167,7 @@ set(CUGRAPH_SOURCES
src/detail/groupby_and_count_mg_v64_e64.cu
src/detail/collect_comm_wrapper_mg_v32_e32.cu
src/detail/collect_comm_wrapper_mg_v64_e64.cu
+ src/sampling/detail/conversion_utilities.cu
src/sampling/random_walks_mg_v64_e64.cu
src/sampling/random_walks_mg_v32_e32.cu
src/community/detail/common_methods_mg_v64_e64.cu
@@ -264,10 +265,10 @@ set(CUGRAPH_SOURCES
src/sampling/detail/sample_edges_mg_v32_e32.cu
src/sampling/detail/shuffle_and_organize_output_mg_v64_e64.cu
src/sampling/detail/shuffle_and_organize_output_mg_v32_e32.cu
- src/sampling/neighbor_sampling_mg_v32_e32.cpp
- src/sampling/neighbor_sampling_mg_v64_e64.cpp
- src/sampling/neighbor_sampling_sg_v32_e32.cpp
- src/sampling/neighbor_sampling_sg_v64_e64.cpp
+ src/sampling/neighbor_sampling_mg_v32_e32.cu
+ src/sampling/neighbor_sampling_mg_v64_e64.cu
+ src/sampling/neighbor_sampling_sg_v32_e32.cu
+ src/sampling/neighbor_sampling_sg_v64_e64.cu
src/sampling/negative_sampling_sg_v32_e32.cu
src/sampling/negative_sampling_sg_v64_e64.cu
src/sampling/negative_sampling_mg_v32_e32.cu
diff --git a/cpp/include/cugraph/detail/utility_wrappers.hpp b/cpp/include/cugraph/detail/utility_wrappers.hpp
index 3d99b85556b..b1afeafd66b 100644
--- a/cpp/include/cugraph/detail/utility_wrappers.hpp
+++ b/cpp/include/cugraph/detail/utility_wrappers.hpp
@@ -65,6 +65,48 @@ void uniform_random_fill(rmm::cuda_stream_view const& stream_view,
template
void scalar_fill(raft::handle_t const& handle, value_t* d_value, size_t size, value_t value);
+/**
+ * @brief Sort a device span
+ *
+ * @tparam value_t type of the value to operate on. Must be either int32_t or int64_t.
+ *
+ * @param [in] handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator,
+ * and handles to various CUDA libraries) to run graph algorithms.
+ * @param[out] values device span to sort
+ *
+ */
+template
+void sort_ints(raft::handle_t const& handle, raft::device_span values);
+
+/**
+ * @brief Keep unique element from a device span
+ *
+ * @tparam value_t type of the value to operate on. Must be either int32_t or int64_t.
+ *
+ * @param [in] handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator,
+ * and handles to various CUDA libraries) to run graph algorithms.
+ * @param[in] values device span of unique elements.
+ * @return the number of unique elements.
+ *
+ */
+template
+size_t unique_ints(raft::handle_t const& handle, raft::device_span values);
+
+/**
+ * @brief Increment the values of a device span by a constant value
+ *
+ * @tparam value_t type of the value to operate on. Must be either int32_t or int64_t.
+ *
+ * @param[out] values device span to update
+ * @param[in] value value to be added to each element of the buffer
+ * @param[in] stream_view stream view
+ *
+ */
+template
+void transform_increment_ints(raft::device_span values,
+ value_t value,
+ rmm::cuda_stream_view const& stream_view);
+
/**
* @brief Fill a buffer with a sequence of values
*
@@ -73,7 +115,7 @@ void scalar_fill(raft::handle_t const& handle, value_t* d_value, size_t size, va
*
* Similar to the function std::iota, wraps the function thrust::sequence
*
- * @tparam value_t type of the value to operate on
+ * @tparam value_t type of the value to operate on.
*
* @param[in] stream_view stream view
* @param[out] d_value device array to fill
diff --git a/cpp/include/cugraph/sampling_functions.hpp b/cpp/include/cugraph/sampling_functions.hpp
index 783cd3a7e2b..3d41e954416 100644
--- a/cpp/include/cugraph/sampling_functions.hpp
+++ b/cpp/include/cugraph/sampling_functions.hpp
@@ -43,6 +43,8 @@ enum class prior_sources_behavior_t { DEFAULT = 0, CARRY_OVER, EXCLUDE };
/**
* @brief Uniform Neighborhood Sampling.
*
+ * @deprecated Replaced with homogeneous_uniform_neighbor_sample
+ *
* This function traverses from a set of starting vertices, traversing outgoing edges and
* randomly selects from these outgoing neighbors to extract a subgraph.
*
@@ -53,19 +55,20 @@ enum class prior_sources_behavior_t { DEFAULT = 0, CARRY_OVER, EXCLUDE };
* encountered in. The label output (optional) identifes the vertex label. The offsets array
* (optional) will be described below and is dependent upon the input parameters.
*
- * If @p starting_vertex_labels is not specified then no organization is applied to the output, the
- * label and offsets values in the return set will be std::nullopt.
+ * If @p starting_vertex_label_offsets is not specified then no organization is applied to the
+ * output, the label and offsets values in the return set will be std::nullopt.
*
- * If @p starting_vertex_labels is specified and @p label_to_output_comm_rank is not specified then
- * the label output has values. This will also result in the output being sorted by vertex label.
- * The offsets array in the return will be a CSR-style offsets array to identify the beginning of
- * each label range in the data. `labels.size() == (offsets.size() - 1)`.
+ * If @p starting_vertex_label_offsets is specified and @p label_to_output_comm_rank is not
+ * specified then the label output has values. This will also result in the output being sorted by
+ * vertex label. The offsets array in the return will be a CSR-style offsets array to identify the
+ * beginning of each label range in the data. `labels.size() == (offsets.size() - 1)`.
*
- * If @p starting_vertex_labels is specified and @p label_to_output_comm_rank is specified then the
- * label output has values. This will also result in the output being sorted by vertex label. The
- * offsets array in the return will be a CSR-style offsets array to identify the beginning of each
- * label range in the data. `labels.size() == (offsets.size() - 1)`. Additionally, the data will
- * be shuffled so that all data with a particular label will be on the specified rank.
+ * If @p starting_vertex_label_offsets is specified and @p label_to_output_comm_rank is specified
+ * then the label output has values. This will also result in the output being sorted by vertex
+ * label. The offsets array in the return will be a CSR-style offsets array to identify the
+ * beginning of each label range in the data. `labels.size() == (offsets.size() - 1)`.
+ * Additionally, the data will be shuffled so that all data with a particular label will be on the
+ * specified rank.
*
* @tparam vertex_t Type of vertex identifiers. Needs to be an integral type.
* @tparam edge_t Type of edge identifiers. Needs to be an integral type.
@@ -83,8 +86,8 @@ enum class prior_sources_behavior_t { DEFAULT = 0, CARRY_OVER, EXCLUDE };
* @param edge_type_view Optional view object holding edge types for @p graph_view.
* @param starting_vertices Device span of starting vertex IDs for the sampling.
* In a multi-gpu context the starting vertices should be local to this GPU.
- * @param starting_vertex_labels Optional device span of labels associted with each starting vertex
- * for the sampling.
+ * @param starting_vertex_label_offsets Optional device span of labels associated with each starting
+ * vertex for the sampling.
* @param label_to_output_comm_rank Optional tuple of device spans mapping label to a particular
* output rank. Element 0 of the tuple identifes the label, Element 1 of the tuple identifies the
* output rank. The label span must be sorted in ascending order.
@@ -126,7 +129,7 @@ uniform_neighbor_sample(
std::optional> edge_id_view,
std::optional> edge_type_view,
raft::device_span starting_vertices,
- std::optional> starting_vertex_labels,
+ std::optional> starting_vertex_label_offsets,
std::optional, raft::device_span>>
label_to_output_comm_rank,
raft::host_span fan_out,
@@ -140,6 +143,8 @@ uniform_neighbor_sample(
/**
* @brief Biased Neighborhood Sampling.
*
+ * @deprecated Replaced with homogeneous_biased_neighbor_sample
+ *
* This function traverses from a set of starting vertices, traversing outgoing edges and
* randomly selects (with edge biases) from these outgoing neighbors to extract a subgraph.
*
@@ -150,24 +155,26 @@ uniform_neighbor_sample(
* encountered in. The label output (optional) identifes the vertex label. The offsets array
* (optional) will be described below and is dependent upon the input parameters.
*
- * If @p starting_vertex_labels is not specified then no organization is applied to the output, the
- * label and offsets values in the return set will be std::nullopt.
+ * If @p starting_vertex_label_offsets is not specified then no organization is applied to the
+ * output, the label and offsets values in the return set will be std::nullopt.
*
- * If @p starting_vertex_labels is specified and @p label_to_output_comm_rank is not specified then
- * the label output has values. This will also result in the output being sorted by vertex label.
- * The offsets array in the return will be a CSR-style offsets array to identify the beginning of
- * each label range in the data. `labels.size() == (offsets.size() - 1)`.
+ * If @p starting_vertex_label_offsets is specified and @p label_to_output_comm_rank is not
+ * specified then the label output has values. This will also result in the output being sorted by
+ * vertex label. The offsets array in the return will be a CSR-style offsets array to identify the
+ * beginning of each label range in the data. `labels.size() == (offsets.size() - 1)`.
*
- * If @p starting_vertex_labels is specified and @p label_to_output_comm_rank is specified then the
- * label output has values. This will also result in the output being sorted by vertex label. The
- * offsets array in the return will be a CSR-style offsets array to identify the beginning of each
- * label range in the data. `labels.size() == (offsets.size() - 1)`. Additionally, the data will
- * be shuffled so that all data with a particular label will be on the specified rank.
+ * If @p starting_vertex_label_offsets is specified and @p label_to_output_comm_rank is specified
+ * then the label output has values. This will also result in the output being sorted by vertex
+ * label. The offsets array in the return will be a CSR-style offsets array to identify the
+ * beginning of each label range in the data. `labels.size() == (offsets.size() - 1)`.
+ * Additionally, the data will be shuffled so that all data with a particular label will be on the
+ * specified rank.
*
* @tparam vertex_t Type of vertex identifiers. Needs to be an integral type.
* @tparam edge_t Type of edge identifiers. Needs to be an integral type.
* @tparam weight_t Type of edge weights. Needs to be a floating point type.
* @tparam edge_type_t Type of edge type. Needs to be an integral type.
+ * @tparam bias_t Type of bias. Needs to be an integral type.
* @tparam label_t Type of label. Needs to be an integral type.
* @tparam store_transposed Flag indicating whether sources (if false) or destinations (if
* true) are major indices
@@ -184,8 +191,8 @@ uniform_neighbor_sample(
* corresponding edge can never be selected.
* @param starting_vertices Device span of starting vertex IDs for the sampling.
* In a multi-gpu context the starting vertices should be local to this GPU.
- * @param starting_vertex_labels Optional device span of labels associted with each starting vertex
- * for the sampling.
+ * @param starting_vertex_label_offsets Optional device span of labels associated with each starting
+ * vertex for the sampling.
* @param label_to_output_comm_rank Optional tuple of device spans mapping label to a particular
* output rank. Element 0 of the tuple identifes the label, Element 1 of the tuple identifies the
* output rank. The label span must be sorted in ascending order.
@@ -229,7 +236,7 @@ biased_neighbor_sample(
std::optional> edge_type_view,
edge_property_view_t edge_bias_view,
raft::device_span starting_vertices,
- std::optional> starting_vertex_labels,
+ std::optional> starting_vertex_label_offsets,
std::optional, raft::device_span>>
label_to_output_comm_rank,
raft::host_span fan_out,
@@ -240,6 +247,349 @@ biased_neighbor_sample(
bool dedupe_sources = false,
bool do_expensive_check = false);
+struct sampling_flags_t {
+ /**
+ * Specifies how to handle prior sources. Default is DEFAULT.
+ */
+ prior_sources_behavior_t prior_sources_behavior{};
+
+ /**
+ * Specifies if the hop information should be returned. Default is false.
+ */
+ bool return_hops{false};
+
+ /**
+ * If true then if a vertex v appears as a destination in hop X multiple times
+ * with the same label, it will only be passed once (for each label) as a source
+ * for the next hop. Default is false.
+ */
+ bool dedupe_sources{false};
+
+ /**
+ * Specifies if random sampling is done with replacement
+ * (true) or without replacement (false). Default is true.
+ */
+ bool with_replacement{true};
+};
+
+/**
+ * @brief Homogeneous Uniform Neighborhood Sampling.
+ *
+ * This function traverses from a set of starting vertices, traversing outgoing edges and
+ * randomly selects (uniformly) from these outgoing neighbors to extract a subgraph.
+ * The branching out to select outgoing neighbors is performed with homogeneous fanouts
+ *
+ * Output from this function is a tuple of vectors (src, dst, weight, edge_id, edge_type, hop,
+ * offsets), identifying the randomly selected edges where the size of src, dst, weight, edge_id,
+ * edge_type and hop is the number of sampled edges while the size of the offsets vector is the
+ * number of labels + 1. src is the source vertex, dst is the destination vertex, weight
+ * (optional) is the edge weight, edge_id (optional) identifies the edge id, edge_type (optional)
+ * identifies the edge type, hop identifies which hop the edge was encountered in.
+ * The offsets array (optional) identifies the offset for each label.
+ *
+ * If @p label_to_output_comm_rank is specified then the data will be shuffled so that all entries
+ * for a particular label are returned on the specified rank.
+ *
+ * @tparam vertex_t Type of vertex identifiers. Needs to be an integral type.
+ * @tparam edge_t Type of edge identifiers. Needs to be an integral type.
+ * @tparam weight_t Type of edge weights. Needs to be a floating point type.
+ * @tparam edge_type_t Type of edge type. Needs to be an integral type.
+ * @tparam store_transposed Flag indicating whether sources (if false) or destinations (if
+ * true) are major indices
+ * @tparam multi_gpu Flag indicating whether template instantiation should target single-GPU (false)
+ * @param handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator, and
+ * handles to various CUDA libraries) to run graph algorithms.
+ * @param rng_state A pre-initialized raft::RngState object for generating random numbers
+ * @param graph_view Graph View object to generate NBR Sampling on.
+ * @param edge_weight_view Optional view object holding edge weights for @p graph_view.
+ * @param edge_id_view Optional view object holding edge ids for @p graph_view.
+ * @param edge_type_view Optional view object holding edge types for @p graph_view.
+ * @param starting_vertices Device span of starting vertex IDs for the sampling.
+ * In a multi-gpu context the starting vertices should be local to this GPU.
+ * @param starting_vertex_label_offsets Optional device span of labels associated with each starting
+ * vertex for the sampling.
+ * @param label_to_output_comm_rank Optional device span identifying which rank should get sampling
+ * outputs of each vertex label. This should be the same on each rank.
+ * @param fan_out Host span defining branching out (fan-out) degree per source vertex for each
+ * level.
+ * @param flags A set of flags indicating which sampling features should be used.
+ * @param do_expensive_check A flag to run expensive checks for input arguments (if set to `true`).
+ * @return tuple device vectors (vertex_t source_vertex, vertex_t destination_vertex,
+ * optional weight_t weight, optional edge_t edge id, optional edge_type_t edge type,
+ * optional int32_t hop, optional label_t label, optional size_t offsets)
+ */
+
+template
+std::tuple,
+ rmm::device_uvector,
+ std::optional>,
+ std::optional>,
+ std::optional>,
+ std::optional>,
+ std::optional>>
+homogeneous_uniform_neighbor_sample(
+ raft::handle_t const& handle,
+ raft::random::RngState& rng_state,
+ graph_view_t const& graph_view,
+ std::optional> edge_weight_view,
+ std::optional> edge_id_view,
+ std::optional> edge_type_view,
+ raft::device_span starting_vertices,
+ std::optional> starting_vertex_label_offsets,
+ std::optional> label_to_output_comm_rank,
+ raft::host_span fan_out,
+ sampling_flags_t sampling_flags,
+ bool do_expensive_check = false);
+
+/**
+ * @brief Homogeneous Biased Neighborhood Sampling.
+ *
+ * This function traverses from a set of starting vertices, traversing outgoing edges and
+ * randomly selects (with edge biases) from these outgoing neighbors to extract a subgraph.
+ * The branching out to select outgoing neighbors is performed with homogeneous fanouts
+ *
+ * Output from this function is a tuple of vectors (src, dst, weight, edge_id, edge_type, hop,
+ * offsets), identifying the randomly selected edges where the size of src, dst, weight, edge_id,
+ * edge_type and hop is the number of sampled edges while the size of the offsets vector is the
+ * number of labels + 1. src is the source vertex, dst is the destination vertex, weight
+ * (optional) is the edge weight, edge_id (optional) identifies the edge id, edge_type (optional)
+ * identifies the edge type, hop identifies which hop the edge was encountered in.
+ * The offsets array (optional) identifies the offset for each label.
+ *
+ * If @p label_to_output_comm_rank is specified then the data will be shuffled so that all entries
+ * for a particular label are returned on the specified rank.
+ *
+ * @tparam vertex_t Type of vertex identifiers. Needs to be an integral type.
+ * @tparam edge_t Type of edge identifiers. Needs to be an integral type.
+ * @tparam weight_t Type of edge weights. Needs to be a floating point type.
+ * @tparam edge_type_t Type of edge type. Needs to be an integral type.
+ * @tparam bias_t Type of bias. Needs to be an integral type.
+ * @tparam store_transposed Flag indicating whether sources (if false) or destinations (if
+ * true) are major indices
+ * @tparam multi_gpu Flag indicating whether template instantiation should target single-GPU (false)
+ * @param handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator, and
+ * handles to various CUDA libraries) to run graph algorithms.
+ * @param rng_state A pre-initialized raft::RngState object for generating random numbers
+ * @param graph_view Graph View object to generate NBR Sampling on.
+ * @param edge_weight_view Optional view object holding edge weights for @p graph_view.
+ * @param edge_id_view Optional view object holding edge ids for @p graph_view.
+ * @param edge_type_view Optional view object holding edge types for @p graph_view.
+ * @param edge_bias_view View object holding edge biases (to be used in biased sampling) for @p
+ * graph_view. Bias values should be non-negative and the sum of edge bias values from any vertex
+ * should not exceed std::numeric_limits::max(). 0 bias value indicates that the
+ * corresponding edge can never be selected.
+ * @param starting_vertices Device span of starting vertex IDs for the sampling.
+ * In a multi-gpu context the starting vertices should be local to this GPU.
+ * @param starting_vertex_label_offsets Optional device span of labels associated with each starting
+ * vertex for the sampling.
+ * @param label_to_output_comm_rank Optional device span identifying which rank should get sampling
+ * outputs of each vertex label. This should be the same on each rank.
+ * @param fan_out Host span defining branching out (fan-out) degree per source vertex for each
+ * level.
+ * @param flags A set of flags indicating which sampling features should be used.
+ * @param do_expensive_check A flag to run expensive checks for input arguments (if set to `true`).
+ * @return tuple device vectors (vertex_t source_vertex, vertex_t destination_vertex,
+ * optional weight_t weight, optional edge_t edge id, optional edge_type_t edge type,
+ * optional int32_t hop, optional label_t label, optional size_t offsets)
+ */
+
+template
+std::tuple,
+ rmm::device_uvector,
+ std::optional>,
+ std::optional>,
+ std::optional>,
+ std::optional>,
+ std::optional>>
+homogeneous_biased_neighbor_sample(
+ raft::handle_t const& handle,
+ raft::random::RngState& rng_state,
+ graph_view_t const& graph_view,
+ std::optional> edge_weight_view,
+ std::optional> edge_id_view,
+ std::optional> edge_type_view,
+ edge_property_view_t edge_bias_view,
+ raft::device_span starting_vertices,
+ std::optional> starting_vertex_label_offsets,
+ std::optional> label_to_output_comm_rank,
+ raft::host_span fan_out,
+ sampling_flags_t sampling_flags,
+ bool do_expensive_check = false);
+
+/**
+ * @brief Heterogeneous Uniform Neighborhood Sampling.
+ *
+ * This function traverses from a set of starting vertices, traversing outgoing edges and
+ * randomly selects (uniformly) from these outgoing neighbors to extract a subgraph.
+ * The branching out to select outgoing neighbors is performed with heterogeneous fanouts
+ * where the number of edge types is bigger than 1.
+ *
+ * Output from this function is a tuple of vectors (src, dst, weight, edge_id, edge_type, hop,
+ * offsets), identifying the randomly selected edges where the size of src, dst, weight, edge_id,
+ * edge_type and hop is the number of sampled edges while the size of the offsets vector is the
+ * number of labels + 1. src is the source vertex, dst is the destination vertex, weight
+ * (optional) is the edge weight, edge_id (optional) identifies the edge id, edge_type (optional)
+ * identifies the edge type, hop identifies which hop the edge was encountered in.
+ * The offsets array (optional) identifies the offset for each label.
+ *
+ * If @p label_to_output_comm_rank is specified then the data will be shuffled so that all entries
+ * for a particular label are returned on the specified rank.
+ *
+ * @tparam vertex_t Type of vertex identifiers. Needs to be an integral type.
+ * @tparam edge_t Type of edge identifiers. Needs to be an integral type.
+ * @tparam weight_t Type of edge weights. Needs to be a floating point type.
+ * @tparam edge_type_t Type of edge type. Needs to be an integral type.
+ * @tparam store_transposed Flag indicating whether sources (if false) or destinations (if
+ * true) are major indices
+ * @tparam multi_gpu Flag indicating whether template instantiation should target single-GPU (false)
+ * @param handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator, and
+ * handles to various CUDA libraries) to run graph algorithms.
+ * @param rng_state A pre-initialized raft::RngState object for generating random numbers
+ * @param graph_view Graph View object to generate NBR Sampling on.
+ * @param edge_weight_view Optional view object holding edge weights for @p graph_view.
+ * @param edge_id_view Optional view object holding edge ids for @p graph_view.
+ * @param edge_type_view Optional view object holding edge types for @p graph_view.
+ * @param starting_vertices Device span of starting vertex IDs for the sampling.
+ * In a multi-gpu context the starting vertices should be local to this GPU.
+ * @param starting_vertex_label_offsets Optional device span of labels associated with each starting
+ * vertex for the sampling.
+ * @param label_to_output_comm_rank Optional device span identifying which rank should get sampling
+ * outputs of each vertex label. This should be the same on each rank.
+ * @param fan_out Host span defining branching out (fan-out) degree per source vertex for each
+ * level. The fanout value at hop x is given by the expression 'fanout[x*num_edge_types +
+ * edge_type_id]'
+ * @param num_edge_types Number of edge types where a value of 1 translates to homogeneous neighbor
+ * sample whereas a value greater than 1 translates to heterogeneous neighbor sample.
+ * @param flags A set of flags indicating which sampling features should be used.
+ * @param do_expensive_check A flag to run expensive checks for input arguments (if set to `true`).
+ * @return tuple device vectors (vertex_t source_vertex, vertex_t destination_vertex,
+ * optional weight_t weight, optional edge_t edge id, optional edge_type_t edge type,
+ * optional int32_t hop, optional label_t label, optional size_t offsets)
+ */
+template
+std::tuple,
+ rmm::device_uvector,
+ std::optional>,
+ std::optional>,
+ std::optional>,
+ std::optional>,
+ std::optional>>
+heterogeneous_uniform_neighbor_sample(
+ raft::handle_t const& handle,
+ raft::random::RngState& rng_state,
+ graph_view_t const& graph_view,
+ std::optional> edge_weight_view,
+ std::optional> edge_id_view,
+ std::optional> edge_type_view,
+ raft::device_span starting_vertices,
+ std::optional> starting_vertex_label_offsets,
+ std::optional> label_to_output_comm_rank,
+ raft::host_span fan_out,
+ edge_type_t num_edge_types,
+ sampling_flags_t sampling_flags,
+ bool do_expensive_check = false);
+
+/**
+ * @brief Heterogeneous Biased Neighborhood Sampling.
+ *
+ * This function traverses from a set of starting vertices, traversing outgoing edges and
+ * randomly selects (with edge biases) from these outgoing neighbors to extract a subgraph.
+ * The branching out to select outgoing neighbors is performed with heterogeneous fanouts
+ * where the number of edge types is bigger than 1.
+ *
+ * Output from this function is a tuple of vectors (src, dst, weight, edge_id, edge_type, hop,
+ * offsets), identifying the randomly selected edges where the size of src, dst, weight, edge_id,
+ * edge_type and hop is the number of sampled edges while the size of the offsets vector is the
+ * number of labels + 1. src is the source vertex, dst is the destination vertex, weight
+ * (optional) is the edge weight, edge_id (optional) identifies the edge id, edge_type (optional)
+ * identifies the edge type, hop identifies which hop the edge was encountered in.
+ * The offsets array (optional) identifies the offset for each label.
+ *
+ * If @p label_to_output_comm_rank is specified then the data will be shuffled so that all entries
+ * for a particular label are returned on the specified rank.
+ *
+ * @tparam vertex_t Type of vertex identifiers. Needs to be an integral type.
+ * @tparam edge_t Type of edge identifiers. Needs to be an integral type.
+ * @tparam weight_t Type of edge weights. Needs to be a floating point type.
+ * @tparam edge_type_t Type of edge type. Needs to be an integral type.
+ * @tparam bias_t Type of bias. Needs to be an integral type.
+ * @tparam store_transposed Flag indicating whether sources (if false) or destinations (if
+ * true) are major indices
+ * @tparam multi_gpu Flag indicating whether template instantiation should target single-GPU (false)
+ * @param handle RAFT handle object to encapsulate resources (e.g. CUDA stream, communicator, and
+ * handles to various CUDA libraries) to run graph algorithms.
+ * @param rng_state A pre-initialized raft::RngState object for generating random numbers
+ * @param graph_view Graph View object to generate NBR Sampling on.
+ * @param edge_weight_view Optional view object holding edge weights for @p graph_view.
+ * @param edge_id_view Optional view object holding edge ids for @p graph_view.
+ * @param edge_type_view Optional view object holding edge types for @p graph_view.
+ * @param edge_bias_view View object holding edge biases (to be used in biased sampling) for @p
+ * graph_view. Bias values should be non-negative and the sum of edge bias values from any vertex
+ * should not exceed std::numeric_limits::max(). 0 bias value indicates that the
+ * corresponding edge can never be selected.
+ * @param starting_vertices Device span of starting vertex IDs for the sampling.
+ * In a multi-gpu context the starting vertices should be local to this GPU.
+ * @param starting_vertex_label_offsets Optional device span of labels associated with each starting
+ * vertex for the sampling.
+ * @param label_to_output_comm_rank Optional device span identifying which rank should get sampling
+ * outputs of each vertex label. This should be the same on each rank.
+ * @param fan_out Host span defining branching out (fan-out) degree per source vertex for each
+ * level. The fanout value at hop x is given by the expression 'fanout[x*num_edge_types +
+ * edge_type_id]'
+ * @param num_edge_types Number of edge types where a value of 1 translates to homogeneous neighbor
+ * sample whereas a value greater than 1 translates to heterogeneous neighbor sample.
+ * @param flags A set of flags indicating which sampling features should be used.
+ * @param do_expensive_check A flag to run expensive checks for input arguments (if set to `true`).
+ * @return tuple device vectors (vertex_t source_vertex, vertex_t destination_vertex,
+ * optional weight_t weight, optional edge_t edge id, optional edge_type_t edge type,
+ * optional int32_t hop, optional label_t label, optional size_t offsets)
+ */
+template
+std::tuple,
+ rmm::device_uvector,
+ std::optional>,
+ std::optional>,
+ std::optional>,
+ std::optional>,
+ std::optional>>
+heterogeneous_biased_neighbor_sample(
+ raft::handle_t const& handle,
+ raft::random::RngState& rng_state,
+ graph_view_t const& graph_view,
+ std::optional> edge_weight_view,
+ std::optional> edge_id_view,
+ std::optional> edge_type_view,
+ edge_property_view_t edge_bias_view,
+ raft::device_span starting_vertices,
+ std::optional> starting_vertex_label_offsets,
+ std::optional> label_to_output_comm_rank,
+ raft::host_span fan_out,
+ edge_type_t num_edge_types,
+ sampling_flags_t sampling_flags,
+ bool do_expensive_check = false);
+
/*
* @brief renumber sampled edge list and compress to the (D)CSR|(D)CSC format.
*
diff --git a/cpp/include/cugraph_c/sampling_algorithms.h b/cpp/include/cugraph_c/sampling_algorithms.h
index bb26e577915..ef75e726d80 100644
--- a/cpp/include/cugraph_c/sampling_algorithms.h
+++ b/cpp/include/cugraph_c/sampling_algorithms.h
@@ -199,6 +199,13 @@ typedef struct {
int32_t align_;
} cugraph_sampling_options_t;
+/**
+ * @brief Opaque sampling options type
+ */
+typedef struct {
+ int32_t align_;
+} sampling_flags_t;
+
/**
* @brief Enumeration for prior sources behavior
*/
@@ -323,6 +330,8 @@ void cugraph_sampling_options_free(cugraph_sampling_options_t* options);
/**
* @brief Uniform Neighborhood Sampling
*
+ * @deprecated This API will be deleted, use cugraph_homogeneous_uniform_neighbor_sample
+ *
* Returns a sample of the neighborhood around specified start vertices. Optionally, each
* start vertex can be associated with a label, allowing the caller to specify multiple batches
* of sampling requests in the same function call - which should improve GPU utilization.
@@ -348,8 +357,8 @@ void cugraph_sampling_options_free(cugraph_sampling_options_t* options);
* label_to_comm_rank[i]. If not specified then the output data will not be shuffled between ranks.
* @param [in] label_offsets Device array of the offsets for each label in the seed list. This
* parameter is only used with the retain_seeds option.
- * @param [in] fanout Host array defining the fan out at each step in the sampling algorithm.
- * We only support fanout values of type INT32
+ * @param [in] fan_out Host array defining the fan out at each step in the sampling
+ * algorithm. We only support fan_out values of type INT32
* @param [in,out] rng_state State of the random number generator, updated with each call
* @param [in] sampling_options
* Opaque pointer defining the sampling options.
@@ -378,6 +387,8 @@ cugraph_error_code_t cugraph_uniform_neighbor_sample(
/**
* @brief Biased Neighborhood Sampling
*
+ * @deprecated This API will be deleted, use cugraph_homogeneous_biased_neighbor_sample.
+ *
* Returns a sample of the neighborhood around specified start vertices. Optionally, each
* start vertex can be associated with a label, allowing the caller to specify multiple batches
* of sampling requests in the same function call - which should improve GPU utilization.
@@ -406,8 +417,8 @@ cugraph_error_code_t cugraph_uniform_neighbor_sample(
* label_to_comm_rank[i]. If not specified then the output data will not be shuffled between ranks.
* @param [in] label_offsets Device array of the offsets for each label in the seed list. This
* parameter is only used with the retain_seeds option.
- * @param [in] fanout Host array defining the fan out at each step in the sampling algorithm.
- * We only support fanout values of type INT32
+ * @param [in] fan_out Host array defining the fan out at each step in the sampling
+ * algorithm. We only support fan_out values of type INT32
* @param [in,out] rng_state State of the random number generator, updated with each call
* @param [in] sampling_options
* Opaque pointer defining the sampling options.
@@ -434,6 +445,186 @@ cugraph_error_code_t cugraph_biased_neighbor_sample(
cugraph_sample_result_t** result,
cugraph_error_t** error);
+/**
+ * @brief Homogeneous Uniform Neighborhood Sampling
+ *
+ * Returns a sample of the neighborhood around specified start vertices and fan_out.
+ * The neighborhood is sampled uniformly.
+ * Optionally, each start vertex can be associated with a label, allowing the caller to specify
+ * multiple batches of sampling requests in the same function call - which should improve GPU
+ * utilization.
+ *
+ * If label is NULL then all start vertices will be considered part of the same batch and the
+ * return value will not have a label column.
+ *
+ * @param [in] handle Handle for accessing resources
+ * * @param [in,out] rng_state State of the random number generator, updated with each call
+ * @param [in] graph Pointer to graph. NOTE: Graph might be modified if the storage
+ * needs to be transposed
+ * @param [in] start_vertices Device array of start vertices for the sampling
+ * @param [in] starting_vertex_label_offsets Device array of the offsets for each label in
+ * the seed list. This parameter is only used with the retain_seeds option.
+ * @param [in] fan_out Host array defining the fan out at each step in the sampling
+ * algorithm. We only support fan_out values of type INT32
+ * @param [in] sampling_options
+ * Opaque pointer defining the sampling options.
+ * @param [in] do_expensive_check
+ * A flag to run expensive checks for input arguments (if set to true)
+ * @param [out] result Output from the uniform_neighbor_sample call
+ * @param [out] error Pointer to an error object storing details of any error. Will
+ * be populated if error code is not CUGRAPH_SUCCESS
+ * @return error code
+ */
+cugraph_error_code_t cugraph_homogeneous_uniform_neighbor_sample(
+ const cugraph_resource_handle_t* handle,
+ cugraph_rng_state_t* rng_state,
+ cugraph_graph_t* graph,
+ const cugraph_type_erased_device_array_view_t* start_vertices,
+ const cugraph_type_erased_device_array_view_t* starting_vertex_label_offsets,
+ const cugraph_type_erased_host_array_view_t* fan_out,
+ const cugraph_sampling_options_t* options,
+ bool_t do_expensive_check,
+ cugraph_sample_result_t** result,
+ cugraph_error_t** error);
+
+/**
+ * @brief Homogeneous Biased Neighborhood Sampling
+ *
+ * Returns a sample of the neighborhood around specified start vertices and fan_out.
+ * The neighborhood is sampled uniformly.
+ * Optionally, each start vertex can be associated with a label, allowing the caller to specify
+ * multiple batches of sampling requests in the same function call - which should improve GPU
+ * utilization.
+ *
+ * If label is NULL then all start vertices will be considered part of the same batch and the
+ * return value will not have a label column.
+ *
+ * @param [in] handle Handle for accessing resources
+ * * @param [in,out] rng_state State of the random number generator, updated with each call
+ * @param [in] graph Pointer to graph. NOTE: Graph might be modified if the storage
+ * needs to be transposed
+ * @param [in] edge_biases Device array of edge biases to use for sampling. If NULL
+ * use the edge weight as the bias. If set to NULL, edges will be sampled uniformly.
+ * @param [in] start_vertices Device array of start vertices for the sampling
+ * @param [in] starting_vertex_label_offsets Device array of the offsets for each label in
+ * the seed list. This parameter is only used with the retain_seeds option.
+ * @param [in] fan_out Host array defining the fan out at each step in the sampling
+ * algorithm. We only support fan_out values of type INT32
+ * @param [in] sampling_options
+ * Opaque pointer defining the sampling options.
+ * @param [in] do_expensive_check
+ * A flag to run expensive checks for input arguments (if set to true)
+ * @param [out] result Output from the uniform_neighbor_sample call
+ * @param [out] error Pointer to an error object storing details of any error. Will
+ * be populated if error code is not CUGRAPH_SUCCESS
+ * @return error code
+ */
+cugraph_error_code_t cugraph_homogeneous_biased_neighbor_sample(
+ const cugraph_resource_handle_t* handle,
+ cugraph_rng_state_t* rng_state,
+ cugraph_graph_t* graph,
+ const cugraph_edge_property_view_t* edge_biases,
+ const cugraph_type_erased_device_array_view_t* start_vertices,
+ const cugraph_type_erased_device_array_view_t* starting_vertex_label_offsets,
+ const cugraph_type_erased_host_array_view_t* fan_out,
+ const cugraph_sampling_options_t* options,
+ bool_t do_expensive_check,
+ cugraph_sample_result_t** result,
+ cugraph_error_t** error);
+
+/**
+ * @brief Heterogeneous Uniform Neighborhood Sampling
+ *
+ * Returns a sample of the neighborhood around specified start vertices and fan_out.
+ * The neighborhood is sampled uniformly.
+ * Optionally, each start vertex can be associated with a label, allowing the caller to specify
+ * multiple batches of sampling requests in the same function call - which should improve GPU
+ * utilization.
+ *
+ * If label is NULL then all start vertices will be considered part of the same batch and the
+ * return value will not have a label column.
+ *
+ * @param [in] handle Handle for accessing resources
+ * * @param [in,out] rng_state State of the random number generator, updated with each call
+ * @param [in] graph Pointer to graph. NOTE: Graph might be modified if the storage
+ * needs to be transposed
+ * @param [in] start_vertices Device array of start vertices for the sampling
+ * @param [in] starting_vertex_label_offsets Device array of the offsets for each label in
+ * the seed list. This parameter is only used with the retain_seeds option.
+ * @param [in] fan_out Host array defining the fan out at each step in the sampling
+ * algorithm. We only support fan_out values of type INT32
+ * @param [in] num_edge_types Number of edge types where a value of 1 translates to homogeneous
+ * neighbor sample whereas a value greater than 1 translates to heterogeneous neighbor sample.
+ * @param [in] sampling_options
+ * Opaque pointer defining the sampling options.
+ * @param [in] do_expensive_check
+ * A flag to run expensive checks for input arguments (if set to true)
+ * @param [out] result Output from the uniform_neighbor_sample call
+ * @param [out] error Pointer to an error object storing details of any error. Will
+ * be populated if error code is not CUGRAPH_SUCCESS
+ * @return error code
+ */
+cugraph_error_code_t cugraph_heterogeneous_uniform_neighbor_sample(
+ const cugraph_resource_handle_t* handle,
+ cugraph_rng_state_t* rng_state,
+ cugraph_graph_t* graph,
+ const cugraph_type_erased_device_array_view_t* start_vertices,
+ const cugraph_type_erased_device_array_view_t* starting_vertex_label_offsets,
+ const cugraph_type_erased_host_array_view_t* fan_out,
+ int num_edge_types,
+ const cugraph_sampling_options_t* options,
+ bool_t do_expensive_check,
+ cugraph_sample_result_t** result,
+ cugraph_error_t** error);
+
+/**
+ * @brief Heterogeneous Biased Neighborhood Sampling
+ *
+ * Returns a sample of the neighborhood around specified start vertices and fan_out.
+ * The neighborhood is sampled uniformly.
+ * Optionally, each start vertex can be associated with a label, allowing the caller to specify
+ * multiple batches of sampling requests in the same function call - which should improve GPU
+ * utilization.
+ *
+ * If label is NULL then all start vertices will be considered part of the same batch and the
+ * return value will not have a label column.
+ *
+ * @param [in] handle Handle for accessing resources
+ * * @param [in,out] rng_state State of the random number generator, updated with each call
+ * @param [in] graph Pointer to graph. NOTE: Graph might be modified if the storage
+ * needs to be transposed
+ * @param [in] edge_biases Device array of edge biases to use for sampling. If NULL
+ * use the edge weight as the bias. If set to NULL, edges will be sampled uniformly.
+ * @param [in] start_vertices Device array of start vertices for the sampling
+ * @param [in] starting_vertex_label_offsets Device array of the offsets for each label in
+ * the seed list. This parameter is only used with the retain_seeds option.
+ * @param [in] fan_out Host array defining the fan out at each step in the sampling
+ * algorithm. We only support fan_out values of type INT32
+ * @param [in] num_edge_types Number of edge types where a value of 1 translates to homogeneous
+ * neighbor sample whereas a value greater than 1 translates to heterogeneous neighbor sample.
+ * @param [in] sampling_options
+ * Opaque pointer defining the sampling options.
+ * @param [in] do_expensive_check
+ * A flag to run expensive checks for input arguments (if set to true)
+ * @param [out] result Output from the uniform_neighbor_sample call
+ * @param [out] error Pointer to an error object storing details of any error. Will
+ * be populated if error code is not CUGRAPH_SUCCESS
+ * @return error code
+ */
+cugraph_error_code_t cugraph_heterogeneous_biased_neighbor_sample(
+ const cugraph_resource_handle_t* handle,
+ cugraph_rng_state_t* rng_state,
+ cugraph_graph_t* graph,
+ const cugraph_edge_property_view_t* edge_biases,
+ const cugraph_type_erased_device_array_view_t* start_vertices,
+ const cugraph_type_erased_device_array_view_t* starting_vertex_label_offsets,
+ const cugraph_type_erased_host_array_view_t* fan_out,
+ int num_edge_types,
+ const cugraph_sampling_options_t* options,
+ bool_t do_expensive_check,
+ cugraph_sample_result_t** result,
+ cugraph_error_t** error);
+
/**
* @deprecated This call should be replaced with cugraph_sample_result_get_majors
* @brief Get the source vertices from the sampling algorithm result
@@ -584,6 +775,26 @@ cugraph_type_erased_device_array_view_t* cugraph_sample_result_get_renumber_map(
cugraph_type_erased_device_array_view_t* cugraph_sample_result_get_renumber_map_offsets(
const cugraph_sample_result_t* result);
+/**
+ * @ingroup samplingC
+ * @brief Get the edge renumber map
+ *
+ * @param [in] result The result from a sampling algorithm
+ * @return type erased array pointing to the renumber map
+ */
+cugraph_type_erased_device_array_view_t* cugraph_sample_result_get_edge_renumber_map(
+ const cugraph_sample_result_t* result);
+
+/**
+ * @ingroup samplingC
+ * @brief Get the edge renumber map offets
+ *
+ * @param [in] result The result from a sampling algorithm
+ * @return type erased array pointing to the renumber map
+ */
+cugraph_type_erased_device_array_view_t* cugraph_sample_result_get_edge_renumber_map_offsets(
+ const cugraph_sample_result_t* result);
+
/**
* @ingroup samplingC
* @brief Free a sampling result
diff --git a/cpp/src/c_api/array.hpp b/cpp/src/c_api/array.hpp
index 048d2ee1cea..0ab30a1cb72 100644
--- a/cpp/src/c_api/array.hpp
+++ b/cpp/src/c_api/array.hpp
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021-2023, NVIDIA CORPORATION.
+ * Copyright (c) 2021-2024, NVIDIA CORPORATION.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -125,6 +125,27 @@ struct cugraph_type_erased_host_array_t {
std::copy(vec.begin(), vec.end(), reinterpret_cast(data_.get()));
}
+ cugraph_type_erased_host_array_t(cugraph_type_erased_host_array_view_t const* view_p)
+ : data_(std::make_unique(view_p->num_bytes_)),
+ size_(view_p->size_),
+ num_bytes_(view_p->num_bytes_),
+ type_(view_p->type_)
+ {
+ std::copy(view_p->data_, view_p->data_ + num_bytes_, data_.get());
+ }
+
+ template
+ T* as_type()
+ {
+ return reinterpret_cast(data_.get());
+ }
+
+ template
+ T const* as_type() const
+ {
+ return reinterpret_cast(data_.get());
+ }
+
auto view()
{
return new cugraph_type_erased_host_array_view_t{data_.get(), size_, num_bytes_, type_};
diff --git a/cpp/src/c_api/graph_functions.cpp b/cpp/src/c_api/graph_functions.cpp
index df741a349d2..8778369dbe6 100644
--- a/cpp/src/c_api/graph_functions.cpp
+++ b/cpp/src/c_api/graph_functions.cpp
@@ -84,7 +84,7 @@ struct create_vertex_pairs_functor : public cugraph::c_api::abstract_functor {
std::nullopt,
std::nullopt);
}
-
+ // FIXME: use std::tuple (template) instead.
result_ = new cugraph::c_api::cugraph_vertex_pairs_t{
new cugraph::c_api::cugraph_type_erased_device_array_t(first_copy, graph_->vertex_type_),
new cugraph::c_api::cugraph_type_erased_device_array_t(second_copy, graph_->vertex_type_)};
diff --git a/cpp/src/c_api/neighbor_sampling.cpp b/cpp/src/c_api/neighbor_sampling.cpp
index 69306806030..be3a44d813a 100644
--- a/cpp/src/c_api/neighbor_sampling.cpp
+++ b/cpp/src/c_api/neighbor_sampling.cpp
@@ -16,12 +16,15 @@
#include "c_api/abstract_functor.hpp"
#include "c_api/graph.hpp"
+#include "c_api/graph_helper.hpp"
#include "c_api/properties.hpp"
#include "c_api/random.hpp"
#include "c_api/resource_handle.hpp"
#include "c_api/utils.hpp"
+#include "sampling/detail/sampling_utils.hpp"
#include
+#include
#include
#include
@@ -44,6 +47,13 @@ struct cugraph_sampling_options_t {
bool_t retain_seeds_{FALSE};
};
+struct sampling_flags_t {
+ prior_sources_behavior_t prior_sources_behavior_{prior_sources_behavior_t::DEFAULT};
+ bool_t return_hops_{FALSE};
+ bool_t dedupe_sources_{FALSE};
+ bool_t with_replacement_{FALSE};
+};
+
struct cugraph_sample_result_t {
cugraph_type_erased_device_array_t* major_offsets_{nullptr};
cugraph_type_erased_device_array_t* majors_{nullptr};
@@ -56,6 +66,8 @@ struct cugraph_sample_result_t {
cugraph_type_erased_device_array_t* label_{nullptr};
cugraph_type_erased_device_array_t* renumber_map_{nullptr};
cugraph_type_erased_device_array_t* renumber_map_offsets_{nullptr};
+ cugraph_type_erased_device_array_t* edge_renumber_map_{nullptr};
+ cugraph_type_erased_device_array_t* edge_renumber_map_offsets_{nullptr};
};
} // namespace c_api
@@ -63,6 +75,7 @@ struct cugraph_sample_result_t {
namespace {
+// Deprecated functor
struct uniform_neighbor_sampling_functor : public cugraph::c_api::abstract_functor {
raft::handle_t const& handle_;
cugraph::c_api::cugraph_graph_t* graph_{nullptr};
@@ -398,11 +411,14 @@ struct uniform_neighbor_sampling_functor : public cugraph::c_api::abstract_funct
: nullptr,
(renumber_map_offsets) ? new cugraph::c_api::cugraph_type_erased_device_array_t(
renumber_map_offsets.value(), SIZE_T)
- : nullptr};
+ : nullptr,
+ nullptr,
+ nullptr};
}
}
};
+// Deprecated functor
struct biased_neighbor_sampling_functor : public cugraph::c_api::abstract_functor {
raft::handle_t const& handle_;
cugraph::c_api::cugraph_graph_t* graph_{nullptr};
@@ -748,7 +764,598 @@ struct biased_neighbor_sampling_functor : public cugraph::c_api::abstract_functo
: nullptr,
(renumber_map_offsets) ? new cugraph::c_api::cugraph_type_erased_device_array_t(
renumber_map_offsets.value(), SIZE_T)
- : nullptr};
+ : nullptr,
+ nullptr,
+ nullptr};
+ }
+ }
+};
+
+struct neighbor_sampling_functor : public cugraph::c_api::abstract_functor {
+ raft::handle_t const& handle_;
+ cugraph::c_api::cugraph_rng_state_t* rng_state_{nullptr};
+ cugraph::c_api::cugraph_graph_t* graph_{nullptr};
+ cugraph::c_api::cugraph_edge_property_view_t const* edge_biases_{nullptr};
+ cugraph::c_api::cugraph_type_erased_device_array_view_t const* start_vertices_{nullptr};
+ cugraph::c_api::cugraph_type_erased_device_array_view_t const* start_vertex_offsets_{nullptr};
+ cugraph::c_api::cugraph_type_erased_host_array_view_t const* fan_out_{nullptr};
+ int num_edge_types_{};
+ cugraph::c_api::cugraph_sampling_options_t options_{};
+ bool is_biased_{false};
+ bool do_expensive_check_{false};
+ cugraph::c_api::cugraph_sample_result_t* result_{nullptr};
+
+ neighbor_sampling_functor(cugraph_resource_handle_t const* handle,
+ cugraph_rng_state_t* rng_state,
+ cugraph_graph_t* graph,
+ cugraph_edge_property_view_t const* edge_biases,
+ cugraph_type_erased_device_array_view_t const* start_vertices,
+ cugraph_type_erased_device_array_view_t const* start_vertex_offsets,
+ cugraph_type_erased_host_array_view_t const* fan_out,
+ int num_edge_types,
+ cugraph::c_api::cugraph_sampling_options_t options,
+ bool is_biased,
+ bool do_expensive_check)
+ : abstract_functor(),
+ handle_(*reinterpret_cast(handle)->handle_),
+ rng_state_(reinterpret_cast(rng_state)),
+ graph_(reinterpret_cast(graph)),
+ edge_biases_(
+ reinterpret_cast(edge_biases)),
+ start_vertices_(
+ reinterpret_cast(
+ start_vertices)),
+ start_vertex_offsets_(
+ reinterpret_cast(
+ start_vertex_offsets)),
+ fan_out_(
+ reinterpret_cast(fan_out)),
+ num_edge_types_(num_edge_types),
+ options_(options),
+ is_biased_(is_biased),
+ do_expensive_check_(do_expensive_check)
+ {
+ }
+
+ template
+ void operator()()
+ {
+ using label_t = int32_t;
+
+ // FIXME: Think about how to handle SG vice MG
+ if constexpr (!cugraph::is_candidate::value) {
+ unsupported();
+ } else {
+ // uniform_nbr_sample expects store_transposed == false
+ if constexpr (store_transposed) {
+ error_code_ = cugraph::c_api::
+ transpose_storage(
+ handle_, graph_, error_.get());
+ if (error_code_ != CUGRAPH_SUCCESS) return;
+ }
+
+ auto graph =
+ reinterpret_cast*>(graph_->graph_);
+
+ auto graph_view = graph->view();
+
+ auto edge_weights = reinterpret_cast<
+ cugraph::edge_property_t,
+ weight_t>*>(graph_->edge_weights_);
+
+ auto edge_ids = reinterpret_cast<
+ cugraph::edge_property_t,
+ edge_t>*>(graph_->edge_ids_);
+
+ auto edge_types = reinterpret_cast<
+ cugraph::edge_property_t,
+ edge_type_t>*>(graph_->edge_types_);
+
+ auto number_map = reinterpret_cast*>(graph_->number_map_);
+
+ auto edge_biases =
+ edge_biases_ ? reinterpret_cast*>(
+ edge_biases_->edge_property_)
+ : nullptr;
+
+ rmm::device_uvector start_vertices(start_vertices_->size_, handle_.get_stream());
+ raft::copy(start_vertices.data(),
+ start_vertices_->as_type(),
+ start_vertices.size(),
+ handle_.get_stream());
+
+ std::optional> start_vertex_labels{std::nullopt};
+ std::optional> local_label_to_comm_rank{std::nullopt};
+ std::optional> label_to_comm_rank{
+ std::nullopt}; // global after allgatherv
+
+ std::optional> renumbered_and_sorted_edge_id_renumber_map(
+ std::nullopt);
+ std::optional>
+ renumbered_and_sorted_edge_id_renumber_map_label_type_offsets(std::nullopt);
+
+ if (start_vertex_offsets_ != nullptr) {
+ // Retrieve the start_vertex_labels
+ start_vertex_labels = cugraph::detail::convert_starting_vertex_label_offsets_to_labels(
+ handle_,
+ raft::device_span{start_vertex_offsets_->as_type(),
+ start_vertex_offsets_->size_});
+
+ // Get the number of labels on each GPU
+
+ if constexpr (multi_gpu) {
+ auto num_local_labels = start_vertex_offsets_->size_ - 1;
+
+ auto global_labels = cugraph::host_scalar_allgather(
+ handle_.get_comms(), num_local_labels, handle_.get_stream());
+
+ std::exclusive_scan(
+ global_labels.begin(), global_labels.end(), global_labels.begin(), label_t{0});
+
+ // Compute the global start_vertex_label_offsets
+
+ cugraph::detail::transform_increment_ints(
+ raft::device_span{(*start_vertex_labels).data(),
+ (*start_vertex_labels).size()},
+ (label_t)global_labels[handle_.get_comms().get_rank()],
+ handle_.get_stream());
+
+ rmm::device_uvector unique_labels((*start_vertex_labels).size(),
+ handle_.get_stream());
+ raft::copy(unique_labels.data(),
+ (*start_vertex_labels).data(),
+ unique_labels.size(),
+ handle_.get_stream());
+
+ // Get unique labels
+ // sort the start_vertex_labels
+ cugraph::detail::sort_ints(
+ handle_.get_stream(),
+ raft::device_span{unique_labels.data(), unique_labels.size()});
+
+ auto num_unique_labels = cugraph::detail::unique_ints(
+ handle_.get_stream(),
+ raft::device_span{unique_labels.data(), unique_labels.size()});
+
+ (*local_label_to_comm_rank).resize(num_unique_labels, handle_.get_stream());
+
+ cugraph::detail::scalar_fill(
+ handle_.get_stream(),
+ (*local_label_to_comm_rank).begin(), // This should be rename to rank
+ (*local_label_to_comm_rank).size(),
+ label_t{handle_.get_comms().get_rank()});
+
+ // Perform allgather to get global_label_to_comm_rank_d_vector
+ auto recvcounts = cugraph::host_scalar_allgather(
+ handle_.get_comms(), num_unique_labels, handle_.get_stream());
+
+ std::vector displacements(recvcounts.size());
+ std::exclusive_scan(
+ recvcounts.begin(), recvcounts.end(), displacements.begin(), size_t{0});
+
+ (*label_to_comm_rank)
+ .resize(displacements.back() + recvcounts.back(), handle_.get_stream());
+
+ cugraph::device_allgatherv(handle_.get_comms(),
+ (*local_label_to_comm_rank).begin(),
+ (*label_to_comm_rank).begin(),
+ recvcounts,
+ displacements,
+ handle_.get_stream());
+
+ std::tie(start_vertices, *start_vertex_labels) =
+ cugraph::detail::shuffle_ext_vertex_value_pairs_to_local_gpu_by_vertex_partitioning(
+ handle_, std::move(start_vertices), std::move(*start_vertex_labels));
+ }
+ } else {
+ if constexpr (multi_gpu) {
+ start_vertices =
+ cugraph::detail::shuffle_ext_vertices_to_local_gpu_by_vertex_partitioning(
+ handle_, std::move(start_vertices));
+ }
+ }
+ //
+ // Need to renumber start_vertices
+ //
+ cugraph::renumber_local_ext_vertices(
+ handle_,
+ start_vertices.data(),
+ start_vertices.size(),
+ number_map->data(),
+ graph_view.local_vertex_partition_range_first(),
+ graph_view.local_vertex_partition_range_last(),
+ do_expensive_check_);
+
+ rmm::device_uvector src(0, handle_.get_stream());
+ rmm::device_uvector dst(0, handle_.get_stream());
+ std::optional> wgt{std::nullopt};
+ std::optional> edge_id{std::nullopt};
+ std::optional> edge_type{std::nullopt};
+ std::optional> hop{std::nullopt};
+ std::optional> edge_label{std::nullopt};
+ std::optional> offsets{std::nullopt};
+
+ // FIXME: For biased sampling, the user should pass either biases or edge weights,
+ // otherwised throw an error and suggest the user to call uniform neighbor sample instead
+
+ if (num_edge_types_ > 1) {
+ // call heterogeneous neighbor sample
+ if (is_biased_) {
+ std::tie(src, dst, wgt, edge_id, edge_type, hop, offsets) =
+ cugraph::heterogeneous_biased_neighbor_sample(
+ handle_,
+ rng_state_->rng_state_,
+ graph_view,
+ (edge_weights != nullptr) ? std::make_optional(edge_weights->view()) : std::nullopt,
+ (edge_ids != nullptr) ? std::make_optional(edge_ids->view()) : std::nullopt,
+ (edge_types != nullptr) ? std::make_optional(edge_types->view()) : std::nullopt,
+ (edge_biases != nullptr) ? *edge_biases : edge_weights->view(),
+ raft::device_span{start_vertices.data(), start_vertices.size()},
+ (start_vertex_offsets_ != nullptr)
+ ? std::make_optional>((*start_vertex_labels).data(),
+ (*start_vertex_labels).size())
+ : std::nullopt,
+ label_to_comm_rank ? std::make_optional(raft::device_span{
+ (*label_to_comm_rank).data(), (*label_to_comm_rank).size()})
+ : std::nullopt,
+ raft::host_span(fan_out_->as_type(), fan_out_->size_),
+ num_edge_types_,
+ cugraph::sampling_flags_t{options_.prior_sources_behavior_,
+ options_.return_hops_,
+ options_.dedupe_sources_,
+ options_.with_replacement_},
+ do_expensive_check_);
+ } else {
+ std::tie(src, dst, wgt, edge_id, edge_type, hop, offsets) =
+ cugraph::heterogeneous_uniform_neighbor_sample(
+ handle_,
+ rng_state_->rng_state_,
+ graph_view,
+ (edge_weights != nullptr) ? std::make_optional(edge_weights->view()) : std::nullopt,
+ (edge_ids != nullptr) ? std::make_optional(edge_ids->view()) : std::nullopt,
+ (edge_types != nullptr) ? std::make_optional(edge_types->view()) : std::nullopt,
+ raft::device_span{start_vertices.data(), start_vertices.size()},
+ (start_vertex_offsets_ != nullptr)
+ ? std::make_optional>((*start_vertex_labels).data(),
+ (*start_vertex_labels).size())
+ : std::nullopt,
+ label_to_comm_rank ? std::make_optional(raft::device_span{
+ (*label_to_comm_rank).data(), (*label_to_comm_rank).size()})
+ : std::nullopt,
+ raft::host_span(fan_out_->as_type(), fan_out_->size_),
+ num_edge_types_,
+ cugraph::sampling_flags_t{options_.prior_sources_behavior_,
+ options_.return_hops_,
+ options_.dedupe_sources_,
+ options_.with_replacement_},
+ do_expensive_check_);
+ }
+ } else {
+ // Call homogeneous neighbor sample
+ if (is_biased_) {
+ std::tie(src, dst, wgt, edge_id, edge_type, hop, offsets) =
+ cugraph::homogeneous_biased_neighbor_sample(
+ handle_,
+ rng_state_->rng_state_,
+ graph_view,
+ (edge_weights != nullptr) ? std::make_optional(edge_weights->view()) : std::nullopt,
+ (edge_ids != nullptr) ? std::make_optional(edge_ids->view()) : std::nullopt,
+ (edge_types != nullptr) ? std::make_optional(edge_types->view()) : std::nullopt,
+ (edge_biases != nullptr) ? *edge_biases : edge_weights->view(),
+ raft::device_span{start_vertices.data(), start_vertices.size()},
+ (start_vertex_offsets_ != nullptr)
+ ? std::make_optional>((*start_vertex_labels).data(),
+ (*start_vertex_labels).size())
+ : std::nullopt,
+ label_to_comm_rank ? std::make_optional(raft::device_span{
+ (*label_to_comm_rank).data(), (*label_to_comm_rank).size()})
+ : std::nullopt,
+ raft::host_span(fan_out_->as_type(), fan_out_->size_),
+ cugraph::sampling_flags_t{options_.prior_sources_behavior_,
+ options_.return_hops_,
+ options_.dedupe_sources_,
+ options_.with_replacement_},
+ do_expensive_check_);
+ } else {
+ std::tie(src, dst, wgt, edge_id, edge_type, hop, offsets) =
+ cugraph::homogeneous_uniform_neighbor_sample(
+ handle_,
+ rng_state_->rng_state_,
+ graph_view,
+ (edge_weights != nullptr) ? std::make_optional(edge_weights->view()) : std::nullopt,
+ (edge_ids != nullptr) ? std::make_optional(edge_ids->view()) : std::nullopt,
+ (edge_types != nullptr) ? std::make_optional(edge_types->view()) : std::nullopt,
+ raft::device_span{start_vertices.data(), start_vertices.size()},
+ (start_vertex_offsets_ != nullptr)
+ ? std::make_optional>((*start_vertex_labels).data(),
+ (*start_vertex_labels).size())
+ : std::nullopt,
+ label_to_comm_rank ? std::make_optional(raft::device_span{
+ (*label_to_comm_rank).data(), (*label_to_comm_rank).size()})
+ : std::nullopt,
+ raft::host_span(fan_out_->as_type(), fan_out_->size_),
+ cugraph::sampling_flags_t{options_.prior_sources_behavior_,
+ options_.return_hops_,
+ options_.dedupe_sources_,
+ options_.with_replacement_},
+ do_expensive_check_);
+ }
+ }
+
+ std::vector vertex_partition_lasts = graph_view.vertex_partition_range_lasts();
+
+ cugraph::unrenumber_int_vertices(handle_,
+ src.data(),
+ src.size(),
+ number_map->data(),
+ vertex_partition_lasts,
+ do_expensive_check_);
+
+ cugraph::unrenumber_int_vertices(handle_,
+ dst.data(),
+ dst.size(),
+ number_map->data(),
+ vertex_partition_lasts,
+ do_expensive_check_);
+
+ std::optional> majors{std::nullopt};
+ rmm::device_uvector minors(0, handle_.get_stream());
+ std::optional> major_offsets{std::nullopt};
+
+ std::optional> label_hop_offsets{std::nullopt};
+
+ std::optional> renumber_map{std::nullopt};
+ std::optional> renumber_map_offsets{std::nullopt};
+
+ bool src_is_major = (options_.compression_type_ == cugraph_compression_type_t::CSR) ||
+ (options_.compression_type_ == cugraph_compression_type_t::DCSR) ||
+ (options_.compression_type_ == cugraph_compression_type_t::COO);
+
+ // Extract the edge_label from the offsets
+ if (offsets) {
+ edge_label = cugraph::c_api::expand_sparse_offsets(
+ raft::device_span{(*offsets).data(), (*offsets).size()},
+ label_t{0},
+ handle_.get_stream());
+ }
+
+ if (options_.renumber_results_) {
+ if (num_edge_types_ == 1) { // homogeneous renumbering
+ if (options_.compression_type_ == cugraph_compression_type_t::COO) {
+ // COO
+
+ rmm::device_uvector output_majors(0, handle_.get_stream());
+ rmm::device_uvector output_renumber_map(0, handle_.get_stream());
+ std::tie(output_majors,
+ minors,
+ wgt,
+ edge_id,
+ edge_type,
+ label_hop_offsets,
+ output_renumber_map,
+ renumber_map_offsets) =
+ cugraph::renumber_and_sort_sampled_edgelist(
+ handle_,
+ std::move(src),
+ std::move(dst),
+ std::move(wgt),
+ std::move(edge_id),
+ std::move(edge_type),
+ std::move(hop),
+ options_.retain_seeds_
+ ? std::make_optional(raft::device_span{
+ start_vertices_->as_type(), start_vertices_->size_})
+ : std::nullopt,
+ options_.retain_seeds_
+ ? std::make_optional(raft::device_span{
+ start_vertex_offsets_->as_type(), start_vertex_offsets_->size_})
+ : std::nullopt,
+ offsets ? std::make_optional(
+ raft::device_span{offsets->data(), offsets->size()})
+ : std::nullopt,
+ offsets ? (*offsets).size() - 1 : size_t{1},
+ hop ? fan_out_->size_ : size_t{1},
+ src_is_major,
+ do_expensive_check_);
+
+ majors.emplace(std::move(output_majors));
+ renumber_map.emplace(std::move(output_renumber_map));
+ } else {
+ // (D)CSC, (D)CSR
+
+ bool doubly_compress =
+ (options_.compression_type_ == cugraph_compression_type_t::DCSR) ||
+ (options_.compression_type_ == cugraph_compression_type_t::DCSC);
+
+ rmm::device_uvector output_major_offsets(0, handle_.get_stream());
+ rmm::device_uvector output_renumber_map(0, handle_.get_stream());
+
+ std::tie(majors,
+ output_major_offsets,
+ minors,
+ wgt,
+ edge_id,
+ edge_type,
+ label_hop_offsets,
+ output_renumber_map,
+ renumber_map_offsets) =
+ cugraph::renumber_and_compress_sampled_edgelist(
+ handle_,
+ std::move(src),
+ std::move(dst),
+ std::move(wgt),
+ std::move(edge_id),
+ std::move(edge_type),
+ std::move(hop),
+ options_.retain_seeds_
+ ? std::make_optional(raft::device_span{
+ start_vertices_->as_type(), start_vertices_->size_})
+ : std::nullopt,
+ options_.retain_seeds_
+ ? std::make_optional(raft::device_span{
+ start_vertex_offsets_->as_type(), start_vertex_offsets_->size_})
+ : std::nullopt,
+ offsets ? std::make_optional(
+ raft::device_span{offsets->data(), offsets->size()})
+ : std::nullopt,
+ edge_label ? (*offsets).size() - 1 : size_t{1}, // FIXME: update edge_label
+ hop ? fan_out_->size_ : size_t{1},
+ src_is_major,
+ options_.compress_per_hop_,
+ doubly_compress,
+ do_expensive_check_);
+
+ renumber_map.emplace(std::move(output_renumber_map));
+ major_offsets.emplace(std::move(output_major_offsets));
+ }
+
+ // These are now represented by label_hop_offsets
+ hop.reset();
+ offsets.reset();
+
+ } else { // heterogeneous renumbering
+
+ rmm::device_uvector vertex_type_offsets(
+ graph_view.local_vertex_partition_range_size(), handle_.get_stream());
+
+ cugraph::detail::sequence_fill(handle_.get_stream(),
+ vertex_type_offsets.begin(),
+ vertex_type_offsets.size(),
+ vertex_t{0} // FIXME: Update array
+ );
+
+ rmm::device_uvector output_majors(0, handle_.get_stream());
+ rmm::device_uvector output_renumber_map(0, handle_.get_stream());
+
+ // extract the edge_type from label_type_hop_offsets
+ std::optional> label_type_hop_offsets{std::nullopt};
+ std::tie(output_majors,
+ minors,
+ wgt,
+ edge_id,
+ label_type_hop_offsets, // Contains information about the type and hop offsets
+ output_renumber_map,
+ (*renumber_map_offsets),
+ renumbered_and_sorted_edge_id_renumber_map,
+ renumbered_and_sorted_edge_id_renumber_map_label_type_offsets) =
+ cugraph::heterogeneous_renumber_and_sort_sampled_edgelist(
+ handle_,
+ std::move(src),
+ std::move(dst),
+ std::move(wgt),
+ std::move(edge_id),
+ std::move(edge_type),
+ std::move(hop),
+ options_.retain_seeds_
+ ? std::make_optional(raft::device_span{
+ start_vertices_->as_type(), start_vertices_->size_})
+ : std::nullopt,
+ options_.retain_seeds_
+ ? std::make_optional(raft::device_span{
+ start_vertex_offsets_->as_type(), start_vertex_offsets_->size_})
+ : std::nullopt,
+ offsets ? std::make_optional(
+ raft::device_span{offsets->data(), offsets->size()})
+ : std::nullopt,
+ raft::device_span{vertex_type_offsets.data(),
+ vertex_type_offsets.size()},
+
+ edge_label ? (*offsets).size() - 1 : size_t{1},
+ hop ? fan_out_->size_ : size_t{1},
+ size_t{1},
+ num_edge_types_,
+ src_is_major,
+ do_expensive_check_);
+ if (edge_type) {
+ (*edge_type)
+ .resize(raft::device_span{(*label_type_hop_offsets).data(),
+ (*label_type_hop_offsets).size()}
+ .back() -
+ 1,
+ handle_.get_stream());
+ cugraph::detail::sequence_fill(
+ handle_.get_stream(), (*edge_type).begin(), (*edge_type).size(), edge_type_t{0});
+ }
+
+ majors.emplace(std::move(output_majors));
+ // FIXME: Need to update renumber_map because default values are being passed
+ renumber_map.emplace(std::move(output_renumber_map));
+ }
+
+ } else {
+ if (options_.compression_type_ != cugraph_compression_type_t::COO) {
+ CUGRAPH_FAIL("Can only use COO format if not renumbering");
+ }
+
+ std::tie(src, dst, wgt, edge_id, edge_type, label_hop_offsets) =
+ cugraph::sort_sampled_edgelist(handle_,
+ std::move(src),
+ std::move(dst),
+ std::move(wgt),
+ std::move(edge_id),
+ std::move(edge_type),
+ std::move(hop),
+ offsets
+ ? std::make_optional(raft::device_span{
+ offsets->data(), offsets->size()})
+ : std::nullopt,
+ // derive label size from offset size instead of performing
+ // thrust::unique on edge_label.
+ edge_label ? (*offsets).size() - 1 : size_t{1},
+ hop ? fan_out_->size_ : size_t{1},
+ src_is_major,
+ do_expensive_check_);
+
+ majors.emplace(std::move(src));
+ minors = std::move(dst);
+
+ hop.reset();
+ offsets.reset();
+ }
+
+ result_ = new cugraph::c_api::cugraph_sample_result_t{
+ (major_offsets)
+ ? new cugraph::c_api::cugraph_type_erased_device_array_t(*major_offsets, SIZE_T)
+ : nullptr,
+ (majors)
+ ? new cugraph::c_api::cugraph_type_erased_device_array_t(*majors, graph_->vertex_type_)
+ : nullptr,
+ new cugraph::c_api::cugraph_type_erased_device_array_t(minors, graph_->vertex_type_),
+ (edge_id)
+ ? new cugraph::c_api::cugraph_type_erased_device_array_t(*edge_id, graph_->edge_type_)
+ : nullptr,
+ (edge_type) ? new cugraph::c_api::cugraph_type_erased_device_array_t(
+ *edge_type, graph_->edge_type_id_type_)
+ : nullptr,
+ (wgt) ? new cugraph::c_api::cugraph_type_erased_device_array_t(*wgt, graph_->weight_type_)
+ : nullptr,
+ (hop) ? new cugraph::c_api::cugraph_type_erased_device_array_t(*hop, INT32)
+ : nullptr, // FIXME get rid of this
+ (label_hop_offsets)
+ ? new cugraph::c_api::cugraph_type_erased_device_array_t(*label_hop_offsets, SIZE_T)
+ : nullptr,
+ (edge_label)
+ ? new cugraph::c_api::cugraph_type_erased_device_array_t(edge_label.value(), INT32)
+ : nullptr,
+ (renumber_map) ? new cugraph::c_api::cugraph_type_erased_device_array_t(
+ renumber_map.value(), graph_->vertex_type_)
+ : nullptr,
+ (renumber_map_offsets) ? new cugraph::c_api::cugraph_type_erased_device_array_t(
+ renumber_map_offsets.value(), SIZE_T)
+ : nullptr,
+ (renumbered_and_sorted_edge_id_renumber_map)
+ ? new cugraph::c_api::cugraph_type_erased_device_array_t(
+ renumbered_and_sorted_edge_id_renumber_map.value(), graph_->edge_type_)
+ : nullptr,
+ (renumbered_and_sorted_edge_id_renumber_map_label_type_offsets)
+ ? new cugraph::c_api::cugraph_type_erased_device_array_t(
+ renumbered_and_sorted_edge_id_renumber_map_label_type_offsets.value(), SIZE_T)
+ : nullptr};
}
}
};
@@ -985,6 +1592,26 @@ extern "C" cugraph_type_erased_device_array_view_t* cugraph_sample_result_get_re
internal_pointer->renumber_map_offsets_->view());
}
+extern "C" cugraph_type_erased_device_array_view_t* cugraph_sample_result_get_edge_renumber_map(
+ const cugraph_sample_result_t* result)
+{
+ auto internal_pointer = reinterpret_cast(result);
+ return internal_pointer->renumber_map_ == nullptr
+ ? NULL
+ : reinterpret_cast(
+ internal_pointer->edge_renumber_map_->view());
+}
+
+extern "C" cugraph_type_erased_device_array_view_t*
+cugraph_sample_result_get_edge_renumber_map_offsets(const cugraph_sample_result_t* result)
+{
+ auto internal_pointer = reinterpret_cast(result);
+ return internal_pointer->renumber_map_ == nullptr
+ ? NULL
+ : reinterpret_cast(
+ internal_pointer->edge_renumber_map_offsets_->view());
+}
+
extern "C" cugraph_error_code_t cugraph_test_uniform_neighborhood_sample_result_create(
const cugraph_resource_handle_t* handle,
const cugraph_type_erased_device_array_view_t* srcs,
@@ -1292,6 +1919,7 @@ cugraph_error_code_t cugraph_uniform_neighbor_sample(
"fan_out should be of type int",
*error);
+ // Deprecated functor
uniform_neighbor_sampling_functor functor{handle,
graph,
start_vertices,
@@ -1369,6 +1997,7 @@ cugraph_error_code_t cugraph_biased_neighbor_sample(
"fan_out should be of type int",
*error);
+ // Deprecated functor
biased_neighbor_sampling_functor functor{handle,
graph,
edge_biases,
@@ -1383,3 +2012,249 @@ cugraph_error_code_t cugraph_biased_neighbor_sample(
do_expensive_check};
return cugraph::c_api::run_algorithm(graph, functor, result, error);
}
+
+cugraph_error_code_t cugraph_heterogeneous_uniform_neighbor_sample(
+ const cugraph_resource_handle_t* handle,
+ cugraph_rng_state_t* rng_state,
+ cugraph_graph_t* graph,
+ const cugraph_type_erased_device_array_view_t* start_vertices,
+ const cugraph_type_erased_device_array_view_t* start_vertex_offsets,
+ const cugraph_type_erased_host_array_view_t* fan_out,
+ int num_edge_types,
+ const cugraph_sampling_options_t* options,
+ bool_t do_expensive_check,
+ cugraph_sample_result_t** result,
+ cugraph_error_t** error)
+{
+ auto options_cpp = *reinterpret_cast(options);
+
+ // FIXME: Should we maintain this contition?
+ CAPI_EXPECTS((!options_cpp.retain_seeds_) || (start_vertex_offsets != nullptr),
+ CUGRAPH_INVALID_INPUT,
+ "must specify start_vertex_offsets if retain_seeds is true",
+ *error);
+
+ CAPI_EXPECTS((start_vertex_offsets == nullptr) ||
+ (reinterpret_cast(
+ start_vertex_offsets)
+ ->type_ == SIZE_T),
+ CUGRAPH_INVALID_INPUT,
+ "start_vertex_offsets should be of type size_t",
+ *error);
+
+ CAPI_EXPECTS(
+ reinterpret_cast(fan_out)
+ ->type_ == INT32,
+ CUGRAPH_INVALID_INPUT,
+ "fan_out should be of type int",
+ *error);
+
+ CAPI_EXPECTS(reinterpret_cast(graph)->vertex_type_ ==
+ reinterpret_cast(
+ start_vertices)
+ ->type_,
+ CUGRAPH_INVALID_INPUT,
+ "vertex type of graph and start_vertices must match",
+ *error);
+
+ neighbor_sampling_functor functor{handle,
+ rng_state,
+ graph,
+ nullptr,
+ start_vertices,
+ start_vertex_offsets,
+ fan_out,
+ num_edge_types,
+ std::move(options_cpp),
+ FALSE,
+ do_expensive_check};
+ return cugraph::c_api::run_algorithm(graph, functor, result, error);
+}
+
+cugraph_error_code_t cugraph_heterogeneous_biased_neighbor_sample(
+ const cugraph_resource_handle_t* handle,
+ cugraph_rng_state_t* rng_state,
+ cugraph_graph_t* graph,
+ const cugraph_edge_property_view_t* edge_biases,
+ const cugraph_type_erased_device_array_view_t* start_vertices,
+ const cugraph_type_erased_device_array_view_t* start_vertex_offsets,
+ const cugraph_type_erased_host_array_view_t* fan_out,
+ int num_edge_types,
+ const cugraph_sampling_options_t* options,
+ bool_t do_expensive_check,
+ cugraph_sample_result_t** result,
+ cugraph_error_t** error)
+{
+ auto options_cpp = *reinterpret_cast(options);
+
+ CAPI_EXPECTS(
+ (edge_biases != nullptr) ||
+ (reinterpret_cast(graph)->edge_weights_ != nullptr),
+ CUGRAPH_INVALID_INPUT,
+ "edge_biases is required if the graph is not weighted",
+ *error);
+
+ // FIXME: Should we maintain this contition?
+ CAPI_EXPECTS((!options_cpp.retain_seeds_) || (start_vertex_offsets != nullptr),
+ CUGRAPH_INVALID_INPUT,
+ "must specify start_vertex_offsets if retain_seeds is true",
+ *error);
+
+ CAPI_EXPECTS((start_vertex_offsets == nullptr) ||
+ (reinterpret_cast(
+ start_vertex_offsets)
+ ->type_ == SIZE_T),
+ CUGRAPH_INVALID_INPUT,
+ "start_vertex_offsets should be of type size_t",
+ *error);
+
+ CAPI_EXPECTS(
+ reinterpret_cast(fan_out)
+ ->type_ == INT32,
+ CUGRAPH_INVALID_INPUT,
+ "fan_out should be of type int",
+ *error);
+
+ CAPI_EXPECTS(reinterpret_cast(graph)->vertex_type_ ==
+ reinterpret_cast(
+ start_vertices)
+ ->type_,
+ CUGRAPH_INVALID_INPUT,
+ "vertex type of graph and start_vertices must match",
+ *error);
+
+ neighbor_sampling_functor functor{handle,
+ rng_state,
+ graph,
+ edge_biases,
+ start_vertices,
+ start_vertex_offsets,
+ fan_out,
+ num_edge_types,
+ std::move(options_cpp),
+ TRUE,
+ do_expensive_check};
+ return cugraph::c_api::run_algorithm(graph, functor, result, error);
+}
+
+cugraph_error_code_t cugraph_homogeneous_uniform_neighbor_sample(
+ const cugraph_resource_handle_t* handle,
+ cugraph_rng_state_t* rng_state,
+ cugraph_graph_t* graph,
+ const cugraph_type_erased_device_array_view_t* start_vertices,
+ const cugraph_type_erased_device_array_view_t* start_vertex_offsets, // RENAME?
+ const cugraph_type_erased_host_array_view_t* fan_out,
+ const cugraph_sampling_options_t* options,
+ bool_t do_expensive_check,
+ cugraph_sample_result_t** result,
+ cugraph_error_t** error)
+{
+ auto options_cpp = *reinterpret_cast(options);
+
+ // FIXME: Should we maintain this contition?
+ CAPI_EXPECTS((!options_cpp.retain_seeds_) || (start_vertex_offsets != nullptr),
+ CUGRAPH_INVALID_INPUT,
+ "must specify start_vertex_offsets if retain_seeds is true",
+ *error);
+
+ CAPI_EXPECTS((start_vertex_offsets == nullptr) ||
+ (reinterpret_cast(
+ start_vertex_offsets)
+ ->type_ == SIZE_T),
+ CUGRAPH_INVALID_INPUT,
+ "start_vertex_offsets should be of type size_t",
+ *error);
+
+ CAPI_EXPECTS(
+ reinterpret_cast(fan_out)
+ ->type_ == INT32,
+ CUGRAPH_INVALID_INPUT,
+ "fan_out type must be INT32",
+ *error);
+
+ CAPI_EXPECTS(reinterpret_cast(graph)->vertex_type_ ==
+ reinterpret_cast(
+ start_vertices)
+ ->type_,
+ CUGRAPH_INVALID_INPUT,
+ "vertex type of graph and start_vertices must match",
+ *error);
+
+ neighbor_sampling_functor functor{handle,
+ rng_state,
+ graph,
+ nullptr,
+ start_vertices,
+ start_vertex_offsets,
+ fan_out,
+ 1, // num_edge_types
+ std::move(options_cpp),
+ FALSE,
+ do_expensive_check};
+ return cugraph::c_api::run_algorithm(graph, functor, result, error);
+}
+
+cugraph_error_code_t cugraph_homogeneous_biased_neighbor_sample(
+ const cugraph_resource_handle_t* handle,
+ cugraph_rng_state_t* rng_state,
+ cugraph_graph_t* graph,
+ const cugraph_edge_property_view_t* edge_biases,
+ const cugraph_type_erased_device_array_view_t* start_vertices,
+ const cugraph_type_erased_device_array_view_t* start_vertex_offsets,
+ const cugraph_type_erased_host_array_view_t* fan_out,
+ const cugraph_sampling_options_t* options,
+ bool_t do_expensive_check,
+ cugraph_sample_result_t** result,
+ cugraph_error_t** error)
+{
+ auto options_cpp = *reinterpret_cast(options);
+
+ CAPI_EXPECTS(
+ (edge_biases != nullptr) ||
+ (reinterpret_cast(graph)->edge_weights_ != nullptr),
+ CUGRAPH_INVALID_INPUT,
+ "edge_biases is required if the graph is not weighted",
+ *error);
+
+ // FIXME: Should we maintain this contition?
+ CAPI_EXPECTS((!options_cpp.retain_seeds_) || (start_vertex_offsets != nullptr),
+ CUGRAPH_INVALID_INPUT,
+ "must specify start_vertex_offsets if retain_seeds is true",
+ *error);
+
+ CAPI_EXPECTS((start_vertex_offsets == nullptr) ||
+ (reinterpret_cast(
+ start_vertex_offsets)
+ ->type_ == SIZE_T),
+ CUGRAPH_INVALID_INPUT,
+ "start_vertex_offsets should be of type size_t",
+ *error);
+
+ CAPI_EXPECTS(
+ reinterpret_cast(fan_out)
+ ->type_ == INT32,
+ CUGRAPH_INVALID_INPUT,
+ "fan_out type must be INT32",
+ *error);
+
+ CAPI_EXPECTS(reinterpret_cast(graph)->vertex_type_ ==
+ reinterpret_cast(
+ start_vertices)
+ ->type_,
+ CUGRAPH_INVALID_INPUT,
+ "vertex type of graph and start_vertices must match",
+ *error);
+
+ neighbor_sampling_functor functor{handle,
+ rng_state,
+ graph,
+ edge_biases,
+ start_vertices,
+ start_vertex_offsets,
+ fan_out,
+ 1, // num_edge_types
+ std::move(options_cpp),
+ TRUE,
+ do_expensive_check};
+ return cugraph::c_api::run_algorithm(graph, functor, result, error);
+}
diff --git a/cpp/src/detail/utility_wrappers_32.cu b/cpp/src/detail/utility_wrappers_32.cu
index de407f12493..879a1adf337 100644
--- a/cpp/src/detail/utility_wrappers_32.cu
+++ b/cpp/src/detail/utility_wrappers_32.cu
@@ -63,6 +63,10 @@ template void scalar_fill(raft::handle_t const& handle, size_t* d_value, size_t
template void scalar_fill(raft::handle_t const& handle, float* d_value, size_t size, float value);
+template void sort_ints(raft::handle_t const& handle, raft::device_span values);
+
+template size_t unique_ints(raft::handle_t const& handle, raft::device_span values);
+
template void sequence_fill(rmm::cuda_stream_view const& stream_view,
int32_t* d_value,
size_t size,
@@ -73,6 +77,10 @@ template void sequence_fill(rmm::cuda_stream_view const& stream_view,
size_t size,
uint32_t start_value);
+template void transform_increment_ints(raft::device_span values,
+ int32_t value,
+ rmm::cuda_stream_view const& stream_view);
+
template void stride_fill(rmm::cuda_stream_view const& stream_view,
int32_t* d_value,
size_t size,
diff --git a/cpp/src/detail/utility_wrappers_64.cu b/cpp/src/detail/utility_wrappers_64.cu
index 2c136d5902b..742cb18d718 100644
--- a/cpp/src/detail/utility_wrappers_64.cu
+++ b/cpp/src/detail/utility_wrappers_64.cu
@@ -61,6 +61,10 @@ template void scalar_fill(raft::handle_t const& handle,
template void scalar_fill(raft::handle_t const& handle, double* d_value, size_t size, double value);
+template void sort_ints(raft::handle_t const& handle, raft::device_span values);
+
+template size_t unique_ints(raft::handle_t const& handle, raft::device_span values);
+
template void sequence_fill(rmm::cuda_stream_view const& stream_view,
int64_t* d_value,
size_t size,
@@ -71,6 +75,10 @@ template void sequence_fill(rmm::cuda_stream_view const& stream_view,
size_t size,
uint64_t start_value);
+template void transform_increment_ints(raft::device_span values,
+ int64_t value,
+ rmm::cuda_stream_view const& stream_view);
+
template void stride_fill(rmm::cuda_stream_view const& stream_view,
int64_t* d_value,
size_t size,
diff --git a/cpp/src/detail/utility_wrappers_impl.cuh b/cpp/src/detail/utility_wrappers_impl.cuh
index 074d7044261..93bd14c4d06 100644
--- a/cpp/src/detail/utility_wrappers_impl.cuh
+++ b/cpp/src/detail/utility_wrappers_impl.cuh
@@ -36,6 +36,7 @@
#include
#include
#include
+#include
namespace cugraph {
namespace detail {
@@ -63,6 +64,20 @@ void scalar_fill(raft::handle_t const& handle, value_t* d_value, size_t size, va
thrust::fill_n(handle.get_thrust_policy(), d_value, size, value);
}
+template
+void sort_ints(raft::handle_t const& handle, raft::device_span values)
+{
+ thrust::sort(handle.get_thrust_policy(), values.begin(), values.end());
+}
+
+template
+size_t unique_ints(raft::handle_t const& handle, raft::device_span values)
+{
+ auto unique_element_last =
+ thrust::unique(handle.get_thrust_policy(), values.begin(), values.end());
+ return thrust::distance(values.begin(), unique_element_last);
+}
+
template
void sequence_fill(rmm::cuda_stream_view const& stream_view,
value_t* d_value,
@@ -72,6 +87,20 @@ void sequence_fill(rmm::cuda_stream_view const& stream_view,
thrust::sequence(rmm::exec_policy(stream_view), d_value, d_value + size, start_value);
}
+template
+void transform_increment_ints(raft::device_span values,
+ value_t incr,
+ rmm::cuda_stream_view const& stream_view)
+{
+ thrust::transform(rmm::exec_policy(stream_view),
+ values.begin(),
+ values.end(),
+ values.begin(),
+ cuda::proclaim_return_type([incr] __device__(value_t value) {
+ return static_cast(value + incr);
+ }));
+}
+
template
void stride_fill(rmm::cuda_stream_view const& stream_view,
value_t* d_value,
diff --git a/cpp/src/link_prediction/similarity_impl.cuh b/cpp/src/link_prediction/similarity_impl.cuh
index b39895129dc..00f73b5c263 100644
--- a/cpp/src/link_prediction/similarity_impl.cuh
+++ b/cpp/src/link_prediction/similarity_impl.cuh
@@ -287,10 +287,8 @@ all_pairs_similarity(raft::handle_t const& handle,
// computing/updating topk with each batch
// FIXME: Experiment with this and adjust as necessary
- // size_t const
- // MAX_PAIRS_PER_BATCH{static_cast(handle.get_device_properties().multiProcessorCount) *
- // (1 << 15)};
- size_t const MAX_PAIRS_PER_BATCH{100};
+ size_t const MAX_PAIRS_PER_BATCH{
+ static_cast(handle.get_device_properties().multiProcessorCount) * (1 << 15)};
rmm::device_uvector degrees = graph_view.compute_out_degrees(handle);
rmm::device_uvector two_hop_degrees(degrees.size() + 1, handle.get_stream());
@@ -362,195 +360,205 @@ all_pairs_similarity(raft::handle_t const& handle,
1,
handle.get_stream());
+ handle.sync_stream();
+
std::tie(batch_offsets, std::ignore) = compute_offset_aligned_element_chunks(
handle,
raft::device_span{two_hop_degree_offsets.data(), two_hop_degree_offsets.size()},
sum_two_hop_degrees,
MAX_PAIRS_PER_BATCH);
- for (size_t batch_number = 0; batch_number < (batch_offsets.size() - 1); ++batch_number) {
- if (batch_offsets[batch_number + 1] > batch_offsets[batch_number]) {
- auto [offsets, v2] =
- k_hop_nbrs(handle,
- graph_view,
- raft::device_span{
- tmp_vertices.data() + batch_offsets[batch_number],
- batch_offsets[batch_number + 1] - batch_offsets[batch_number]},
- 2,
- do_expensive_check);
-
- auto v1 = cugraph::detail::expand_sparse_offsets(
- raft::device_span