chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:35:51 +08:00
commit c36a561cd8
2172 changed files with 455595 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
"""
This package contains DistGNN and Libra based graph partitioning tools.
"""
from . import partition, tools
+4
View File
@@ -0,0 +1,4 @@
"""
This package contains Libra graph partitioner.
"""
from .libra_partition import partition_graph
@@ -0,0 +1,353 @@
r"""Libra partition functions.
Libra partition is a vertex-cut based partitioning algorithm from
`Distributed Power-law Graph Computing:
Theoretical and Empirical Analysis
<https://proceedings.neurips.cc/paper/2014/file/67d16d00201083a2b118dd5128dd6f59-Paper.pdf>`__
from Xie et al.
"""
# Copyright (c) 2021 Intel Corporation
# \file distgnn/partition/libra_partition.py
# \brief Libra - Vertex-cut based graph partitioner for distributed training
# \author Vasimuddin Md <vasimuddin.md@intel.com>,
# Guixiang Ma <guixiang.ma@intel.com>
# Sanchit Misra <sanchit.misra@intel.com>,
# Ramanarayan Mohanty <ramanarayan.mohanty@intel.com>,
# Sasikanth Avancha <sasikanth.avancha@intel.com>
# Nesreen K. Ahmed <nesreen.k.ahmed@intel.com>
# \cite Distributed Power-law Graph Computing: Theoretical and Empirical Analysis
import json
import os
import time
import torch as th
from dgl import DGLGraph
from dgl._sparse_ops import (
libra2dgl_build_adjlist,
libra2dgl_build_dict,
libra2dgl_set_lr,
libra_vertex_cut,
)
from dgl.base import DGLError
from dgl.data.utils import save_graphs, save_tensors
def libra_partition(num_community, G, resultdir):
"""
Performs vertex-cut based graph partitioning and converts the partitioning
output to DGL input format.
Parameters
----------
num_community : Number of partitions to create
G : Input graph to be partitioned
resultdir : Output location for storing the partitioned graphs
Output
------
1. Creates X partition folder as XCommunities (say, X=2, so, 2Communities)
XCommunities contains file name communityZ.txt per partition Z (Z <- 0 .. X-1);
each such file contains a list of edges assigned to that partition.
These files constitute the output of Libra graph partitioner
(An intermediate result of this function).
2. The folder also contains partZ folders, each of these folders stores
DGL/DistGNN graphs for the Z partitions;
these graph files are used as input to DistGNN.
3. The folder also contains a json file which contains partitions' information.
"""
num_nodes = G.num_nodes() # number of nodes
num_edges = G.num_edges() # number of edges
print("Number of nodes in the graph: ", num_nodes)
print("Number of edges in the graph: ", num_edges)
in_d = G.in_degrees()
out_d = G.out_degrees()
node_degree = in_d + out_d
edgenum_unassigned = node_degree.clone()
u_t, v_t = G.edges()
weight_ = th.ones(u_t.shape[0], dtype=th.int64)
community_weights = th.zeros(num_community, dtype=th.int64)
# self_loop = 0
# for p, q in zip(u_t, v_t):
# if p == q:
# self_loop += 1
# print("#self loops in the dataset: ", self_loop)
# del G
## call to C/C++ code
out = th.zeros(u_t.shape[0], dtype=th.int32)
libra_vertex_cut(
num_community,
node_degree,
edgenum_unassigned,
community_weights,
u_t,
v_t,
weight_,
out,
num_nodes,
num_edges,
resultdir,
)
print("Max partition size: ", int(community_weights.max()))
print(" ** Converting libra partitions to dgl graphs **")
fsize = int(community_weights.max()) + 1024 ## max edges in partition
# print("fsize: ", fsize, flush=True)
node_map = th.zeros(num_community, dtype=th.int64)
indices = th.zeros(num_nodes, dtype=th.int64)
lrtensor = th.zeros(num_nodes, dtype=th.int64)
gdt_key = th.zeros(num_nodes, dtype=th.int64)
gdt_value = th.zeros([num_nodes, num_community], dtype=th.int64)
offset = th.zeros(1, dtype=th.int64)
ldt_ar = []
gg_ar = [DGLGraph() for i in range(num_community)]
part_nodes = []
print(">>> ", "num_nodes ", " ", "num_edges")
## Iterator over number of partitions
for i in range(num_community):
g = gg_ar[i]
a_t = th.zeros(fsize, dtype=th.int64)
b_t = th.zeros(fsize, dtype=th.int64)
ldt_key = th.zeros(fsize, dtype=th.int64)
ldt_ar.append(ldt_key)
## building node, parition dictionary
## Assign local node ids and mapping to global node ids
ret = libra2dgl_build_dict(
a_t,
b_t,
indices,
ldt_key,
gdt_key,
gdt_value,
node_map,
offset,
num_community,
i,
fsize,
resultdir,
)
num_nodes_partition = int(ret[0])
num_edges_partition = int(ret[1])
part_nodes.append(num_nodes_partition)
print(">>> ", num_nodes_partition, " ", num_edges_partition)
g.add_edges(a_t[0:num_edges_partition], b_t[0:num_edges_partition])
########################################################
## fixing lr - 1-level tree for the split-nodes
libra2dgl_set_lr(gdt_key, gdt_value, lrtensor, num_community, num_nodes)
########################################################
# graph_name = dataset
graph_name = resultdir.split("_")[-1].split("/")[0]
part_method = "Libra"
num_parts = num_community ## number of paritions/communities
num_hops = 0
node_map_val = node_map.tolist()
edge_map_val = 0
out_path = resultdir
part_metadata = {
"graph_name": graph_name,
"num_nodes": G.num_nodes(),
"num_edges": G.num_edges(),
"part_method": part_method,
"num_parts": num_parts,
"halo_hops": num_hops,
"node_map": node_map_val,
"edge_map": edge_map_val,
}
############################################################
for i in range(num_community):
g = gg_ar[0]
num_nodes_partition = part_nodes[i]
adj = th.zeros([num_nodes_partition, num_community - 1], dtype=th.int64)
inner_node = th.zeros(num_nodes_partition, dtype=th.int32)
lr_t = th.zeros(num_nodes_partition, dtype=th.int64)
ldt = ldt_ar[0]
try:
feat = G.ndata["feat"]
except KeyError:
feat = G.ndata["features"]
try:
labels = G.ndata["label"]
except KeyError:
labels = G.ndata["labels"]
trainm = G.ndata["train_mask"].int()
testm = G.ndata["test_mask"].int()
valm = G.ndata["val_mask"].int()
feat_size = feat.shape[1]
gfeat = th.zeros([num_nodes_partition, feat_size], dtype=feat.dtype)
glabels = th.zeros(num_nodes_partition, dtype=labels.dtype)
gtrainm = th.zeros(num_nodes_partition, dtype=trainm.dtype)
gtestm = th.zeros(num_nodes_partition, dtype=testm.dtype)
gvalm = th.zeros(num_nodes_partition, dtype=valm.dtype)
## build remote node databse per local node
## gather feats, train, test, val, and labels for each partition
libra2dgl_build_adjlist(
feat,
gfeat,
adj,
inner_node,
ldt,
gdt_key,
gdt_value,
node_map,
lr_t,
lrtensor,
num_nodes_partition,
num_community,
i,
feat_size,
labels,
trainm,
testm,
valm,
glabels,
gtrainm,
gtestm,
gvalm,
feat.shape[0],
)
g.ndata["adj"] = adj ## database of remote clones
g.ndata["inner_node"] = inner_node ## split node '0' else '1'
g.ndata["feat"] = gfeat ## gathered features
g.ndata["lf"] = lr_t ## 1-level tree among split nodes
g.ndata["label"] = glabels
g.ndata["train_mask"] = gtrainm
g.ndata["test_mask"] = gtestm
g.ndata["val_mask"] = gvalm
# Validation code, run only small graphs
# for l in range(num_nodes_partition):
# index = int(ldt[l])
# assert glabels[l] == labels[index]
# assert gtrainm[l] == trainm[index]
# assert gtestm[l] == testm[index]
# for j in range(feat_size):
# assert gfeat[l][j] == feat[index][j]
print("Writing partition {} to file".format(i), flush=True)
part = g
part_id = i
part_dir = os.path.join(out_path, "part" + str(part_id))
node_feat_file = os.path.join(part_dir, "node_feat.dgl")
edge_feat_file = os.path.join(part_dir, "edge_feat.dgl")
part_graph_file = os.path.join(part_dir, "graph.dgl")
part_metadata["part-{}".format(part_id)] = {
"node_feats": node_feat_file,
"edge_feats": edge_feat_file,
"part_graph": part_graph_file,
}
os.makedirs(part_dir, mode=0o775, exist_ok=True)
save_tensors(node_feat_file, part.ndata)
save_graphs(part_graph_file, [part])
del g
del gg_ar[0]
del ldt
del ldt_ar[0]
with open("{}/{}.json".format(out_path, graph_name), "w") as outfile:
json.dump(part_metadata, outfile, sort_keys=True, indent=4)
print("Conversion libra2dgl completed !!!")
def partition_graph(num_community, G, resultdir):
"""
Performs vertex-cut based graph partitioning and converts the partitioning
output to DGL input format.
Given a graph, this function will create a folder named ``XCommunities`` where ``X``
stands for the number of communities. It will contain ``X`` files named
``communityZ.txt`` for each partition Z (from 0 to X-1);
each such file contains a list of edges assigned to that partition.
These files constitute the output of Libra graph partitioner.
The folder also contains X subfolders named ``partZ``, each of these folders stores
DGL/DistGNN graphs for partition Z; these graph files are used as input to
DistGNN.
The folder also contains a json file which contains partitions' information.
Currently we require the graph's node data to contain the following columns:
* ``features`` for node features.
* ``label`` for node labels.
* ``train_mask`` as a boolean mask of training node set.
* ``val_mask`` as a boolean mask of validation node set.
* ``test_mask`` as a boolean mask of test node set.
Parameters
----------
num_community : int
Number of partitions to create.
G : DGLGraph
Input graph to be partitioned.
resultdir : str
Output location for storing the partitioned graphs.
"""
print("num partitions: ", num_community)
print("output location: ", resultdir)
## create ouptut directory
try:
os.makedirs(resultdir, mode=0o775, exist_ok=True)
except:
raise DGLError("Error: Could not create directory: ", resultdir)
tic = time.time()
print(
"####################################################################"
)
print("Executing parititons: ", num_community)
ltic = time.time()
try:
resultdir = os.path.join(resultdir, str(num_community) + "Communities")
os.makedirs(resultdir, mode=0o775, exist_ok=True)
except:
raise DGLError("Error: Could not create sub-directory: ", resultdir)
## Libra partitioning
libra_partition(num_community, G, resultdir)
ltoc = time.time()
print(
"Time taken by {} partitions {:0.4f} sec".format(
num_community, ltoc - ltic
)
)
print()
toc = time.time()
print(
"Generated ",
num_community,
" partitions in {:0.4f} sec".format(toc - tic),
flush=True,
)
print("Partitioning completed successfully !!!")
+4
View File
@@ -0,0 +1,4 @@
"""
This package contains extra routines related to Libra graph partitioner.
"""
from .tools import load_proteins
+162
View File
@@ -0,0 +1,162 @@
r"""
Copyright (c) 2021 Intel Corporation
\file distgnn/tools/tools.py
\brief Tools for use in Libra graph partitioner.
\author Vasimuddin Md <vasimuddin.md@intel.com>
"""
import os
import random
import requests
import torch as th
from scipy.io import mmread
import dgl
from dgl.base import DGLError
from dgl.data.utils import load_graphs, save_graphs, save_tensors
def rep_per_node(prefix, num_community):
"""
Used on Libra partitioned data.
This function reports number of split-copes per node (replication) of
a partitioned graph
Parameters
----------
prefix: Partition folder location (contains replicationlist.csv)
num_community: number of partitions or communities
"""
ifile = os.path.join(prefix, "replicationlist.csv")
fhandle = open(ifile, "r")
r_dt = {}
fline = fhandle.readline() ## reading first line, contains the comment.
print(fline)
for line in fhandle:
if line[0] == "#":
raise DGLError("[Bug] Read Hash char in rep_per_node func.")
node = line.strip("\n")
if r_dt.get(node, -100) == -100:
r_dt[node] = 1
else:
r_dt[node] += 1
fhandle.close()
## sanity checks
for v in r_dt.values():
if v >= num_community:
raise DGLError(
"[Bug] Unexpected event in rep_per_node() in tools.py."
)
return r_dt
def download_proteins():
"""
Downloads the proteins dataset
"""
print("Downloading dataset...")
print("This might a take while..")
url = "https://portal.nersc.gov/project/m1982/GNN/"
file_name = "subgraph3_iso_vs_iso_30_70length_ALL.m100.propermm.mtx"
url = url + file_name
try:
req = requests.get(url)
except:
raise DGLError(
"Error: Failed to download Proteins dataset!! Aborting.."
)
with open("proteins.mtx", "wb") as handle:
handle.write(req.content)
def proteins_mtx2dgl():
"""
This function converts Proteins dataset from mtx to dgl format.
"""
print("Converting mtx2dgl..")
print("This might a take while..")
a_mtx = mmread("proteins.mtx")
coo = a_mtx.tocoo()
u = th.tensor(coo.row, dtype=th.int64)
v = th.tensor(coo.col, dtype=th.int64)
g = dgl.DGLGraph()
g.add_edges(u, v)
n = g.num_nodes()
feat_size = 128 ## arbitrary number
feats = th.empty([n, feat_size], dtype=th.float32)
## arbitrary numbers
train_size = 1000000
test_size = 500000
val_size = 5000
nlabels = 256
train_mask = th.zeros(n, dtype=th.bool)
test_mask = th.zeros(n, dtype=th.bool)
val_mask = th.zeros(n, dtype=th.bool)
label = th.zeros(n, dtype=th.int64)
for i in range(train_size):
train_mask[i] = True
for i in range(test_size):
test_mask[train_size + i] = True
for i in range(val_size):
val_mask[train_size + test_size + i] = True
for i in range(n):
label[i] = random.choice(range(nlabels))
g.ndata["feat"] = feats
g.ndata["train_mask"] = train_mask
g.ndata["test_mask"] = test_mask
g.ndata["val_mask"] = val_mask
g.ndata["label"] = label
return g
def save(g, dataset):
"""
This function saves input dataset to dgl format
Parameters
----------
g : graph to be saved
dataset : output folder name
"""
print("Saving dataset..")
part_dir = os.path.join("./" + dataset)
node_feat_file = os.path.join(part_dir, "node_feat.dgl")
part_graph_file = os.path.join(part_dir, "graph.dgl")
os.makedirs(part_dir, mode=0o775, exist_ok=True)
save_tensors(node_feat_file, g.ndata)
save_graphs(part_graph_file, [g])
print("Graph saved successfully !!")
def load_proteins(dataset):
"""
This function downloads, converts, and load Proteins graph dataset
Parameter
---------
dataset: output folder name
"""
part_dir = dataset
graph_file = os.path.join(part_dir + "/graph.dgl")
if not os.path.exists("proteins.mtx"):
download_proteins()
if not os.path.exists(graph_file):
g = proteins_mtx2dgl()
save(g, dataset)
## load
graph = load_graphs(graph_file)[0][0]
return graph