The arithmetic underneath

Buying the accuracy back

Factorise in single precision, then correct the answer using residuals computed in double, and the result is what a full double-precision solve would have given. Compute those residuals in single instead and the identical algorithm, at identical cost, recovers nothing.

Every other essay in this field turns the precision down in order to watch something break. This one turns it down on purpose and then tries to get the accuracy back afterwards, which is what the hardware has spent fifteen years making the interesting question.

The arithmetic units that are fast are the low-precision ones, by a factor that is not small and is growing. So the question is not whether low precision loses accuracy — it does, and what a float can hold is about how much. The question is whether the accuracy can be bought back more cheaply than it cost to keep, and the answer is yes, on one side of a computable line.

Iterative refinement from a 24-bit factorisation, κ = 10⁴A semi-logarithmic plot of forward error against refinement step. One curve falls steeply to the level of a double-precision solve; the other is nearly flat.012345610⁻¹⁶10⁻¹³10⁻¹⁰10⁻⁷10⁻⁴10⁻¹refinement step‖x − x*‖ / ‖x*‖a full double-precision solveresidual in24-bitresidual indoubleone argument apartκ·u of the factorisation6·10⁻⁴double residual, final3.2·10⁻¹³same-precision, final1.3·10⁻⁴30×30, κ = 10⁴, same factors in both runsidentical cost
Fig. 1 Two runs of iterative refinement from the same single-precision factorisation. One computes its residuals in double and reaches what a full double solve reaches. The other computes them in single and is nearly flat. Drag the factorisation’s precision and watch the lower curve stop working.

The method

Factorise A once, in low precision. Solve, getting a low-precision answer. Then repeat: compute the residual r = b − Ax accurately, solve Ad = r using the factors already in hand, and add d to x.

The expensive step — the O(n³) factorisation — happens once, at low cost. The correction steps are O(n²) each and there are two or three of them. So the arithmetic is dominated by a factorisation done in the fast format, and the answer is the one the slow format would have produced.

At κ = 10⁴, from a 24-bit factorisation: the initial solve has a relative error of 9.6·10⁻⁵. After two refinement steps it is 3.2·10⁻¹³.

A full double-precision solve of the same system gives 3.1·10⁻¹³.

Refinement matched it to three per cent, from factors carrying a third of the bits.

Why the target is κu and not machine epsilon

The check for this was written wrongly first, demanding a final error below 10⁻¹³ and failing at 3.2·10⁻¹³. The failure was correct and the demand was not.

The accuracy available to any method on this system is about κ·u — the condition number times the unit roundoff — which is the site’s organising identity and is 1.1·10⁻¹² here. Asking refinement for better than that is asking for better than the problem permits, and a method that delivered it would be evidence of a bug rather than of quality.

So the assertion compares against a full double-precision solve of the same system, computed in the check. That is the right target: it is the answer the expensive route would have produced, it is subject to the same conditioning, and matching it is the strongest true claim available.

A tolerance chosen to make a check pass and a tolerance derived from what the problem permits can be the same number and are not the same assertion. This one had to be moved from the first to the second.

Why the factors can be reused at all

One step in the description passes quickly and is doing more work than it looks.

The correction solves Ad = r using the factors already computed, not a fresh factorisation. That is what makes the correction O(n²) rather than O(n³), and it is the entire economics of the method — if each step needed a new factorisation there would be no saving over factorising once in double.

It works because the factors are a good approximation to A’s factors, and a solve against approximately correct factors returns an approximately correct correction. The correction only has to be accurate in its leading digits, because it is small: an error of 10% in a correction of size 10⁻⁵ leaves a residual of 10⁻⁶, which the next step handles.

That tolerance for inaccuracy is why low-precision factors are usable and why the method degrades gracefully as the factorisation precision falls — right up to the threshold, where the correction’s relative error passes 100% and it stops being a correction at all.

It also means the same factors serve every right-hand side, which is the property that makes refinement attractive in a code solving the same system repeatedly: one low-precision factorisation, amortised over thousands of solves, each refined to full accuracy in two O(n²) steps.

The pair

Now the part this site exists for.

Two runs. Same matrix, same right-hand side, the same factorisation object reused, the same number of steps, the same arithmetic operations in the same order. One argument differs: the precision the residual is computed in.

Residual computed in Final error after 6 steps
double 1.5·10⁻¹³
single 8.8·10⁻⁵

Eight orders of magnitude, from one argument.

And the second number is not merely worse. The single-precision-residual run improves by a total factor of 1.51 across all six steps — it starts at 8.8·10⁻⁵ and ends at 8.8·10⁻⁵. It is not converging slowly. It is doing nothing, at exactly the cost of the version that works.

The build asserts that: the ineffective run’s total gain must be under 30×, which is the assertion that distinguishes inert from slow.

Why the residual is the whole method

The mechanism is short and it explains the entire table.

The correction d is meant to be the error in x. Computing it requires knowing r = b − Ax accurately — and r is a difference of nearly equal quantities, because x is nearly right. That is cancellation, and cancellation takes the answer is the essay for what it costs: the leading digits agree and are subtracted away, and what remains is built from the digits that were least reliable.

If x is accurate to eight digits and the residual is computed in a format carrying eight digits, then every digit that survives the cancellation is noise. The correction is a random vector of the right magnitude. Adding it changes the answer and does not improve it.

Computing the residual in double gives sixteen digits, eight of which survive the cancellation intact, and the correction is accurate to eight digits — which is enough, because the correction is small and only its leading digits matter.

The precision of the factorisation sets how big the error is. The precision of the residual sets whether the error can be seen. The first is what everybody thinks the method is about.

Relative error of two algebraically identical expressions for (1 − cos x)/x²A log–log plot of relative error against x. The expression written as it reads loses accuracy below x = 10⁻⁴ and is entirely wrong by 10⁻⁸; the rearranged form stays at rounding level.10⁻¹²10⁻¹⁰10⁻⁸10⁻⁶10⁻⁴10⁻²110⁻¹⁷10⁻¹⁴10⁻¹¹10⁻⁸10⁻⁵10⁻²10¹xrelative error of the computed value(1 − cos x)/x², as written2 sin²(x/2)/x²no digits left at alldouble precision throughoutone function, two spellings
Fig. 2 The mechanism, from the arithmetic field. Subtracting two nearly equal numbers destroys the leading digits and promotes the trailing ones, which were the least reliable. A residual is that subtraction, performed deliberately, on quantities chosen to be nearly equal.

Why it converges in two steps and not twenty

The speed of the recovery is worth its own note, because it is what makes the method a bargain rather than a trade.

Each refinement step roughly squares the accuracy, until it hits the working precision and stops. A single-precision solve is accurate to about 10⁻⁵ here; one step takes it to something like 10⁻⁹, and the second to 10⁻¹³, at which point the working precision is exhausted and further steps change nothing.

So the count is not “iterate until converged” in the sense the iterative field uses. It is two or three steps, decided in advance by how many decades separate the low precision from the high one, and the site’s assertion is written that way: convergence must happen within three steps, not eventually.

That is the quadratic behaviour of a Newton-type correction, and it is the same shape as verify-kit’s newton — a residual, a solve against an approximate Jacobian, an update. Iterative refinement is Newton’s method applied to a linear system, with the LU factors standing in for the Jacobian. The factors are inexact, which is exactly the situation in which Newton’s method still converges quadratically provided the inexactness is small enough — and “small enough” is κ·u_low below 1, which is the threshold.

The three precisions, which are really three

The essay has been describing two precisions and there are three, which matters once the hardware formats arrive.

The factorisation precision is what the O(n³) work runs in, and it is the one chosen for speed.

The residual precision must exceed it, and is what this essay is about.

The working precision is what x is stored and updated in, and it sets the best answer available. It is usually the same as the residual precision, and it need not be — there are schemes with three distinct formats, factorising in half, working in single, and computing residuals in double, which is the arrangement current GPU libraries actually use.

The site’s refine exposes the first two as separate arguments precisely so that setting them equal is one call away and can be run rather than argued about. The inert run in the table above is that call.

The threshold depends on the factorisation precision alone. The attainable accuracy depends on the working precision alone. Those are different parameters governing different things, and running them together as “the precision” is what makes the technique sound like a trade when it is closer to a free lunch on one side of a line.

The threshold

Refinement works while κ·u_low is below 1, and fails past it. The reason is direct: the factorisation’s error acts as a perturbation of size u_low, the correction step solves with those same bad factors, and when κ times that perturbation exceeds 1 the correction is not a correction.

The slider crosses it. At κ = 10⁴, a 24-bit factorisation gives κ·u = 6·10⁻⁴ and refinement works completely. A 14-bit factorisation gives κ·u ≈ 0.6 and it is marginal. A 10-bit factorisation gives 9.8, and the method does not merely stop improving — the iteration walks away from the answer, ending at 2.4·10⁴ from a starting point of 6.5.

That is asserted on the frames where it applies, and the two claims are made conditionally: below the threshold refinement recovers, above it, it does not reach working precision. Asserting recovery at every slider position would mean asserting something false over the left third of the range, and the figure would be drawing a method that fails while claiming it works.

What is asserted here

Both runs start from the same solve, byte-identical, because they share a factorisation — which is what makes the comparison a comparison of one argument.

Below the threshold, a double residual recovers the accuracy and a same-precision one does not, separated by at least five decades.

And reaches what a double solve reaches, checked against one computed in the same figure.

Above the threshold, refinement does not reach working precision — the honest half.

And the ineffective run stops improving almost at once, gain under 30× across six steps.

The refusals: the claim that refinement converges at any κ must throw, and so must the claim that more steps help past the threshold. Both do.

The second refusal is the interesting one

“More steps help” is the harder of the two to disbelieve, because every other iteration on this site improves with iterating. Conjugate gradients get closer. The QR algorithm deflates. Jacobi grinds towards an answer at 0.992 per step, slowly, but towards.

Refinement past its threshold does not. Thirty steps produce no more than three, because each step is computing a correction from factors too inaccurate to supply one, and repeating a computation that carries no information carries no more information the tenth time.

The check runs both and requires the claim that thirty steps buys two more decades to fail. It fails.

That is a genuinely different failure shape from the rest of the site’s iterative content, and it is worth naming: an iteration that is not a contraction is not slow, it is stationary. The distinction is invisible from a single run, since a stationary iteration and a very slow one both show a nearly flat curve, and it takes running two lengths and comparing to tell them apart.

What refinement fixes and what it does not

A precise statement, because “improves the answer” covers two very different things and refinement does one of them.

A low-precision solve is backward stable in low precision: it returns the exact answer to a problem within u_low of the one posed. Its forward error is therefore about κ·u_low, and it is large not because the algorithm misbehaved but because κ amplified a perturbation the arithmetic could not avoid — which is the site’s spine and is the exact answer to a nearby problem’s subject.

Refinement moves the backward error down to the working precision. The answer then has forward error κ·u_working rather than κ·u_low, and both factors of κ are still there.

So refinement recovers what the low precision cost and nothing else. It does not improve conditioning, does not rescue an ill-posed problem, and does not give an answer better than the working precision permits — which is exactly why the assertion targets a double solve rather than machine epsilon.

There is a variant that does more, and it is worth knowing that it exists. Refinement with the residual computed in extended precision — higher than the working precision — can produce a forward error at the level of the working precision itself rather than κ·u_working, on problems where κ·u is below 1. That is a genuinely stronger result: it makes the answer as good as the storage allows rather than as good as the conditioning allows, and it is why the LAPACK expert drivers can report componentwise error bounds that look better than the norm-wise analysis suggests.

Where this is actually used

Not as an exotic technique. Iterative refinement is inside things people use without knowing.

LAPACK’s expert drivers offer it as an option and use it to produce the error bounds they report.

Sparse direct solvers using static pivoting deliberately perturb a pivot that is too small — which would otherwise force a reordering and destroy the sparsity plan that the order decides the memory is about — and then repair the damage with refinement. There, refinement is buying back accuracy lost to a structural decision rather than to a precision one, and the mechanism is identical.

And GPU linear algebra, where the whole argument is the hardware one: factorise in the format the tensor cores are fast at, refine in the format the answer needs.

The third is why this is a current subject rather than a historical one, and it is where the hardware went’s subject — because the formats on offer have eight and eleven mantissa bits rather than twenty-four, and the threshold moves accordingly.

The general lesson

Two sentences, and both transfer past this subject.

Where a quantity is computed can matter more than how it is computed. The residual in this essay is one subtraction. The algorithm around it is thirty lines. Everything that decides whether the method works is in the format of that one subtraction, and no amount of care applied to the surrounding thirty lines substitutes for it.

And a computation that carries no information does not become informative by being repeated. The inert run performs the same operations as the working one, in the same order, six times, and gains a factor of 1.5. That is the shape of every iteration built on a measurement too coarse to see what it is measuring, and the diagnostic is the one the check uses: run it twice as long and see whether anything changes.

Forward error of a 6×6 Hilbert solve at eight precisionsA bar for each significand width from 12 to 53 bits showing the relative error in the computed solution, with the condition number times the unit roundoff marked as a prediction.κ = 1.5·10⁷ · the exact answer is (1, 2, …, 6)12 bits3.316 bits0.8620 bits4.324 bits0.09330 bits7.6·10⁻⁴36 bits6.2·10⁻⁶43 bits2·10⁻⁷53 bits4.5·10⁻¹¹dashed: κ · unit roundoffone matrix, eight arithmeticsmeasured against a known answer
Fig. 3 The problem refinement is answering. Error against precision for a plain solve — every bit removed costs accuracy in proportion, and there is no way back from within the arithmetic that produced it.
How far short of a double solve refinement finishes, and where each format stopsA log-log plot of how far short of a double-precision solve iterative refinement finishes, against the condition number, for three low-precision formats. Each curve sits flat at one and then climbs steeply past a vertical mark showing that format's threshold.10¹10²10³10⁴10⁵10⁶10⁷10⁸10⁹10¹⁰110⁴10⁸10¹²10¹⁶condition number κ(A)× short of a double solvebf16fp16fp32bf16fp16fp32the reference is soundreference solve, worst backward error1.1·10⁻¹⁶bfloat16 threshold κ256fp32 threshold κ1.7·10⁷eight refinement steps, residual always in doubleflat at 1 means it reached double
Fig. 4 And where the answer stops applying, for each format the hardware sells. Every curve is flat at 1 — refinement reached the double answer — until its own threshold, and then climbs.
Backward error, forward error and the condition number, with measured valuesTwo boxes at the top — the problem posed and the nearby problem the algorithm answered exactly — and two answers below them, with the distances between all four labelled by numbers from a Hilbert solve.the problem you posedA = H10b = A·(1, 2, …, 10)the problem it answered exactlyA + δA, b + δb‖δ‖ / ‖A‖ = 2.3·10⁻¹⁷the answer you wantedx = (1, 2, …, 10), exactlythe answer you gotx̂, wrong by 2.7·10⁻⁴ relativebackward error 2.3·10⁻¹⁷forward error 2.7·10⁻⁴κ = 1.6·10¹³κ · η = 3.6·10⁻⁴, and the measured forward error is 2.7·10⁻⁴.The algorithm is not at fault. The problem is.H10, LU with partial pivotingresidual and error differ
Fig. 5 What refinement is moving and what it is not. It brings the backward error down to the working precision; the condition number stands where it was, and the forward error is still their product.
Backward and forward error against the condition numberA log–log plot over twelve decades of condition number. The backward error is a flat line at ten to the minus sixteen; the forward error rises in proportion to the condition number.110²10⁴10⁶10⁸10¹⁰10¹²10¹⁴10⁻¹⁷10⁻¹⁴10⁻¹¹10⁻⁸10⁻⁵10⁻²10¹condition number κ(A)relative errorforward errorbackward errorpredicted: κ · u8×8, 20 seeds per κ; dashed is the worstthe problem worsens, not the method
Fig. 6 What refinement moves and what it leaves. It brings the backward error down to the working precision; the condition number stands where it was, so the forward error is still κ times the first.
The spacing between consecutive numbers at 24-bit precisionA log–log staircase of the gap between neighbouring representable numbers against magnitude. The gap doubles at every power of two and reaches one whole unit partway along.110³10⁶10⁹10¹²10¹⁵10¹⁸10⁻¹⁸10⁻¹⁵10⁻¹²10⁻⁹10⁻⁶10⁻³110³magnitude of the numbergap to the next representable numberat 1: 1.2·10⁻⁷at a million: 0.063gap reaches 1: 124-bit significandthe gap follows the magnitude
Fig. 7 The spacing underneath the threshold. A format’s unit roundoff is the gap between representable numbers, and κ·u passing one is that gap being amplified past the whole of the answer.
Refinement thresholds by format, against a problem at κ = 10⁴A horizontal bar chart of the condition number at which iterative refinement stops working, one bar per floating-point format, with a marked line at the condition number of the problem.bfloat16 · 8 bits1/u = 256fp16 · 11 bits1/u = 2048tf32 · 11 bits1/u = 2048fp32 · 24 bits1/u = 1.7·10⁷fp64 · 53 bits1/u = 9·10¹⁵κ·u = 39.063 — past the thresholdκ·u = 4.883 — past the thresholdκ·u = 4.883 — past the thresholdκ·u = 6·10⁻⁴ — refinement recoversκ·u = 1.1·10⁻¹² — refinement recoversthe problem is at κ = 10⁴; a format works when κ·u < 1thresholds are 1/u, a property of the arithmetic2 of 5 formats clear this κ
Fig. 8 The same ladder at this essay’s κ = 10⁴. Only fp32 clears it, which is why the pair above is drawn from a twenty-four-bit factorisation — at eleven bits κ·u is 4.9 and there is no accuracy to buy back. The threshold is 1/u exactly, checked as an identity rather than drawn as one.