import json import os import tempfile import dgl import dgl.backend as F import dgl.graphbolt as gb import numpy as np import pyarrow.parquet as pq import pytest import torch from dgl.data.utils import load_graphs, load_tensors from dgl.distributed.partition import ( _etype_str_to_tuple, _etype_tuple_to_str, _get_inner_edge_mask, _get_inner_node_mask, load_partition, RESERVED_FIELD_DTYPE, ) from distpartitioning import array_readwriter from distpartitioning.utils import generate_read_list from pytest_utils import create_chunked_dataset def _verify_metadata_gb(gpb, g, num_parts, part_id, part_sizes): """ check list: make sure the number of nodes and edges is correct. make sure the number of parts is correct. make sure the number of nodes and edges in each part is corrcet. """ assert gpb._num_nodes() == g.num_nodes() assert gpb._num_edges() == g.num_edges() assert gpb.num_partitions() == num_parts gpb_meta = gpb.metadata() assert len(gpb_meta) == num_parts assert len(gpb.partid2nids(part_id)) == gpb_meta[part_id]["num_nodes"] assert len(gpb.partid2eids(part_id)) == gpb_meta[part_id]["num_edges"] part_sizes.append( (gpb_meta[part_id]["num_nodes"], gpb_meta[part_id]["num_edges"]) ) def _verify_local_id_gb(part_g, part_id, gpb): """ check list: make sure the type of local id is correct. make sure local id have a right order. """ nid = F.boolean_mask( part_g.node_attributes[dgl.NID], part_g.node_attributes["inner_node"], ) local_nid = gpb.nid2localnid(nid, part_id) assert F.dtype(local_nid) in (F.int64, F.int32) assert np.all(F.asnumpy(local_nid) == np.arange(0, len(local_nid))) eid = F.boolean_mask( part_g.edge_attributes[dgl.EID], part_g.edge_attributes["inner_edge"], ) local_eid = gpb.eid2localeid(eid, part_id) assert F.dtype(local_eid) in (F.int64, F.int32) assert np.all(np.sort(F.asnumpy(local_eid)) == np.arange(0, len(local_eid))) return local_nid, local_eid def _verify_map_gb( part_g, part_id, gpb, ): """ check list: make sure the map node and its data type is correct. """ # Check the node map. local_nodes = F.boolean_mask( part_g.node_attributes[dgl.NID], part_g.node_attributes["inner_node"], ) inner_node_index = F.nonzero_1d(part_g.node_attributes["inner_node"]) mapping_nodes = gpb.partid2nids(part_id) assert F.dtype(mapping_nodes) in (F.int32, F.int64) assert np.all( np.sort(F.asnumpy(local_nodes)) == np.sort(F.asnumpy(mapping_nodes)) ) assert np.all( F.asnumpy(inner_node_index) == np.arange(len(inner_node_index)) ) # Check the edge map. local_edges = F.boolean_mask( part_g.edge_attributes[dgl.EID], part_g.edge_attributes["inner_edge"], ) inner_edge_index = F.nonzero_1d(part_g.edge_attributes["inner_edge"]) mapping_edges = gpb.partid2eids(part_id) assert F.dtype(mapping_edges) in (F.int32, F.int64) assert np.all( np.sort(F.asnumpy(local_edges)) == np.sort(F.asnumpy(mapping_edges)) ) assert np.all( F.asnumpy(inner_edge_index) == np.arange(len(inner_edge_index)) ) return local_nodes, local_edges def _verify_local_and_map_id_gb( part_g, part_id, gpb, store_inner_node, store_inner_edge, store_eids, ): """ check list: make sure local id are correct. make sure mapping id are correct. """ if store_inner_node and store_inner_edge and store_eids: _verify_local_id_gb(part_g, part_id, gpb) _verify_map_gb(part_g, part_id, gpb) def _get_part_IDs(part_g): # These are partition-local IDs. num_columns = part_g.csc_indptr.diff() part_src_ids = part_g.indices part_dst_ids = torch.arange(part_g.total_num_nodes).repeat_interleave( num_columns ) # These are reshuffled global homogeneous IDs. part_src_ids = F.gather_row(part_g.node_attributes[dgl.NID], part_src_ids) part_dst_ids = F.gather_row(part_g.node_attributes[dgl.NID], part_dst_ids) return part_src_ids, part_dst_ids def _verify_node_type_ID_gb(part_g, gpb): """ check list: make sure ntype id have correct data type """ part_src_ids, part_dst_ids = _get_part_IDs(part_g) # These are reshuffled per-type IDs. src_ntype_ids, part_src_ids = gpb.map_to_per_ntype(part_src_ids) dst_ntype_ids, part_dst_ids = gpb.map_to_per_ntype(part_dst_ids) # `IdMap` is in int64 by default. assert src_ntype_ids.dtype == F.int64 assert dst_ntype_ids.dtype == F.int64 with pytest.raises(dgl.utils.internal.InconsistentDtypeException): gpb.map_to_per_ntype(F.tensor([0], F.int32)) with pytest.raises(dgl.utils.internal.InconsistentDtypeException): gpb.map_to_per_etype(F.tensor([0], F.int32)) return ( part_src_ids, part_dst_ids, src_ntype_ids, part_src_ids, dst_ntype_ids, ) def _verify_orig_edge_IDs_gb( g, orig_nids, orig_eids, part_eids, part_src_ids, part_dst_ids, src_ntype=None, dst_ntype=None, etype=None, ): """ check list: make sure orig edge id are correct after """ if src_ntype is not None and dst_ntype is not None: orig_src_nid = orig_nids[src_ntype] orig_dst_nid = orig_nids[dst_ntype] else: orig_src_nid = orig_nids orig_dst_nid = orig_nids orig_src_ids = F.gather_row(orig_src_nid, part_src_ids) orig_dst_ids = F.gather_row(orig_dst_nid, part_dst_ids) if etype is not None: orig_eids = orig_eids[etype] orig_eids1 = F.gather_row(orig_eids, part_eids) orig_eids2 = g.edge_ids(orig_src_ids, orig_dst_ids, etype=etype) assert len(orig_eids1) == len(orig_eids2) assert np.all(F.asnumpy(orig_eids1) == F.asnumpy(orig_eids2)) def _verify_orig_IDs_gb( part_g, gpb, g, is_homo=False, part_src_ids=None, part_dst_ids=None, src_ntype_ids=None, dst_ntype_ids=None, orig_nids=None, orig_eids=None, ): """ check list: make sure orig edge id are correct. make sure hetero ntype id are correct. """ part_eids = part_g.edge_attributes[dgl.EID] if is_homo: _verify_orig_edge_IDs_gb( g, orig_nids, orig_eids, part_eids, part_src_ids, part_dst_ids ) local_orig_nids = orig_nids[part_g.node_attributes[dgl.NID]] local_orig_eids = orig_eids[part_g.edge_attributes[dgl.EID]] part_g.node_attributes["feats"] = F.gather_row( g.ndata["feats"], local_orig_nids ) part_g.edge_attributes["feats"] = F.gather_row( g.edata["feats"], local_orig_eids ) else: etype_ids, part_eids = gpb.map_to_per_etype(part_eids) # `IdMap` is in int64 by default. assert etype_ids.dtype == F.int64 # These are original per-type IDs. for etype_id, etype in enumerate(g.canonical_etypes): part_src_ids1 = F.boolean_mask(part_src_ids, etype_ids == etype_id) src_ntype_ids1 = F.boolean_mask( src_ntype_ids, etype_ids == etype_id ) part_dst_ids1 = F.boolean_mask(part_dst_ids, etype_ids == etype_id) dst_ntype_ids1 = F.boolean_mask( dst_ntype_ids, etype_ids == etype_id ) part_eids1 = F.boolean_mask(part_eids, etype_ids == etype_id) assert np.all(F.asnumpy(src_ntype_ids1 == src_ntype_ids1[0])) assert np.all(F.asnumpy(dst_ntype_ids1 == dst_ntype_ids1[0])) src_ntype = g.ntypes[F.as_scalar(src_ntype_ids1[0])] dst_ntype = g.ntypes[F.as_scalar(dst_ntype_ids1[0])] _verify_orig_edge_IDs_gb( g, orig_nids, orig_eids, part_eids1, part_src_ids1, part_dst_ids1, src_ntype, dst_ntype, etype, ) def _verify_constructed_id_gb(part_sizes, gpb): """ verify the part id of each node by constructed nids. check list: make sure each node' part id and its type are corect """ node_map = [] edge_map = [] for part_i, (num_nodes, num_edges) in enumerate(part_sizes): node_map.append(np.ones(num_nodes) * part_i) edge_map.append(np.ones(num_edges) * part_i) node_map = np.concatenate(node_map) edge_map = np.concatenate(edge_map) nid2pid = gpb.nid2partid(F.arange(0, len(node_map))) assert F.dtype(nid2pid) in (F.int32, F.int64) assert np.all(F.asnumpy(nid2pid) == node_map) eid2pid = gpb.eid2partid(F.arange(0, len(edge_map))) assert F.dtype(eid2pid) in (F.int32, F.int64) assert np.all(F.asnumpy(eid2pid) == edge_map) def _verify_IDs_gb( g, part_g, part_id, gpb, part_sizes, orig_nids, orig_eids, store_inner_node, store_inner_edge, store_eids, is_homo, ): # verify local id and mapping id _verify_local_and_map_id_gb( part_g, part_id, gpb, store_inner_node, store_inner_edge, store_eids, ) # Verify the mapping between the reshuffled IDs and the original IDs. ( part_src_ids, part_dst_ids, src_ntype_ids, part_src_ids, dst_ntype_ids, ) = _verify_node_type_ID_gb(part_g, gpb) if store_eids: _verify_orig_IDs_gb( part_g, gpb, g, part_src_ids=part_src_ids, part_dst_ids=part_dst_ids, src_ntype_ids=src_ntype_ids, dst_ntype_ids=dst_ntype_ids, orig_nids=orig_nids, orig_eids=orig_eids, is_homo=is_homo, ) _verify_constructed_id_gb(part_sizes, gpb) def _collect_data_gb( parts, part_g, gpbs, gpb, tot_node_feats, node_feats, tot_edge_feats, edge_feats, shuffled_labels, shuffled_edata, test_ntype, test_etype, ): if test_ntype != None: shuffled_labels.append(node_feats[test_ntype + "/label"]) shuffled_edata.append( edge_feats[_etype_tuple_to_str(test_etype) + "/count"] ) else: shuffled_labels.append(node_feats["_N/labels"]) shuffled_edata.append(edge_feats["_N:_E:_N/feats"]) parts.append(part_g) gpbs.append(gpb) tot_node_feats.append(node_feats) tot_edge_feats.append(edge_feats) def _verify_node_feats(g, part, gpb, orig_nids, node_feats, is_homo=False): for ntype in g.ntypes: ndata = ( part.node_attributes if isinstance(part, gb.FusedCSCSamplingGraph) else part.ndata ) ntype_id = g.get_ntype_id(ntype) inner_node_mask = _get_inner_node_mask( part, ntype_id, (gpb if isinstance(part, gb.FusedCSCSamplingGraph) else None), ) inner_nids = F.boolean_mask(ndata[dgl.NID], inner_node_mask) ntype_ids, inner_type_nids = gpb.map_to_per_ntype(inner_nids) partid = gpb.nid2partid(inner_type_nids, ntype) if is_homo: assert np.all(F.asnumpy(ntype_ids) == ntype_id) assert np.all(F.asnumpy(partid) == gpb.partid) if is_homo: orig_id = orig_nids[inner_type_nids] else: orig_id = orig_nids[ntype][inner_type_nids] local_nids = gpb.nid2localnid(inner_type_nids, gpb.partid, ntype) for name in g.nodes[ntype].data: if name in [dgl.NID, "inner_node"]: continue true_feats = F.gather_row(g.nodes[ntype].data[name], orig_id) ndata = F.gather_row(node_feats[ntype + "/" + name], local_nids) assert np.all(F.asnumpy(ndata == true_feats)) def _verify_edge_feats(g, part, gpb, orig_eids, edge_feats, is_homo=False): for etype in g.canonical_etypes: edata = ( part.edge_attributes if isinstance(part, gb.FusedCSCSamplingGraph) else part.edata ) etype_id = g.get_etype_id(etype) inner_edge_mask = _get_inner_edge_mask(part, etype_id) inner_eids = F.boolean_mask(edata[dgl.EID], inner_edge_mask) etype_ids, inner_type_eids = gpb.map_to_per_etype(inner_eids) partid = gpb.eid2partid(inner_type_eids, etype) assert np.all(F.asnumpy(etype_ids) == etype_id) assert np.all(F.asnumpy(partid) == gpb.partid) if is_homo: orig_id = orig_eids[inner_type_eids] else: orig_id = orig_eids[etype][inner_type_eids] local_eids = gpb.eid2localeid(inner_type_eids, gpb.partid, etype) for name in g.edges[etype].data: if name in [dgl.EID, "inner_edge"]: continue true_feats = F.gather_row(g.edges[etype].data[name], orig_id) edata = F.gather_row( edge_feats[_etype_tuple_to_str(etype) + "/" + name], local_eids, ) assert np.all(F.asnumpy(edata == true_feats)) def _verify_shuffled_labels_gb( g, shuffled_labels, shuffled_edata, orig_nids, orig_eids, test_ntype=None, test_etype=None, ): """ check list: make sure node data are correct. make sure edge data are correct. """ shuffled_labels = F.asnumpy(F.cat(shuffled_labels, 0)) shuffled_edata = F.asnumpy(F.cat(shuffled_edata, 0)) orig_labels = np.zeros(shuffled_labels.shape, dtype=shuffled_labels.dtype) orig_edata = np.zeros(shuffled_edata.shape, dtype=shuffled_edata.dtype) orig_nid = orig_nids if test_ntype is None else orig_nids[test_ntype] orig_eid = orig_eids if test_etype is None else orig_eids[test_etype] nlabel = ( g.ndata["labels"] if test_ntype is None else g.nodes[test_ntype].data["label"] ) edata = ( g.edata["feats"] if test_etype is None else g.edges[test_etype].data["count"] ) orig_labels[F.asnumpy(orig_nid)] = shuffled_labels orig_edata[F.asnumpy(orig_eid)] = shuffled_edata assert np.all(orig_labels == F.asnumpy(nlabel)) assert np.all(orig_edata == F.asnumpy(edata)) def verify_graph_feats_gb( g, gpbs, parts, tot_node_feats, tot_edge_feats, orig_nids, orig_eids, shuffled_labels, shuffled_edata, test_ntype, test_etype, store_inner_node=False, store_inner_edge=False, store_eids=False, is_homo=False, ): """ check list: make sure the feats of nodes and edges are correct """ for part_id in range(len(parts)): part = parts[part_id] gpb = gpbs[part_id] node_feats = tot_node_feats[part_id] edge_feats = tot_edge_feats[part_id] if store_inner_node: _verify_node_feats( g, part, gpb, orig_nids, node_feats, is_homo=is_homo, ) if store_inner_edge and store_eids: _verify_edge_feats( g, part, gpb, orig_eids, edge_feats, is_homo=is_homo, ) _verify_shuffled_labels_gb( g, shuffled_labels, shuffled_edata, orig_nids, orig_eids, test_ntype, test_etype, ) def _verify_graphbolt_attributes( parts, store_inner_node, store_inner_edge, store_eids ): """ check list: make sure arguments work. """ for part in parts: assert store_inner_edge == ("inner_edge" in part.edge_attributes) assert store_inner_node == ("inner_node" in part.node_attributes) assert store_eids == (dgl.EID in part.edge_attributes) def _verify_graphbolt_part( g, test_dir, orig_nids, orig_eids, graph_name, num_parts, store_inner_node, store_inner_edge, store_eids, part_config=None, test_ntype=None, test_etype=None, is_homo=False, ): """ check list: _verify_metadata_gb: data type, ID's order and ID's number of edges and nodes _verify_IDs_gb: local id, mapping id,node type id, orig edge, hetero ntype id verify_graph_feats_gb: nodes and edges' feats _verify_graphbolt_attributes: arguments """ parts = [] tot_node_feats = [] tot_edge_feats = [] shuffled_labels = [] shuffled_edata = [] part_sizes = [] gpbs = [] if part_config is None: part_config = os.path.join(test_dir, f"{graph_name}.json") # test each part for part_id in range(num_parts): part_g, node_feats, edge_feats, gpb, _, _, _ = load_partition( part_config, part_id, load_feats=True, use_graphbolt=True ) # verify metadata _verify_metadata_gb( gpb, g, num_parts, part_id, part_sizes, ) # verify eid and nid _verify_IDs_gb( g, part_g, part_id, gpb, part_sizes, orig_nids, orig_eids, store_inner_node, store_inner_edge, store_eids, is_homo, ) # collect shuffled data and parts _collect_data_gb( parts, part_g, gpbs, gpb, tot_node_feats, node_feats, tot_edge_feats, edge_feats, shuffled_labels, shuffled_edata, test_ntype, test_etype, ) # verify graph feats verify_graph_feats_gb( g, gpbs, parts, tot_node_feats, tot_edge_feats, orig_nids, orig_eids, shuffled_labels=shuffled_labels, shuffled_edata=shuffled_edata, test_ntype=test_ntype, test_etype=test_etype, store_inner_node=store_inner_node, store_inner_edge=store_inner_edge, store_eids=store_eids, is_homo=is_homo, ) _verify_graphbolt_attributes( parts, store_inner_node, store_inner_edge, store_eids ) return parts def _verify_hetero_graph_node_edge_num( g, parts, store_inner_edge, debug_mode, ): """ check list: make sure edge type are correct. make sure the number of nodes in each node type are correct. make sure the number of nodes in each node type are correct. """ num_nodes = {ntype: 0 for ntype in g.ntypes} num_edges = {etype: 0 for etype in g.canonical_etypes} for part in parts: edata = ( part.edge_attributes if isinstance(part, gb.FusedCSCSamplingGraph) else part.edata ) if dgl.ETYPE in edata: assert len(g.canonical_etypes) == len(F.unique(edata[dgl.ETYPE])) if debug_mode or isinstance(part, dgl.DGLGraph): for ntype in g.ntypes: ntype_id = g.get_ntype_id(ntype) inner_node_mask = _get_inner_node_mask(part, ntype_id) num_inner_nodes = F.sum(F.astype(inner_node_mask, F.int64), 0) num_nodes[ntype] += num_inner_nodes if store_inner_edge or isinstance(part, dgl.DGLGraph): for etype in g.canonical_etypes: etype_id = g.get_etype_id(etype) inner_edge_mask = _get_inner_edge_mask(part, etype_id) num_inner_edges = F.sum(F.astype(inner_edge_mask, F.int64), 0) num_edges[etype] += num_inner_edges # Verify the number of nodes are correct. if debug_mode or isinstance(part, dgl.DGLGraph): for ntype in g.ntypes: print( "node {}: {}, {}".format( ntype, g.num_nodes(ntype), num_nodes[ntype] ) ) assert g.num_nodes(ntype) == num_nodes[ntype] # Verify the number of edges are correct. if store_inner_edge or isinstance(part, dgl.DGLGraph): for etype in g.canonical_etypes: print( "edge {}: {}, {}".format( etype, g.num_edges(etype), num_edges[etype] ) ) assert g.num_edges(etype) == num_edges[etype] def _verify_edge_id_range_hetero( g, part, eids, ): """ check list: make sure inner_eids fall into a range. make sure all edges are included. """ edata = ( part.edge_attributes if isinstance(part, gb.FusedCSCSamplingGraph) else part.edata ) etype = ( part.type_per_edge if isinstance(part, gb.FusedCSCSamplingGraph) else edata[dgl.ETYPE] ) eid = torch.arange(len(edata[dgl.EID])) etype_arr = F.gather_row(etype, eid) eid_arr = F.gather_row(edata[dgl.EID], eid) for etype in g.canonical_etypes: etype_id = g.get_etype_id(etype) eids[etype].append(F.boolean_mask(eid_arr, etype_arr == etype_id)) # Make sure edge Ids fall into a range. inner_edge_mask = _get_inner_edge_mask(part, etype_id) inner_eids = np.sort( F.asnumpy(F.boolean_mask(edata[dgl.EID], inner_edge_mask)) ) assert np.all( inner_eids == np.arange(inner_eids[0], inner_eids[-1] + 1) ) return eids def _verify_node_id_range_hetero(g, part, nids): """ check list: make sure inner nodes have Ids fall into a range. """ for ntype in g.ntypes: ntype_id = g.get_ntype_id(ntype) # Make sure inner nodes have Ids fall into a range. inner_node_mask = _get_inner_node_mask(part, ntype_id) inner_nids = F.boolean_mask( part.node_attributes[dgl.NID], inner_node_mask ) assert np.all( F.asnumpy( inner_nids == F.arange( F.as_scalar(inner_nids[0]), F.as_scalar(inner_nids[-1]) + 1, ) ) ) nids[ntype].append(inner_nids) return nids def _verify_graph_attributes_hetero( g, parts, store_inner_edge, store_inner_node, ): """ check list: make sure edge ids fall into a range. make sure inner nodes have Ids fall into a range. make sure all nodes is included. make sure all edges is included. """ nids = {ntype: [] for ntype in g.ntypes} eids = {etype: [] for etype in g.canonical_etypes} # check edge id. if store_inner_edge or isinstance(parts[0], dgl.DGLGraph): for part in parts: # collect eids eids = _verify_edge_id_range_hetero(g, part, eids) for etype in eids: eids_type = F.cat(eids[etype], 0) uniq_ids = F.unique(eids_type) # We should get all nodes. assert len(uniq_ids) == g.num_edges(etype) # check node id. if store_inner_node or isinstance(parts[0], dgl.DGLGraph): for part in parts: nids = _verify_node_id_range_hetero(g, part, nids) for ntype in nids: nids_type = F.cat(nids[ntype], 0) uniq_ids = F.unique(nids_type) # We should get all nodes. assert len(uniq_ids) == g.num_nodes(ntype) def _verify_hetero_graph( g, parts, store_eids=False, store_inner_edge=False, store_inner_node=False, debug_mode=False, ): _verify_hetero_graph_node_edge_num( g, parts, store_inner_edge=store_inner_edge, debug_mode=debug_mode, ) if store_eids: _verify_graph_attributes_hetero( g, parts, store_inner_edge=store_inner_edge, store_inner_node=store_inner_node, ) def _test_pipeline_graphbolt( num_chunks, num_parts, world_size, graph_formats=None, data_fmt="numpy", num_chunks_nodes=None, num_chunks_edges=None, num_chunks_node_data=None, num_chunks_edge_data=None, use_verify_partitions=False, store_eids=True, store_inner_edge=True, store_inner_node=True, ): if num_parts % world_size != 0: # num_parts should be a multiple of world_size return with tempfile.TemporaryDirectory() as root_dir: g = create_chunked_dataset( root_dir, num_chunks, data_fmt=data_fmt, num_chunks_nodes=num_chunks_nodes, num_chunks_edges=num_chunks_edges, num_chunks_node_data=num_chunks_node_data, num_chunks_edge_data=num_chunks_edge_data, ) graph_name = "test" test_ntype = "paper" test_etype = ("paper", "cites", "paper") # Step1: graph partition in_dir = os.path.join(root_dir, "chunked-data") output_dir = os.path.join(root_dir, "parted_data") os.system( "python3 tools/partition_algo/random_partition.py " "--in_dir {} --out_dir {} --num_partitions {}".format( in_dir, output_dir, num_parts ) ) for ntype in ["author", "institution", "paper"]: fname = os.path.join(output_dir, "{}.txt".format(ntype)) with open(fname, "r") as f: header = f.readline().rstrip() assert isinstance(int(header), int) # Step2: data dispatch partition_dir = os.path.join(root_dir, "parted_data") out_dir = os.path.join(root_dir, "partitioned") ip_config = os.path.join(root_dir, "ip_config.txt") with open(ip_config, "w") as f: for i in range(world_size): f.write(f"127.0.0.{i + 1}\n") cmd = "python3 tools/dispatch_data.py " cmd += f" --in-dir {in_dir} " cmd += f" --partitions-dir {partition_dir} " cmd += f" --out-dir {out_dir} " cmd += f" --ip-config {ip_config} " cmd += " --ssh-port 22 " cmd += " --process-group-timeout 60 " cmd += " --save-orig-nids " cmd += " --save-orig-eids " cmd += " --use-graphbolt " cmd += f" --graph-formats {graph_formats} " if graph_formats else "" if store_eids: cmd += " --store-eids " if store_inner_edge: cmd += " --store-inner-edge " if store_inner_node: cmd += " --store-inner-node " os.system(cmd) # check if verify_partitions.py is used for validation. if use_verify_partitions: cmd = "python3 tools/verify_partitions.py " cmd += f" --orig-dataset-dir {in_dir}" cmd += f" --part-graph {out_dir}" cmd += f" --partitions-dir {output_dir}" os.system(cmd) return # read original node/edge IDs def read_orig_ids(fname): orig_ids = {} for i in range(num_parts): ids_path = os.path.join(out_dir, f"part{i}", fname) part_ids = load_tensors(ids_path) for type, data in part_ids.items(): if type not in orig_ids: orig_ids[type] = data else: orig_ids[type] = torch.cat((orig_ids[type], data)) return orig_ids orig_nids, orig_eids = None, None orig_nids = read_orig_ids("orig_nids.dgl") orig_eids_str = read_orig_ids("orig_eids.dgl") orig_eids = {} # transmit etype from string to tuple. for etype, eids in orig_eids_str.items(): orig_eids[_etype_str_to_tuple(etype)] = eids # load partitions and verify part_config = os.path.join(out_dir, "metadata.json") parts = _verify_graphbolt_part( g, root_dir, orig_nids, orig_eids, graph_name, num_parts, store_inner_node, store_inner_edge, store_eids, test_ntype=test_ntype, test_etype=test_etype, part_config=part_config, is_homo=False, ) _verify_hetero_graph( g, parts, store_eids=store_eids, store_inner_edge=store_inner_edge, ) @pytest.mark.parametrize( "num_chunks, num_parts, world_size", [[4, 4, 4], [8, 4, 2], [8, 4, 4], [9, 6, 3], [11, 11, 1], [11, 4, 1]], ) def test_pipeline_basics(num_chunks, num_parts, world_size): _test_pipeline_graphbolt( num_chunks, num_parts, world_size, ) _test_pipeline_graphbolt( num_chunks, num_parts, world_size, use_verify_partitions=False ) @pytest.mark.parametrize("store_inner_node", [True, False]) @pytest.mark.parametrize("store_inner_edge", [True, False]) @pytest.mark.parametrize("store_eids", [True, False]) def test_pipeline_attributes(store_inner_node, store_inner_edge, store_eids): _test_pipeline_graphbolt( 4, 4, 4, store_inner_node=store_inner_node, store_inner_edge=store_inner_edge, store_eids=store_eids, ) @pytest.mark.parametrize( "num_chunks, " "num_parts, " "world_size, " "num_chunks_node_data, " "num_chunks_edge_data", [ # Test cases where no. of chunks more than # no. of partitions [8, 4, 4, 8, 8], [8, 4, 2, 8, 8], [9, 7, 5, 9, 9], [8, 8, 4, 8, 8], # Test cases where no. of chunks smaller # than no. of partitions [7, 8, 4, 7, 7], [1, 8, 4, 1, 1], [1, 4, 4, 1, 1], [3, 4, 4, 3, 3], [1, 4, 2, 1, 1], [3, 4, 2, 3, 3], [1, 5, 3, 1, 1], ], ) def test_pipeline_arbitrary_chunks( num_chunks, num_parts, world_size, num_chunks_node_data, num_chunks_edge_data, ): _test_pipeline_graphbolt( num_chunks, num_parts, world_size, num_chunks_node_data=num_chunks_node_data, num_chunks_edge_data=num_chunks_edge_data, ) @pytest.mark.parametrize("data_fmt", ["numpy", "parquet"]) def test_pipeline_feature_format(data_fmt): _test_pipeline_graphbolt(4, 4, 4, data_fmt=data_fmt)