项目文件夹

文件
Quan (Andy) Gan 3b0c0cec46 enable sparse on windows and mac (#5277)
* enable sparse on windows and mac

* that was stupid

* let's see what's going on..

* [Sparse] Fix the import error on Mac OS.

When using template functions that are defined in source files from DGL,
the loader of MacOS somehow cannot find their definitions. This fix simply
avoids depending on template functions from DGL headers.

With this fix, the sparse tests all pass on the MAC environment.

* ok this is the problem

* make errors clearer

* uh

* test

* Update __init__.py

* disabling ddp on windows

---------

Co-authored-by: czkkkkkk <zekucai@gmail.com>
2023-02-15 16:08:20 +08:00

161 行
4.8 KiB
Python

import doctest
import operator
import sys
import backend as F
import dgl.sparse as dglsp
import pytest
import torch
dgl_op_map = {
"sum": "sum",
"amin": "smin",
"amax": "smax",
"mean": "smean",
"prod": "sprod",
}
default_entry = {
"sum": 0,
"amin": float("inf"),
"amax": float("-inf"),
"mean": 0,
"prod": 1,
}
binary_op_map = {
"sum": operator.add,
"amin": torch.min,
"amax": torch.max,
"mean": operator.add,
"prod": operator.mul,
}
NUM_ROWS = 10
NUM_COLS = 15
def _coalesce_dense(row, col, val, nrows, ncols, op):
# Sparse matrix coalescing on a dense matrix.
#
# It is done by stacking every non-zero entry on an individual slice
# of an (nrows x ncols x nnz), that is, construct a tensor A with
# shape (nrows, ncols, len(val)) where
#
# A[row[i], col[i], i] = val[i]
#
# and then reducing on the third "nnz" dimension.
#
# The mask matrix M has the same sparsity pattern as A with 1 being
# the non-zero entries. This is used for division if the reduce
# operator is mean.
M = torch.zeros(NUM_ROWS, NUM_COLS, device=F.ctx())
A = torch.full(
(NUM_ROWS, NUM_COLS, 20) + val.shape[1:],
default_entry[op],
device=F.ctx(),
dtype=val.dtype,
)
A = torch.index_put(A, (row, col, torch.arange(20)), val)
for i in range(20):
M[row[i], col[i]] += 1
if op == "mean":
A = A.sum(2)
else:
A = getattr(A, op)(2)
M = M.view(NUM_ROWS, NUM_COLS, *([1] * (val.dim() - 1)))
return A, M
# Add docstring tests of dglsp.reduction to unit tests
@pytest.mark.parametrize(
"func", ["reduce", "sum", "smin", "smax", "sprod", "smean"]
)
def test_docstring(func):
globs = {"torch": torch, "dglsp": dglsp}
runner = doctest.DebugRunner()
finder = doctest.DocTestFinder()
obj = getattr(dglsp, func)
for test in finder.find(obj, func, globs=globs):
runner.run(test)
@pytest.mark.parametrize("shape", [(20,), (20, 20)])
@pytest.mark.parametrize("op", ["sum", "amin", "amax", "mean", "prod"])
@pytest.mark.parametrize("use_reduce", [False, True])
def test_reduce_all(shape, op, use_reduce):
row = torch.randint(0, NUM_ROWS, (20,), device=F.ctx())
col = torch.randint(0, NUM_COLS, (20,), device=F.ctx())
val = torch.randn(*shape, device=F.ctx())
val2 = val.clone()
val = val.requires_grad_()
val2 = val2.requires_grad_()
A = dglsp.from_coo(row, col, val, shape=(NUM_ROWS, NUM_COLS))
A2, M = _coalesce_dense(row, col, val2, NUM_ROWS, NUM_COLS, op)
if not use_reduce:
output = getattr(A, dgl_op_map[op])()
else:
output = A.reduce(rtype=dgl_op_map[op])
if op == "mean":
output2 = A2.sum((0, 1)) / M.sum()
elif op == "prod":
output2 = A2.prod(0).prod(0) # prod() does not support tuple of dims
else:
output2 = getattr(A2, op)((0, 1))
assert (output - output2).abs().max() < 1e-4
head = torch.randn(*output.shape).to(val) if output.dim() > 0 else None
output.backward(head)
output2.backward(head)
assert (val.grad - val2.grad).abs().max() < 1e-4
@pytest.mark.parametrize("shape", [(20,), (20, 20)])
@pytest.mark.parametrize("dim", [0, 1])
@pytest.mark.parametrize("empty_nnz", [False, True])
@pytest.mark.parametrize("op", ["sum", "amin", "amax", "mean", "prod"])
@pytest.mark.parametrize("use_reduce", [False, True])
def test_reduce_along(shape, dim, empty_nnz, op, use_reduce):
row = torch.randint(0, NUM_ROWS, (20,), device=F.ctx())
col = torch.randint(0, NUM_COLS, (20,), device=F.ctx())
if dim == 0:
mask = torch.bincount(col, minlength=NUM_COLS) == 0
else:
mask = torch.bincount(row, minlength=NUM_ROWS) == 0
val = torch.randn(*shape, device=F.ctx())
val2 = val.clone()
val = val.requires_grad_()
val2 = val2.requires_grad_()
# empty_nnz controls whether at least one column or one row has no
# non-zero entry.
if empty_nnz:
row[row == 0] = 1
col[col == 0] = 1
A = dglsp.from_coo(row, col, val, shape=(NUM_ROWS, NUM_COLS))
A2, M = _coalesce_dense(row, col, val2, NUM_ROWS, NUM_COLS, op)
if not use_reduce:
output = getattr(A, dgl_op_map[op])(dim)
else:
output = A.reduce(dim=dim, rtype=dgl_op_map[op])
if op == "mean":
output2 = A2.sum(dim) / M.sum(dim)
else:
output2 = getattr(A2, op)(dim)
zero_entry_idx = (M.sum(dim) != 0).nonzero(as_tuple=True)[0]
output3 = torch.index_put(
torch.zeros_like(output2), (zero_entry_idx,), output2[zero_entry_idx]
)
assert (output - output3).abs().max() < 1e-4
head = torch.randn(*output.shape).to(val) if output.dim() > 0 else None
output.backward(head)
output3.backward(head)
assert (val.grad - val2.grad).abs().max() < 1e-4