add gmres dense bprop

This commit is contained in:
z00512249 2022-03-22 09:35:27 +08:00
parent 5601dc4cb5
commit 0ea895bc84
2 changed files with 154 additions and 64 deletions

View File

@ -72,7 +72,7 @@ def _high_precision_cho_solve(a, b, data_type=mstype.float64):
return y.astype(data_type) return y.astype(data_type)
def _batch_gmres(A, x0, b, tol, atol, restart, maxiter, M): def _batch_gmres(A, b, x0, tol, restart, maxiter, M, atol):
""" """
batched gmres: solve the least squares problem from scratch at the end of each GMRES iteration. batched gmres: solve the least squares problem from scratch at the end of each GMRES iteration.
It does not allow for early termination, but has much less overhead on GPUs. It does not allow for early termination, but has much less overhead on GPUs.
@ -106,7 +106,7 @@ def _batch_gmres(A, x0, b, tol, atol, restart, maxiter, M):
return x, F.select(residual_norm > atol, k, _INT_ZERO) return x, F.select(residual_norm > atol, k, _INT_ZERO)
def _incremental_gmres(A, x0, b, tol, atol, restart, maxiter, M): def _incremental_gmres(A, b, x0, tol, restart, maxiter, M, atol):
""" """
incremental gmres: builds a QR decomposition for the Krylov subspace incrementally during incremental gmres: builds a QR decomposition for the Krylov subspace incrementally during
the GMRES process using Givens rotations. This improves numerical stability and gives a free estimate of the GMRES process using Givens rotations. This improves numerical stability and gives a free estimate of
@ -139,7 +139,7 @@ def _incremental_gmres(A, x0, b, tol, atol, restart, maxiter, M):
while mnp.logical_and(mnp.less(k, restart), mnp.less(ptol, err)): while mnp.logical_and(mnp.less(k, restart), mnp.less(ptol, err)):
V, R, _ = arnoldi_iteration(k, A, M, V, R) V, R, _ = arnoldi_iteration(k, A, M, V, R)
# Givens rotation # Givens rotation
row_k = R[k, :].copy() row_k = R[k, :]
i = _INT_ZERO i = _INT_ZERO
while i < k: while i < k:
row_k = rotate_vectors(row_k, i, givens[i, 0], givens[i, 1]) row_k = rotate_vectors(row_k, i, givens[i, 0], givens[i, 1])
@ -185,16 +185,16 @@ class GMRES(nn.Cell):
self.M = M self.M = M
self.solve_method = solve_method self.solve_method = solve_method
def construct(self, b, x0, tol, atol, restart, maxiter): def construct(self, b, x0, tol, restart, maxiter, atol):
# Constant tensor which avoids loop unrolling # Constant tensor which avoids loop unrolling
A = _normalize_matvec(self.A) A = _normalize_matvec(self.A)
M = _normalize_matvec(self.M) M = _normalize_matvec(self.M)
x = x0 x = x0
info = _to_tensor(0) info = _to_tensor(0)
if self.solve_method == 'batched': if self.solve_method == 'batched':
x, info = _batch_gmres(A, x0, b, tol, atol, restart, maxiter, M) x, info = _batch_gmres(A, b, x0, tol, restart, maxiter, M, atol)
elif self.solve_method == "incremental": elif self.solve_method == "incremental":
x, info = _incremental_gmres(A, x0, b, tol, atol, restart, maxiter, M) x, info = _incremental_gmres(A, b, x0, tol, restart, maxiter, M, atol)
else: else:
_raise_value_error("solve_method should be in ('incremental' or 'batched'), but got ", self.solve_method, _raise_value_error("solve_method should be in ('incremental' or 'batched'), but got ", self.solve_method,
".") ".")
@ -210,20 +210,38 @@ class GMRESV2(nn.Cell):
super(GMRESV2, self).__init__() super(GMRESV2, self).__init__()
self.solve_method = solve_method self.solve_method = solve_method
def construct(self, A, b, x0, tol, atol, restart, maxiter, M): def construct(self, A, b, x0, tol, restart, maxiter, M, atol):
A = _normalize_matvec(A) A = _normalize_matvec(A)
M = _normalize_matvec(M) M = _normalize_matvec(M)
x = x0 x = x0
info = _to_tensor(0) info = _to_tensor(0)
if self.solve_method == 'batched': if self.solve_method == 'batched':
x, info = _batch_gmres(A, x0, b, tol, atol, restart, maxiter, M) x, info = _batch_gmres(A, b, x0, tol, restart, maxiter, M, atol)
elif self.solve_method == "incremental": elif self.solve_method == "incremental":
x, info = _incremental_gmres(A, x0, b, tol, atol, restart, maxiter, M) x, info = _incremental_gmres(A, b, x0, tol, restart, maxiter, M, atol)
else: else:
_raise_value_error("solve_method should be in ('incremental' or 'batched'), but got ", self.solve_method, _raise_value_error("solve_method should be in ('incremental' or 'batched'), but got ", self.solve_method,
".") ".")
return x, info return x, info
def bprop(self, A, b, x0, tol, restart, maxiter, M, atol, out, dout):
"""
Derivatives of `gmres` are implemented via implicit differentiation with
another `gmres` solve, rather than by differentiating *through* the solver.
They will be accurate only if both solves converge.
"""
n = b.shape[0]
if not isinstance(M, (Tensor, CSRTensor)):
M = F.eye(n, n, b.dtype)
grad_b, _ = self.construct(A.T, dout[0], x0, tol, restart, maxiter, M, atol)
if isinstance(A, CSRTensor):
grad_a_dense = -1 * F.reshape(grad_b, (n, 1)) * F.reshape(out[0], (1, n))
values = F.csr_gather(A.indptr, A.indices, grad_a_dense, A.shape)
grad_a = CSRTensor(A.indptr, A.indices, values, A.shape)
else:
grad_a = -1 * F.reshape(grad_b, (n, 1)) * F.reshape(out[0], (1, n))
return grad_a, grad_b, zeros_like(x0), zeros_like(tol), zeros_like(atol), zeros_like(maxiter), zeros_like(M)
def gmres(A, b, x0=None, *, tol=1e-5, restart=20, maxiter=None, def gmres(A, b, x0=None, *, tol=1e-5, restart=20, maxiter=None,
M=None, callback=None, restrt=None, atol=0.0, callback_type=None, solve_method='batched'): M=None, callback=None, restrt=None, atol=0.0, callback_type=None, solve_method='batched'):
@ -322,12 +340,43 @@ def gmres(A, b, x0=None, *, tol=1e-5, restart=20, maxiter=None,
if restart > size: if restart > size:
restart = size restart = size
if not is_within_graph(A): if not is_within_graph(A):
x, info = GMRES(A, M, solve_method)(b, x0, tol, atol, restart, maxiter) x, info = GMRES(A, M, solve_method)(b, x0, tol, restart, maxiter, atol)
else: else:
x, info = GMRESV2(solve_method)(A, b, x0, tol, atol, restart, maxiter, M) x, info = GMRESV2(solve_method)(A, b, x0, tol, restart, maxiter, M, atol)
return x, info return x, info
def _cg(A, b, x0, tol, atol, maxiter, M):
"""
Figure 2.5 from Barrett R, et al. 'Templates for the sulution of linear systems:
building blocks for iterative methods', 1994, pg. 12-14
"""
# Constant tensor which avoids loop unrolling
_INT_ZERO = _to_tensor(0)
atol_ = mnp.maximum(atol, tol * _norm(b))
r = b - A(x0)
z = p = M(r)
rho = mnp.dot(r, z)
k = _INT_ZERO
x = x0
while k < maxiter and _norm(r) > atol_:
q = A(p)
alpha = rho / mnp.dot(p, q)
x = x + alpha * p
r = r - alpha * q
z = M(r)
rho_ = mnp.dot(r, z)
beta = rho_ / rho
p = z + beta * p
rho = rho_
k += 1
return x, F.select(_norm(r) > atol_, k, _INT_ZERO)
class CG(nn.Cell): class CG(nn.Cell):
"""Figure 2.5 from Barrett R, et al. 'Templates for the sulution of linear systems: """Figure 2.5 from Barrett R, et al. 'Templates for the sulution of linear systems:
building blocks for iterative methods', 1994, pg. 12-14 building blocks for iterative methods', 1994, pg. 12-14
@ -339,34 +388,9 @@ class CG(nn.Cell):
self.M = M self.M = M
def construct(self, b, x0, tol, atol, maxiter): def construct(self, b, x0, tol, atol, maxiter):
# Constant tensor which avoids loop unrolling
_INT_ZERO = _to_tensor(0)
A = _normalize_matvec(self.A) A = _normalize_matvec(self.A)
M = _normalize_matvec(self.M) M = _normalize_matvec(self.M)
return _cg(A, b, x0, tol, atol, maxiter, M)
atol_ = mnp.maximum(atol, tol * _norm(b))
r = b - A(x0)
z = p = M(r)
rho = mnp.dot(r, z)
k = _INT_ZERO
x = x0
while k < maxiter and _norm(r) > atol_:
q = A(p)
alpha = rho / mnp.dot(p, q)
x = x + alpha * p
r = r - alpha * q
z = M(r)
rho_ = mnp.dot(r, z)
beta = rho_ / rho
p = z + beta * p
rho = rho_
k += 1
return x, F.select(_norm(r) > atol_, k, _INT_ZERO)
class CGv2(nn.Cell): class CGv2(nn.Cell):
@ -378,34 +402,9 @@ class CGv2(nn.Cell):
super(CGv2, self).__init__() super(CGv2, self).__init__()
def construct(self, A, b, x0, tol, atol, maxiter, M): def construct(self, A, b, x0, tol, atol, maxiter, M):
# Constant tensor which avoids loop unrolling
_INT_ZERO = _to_tensor(0)
A = _normalize_matvec(A) A = _normalize_matvec(A)
M = _normalize_matvec(M) M = _normalize_matvec(M)
return _cg(A, b, x0, tol, atol, maxiter, M)
atol_ = mnp.maximum(atol, tol * _norm(b))
r = b - A(x0)
z = p = M(r)
rho = mnp.dot(r, z)
k = _INT_ZERO
x = x0
while k < maxiter and _norm(r) > atol_:
q = A(p)
alpha = rho / mnp.dot(p, q)
x = x + alpha * p
r = r - alpha * q
z = M(r)
rho_ = mnp.dot(r, z)
beta = rho_ / rho
p = z + beta * p
rho = rho_
k += 1
return x, F.select(_norm(r) > atol_, k, _INT_ZERO)
def bprop(self, A, b, x0, tol, atol, maxiter, M, out, dout): def bprop(self, A, b, x0, tol, atol, maxiter, M, out, dout):
""" """

View File

@ -432,12 +432,103 @@ def test_gmres_against_graph_scipy(n, tensor_type, dtype, error, preconditioner,
ms_output, _ = msp.sparse.linalg.gmres(a, b, x0, tol=tol, restart=restart, maxiter=maxiter, ms_output, _ = msp.sparse.linalg.gmres(a, b, x0, tol=tol, restart=restart, maxiter=maxiter,
M=m, atol=atol) M=m, atol=atol)
assert onp.allclose(scipy_output, ms_output.asnumpy(), rtol=error, atol=error) assert onp.allclose(scipy_output, ms_output.asnumpy(), rtol=error, atol=error)
# With in graph's construct # With in graph's construct
ms_net_output, _ = TestNet(solve_method)(a, b, x0, tol, restart, maxiter, m, atol) ms_net_output, _ = TestNet(solve_method)(a, b, x0, tol, restart, maxiter, m, atol)
assert onp.allclose(scipy_output, ms_net_output.asnumpy(), rtol=error, atol=error) assert onp.allclose(scipy_output, ms_net_output.asnumpy(), rtol=error, atol=error)
@pytest.mark.level0
@pytest.mark.platform_x86_cpu
@pytest.mark.platform_x86_gpu_training
@pytest.mark.env_onecard
@pytest.mark.parametrize('tensor_type, dtype, error', [('Tensor', onp.float64, 1e-5), ('Tensor', onp.float32, 1e-4),
('CSRTensor', onp.float32, 1e-4)])
@pytest.mark.parametrize('preconditioner', ['identity', 'exact', 'random'])
@pytest.mark.parametrize('solve_method', ['incremental', 'batched'])
@pytest.mark.parametrize('a, b, grad_a, grad_b', [
([[1.96822833, 0.82204467, 1.03749232, 0.88915326, 0.44986806, 1.11167143],
[0.82204467, 2.25216591, 1.40235719, 0.70838919, 0.81377919, 1.06000368],
[1.03749232, 1.40235719, 2.90618746, 0.7126087, 0.81029544, 1.28673025],
[0.88915326, 0.70838919, 0.7126087, 2.17515263, 0.40443765, 1.02082996],
[0.44986806, 0.81377919, 0.81029544, 0.40443765, 1.60570668, 0.62292701],
[1.11167143, 1.06000368, 1.28673025, 1.02082996, 0.62292701, 2.30795277]],
[0.79363745, 0.58000418, 0.1622986, 0.70075235, 0.96455108, 0.50000836],
[[-0.07867674, -0.01521201, 0.06394698, -0.03854052, -0.13523701, 0.01326866],
[-0.03508505, -0.00678363, 0.02851647, -0.01718673, -0.06030749, 0.00591702],
[-0.00586019, -0.00113306, 0.00476305, -0.00287067, -0.01007304, 0.00098831],
[-0.07704304, -0.01489613, 0.06261914, -0.03774023, -0.13242886, 0.01299314],
[-0.14497008, -0.02802971, 0.11782896, -0.07101491, -0.24918826, 0.02444888],
[-0.01868565, -0.00361284, 0.01518735, -0.00915334, -0.03211867, 0.00315129]],
[0.22853142, 0.10191113, 0.01702201, 0.22378603, 0.42109291, 0.054276]),
([[1.85910724, 0.73233206, 0.65960803, 1.03821349, 0.55277616],
[0.73233206, 1.69548841, 0.59992146, 1.01518264, 0.50824059],
[0.65960803, 0.59992146, 1.98169091, 1.45565213, 0.47901749],
[1.03821349, 1.01518264, 1.45565213, 3.3133049, 0.75598147],
[0.55277616, 0.50824059, 0.47901749, 0.75598147, 1.46831254]],
[0.59674531, 0.226012, 0.10694568, 0.22030621, 0.34982629],
[[-0.07498642, 0.00167461, 0.01353184, 0.01008293, -0.03770084],
[-0.09940184, 0.00221986, 0.01793778, 0.01336592, -0.04997616],
[-0.09572781, 0.00213781, 0.01727477, 0.01287189, -0.04812897],
[0.03135044, -0.00070012, -0.00565741, -0.00421549, 0.01576203],
[-0.14053766, 0.00313851, 0.02536103, 0.01889718, -0.07065797]],
[0.23398106, 0.31016481, 0.29870068, -0.09782316, 0.43852141]),
])
def test_gmres_grad(tensor_type, dtype, error, preconditioner, solve_method, a, b, grad_a, grad_b):
"""
Feature: ALL TO ALL
Description: test cases for gmres grad [N x N] X [N X 1]
Expectation: the result match jax grad
"""
if not _is_valid_platform(tensor_type):
return
# Input CSRTensor of gmres grad in mindspore graph or pynative mode is not supported, just ignored it.
# Root cause: CSRTensor has no distribute function of T.
if tensor_type == "CSRTensor":
return
# Gmres grad in construct
class GmresGradNet(nn.Cell):
def __init__(self, solve_method):
super(GmresGradNet, self).__init__()
self.sum = ops.ReduceSum()
self.gmres = msp.sparse.linalg.gmres
self.solve_method = solve_method
def construct(self, a, b, x0, tol, m, atol):
# For restart && maxiter args, we maintain default values to ensure gmres can coverage.
x, _ = self.gmres(a, b, x0, tol=tol, M=m, atol=atol,
solve_method=self.solve_method)
return self.sum(x)
gmres_grad_net = ops.GradOperation(get_all=True)(GmresGradNet(solve_method))
tol = float(onp.finfo(dtype=dtype).eps)
atol = tol
a = onp.array(a, dtype=dtype)
b = onp.array(b, dtype=dtype)
x0 = onp.zeros_like(b).astype(dtype)
m = _fetch_preconditioner(preconditioner, a)
expect_grad_a = grad_a
expect_grad_b = grad_b
a = to_tensor((a, tensor_type))
b = Tensor(b)
x0 = Tensor(x0)
m = to_tensor((m, tensor_type)) if m is not None else m
# PyNative Mode
context.set_context(mode=context.PYNATIVE_MODE)
grad_a, grad_b = gmres_grad_net(a, b, x0, tol, m, atol)[:2]
assert onp.allclose(expect_grad_a, to_ndarray(grad_a), rtol=error, atol=error)
assert onp.allclose(expect_grad_b, to_ndarray(grad_b), rtol=error, atol=error)
# Graph Mode
context.set_context(mode=context.GRAPH_MODE)
grad_a, grad_b = gmres_grad_net(a, b, x0, tol, m, atol)[:2]
assert onp.allclose(expect_grad_a, to_ndarray(grad_a), rtol=error, atol=error)
assert onp.allclose(expect_grad_b, to_ndarray(grad_b), rtol=error, atol=error)
@pytest.mark.level0 @pytest.mark.level0
@pytest.mark.platform_x86_gpu_training @pytest.mark.platform_x86_gpu_training
@pytest.mark.platform_x86_cpu @pytest.mark.platform_x86_cpu