Problem
The direct methods factor A once and then solve by substitution. For an n × n dense matrix that costs about n³/3 operations and n² numbers of memory. At n = 10⁵ that is 3 × 10¹⁴ operations and 80 GB in float64, and machine learning meets far bigger systems: the Hessian of a model with 10⁸ parameters has 10¹⁶ entries, so it can never be stored, let alone factored. Yet second-order optimizers, Gaussian processes, ridge regression on sparse text features and spectral methods all need to solve Ax = b or find a few eigenvectors of A.
What these matrices do allow is cheap products. A sparse matrix with a few nonzeros per row multiplies a vector in O(nnz) time. XᵀX + αI multiplies a vector as Xᵀ(Xv) + αv without ever being formed. And automatic differentiation computes a Hessian-vector product Hv for about the cost of two gradients. An iterative solver is an algorithm that needs nothing else.
Intuition
For a symmetric positive-definite A, solving Ax = b is the same as minimizing the quadratic bowl f(x) = ½xᵀAx − bᵀx, because its gradient is Ax − b. The residual r = b − Ax is the negative gradient, so the obvious method is gradient descent with the best step length along r. That is steepest descent, and on a stretched bowl it zig-zags: each exact line search ends where the new gradient is perpendicular to the old one, so it keeps undoing part of its previous progress.
Conjugate gradient (CG) fixes this by choosing each new direction so that minimizing along it never spoils the minimization already done along the earlier ones. The directions are not perpendicular but A-conjugate, piᵀApj = 0: perpendicular after the bowl is stretched back into a round one. In n dimensions there are only n such directions, so CG reaches the exact answer in at most n steps, and usually is close long before.
Both runs computed by steepest_descent and conjugate_gradient in the
page's helpers. The first step is identical, since CG's first direction is the residual. After
that, steepest descent turns 90° every step, while CG's second direction p₂ satisfies
p₁ᵀAp₂ = 9e-16, zero up to rounding.
Mechanics
Matrix-free: only A·v
Every method on this page touches A through one operation, v ↦ Av, plus a handful of vector
additions and dot products. Each iteration costs one product and O(n) extra work, and the
memory is a few vectors of length n. Libraries make this explicit:
SciPy's LinearOperator, which scikit-learn wraps around XᵀX + αI, and a closure
over autograd for a Hessian. How fast the method converges depends on A's eigenvalues, measured
by the condition number κ = λmax/λmin.
Steepest descent
From xk with residual rk = b − Axk, step along rk by the α that minimizes f on that line: αk = rkᵀrk / rkᵀArk. Then xk+1 = xk + αkrk and rk+1 = rk − αkArk, reusing the one product. The exact line search makes consecutive residuals orthogonal, which is the zig-zag. The error, measured in the A-norm ‖e‖A = √(eᵀAe), shrinks by at least a factor (κ − 1)/(κ + 1) per step. At κ = 100 that is 0.98: about 115 steps per factor of 10.
Conjugate gradient
CG (Hestenes and Stiefel, 1952) keeps the residual but builds each search direction from it and the previous direction:
- r₀ = b − Ax₀, p₀ = r₀.
- αk = rkᵀrk / pkᵀApk; xk+1 = xk + αkpk; rk+1 = rk − αkApk.
- βk = rk+1ᵀrk+1 / rkᵀrk; pk+1 = rk+1 + βkpk.
That short recurrence is enough to make every new direction A-conjugate to all earlier ones, not just the last, because A is symmetric. Two consequences follow. After k steps, xk is the point that minimizes ‖x − x*‖A over everything reachable from x₀ in the Krylov subspace span{r₀, Ar₀, A²r₀, …, Ak−1r₀}, the best answer any method could build from k products. And in exact arithmetic CG terminates in at most n steps, or in as many steps as A has distinct eigenvalues. The general error bound is
‖xk − x*‖A ≤ 2 ((√κ − 1)/(√κ + 1))k ‖x₀ − x*‖A,
so the number of steps grows with √κ instead of κ: at κ = 100 the factor is 9/11 ≈ 0.82, about 12 steps per factor of 10. In floating point the conjugacy slowly decays and CG can need more than n steps, but it still converges, and in practice it is stopped long before n.
Preconditioning
If κ is the problem, change the system. With a matrix M ≈ A that is cheap to invert, CG on M−1A converges at the rate set by κ(M−1A). The algorithm changes in one line: each new residual is passed through z = M−1r before it is used to build the direction. The simplest choice is Jacobi, M = diag(A), which fixes badly scaled variables for free. Stronger ones are incomplete Cholesky (Cholesky that keeps A's sparsity pattern), multigrid, and in optimization the diagonal or Kronecker-factored curvature estimates that Adam, Shampoo and K-FAC maintain. A good preconditioner is the main lever: it can turn thousands of iterations into tens.
The Krylov family
CG is one member of a family that all search the same Krylov subspace and differ in what matrix they accept and what they minimize:
- MINRES minimizes ‖r‖ for symmetric matrices that are indefinite, where CG's bowl has no bottom. GMRES does the same for any square matrix, at the price of storing every direction.
- CGLS and LSQR solve least squares min ‖Ax − b‖ as CG on AᵀA without
forming it or squaring the condition number in the arithmetic; LSQR's
dampargument adds a ridge penalty. - Lanczos runs the same three-term recurrence to build an orthonormal basis in which A is tridiagonal; the eigenvalues of that small tridiagonal matrix converge to A's extreme eigenvalues within a few dozen products. It is CG's twin: CG's α and β are the entries of that tridiagonal matrix.
- LOBPCG (Knyazev, 2001) finds a block of extreme eigenpairs by minimizing
the Rayleigh quotient xᵀAx/xᵀx over the current vectors, their residuals and the previous
vectors, accepts a preconditioner, and is what
torch.lobpcgimplements.
Where they appear in ML
- Ridge regression. scikit-learn's
Ridge(solver="sparse_cg")wraps the product v ↦ Xᵀ(Xv) + αv in aLinearOperatorand hands it to SciPy's CG (_solve_sparse_cg); with more features than samples it switches to the n_samples × n_samples kernel form XXᵀ + αI.solver="lsqr"calls LSQR with damp = √α (_solve_lsqr). Both keep a sparse X sparse. - Newton-CG.
LogisticRegression(solver="newton-cg")computes each Newton step by solving H s = −g approximately with an inner CG over Hessian-vector products (_cg), stopping when the residual's 1-norm drops below min(0.5, √‖g‖₁)·‖g‖₁ and bailing out on zero or negative curvature, then line-searches along s (_newton_cg). This is truncated Newton: loose solves far from the optimum, tighter ones near it. - Hessian-free optimization. The same idea for neural networks (Martens,
2010): Hessian- or Gauss-Newton-vector products from autograd, a damped CG inner loop, no
Hessian ever stored. PyTorch exposes the product as
autograd.functional.hvpand its transposevhp. Influence functions and implicit differentiation solve Hx = v the same way. - Gaussian processes. Exact GP inference needs (K + σ²I)⁻¹y and log det. GPyTorch replaces the Cholesky with batched, preconditioned CG plus Lanczos for the log-determinant, which turns O(n³) into products a GPU does well (Gardner et al., 2018).
- Spectral methods. Spectral clustering, Laplacian eigenmaps and the top
Hessian eigenvalues used to study sharpness all need only a few extreme eigenpairs:
torch.lobpcgcomputes the k largest or smallest of a dense or sparse symmetric matrix.
| Component | Shape/Params | Meaning | Source |
|---|---|---|---|
_cg |
fhess_p callable, fgrad [n], maxiter, tol |
CG on H x = −g using only Hessian-vector products; stops on sum |r| ≤ tol or curvature ≤ 16·eps·‖p‖² (capped CG), falling back to the steepest-descent step if the first direction has negative curvature. | optimize.py |
_newton_cg |
grad_hess, func, grad, x0, tol=1e-4, maxiter=100, maxinner=200 |
Truncated Newton: an inner _cg solve with tolerance min(0.5, √‖g‖₁)·‖g‖₁, then a Wolfe line search. |
optimize.py |
_solve_sparse_cg |
X [n_samples, n_features], alpha, tol=1e-4 |
Ridge by CG on (XᵀX + αI)w = Xᵀy, or on the kernel form when n_features > n_samples; X is never densified. | _ridge.py |
_solve_lsqr |
X, alpha, damp = √α, atol = btol = tol |
Ridge as damped least squares with SciPy's LSQR; returns the iteration counts too. | _ridge.py |
torch.lobpcg |
A [*, m, m], k, B, X [*, m, n], iK (preconditioner), largest=True, method="ortho" |
k extreme eigenpairs of a symmetric (generalized) eigenproblem; needs m ≥ 3n; default tol = √(float eps), niter = 1000. | _lobpcg.py |
autograd.functional.hvp |
func, inputs, v → (func(inputs), H·v) |
Hessian-vector product by double backward, without forming H; the docstring notes vhp is faster and equal for twice-differentiable functions. |
functional.py |
The dashed line is the 10⁻⁸ target. CG's residual is not monotone, since CG minimizes the error in the A-norm, not ‖r‖; it can rise for a few steps and still be making progress.
Worked example
Two steps of CG by hand
Solve Ax = b with A = [[3, 2], [2, 6]], b = [2, -8], from x₀ = [-2, -2]. A's eigenvalues are 2 and 7, so κ = 3.5.
- r₀ = b − Ax₀ = [2, -8] − [−10, −16] = [12, 8], and p₀ = r₀.
- Ap₀ = [52, 72], r₀ᵀr₀ = 208 and p₀ᵀAp₀ = 12·52 + 8·72 = 1,200, so α₀ = 208/1,200 = 0.1733, giving x₁ = [0.08, -0.6133]. Steepest descent takes exactly this step too.
- r₁ = r₀ − α₀Ap₀ = [12 − 9.013, 8 − 12.48] ≈ [2.987, −4.48], so β₀ = r₁ᵀr₁ / r₀ᵀr₀ ≈ 28.99/208 ≈ 0.139 and p₁ = r₁ + β₀p₀ ≈ [4.659, −3.365].
- α₁ = r₁ᵀr₁ / p₁ᵀAp₁ ≈ 28.99/70.35 = 0.4121 gives x₂ = [2, -2]: the exact solution, after two products with A, as CG promises for a 2 × 2 system.
Steepest descent from the same start needs 10 steps to bring ‖r‖ below 0.1% of ‖r₀‖. Its rate bound here is (κ − 1)/(κ + 1) = 0.5556 per step.
A badly scaled 40 × 40 system
Take the tridiagonal matrix T with 3 on the diagonal and −1 beside it, and rescale its rows and columns by s = 1, 8, 4, 11, 7, 3, 10, 6, … (sᵢ = 1 + (7i mod 11)), so Aij = siTijsj. This is what unnormalized features do to XᵀX. Solve Ax = 1 from x₀ = 0 to a relative residual of 10⁻⁸:
| Method | κ that governs it | Bound on steps | Actual steps |
|---|---|---|---|
| Steepest descent | 177.7 | 1,637 | 1,460 |
| Conjugate gradient | 177.7 | 128 (and ≤ 40 in exact arithmetic) | 30 |
| CG with Jacobi, M = diag(A) | 4.96 | 20 | 20 |
| LU, for comparison | — | — | one factorization, ≈ 40³/3 ≈ 21,000 multiply-adds |
Steepest descent lands close to its κ bound. CG beats its √κ bound by a factor of four, because the bound assumes the worst spread of eigenvalues and this matrix's are clustered. Jacobi scaling undoes the si exactly, leaving D−½AD−½ with κ ≈ 4.96, essentially T's own, and CG then needs 20 products of a matrix with three nonzeros per row. The preconditioned answer agrees with LU's to 4e-10.
Implementation
def conjugate_gradient(matvec, b, x, tol=1e-8, max_steps=1000, inv_diag=None):
dot = lambda u, v: sum(p * q for p, q in zip(u, v))
r = [bi - ai for bi, ai in zip(b, matvec(x))]
z = r if inv_diag is None else [m * ri for m, ri in zip(inv_diag, r)]
p, rz, r0 = list(z), dot(r, z), dot(r, r) ** 0.5
for _ in range(max_steps):
if dot(r, r) ** 0.5 <= tol * r0:
break
ap = matvec(p) # the only use of A
alpha = rz / dot(p, ap)
x = [xi + alpha * pi for xi, pi in zip(x, p)]
r = [ri - alpha * api for ri, api in zip(r, ap)]
z = r if inv_diag is None else [m * ri for m, ri in zip(inv_diag, r)]
rz, rz_old = dot(r, z), rz
p = [zi + (rz / rz_old) * pi for zi, pi in zip(z, p)]
return x
A = [[3., 2.], [2., 6.]]
conjugate_gradient(lambda v: [sum(a * x for a, x in zip(row, v)) for row in A], [2., -8.], [-2., -2.])
# [2.0, -2.0] after two steps
Matrix-free with PyTorch: a Newton step on a loss, using Hessian-vector products from autograd.
import torch
from torch.autograd.functional import hvp
def loss(w):
return torch.nn.functional.softplus(-y * (X @ w)).mean() + 1e-3 * w.square().sum()
w = torch.zeros(X.shape[1], dtype=torch.float64)
g = torch.func.grad(loss)(w)
step = conjugate_gradient(lambda v: hvp(loss, w, torch.tensor(v, dtype=w.dtype))[1].tolist(),
(-g).tolist(), [0.0] * len(w), tol=1e-4)
# extreme eigenpairs of a large symmetric matrix, dense or sparse
E, V = torch.lobpcg(S, k=4, largest=True) # S: [m, m] with m >= 3 * 4
# the same solvers in scikit-learn
from sklearn.linear_model import LogisticRegression, Ridge
Ridge(alpha=1.0, solver="sparse_cg").fit(X_sparse, y) # CG on X^T X + alpha I, never formed
LogisticRegression(solver="newton-cg").fit(X, y) # inner CG on Hessian-vector products
Tradeoffs
- Direct or iterative. Below a few thousand unknowns with a dense matrix, a Cholesky or LU factorization is faster, exact and reusable for many right-hand sides. Iterative methods win when the matrix is sparse, structured or implicit, when an approximate answer is enough, or when memory rules out n².
- Convergence depends on the spectrum. CG is fast when κ is small or the eigenvalues are clustered, and slow otherwise; there is no fixed cost. Budget for a preconditioner before tuning anything else.
- Symmetric positive-definite only. CG can fail silently on an indefinite or non-symmetric A: pᵀAp ≤ 0 breaks its step. Use MINRES or GMRES there, and in optimization treat negative curvature as a signal, as scikit-learn's capped CG does.
- Floating point. Conjugacy decays with rounding, so CG may need more than n steps and Lanczos produces spurious copies of converged eigenvalues unless it re-orthogonalizes. Low precision makes both worse; these solvers usually run in float32 or float64.
- Stopping rules. The residual is not the error: a small ‖r‖ can hide a large error when κ is large. Inside an outer optimizer, solve loosely early and tightly late (truncated Newton), since accuracy beyond the outer step's needs is wasted.
Check yourself
A 10⁶ × 10⁶ sparse SPD matrix has 5 nonzeros per row and κ = 10⁴. Roughly how much work is CG to 10⁻⁶?
The √κ bound gives about ½·100·ln(2·10⁶) ≈ 730 iterations, each one product of 5·10⁶ multiply-adds plus a few vector operations, so a few times 10⁹ operations in all. A dense Cholesky would be 3·10¹⁷ and would not fit in memory.
Why does CG solve any 2 × 2 SPD system in at most two steps, while steepest descent may need many?
CG's two directions are A-conjugate, so minimizing along the second never undoes the first: after two steps it has minimized f over the whole plane. Steepest descent only ever minimizes along one direction at a time, and on a stretched bowl each step partly undoes the last.
Newton-CG on a non-convex loss hits pᵀHp < 0 inside its inner loop. What should it do?
Stop the inner CG: the quadratic model has no minimum along p. Return the step built so far, or, at the first iteration, the steepest-descent direction, which is what scikit-learn's _cg does. Trust-region variants instead move along p to the boundary.
A design matrix's columns are on very different scales. Which preconditioner is free, and what does it correspond to in data terms?
Jacobi, dividing by the diagonal of XᵀX, which is each column's squared norm. It is the solver-side version of standardizing the features.
Connections
Read first
Read next
No page builds on this one yet.
References
- Hestenes and Stiefel. Methods of Conjugate Gradients for Solving Linear Systems. Journal of Research of the National Bureau of Standards 49(6), 409–436, 1952.
- Shewchuk. An Introduction to the Conjugate Gradient Method Without the Agonizing Pain. Carnegie Mellon University technical report CMU-CS-94-125, 1994. The 2 × 2 example above is its running example.
- Saad. Iterative Methods for Sparse Linear Systems, 2nd ed. SIAM, 2003: chapters 6 (Krylov subspaces, CG, GMRES) and 9–10 (preconditioning).
- Paige and Saunders. LSQR: An Algorithm for Sparse Linear Equations and Sparse Least Squares. ACM Transactions on Mathematical Software 8(1), 43–71, 1982.
- Knyazev. Toward the Optimal Preconditioned Eigensolver: Locally Optimal Block Preconditioned Conjugate Gradient Method. SIAM Journal on Scientific Computing 23(2), 517–541, 2001.
- Pearlmutter. Fast Exact Multiplication by the Hessian. Neural Computation 6(1), 147–160, 1994.
- Martens. Deep Learning via Hessian-Free Optimization. ICML, 2010.
- Gardner, Pleiss, Bindel, Weinberger and Wilson. GPyTorch: Blackbox Matrix-Matrix Gaussian Process Inference with GPU Acceleration. NeurIPS, 2018.
- Royer, O'Neill and Wright. A Newton-CG Algorithm with Complexity Guarantees for Smooth Unconstrained Optimization. 2018 (arXiv 1803.02924), the capped CG cited in scikit-learn's
_cg. - scikit-learn 1.9.1:
_cg,_newton_cg,_solve_sparse_cg,_solve_lsqr. - PyTorch v2.15.0:
torch.lobpcg,_lobpcg,autograd.functional.hvp,vhp.