Result#
A result holds everything ExactRef works out for one call: the exact answer rounded to the output format, certified enclosures of the exact answer, and the bounds that say which outputs a correct implementation can produce.
Every operation returns an er.Result. Results
walks through it with examples; this page lists every method.
At a glance#
Attribute or method |
What it gives |
|---|---|
|
the exact answer, rounded once to the output format |
|
certified enclosures of the exact answer |
|
|
|
per element, how far a correct implementation's finite output can be from |
|
the lowest and highest finite output a correct implementation can produce |
|
where a correct implementation may output |
|
per element, whether a correct implementation can produce |
|
the output of normal accumulation in index order, emulated exactly |
|
the operation's backward on the same inputs |
|
a result for one of several outputs |
|
the same result in another layout |
|
the operation's required rounding steps |
|
the operation's name |
tolerance(), finite_range(), may_be() and covers() take the same
options: compute, ulps, flush_to_zero and standard.
Full specification#
- class Result#
What an operation definition returns.
rounded: the exact answer rounded once to the output format.exact: the exact answer as certified enclosures (ExactArray).tolerance(): how far normal accumulation's finite output can be fromroundedon these inputs, a proven bound;tolerance(standard="any_order")covers every accumulation order that does not overflow.finite_range(),may_be()andcovers(): the lowest and highest finite output, the infinities and NaN an implementation may produce, and the judgment of an implementation's output.normal(): the output of normal accumulation in index order on these inputs, computed when asked for.
For one output these are the arrays themselves; several outputs use PyTorch's names, for example
result.rounded.Q, andresult["Q"]is a result holding just that output.guaranteestates whatroundedsatisfies, andboundarieslists the operation's required rounding boundaries.backward(grad)runs the operation's backward from the same inputs.A result with one output can be passed in place of an array to any operation, backward or algorithm. The implementation under test stored that output, so each stored value is one this result's bounds allow: within its tolerance of
rounded(part by part for complex values), or an infinity or NaN thatmay_be()allows. The later result'sroundedandexactare the exact answer onrounded, rounded once; its bounds cover every value these bounds allow, taken with the same arguments: an infinite radius leaves no bound in the outputs it reaches, and a stored infinity or NaN passes on as their flags (proofs/foundations.md, Results as inputs); itsnormal()works on thisnormal(); and an algorithm'srun()usesrounded.Operations build results from results through three internal methods, their interface to this class:
_single(the one output'srounded),_bounds_of(its bounds for tolerance arguments) and_with_bounds(this result with other bounds).- guarantee#
What
roundedsatisfies:"correctly_rounded"for floating-point and complex outputs,"exact"for integer outputs.
- op#
The operation's name, such as
"matmul"or"matmul.backward".
- tolerance(compute=None, *, ulps=None, flush_to_zero: bool = False, standard: str = 'normal')#
Worst-case
|implementation output - rounded|per element.The bound covers every implementation that loses precision only at the operation's required boundaries:
one rounding to
computeat each required rounding boundary,accumulation in
compute: withstandard="normal"(the default), normal accumulation, which adds in index order or in 2 or 4 SIMD-style lanes, rounds each product and then adds it, and starts a new partial sum where adding the next value would overflow; withstandard="any_order", any order (any summation tree, with or without fused multiply-add) that does not overflow,library functions within
ulpsunits in the last place ofcompute, and for matrix functions a backward error measured inulps(see each operation),the final rounding to the output format.
An output within the normal tolerance is no worse than normal accumulation can be. Beyond it but within the any-order tolerance, it is technically correct but worse than normal accumulation can be (for example from a poor accumulation order). Beyond the any-order tolerance, no implementation that rounds only there without overflowing could produce it. Matrix functions bound a backward error instead and give the same tolerance for both standards.
computedefaults to float32 for inputs narrower than float32 and to the input format otherwise. Withflush_to_zero, the implementation flushes subnormal values of the compute format to zero, astorch.set_flush_denormal(True)does: such a value may be read as zero, and a result that would be one may be produced as zero.The result is a float64 array rounded upward, counting only finite output values the output format can represent. For complex outputs it is complex128 with one bound per part: its real part bounds
|Re(output - rounded)|and its imaginary part|Im(output - rounded)|. A finite reference gets a finite tolerance wherever its finite outputs can be bounded;infmarks elements with no finite bound, andnanelements whose reference is not finite.finite_range()gives the lowest and highest finite output,may_be()the infinities and NaN an implementation may produce, andcovers()judges an implementation's output. Results are kept, so repeating a call with the same arguments returns at once. Each operation's bound is proven inproofs/.
- finite_range(compute=None, *, ulps=None, flush_to_zero: bool = False, standard: str = 'normal')#
The lowest and highest finite output of a correct implementation, per element, as
(low, high)arrays of the output's dtype; the arguments aretolerance()'s.They lie within
tolerance()ofrounded, and can be closer on one side. Where the reference is an infinity only because a finite exact value rounds past the largest finite value, they hold the finite outputs that can still occur. Where no finite output can occur, both are NaN, solow <= output <= highis false.
- may_be(kind: str, compute=None, *, ulps=None, flush_to_zero: bool = False, standard: str = 'normal')#
Where a correct implementation may output
kind("+inf","-inf"or"nan"), as a bool array; for complex outputs it has a last axis of 2 (real part, imaginary part). The other arguments aretolerance()'s. A reference that is not finite counts, and formats without infinity produce NaN where a value overflows. It can be true where no correct implementation overflows (an overflow check uses the error bound), never false where one can.
- covers(output, compute=None, *, ulps=None, flush_to_zero: bool = False, standard: str = 'normal')#
Where a correct implementation can produce
output, as a bool array: a finite value withinfinite_range(), or an infinity or NaN thatmay_be()allows.outputis an implementation's output (anythingasarraytakes) with this result's dtype and shape; for complex outputs the array has a last axis of 2 (real part, imaginary part).assert result.covers(output).all()checks an implementation. The other arguments aretolerance()'s; the standard, the compute format and flushing describe the implementations judged correct. A result with several outputs is checked one output at a time, asresult[name].
- normal(compute=None, *, flush_to_zero: bool = False)#
The output of normal accumulation in index order on these inputs.
Each product is rounded to
computeand added in index order, each sum rounded tocompute(to nearest, ties to even); where adding the next value would overflow, it starts a new partial sum, and the partial sums are then combined the same way. The result is rounded to the output format asroundedis.computeandflush_to_zeroare as intolerance(); each operation says what else its normal answer fixes. It is computed only when called, and kept.
- reshape(shape) Result#
This one-output result with its elements in row-major order in a new
shape.Moving elements rounds nothing:
rounded,exact,tolerance()andnormal()are this result's, moved the same way, so the view can be passed to a later operation in place of an array. A view has no backward of its own: callbackwardon this result, with the gradient reshaped back.
import exactref as er
# A 1x2 float32 matrix
A = er.asarray([[1.0, 2.0]], "float32")
# The product of A and a 2x1 float32 matrix, as a Result
r = er.matmul(A, er.asarray([[3.0], [4.0]], "float32"))
# The operation's name, and what r.rounded satisfies
print("op:", r.op)
print("guarantee:", r.guarantee)
op: matmul
guarantee: correctly_rounded
Outputs#
- class Outputs#
Several outputs of one operation, by name:
outputs.Qoroutputs["Q"].
# matmul's backward on the same inputs, for the output gradient [[1.0]]
g = r.backward(er.asarray([[1.0]], "float32"))
# g has several outputs; listing g.rounded gives their names
print("outputs:", list(g.rounded))
# One output, by attribute and by name
print("grad_A by attribute:", g.rounded.grad_A.tolist())
print("grad_A by name:", g.rounded["grad_A"].tolist())
outputs: ['grad_A', 'grad_B']
grad_A by attribute: [[3.0, 4.0]]
grad_A by name: [[3.0, 4.0]]
Boundary#
r.boundaries maps each required rounding step's name to a Boundary.
- class Boundary#
Boundary(kind: 'str', description: 'str', applies_to: 'str | None' = None)
# matmul's required rounding step for the sum of each element's products
b = r.boundaries["accumulate"]
# Its kind: "accumulate" is a sum of terms
print("kind:", b.kind)
# What the sum adds up: "dot" for products, "sum" for plain terms
print("applies to:", b.applies_to)
kind: accumulate
applies to: dot