项目文件夹

文件
2023-03-29 11:00:26 +08:00

613 行
17 KiB
Python

import inspect
import json
import os
import pickle
import shutil
import time
import zipfile
from functools import partial, reduce, wraps
from timeit import default_timer
import dgl
import numpy as np
import pandas
import requests
import torch
from ogb.nodeproppred import DglNodePropPredDataset
def _download(url, path, filename):
fn = os.path.join(path, filename)
if os.path.exists(fn):
return
os.makedirs(path, exist_ok=True)
f_remote = requests.get(url, stream=True)
sz = f_remote.headers.get("content-length")
assert f_remote.status_code == 200, "fail to open {}".format(url)
with open(fn, "wb") as writer:
for chunk in f_remote.iter_content(chunk_size=1024 * 1024):
writer.write(chunk)
print("Download finished.")
import traceback
from _thread import start_new_thread
# GRAPH_CACHE = {}
import torch.multiprocessing as mp
def thread_wrapped_func(func):
"""
Wraps a process entry point to make it work with OpenMP.
"""
@wraps(func)
def decorated_function(*args, **kwargs):
queue = mp.Queue()
def _queue_result():
exception, trace, res = None, None, None
try:
res = func(*args, **kwargs)
except Exception as e:
exception = e
trace = traceback.format_exc()
queue.put((res, exception, trace))
start_new_thread(_queue_result, ())
result, exception, trace = queue.get()
if exception is None:
return result
else:
assert isinstance(exception, Exception)
raise exception.__class__(trace)
return decorated_function
def get_graph(name, format=None):
# global GRAPH_CACHE
# if name in GRAPH_CACHE:
# return GRAPH_CACHE[name].to(format)
if isinstance(format, str):
format = [format] # didn't specify format
if format is None:
format = ["csc", "csr", "coo"]
g = None
if name == "cora":
g = dgl.data.CoraGraphDataset(verbose=False)[0]
elif name == "pubmed":
g = dgl.data.PubmedGraphDataset(verbose=False)[0]
elif name == "livejournal":
bin_path = "/tmp/dataset/livejournal/livejournal_{}.bin".format(format)
if os.path.exists(bin_path):
g_list, _ = dgl.load_graphs(bin_path)
g = g_list[0]
else:
g = get_livejournal().formats(format)
dgl.save_graphs(bin_path, [g])
elif name == "friendster":
bin_path = "/tmp/dataset/friendster/friendster_{}.bin".format(format)
if os.path.exists(bin_path):
g_list, _ = dgl.load_graphs(bin_path)
g = g_list[0]
else:
# the original node IDs of friendster are not consecutive, so we compact it
g = dgl.compact_graphs(get_friendster()).formats(format)
dgl.save_graphs(bin_path, [g])
elif name == "reddit":
bin_path = "/tmp/dataset/reddit/reddit_{}.bin".format(format)
if os.path.exists(bin_path):
g_list, _ = dgl.load_graphs(bin_path)
g = g_list[0]
else:
g = dgl.data.RedditDataset(self_loop=True)[0].formats(format)
dgl.save_graphs(bin_path, [g])
elif name.startswith("ogb"):
g = get_ogb_graph(name)
else:
raise Exception("Unknown dataset")
# GRAPH_CACHE[name] = g
g = g.formats(format)
return g
def get_ogb_graph(name):
os.symlink("/tmp/dataset/", os.path.join(os.getcwd(), "dataset"))
data = DglNodePropPredDataset(name=name)
return data[0][0]
def get_livejournal():
# Same as https://snap.stanford.edu/data/soc-LiveJournal1.txt.gz
_download(
"https://dgl-asv-data.s3-us-west-2.amazonaws.com/dataset/livejournal/soc-LiveJournal1.txt.gz",
"/tmp/dataset/livejournal",
"soc-LiveJournal1.txt.gz",
)
df = pandas.read_csv(
"/tmp/dataset/livejournal/soc-LiveJournal1.txt.gz",
sep="\t",
skiprows=4,
header=None,
names=["src", "dst"],
compression="gzip",
)
src = df["src"].values
dst = df["dst"].values
print("construct the graph")
return dgl.graph((src, dst))
def get_friendster():
# Same as https://snap.stanford.edu/data/bigdata/communities/com-friendster.ungraph.txt.gz
_download(
"https://dgl-asv-data.s3-us-west-2.amazonaws.com/dataset/friendster/com-friendster.ungraph.txt.gz",
"/tmp/dataset/friendster",
"com-friendster.ungraph.txt.gz",
)
df = pandas.read_csv(
"/tmp/dataset/friendster/com-friendster.ungraph.txt.gz",
sep="\t",
skiprows=4,
header=None,
names=["src", "dst"],
compression="gzip",
)
src = df["src"].values
dst = df["dst"].values
print("construct the graph")
return dgl.graph((src, dst))
class OGBDataset(object):
def __init__(self, g, num_labels, predict_category=None):
self._g = g
self._num_labels = num_labels
self._predict_category = predict_category
@property
def num_labels(self):
return self._num_labels
@property
def num_classes(self):
return self._num_labels
@property
def predict_category(self):
return self._predict_category
def __getitem__(self, idx):
return self._g
def load_ogb_product():
name = "ogbn-products"
os.symlink("/tmp/dataset/", os.path.join(os.getcwd(), "dataset"))
print("load", name)
data = DglNodePropPredDataset(name=name)
print("finish loading", name)
splitted_idx = data.get_idx_split()
graph, labels = data[0]
labels = labels[:, 0]
graph.ndata["label"] = labels
in_feats = graph.ndata["feat"].shape[1]
num_labels = len(
torch.unique(labels[torch.logical_not(torch.isnan(labels))])
)
# Find the node IDs in the training, validation, and test set.
train_nid, val_nid, test_nid = (
splitted_idx["train"],
splitted_idx["valid"],
splitted_idx["test"],
)
train_mask = torch.zeros((graph.num_nodes(),), dtype=torch.bool)
train_mask[train_nid] = True
val_mask = torch.zeros((graph.num_nodes(),), dtype=torch.bool)
val_mask[val_nid] = True
test_mask = torch.zeros((graph.num_nodes(),), dtype=torch.bool)
test_mask[test_nid] = True
graph.ndata["train_mask"] = train_mask
graph.ndata["val_mask"] = val_mask
graph.ndata["test_mask"] = test_mask
return OGBDataset(graph, num_labels)
def load_ogb_mag():
name = "ogbn-mag"
os.symlink("/tmp/dataset/", os.path.join(os.getcwd(), "dataset"))
print("load", name)
dataset = DglNodePropPredDataset(name=name)
print("finish loading", name)
split_idx = dataset.get_idx_split()
train_idx = split_idx["train"]["paper"]
val_idx = split_idx["valid"]["paper"]
test_idx = split_idx["test"]["paper"]
hg_orig, labels = dataset[0]
subgs = {}
for etype in hg_orig.canonical_etypes:
u, v = hg_orig.all_edges(etype=etype)
subgs[etype] = (u, v)
subgs[(etype[2], "rev-" + etype[1], etype[0])] = (v, u)
hg = dgl.heterograph(subgs)
hg.nodes["paper"].data["feat"] = hg_orig.nodes["paper"].data["feat"]
hg.nodes["paper"].data["labels"] = labels["paper"].squeeze()
train_mask = torch.zeros((hg.num_nodes("paper"),), dtype=torch.bool)
train_mask[train_idx] = True
val_mask = torch.zeros((hg.num_nodes("paper"),), dtype=torch.bool)
val_mask[val_idx] = True
test_mask = torch.zeros((hg.num_nodes("paper"),), dtype=torch.bool)
test_mask[test_idx] = True
hg.nodes["paper"].data["train_mask"] = train_mask
hg.nodes["paper"].data["val_mask"] = val_mask
hg.nodes["paper"].data["test_mask"] = test_mask
num_classes = dataset.num_classes
return OGBDataset(hg, num_classes, "paper")
class PinsageDataset:
def __init__(self, g, user_ntype, item_ntype, textset):
self._g = g
self._user_ntype = user_ntype
self._item_ntype = item_ntype
self._textset = textset
@property
def user_ntype(self):
return self._user_ntype
@property
def item_ntype(self):
return self._item_ntype
@property
def textset(self):
return self._textset
def __getitem__(self, idx):
return self._g
def load_nowplaying_rs():
import torchtext.legacy as torchtext
# follow examples/pytorch/pinsage/README to create train_g.bin
name = "train_g.bin"
dataset_dir = os.path.join(os.getcwd(), "dataset")
os.symlink("/tmp/dataset/", dataset_dir)
dataset_path = os.path.join(dataset_dir, "nowplaying_rs", name)
g_list, _ = dgl.load_graphs(dataset_path)
g = g_list[0]
user_ntype = "user"
item_ntype = "track"
# Assign user and movie IDs and use them as features (to learn an individual trainable
# embedding for each entity)
g.nodes[user_ntype].data["id"] = torch.arange(g.num_nodes(user_ntype))
g.nodes[item_ntype].data["id"] = torch.arange(g.num_nodes(item_ntype))
# Prepare torchtext dataset and vocabulary
fields = {}
examples = []
for i in range(g.num_nodes(item_ntype)):
example = torchtext.data.Example.fromlist([], [])
examples.append(example)
textset = torchtext.data.Dataset(examples, fields)
return PinsageDataset(g, user_ntype, item_ntype, textset)
def process_data(name):
if name == "cora":
return dgl.data.CoraGraphDataset()
elif name == "pubmed":
return dgl.data.PubmedGraphDataset()
elif name == "aifb":
return dgl.data.AIFBDataset()
elif name == "mutag":
return dgl.data.MUTAGDataset()
elif name == "bgs":
return dgl.data.BGSDataset()
elif name == "am":
return dgl.data.AMDataset()
elif name == "reddit":
return dgl.data.RedditDataset(self_loop=True)
elif name == "ogbn-products":
return load_ogb_product()
elif name == "ogbn-mag":
return load_ogb_mag()
elif name == "nowplaying_rs":
return load_nowplaying_rs()
else:
raise ValueError("Invalid dataset name:", name)
def get_bench_device():
device = os.environ.get("DGL_BENCH_DEVICE", "cpu")
if device.lower() == "gpu":
return "cuda:0"
else:
return device
def setup_track_time(*args, **kwargs):
# fix random seed
np.random.seed(42)
torch.random.manual_seed(42)
def setup_track_acc(*args, **kwargs):
# fix random seed
np.random.seed(42)
torch.random.manual_seed(42)
def setup_track_flops(*args, **kwargs):
# fix random seed
np.random.seed(42)
torch.random.manual_seed(42)
TRACK_UNITS = {
"time": "s",
"acc": "%",
"flops": "GFLOPS",
}
TRACK_SETUP = {
"time": setup_track_time,
"acc": setup_track_acc,
"flops": setup_track_flops,
}
def parametrize(param_name, params):
"""Decorator for benchmarking over a set of parameters.
Parameters
----------
param_name : str
Parameter name. Must be one of the arguments of the decorated function.
params : list[any]
List of values to benchmark for the given parameter name. Recommend
to use Python's native object type (e.g., int, str, list[int]) because
ASV will display them on the plot.
Examples
--------
Benchmark function `foo` when argument `x` is equal to 10 or 20.
.. code::
@benchmark('time')
@parametrize('x', [10, 20]):
def foo(x):
pass
Benchmark function with multiple parametrizations. It will run the function
with all possible combinations. The example below generates 6 benchmarks.
.. code::
@benchmark('time')
@parametrize('x', [10, 20]):
@parametrize('y', [-1, -2, -3]):
def foo(x, y):
pass
When using multiple parametrizations, it can have arbitrary order. The example
below is the same as the above one.
.. code::
@benchmark('time')
@parametrize('y', [-1, -2, -3]):
@parametrize('x', [10, 20]):
def foo(x, y):
pass
"""
def _wrapper(func):
sig_params = inspect.signature(func).parameters.keys()
num_params = len(sig_params)
if getattr(func, "params", None) is None:
func.params = [None] * num_params
if getattr(func, "param_names", None) is None:
func.param_names = [None] * num_params
found_param = False
for i, sig_param in enumerate(sig_params):
if sig_param == param_name:
func.params[i] = params
func.param_names[i] = param_name
found_param = True
break
if not found_param:
raise ValueError("Invalid parameter name:", param_name)
return func
return _wrapper
def noop_decorator(param_name, params):
"""noop decorator"""
def _wrapper(func):
return func
return _wrapper
class TestFilter:
def __init__(self):
self.conf = None
if "DGL_REG_CONF" in os.environ:
current_dir = os.path.dirname(os.path.abspath(__file__))
path = os.path.join(
current_dir, "../../", os.environ["DGL_REG_CONF"]
)
with open(path, "r") as f:
self.conf = json.load(f)
if "INSTANCE_TYPE" in os.environ:
instance_type = os.environ["INSTANCE_TYPE"]
else:
raise Exception(
"Must set both DGL_REG_CONF and INSTANCE_TYPE as env"
)
self.enabled_tests = self.conf[instance_type]["tests"]
else:
import logging
logging.warning("No regression test conf file specified")
def check(self, func):
funcfullname = inspect.getmodule(func).__name__ + "." + func.__name__
if self.conf is None:
return True
else:
for enabled_testname in self.enabled_tests:
if enabled_testname in funcfullname:
return True
return False
filter = TestFilter()
device = os.environ.get("DGL_BENCH_DEVICE", "cpu")
if device == "cpu":
parametrize_cpu = parametrize
parametrize_gpu = noop_decorator
elif device == "gpu":
parametrize_cpu = noop_decorator
parametrize_gpu = parametrize
else:
raise Exception(
"Unknown device. Must be one of ['cpu', 'gpu'], but got {}".format(
device
)
)
def skip_if_gpu():
"""skip if DGL_BENCH_DEVICE is gpu"""
device = os.environ.get("DGL_BENCH_DEVICE", "cpu")
def _wrapper(func):
if device == "gpu":
# skip if not enabled
func.benchmark_name = "skip_" + func.__name__
return func
return _wrapper
def _cuda_device_count(q):
import torch
q.put(torch.cuda.device_count())
def get_num_gpu():
import multiprocessing as mp
q = mp.Queue()
p = mp.Process(target=_cuda_device_count, args=(q,))
p.start()
p.join()
return q.get(block=False)
GPU_COUNT = get_num_gpu()
def skip_if_not_4gpu():
"""skip if DGL_BENCH_DEVICE is gpu"""
def _wrapper(func):
if GPU_COUNT < 4:
# skip if not enabled
print("Skip {}".format(func.__name__))
func.benchmark_name = "skip_" + func.__name__
return func
return _wrapper
def benchmark(track_type, timeout=60):
"""Decorator for indicating the benchmark type.
Parameters
----------
track_type : str
Type. Must be either:
- 'time' : For timing. Unit: second.
- 'acc' : For accuracy. Unit: percentage, value between 0 and 100.
- 'flops' : Unit: GFlops, number of floating point operations per second.
timeout : int
Timeout threshold in second.
Examples
--------
.. code::
@benchmark('time')
def foo():
pass
"""
assert track_type in ["time", "acc", "flops"]
def _wrapper(func):
func.unit = TRACK_UNITS[track_type]
func.setup = TRACK_SETUP[track_type]
func.timeout = timeout
if not filter.check(func):
# skip if not enabled
func.benchmark_name = "skip_" + func.__name__
return func
return _wrapper
#####################################
# Timer
#####################################
class Timer:
def __init__(self, device=None):
self.timer = default_timer
if device is None:
self.device = get_bench_device()
else:
self.device = device
def __enter__(self):
if self.device == "cuda:0":
self.start_event = torch.cuda.Event(enable_timing=True)
self.end_event = torch.cuda.Event(enable_timing=True)
self.start_event.record()
else:
self.tic = self.timer()
return self
def __exit__(self, type, value, traceback):
if self.device == "cuda:0":
self.end_event.record()
torch.cuda.synchronize() # Wait for the events to be recorded!
self.elapsed_secs = (
self.start_event.elapsed_time(self.end_event) / 1e3
)
else:
self.elapsed_secs = self.timer() - self.tic