dmlc--dgl
2e8ae9f980
* chunked graph data format * Update * Update * Update task_distributed_test.sh * Update * Update * Revert "Update" This reverts commit 03c461870f19375fb03125b061fc853ab555577f. * Update * Update * ssh-keygen * CI * install openssh * openssh * Update * CI * Update * Update Co-authored-by: Ubuntu <ubuntu@ip-172-31-53-142.us-west-2.compute.internal> Co-authored-by: Ubuntu <ubuntu@ip-172-31-16-87.us-west-2.compute.internal> Co-authored-by: Ubuntu <ubuntu@ip-172-31-20-21.us-west-2.compute.internal> Co-authored-by: Ubuntu <ubuntu@ip-172-31-9-26.ap-northeast-1.compute.internal>
179 行
7.2 KiB
Python
179 行
7.2 KiB
Python
# See the __main__ block for usage of chunk_graph().
|
|
import pathlib
|
|
import json
|
|
from contextlib import contextmanager
|
|
import logging
|
|
import os
|
|
|
|
import torch
|
|
import dgl
|
|
|
|
from utils import setdir
|
|
from utils import array_readwriter
|
|
|
|
def chunk_numpy_array(arr, fmt_meta, chunk_sizes, path_fmt):
|
|
paths = []
|
|
offset = 0
|
|
|
|
for j, n in enumerate(chunk_sizes):
|
|
path = os.path.abspath(path_fmt % j)
|
|
arr_chunk = arr[offset:offset + n]
|
|
logging.info('Chunking %d-%d' % (offset, offset + n))
|
|
array_readwriter.get_array_parser(**fmt_meta).write(path, arr_chunk)
|
|
offset += n
|
|
paths.append(path)
|
|
|
|
return paths
|
|
|
|
def _chunk_graph(g, name, ndata_paths, edata_paths, num_chunks, output_path):
|
|
# First deal with ndata and edata that are homogeneous (i.e. not a dict-of-dict)
|
|
if len(g.ntypes) == 1 and not isinstance(next(iter(ndata_paths.values())), dict):
|
|
ndata_paths = {g.ntypes[0]: ndata_paths}
|
|
if len(g.etypes) == 1 and not isinstance(next(iter(edata_paths.values())), dict):
|
|
edata_paths = {g.etypes[0]: ndata_paths}
|
|
# Then convert all edge types to canonical edge types
|
|
etypestrs = {etype: ':'.join(etype) for etype in g.canonical_etypes}
|
|
edata_paths = {':'.join(g.to_canonical_etype(k)): v for k, v in edata_paths.items()}
|
|
|
|
metadata = {}
|
|
|
|
metadata['graph_name'] = name
|
|
metadata['node_type'] = g.ntypes
|
|
|
|
# Compute the number of nodes per chunk per node type
|
|
metadata['num_nodes_per_chunk'] = num_nodes_per_chunk = []
|
|
for ntype in g.ntypes:
|
|
num_nodes = g.num_nodes(ntype)
|
|
num_nodes_list = []
|
|
for i in range(num_chunks):
|
|
n = num_nodes // num_chunks + (i < num_nodes % num_chunks)
|
|
num_nodes_list.append(n)
|
|
num_nodes_per_chunk.append(num_nodes_list)
|
|
num_nodes_per_chunk_dict = {k: v for k, v in zip(g.ntypes, num_nodes_per_chunk)}
|
|
|
|
metadata['edge_type'] = [etypestrs[etype] for etype in g.canonical_etypes]
|
|
|
|
# Compute the number of edges per chunk per edge type
|
|
metadata['num_edges_per_chunk'] = num_edges_per_chunk = []
|
|
for etype in g.canonical_etypes:
|
|
num_edges = g.num_edges(etype)
|
|
num_edges_list = []
|
|
for i in range(num_chunks):
|
|
n = num_edges // num_chunks + (i < num_edges % num_chunks)
|
|
num_edges_list.append(n)
|
|
num_edges_per_chunk.append(num_edges_list)
|
|
num_edges_per_chunk_dict = {k: v for k, v in zip(g.canonical_etypes, num_edges_per_chunk)}
|
|
|
|
# Split edge index
|
|
metadata['edges'] = {}
|
|
with setdir('edge_index'):
|
|
for etype in g.canonical_etypes:
|
|
etypestr = etypestrs[etype]
|
|
logging.info('Chunking edge index for %s' % etypestr)
|
|
edges_meta = {}
|
|
fmt_meta = {"name": "csv", "delimiter": " "}
|
|
edges_meta['format'] = fmt_meta
|
|
|
|
srcdst = torch.stack(g.edges(etype=etype), 1)
|
|
edges_meta['data'] = chunk_numpy_array(
|
|
srcdst.numpy(), fmt_meta, num_edges_per_chunk_dict[etype],
|
|
etypestr + '%d.txt')
|
|
metadata['edges'][etypestr] = edges_meta
|
|
|
|
# Chunk node data
|
|
metadata['node_data'] = {}
|
|
with setdir('node_data'):
|
|
for ntype, ndata_per_type in ndata_paths.items():
|
|
ndata_meta = {}
|
|
with setdir(ntype):
|
|
for key, path in ndata_per_type.items():
|
|
logging.info('Chunking node data for type %s key %s' % (ntype, key))
|
|
ndata_key_meta = {}
|
|
reader_fmt_meta = writer_fmt_meta = {"name": "numpy"}
|
|
arr = array_readwriter.get_array_parser(**reader_fmt_meta).read(path)
|
|
ndata_key_meta['format'] = writer_fmt_meta
|
|
ndata_key_meta['data'] = chunk_numpy_array(
|
|
arr, writer_fmt_meta, num_nodes_per_chunk_dict[ntype],
|
|
key + '-%d.npy')
|
|
ndata_meta[key] = ndata_key_meta
|
|
|
|
metadata['node_data'][ntype] = ndata_meta
|
|
|
|
# Chunk edge data
|
|
metadata['edge_data'] = {}
|
|
with setdir('edge_data'):
|
|
for etypestr, edata_per_type in edata_paths.items():
|
|
edata_meta = {}
|
|
with setdir(etypestr):
|
|
for key, path in edata_per_type.items():
|
|
logging.info('Chunking edge data for type %s key %s' % (etypestr, key))
|
|
edata_key_meta = {}
|
|
reader_fmt_meta = writer_fmt_meta = {"name": "numpy"}
|
|
arr = array_readwriter.get_array_parser(**reader_fmt_meta).read(path)
|
|
edata_key_meta['format'] = writer_fmt_meta
|
|
etype = tuple(etypestr.split(':'))
|
|
edata_key_meta['data'] = chunk_numpy_array(
|
|
arr, writer_fmt_meta, num_edges_per_chunk_dict[etype],
|
|
key + '-%d.npy')
|
|
edata_meta[key] = edata_key_meta
|
|
|
|
metadata['edge_data'][etypestr] = edata_meta
|
|
|
|
metadata_path = 'metadata.json'
|
|
with open(metadata_path, 'w') as f:
|
|
json.dump(metadata, f)
|
|
logging.info('Saved metadata in %s' % os.path.abspath(metadata_path))
|
|
|
|
def chunk_graph(g, name, ndata_paths, edata_paths, num_chunks, output_path):
|
|
"""
|
|
Split the graph into multiple chunks.
|
|
|
|
A directory will be created at :attr:`output_path` with the metadata and chunked
|
|
edge list as well as the node/edge data.
|
|
|
|
Parameters
|
|
----------
|
|
g : DGLGraph
|
|
The graph.
|
|
name : str
|
|
The name of the graph, to be used later in DistDGL training.
|
|
ndata_paths : dict[str, pathlike] or dict[ntype, dict[str, pathlike]]
|
|
The dictionary of paths pointing to the corresponding numpy array file for each
|
|
node data key.
|
|
edata_paths : dict[str, pathlike] or dict[etype, dict[str, pathlike]]
|
|
The dictionary of paths pointing to the corresponding numpy array file for each
|
|
edge data key.
|
|
num_chunks : int
|
|
The number of chunks
|
|
output_path : pathlike
|
|
The output directory saving the chunked graph.
|
|
"""
|
|
for ntype, ndata in ndata_paths.items():
|
|
for key in ndata.keys():
|
|
ndata[key] = os.path.abspath(ndata[key])
|
|
for etype, edata in edata_paths.items():
|
|
for key in edata.keys():
|
|
edata[key] = os.path.abspath(edata[key])
|
|
with setdir(output_path):
|
|
_chunk_graph(g, name, ndata_paths, edata_paths, num_chunks, output_path)
|
|
|
|
if __name__ == '__main__':
|
|
logging.basicConfig(level='INFO')
|
|
input_dir = '/data'
|
|
output_dir = '/chunked-data'
|
|
(g,), _ = dgl.load_graphs(os.path.join(input_dir, 'graph.dgl'))
|
|
chunk_graph(
|
|
g,
|
|
'mag240m',
|
|
{'paper': {
|
|
'feat': os.path.join(input_dir, 'paper/feat.npy'),
|
|
'label': os.path.join(input_dir, 'paper/label.npy'),
|
|
'year': os.path.join(input_dir, 'paper/year.npy')}},
|
|
{'cites': {'count': os.path.join(input_dir, 'cites/count.npy')},
|
|
'writes': {'year': os.path.join(input_dir, 'writes/year.npy')},
|
|
# you can put the same data file if they indeed share the features.
|
|
'rev_writes': {'year': os.path.join(input_dir, 'writes/year.npy')}},
|
|
4,
|
|
output_dir)
|
|
# The generated metadata goes as in tools/sample-config/mag240m-metadata.json.
|