| name | dgl |
| description | DGL (Deep Graph Library) — graph construction, message passing, GNN layers, heterogeneous graphs, and batching for fraud detection on blockchain graphs |
DGL
Graph construction
import dgl
import torch
src = torch.tensor([0, 1, 2, 3])
dst = torch.tensor([1, 2, 3, 0])
g = dgl.graph((src, dst))
g.ndata["feat"] = torch.randn(g.num_nodes(), 64)
g.edata["weight"] = torch.ones(g.num_edges())
import networkx as nx
nx_g = nx.DiGraph()
nx_g.add_edges_from([(0, 1), (1, 2)])
g = dgl.from_networkx(nx_g)
g = dgl.add_self_loop(g)
Heterogeneous graphs
g = dgl.heterograph({
("account", "sends", "account"): (src_acct, dst_acct),
("account", "creates", "contract"): (src_acct, dst_contract),
})
g.nodes["account"].data["feat"] = account_feats
g.edges["sends"].data["amount"] = tx_amounts
Message passing (custom MPNN)
import dgl.function as fn
g.update_all(
fn.copy_u("h", "m"),
fn.mean("m", "h_agg"),
)
h_new = g.ndata["h_agg"]
g.edata["e"] = edge_weights
g.update_all(
fn.u_mul_e("h", "e", "m"),
fn.sum("m", "h_agg"),
)
def message_fn(edges):
return {"m": edges.src["h"] * edges.data["w"]}
def reduce_fn(nodes):
return {"h": nodes.mailbox["m"].sum(dim=1)}
g.update_all(message_fn, reduce_fn)
Common GNN layers
from dgl.nn import GraphConv, GATConv, SAGEConv
conv = GraphConv(in_feats=64, out_feats=128, norm="both", allow_zero_in_degree=True)
h = conv(g, g.ndata["feat"])
gat = GATConv(64, 32, num_heads=4, feat_drop=0.1, attn_drop=0.1)
h = gat(g, g.ndata["feat"])
h = h.flatten(1)
sage = SAGEConv(64, 128, aggregator_type="mean")
h = sage(g, g.ndata["feat"])
Mini-batch training with NodeDataLoader
from dgl.dataloading import NodeDataLoader, MultiLayerNeighborSampler
sampler = MultiLayerNeighborSampler([15, 10])
train_ids = torch.where(g.ndata["train_mask"])[0]
loader = NodeDataLoader(
g, train_ids, sampler,
batch_size=1024,
shuffle=True,
drop_last=False,
num_workers=4,
)
for input_nodes, output_nodes, blocks in loader:
h = blocks[0].srcdata["feat"]
for i, (block, layer) in enumerate(zip(blocks, gnn_layers)):
h = layer(block, h)
loss = loss_fn(h, blocks[-1].dstdata["label"])
Graph batching
graphs = [dgl.graph(...) for _ in range(batch_size)]
batched = dgl.batch(graphs)
graphs_list = dgl.unbatch(batched)
import dgl
h_graph = dgl.mean_nodes(batched, "h")
Device handling
g = g.to("cuda")
g = g.to(torch.device("cuda:0"))
loader = NodeDataLoader(..., device="cuda")
Pitfalls
allow_zero_in_degree=True must be set for GCN/GAT when isolated nodes exist — otherwise raises an error silently giving wrong results in older versions.
- Prefer built-in
dgl.function message/reduce ops over UDFs — they use fused CUDA kernels and are 2–5× faster.
MultiLayerNeighborSampler samples in reverse layer order; blocks[0] is the input layer, blocks[-1] contains output nodes.
- Moving a graph to GPU with
.to("cuda") also moves all ndata/edata tensors — don't move them separately.
- For heterogeneous graphs, use
dgl.nn.HeteroGraphConv to apply different convolutions per relation type.