Skip to content

optora.core.convergence

Convergence tracking for iterative loops without per-iteration host syncs.

ConvergenceStatus

Convergence diagnostics of a finished solve, materialized on demand.

A loop produces its convergence flag and iteration count on the iterate's device, so reporting them as a Python bool and int costs two synchronizations per solve: exactly the cost ConvergenceTracker removes from the loop, paid again once the loop is over. Under nesting that becomes the dominant cost, since every outer iteration of an optora.dro.MinimaxSolver runs an inner dual solve whose diagnostics nothing ever reads.

ConvergenceStatus therefore keeps the diagnostics as the tensors the loop computed and converts them on first access, caching each conversion so repeated reads cost one synchronization at most. A solve whose diagnostics are never read never synchronizes for them.

converged cached property

Whether the loop's convergence criterion was met, read on the host.

num_iterations cached property

Number of iterations the loop performed, read on the host.

__init__(converged, num_iterations)

Hold a finished loop's convergence state on its device.

Parameters:

Name Type Description Default
converged Tensor

Zero-dimensional boolean tensor recording whether the loop's residual fell below its tolerance.

required
num_iterations Tensor

Zero-dimensional integer tensor holding the number of iterations performed before convergence.

required

ConvergenceDiagnostics

Host-side view of the convergence diagnostics of a solver result.

Mixed into every solver result so result.converged and result.num_iterations read as plain Python values while the solve itself stays asynchronous: status holds them as device tensors until one of these properties is read.

Attributes:

Name Type Description
status ConvergenceStatus

Device-side convergence diagnostics of the solve, converted to host values only when read.

converged property

Whether the convergence criterion was met before the iteration budget.

num_iterations property

Number of iterations actually performed.

ConvergenceTracker

Convergence state of an iterative loop, held on the iterate's device.

A textbook iterative method tests residual < tol with a Python if, which forces a device-to-host copy of the residual on every iteration. Optora composes such loops (an optora.dro ambiguity set's inner dual solve runs inside a MinimaxSolver outer solve, and a Wasserstein ambiguity set additionally runs Sinkhorn iterations), so a single solve would issue thousands of accelerator stalls.

ConvergenceTracker keeps the convergence flag and the iteration count as tensors on the iterate's device, updates them with ordinary elementwise kernels, and copies them to the host at most once every check_interval iterations. Callers must freeze their iterates with torch.where(tracker.converged, ...), which makes the iterations that run between two host reads exact no-ops: the returned solution is identical to one produced by testing convergence every iteration, whatever check_interval is. Larger values trade those redundant frozen iterations for fewer synchronizations.

That trade is only favourable when an iteration is cheap relative to a synchronization, which holds for the inner loops this was written for (a few elementwise kernels per iteration against a host read costing microseconds). Raise check_interval only in that regime. Loops whose single iteration is expensive — notably an outer solve whose objective is an optora.dro worst-case expectation, where one frozen iteration is an entire inner dual solve — should use check_interval=1 and pay the synchronization instead.

Attributes:

Name Type Description
converged

Zero-dimensional boolean tensor, True once the residual has fallen below the tolerance. Use it as the predicate of the torch.where that freezes the loop's iterates.

check_interval

Number of iterations between host reads of converged.

__init__(tol, check_interval, reference)

Initialize the tracker on the device of reference.

Parameters:

Name Type Description Default
tol float

Positive tolerance the residual is compared against. Kept as a Python float: comparing a tensor against a Python scalar passes it to the comparison kernel directly, whereas materializing it as a tensor would copy it to the device on every solve.

required
check_interval int

Number of iterations between host reads of the convergence flag.

required
reference Tensor

Iterate whose device the convergence state is held on.

required

start(residual)

Record the residual of the starting iterate, before any iteration.

A loop that evaluates its residual at the current iterate and steps with what it learned there tests the starting iterate once before iterating. That test is not an iteration, so it latches convergence without advancing the iteration count: a loop that starts at its own solution reports zero iterations.

Parameters:

Name Type Description Default
residual Tensor

Zero-dimensional nonnegative convergence residual at the starting iterate.

required

Returns:

Type Description
Tensor

The updated converged flag, to be used as the predicate of a

Tensor

torch.where that freezes the first iteration's update.

update(residual)

Record one iteration's residual without synchronizing.

Iterations performed after convergence do not advance the iteration count, so the reported count matches what a loop that exited the moment the residual dropped below the tolerance would report.

Parameters:

Name Type Description Default
residual Tensor

Zero-dimensional nonnegative convergence residual, for example a gradient norm or the change in a dual potential.

required

Returns:

Type Description
Tensor

The updated converged flag, to be used as the predicate of a

Tensor

torch.where that freezes this iteration's update.

should_stop(iteration)

Check whether the loop may exit, synchronizing at most periodically.

Parameters:

Name Type Description Default
iteration int

Zero-based index of the iteration that just ran.

required

Returns:

Type Description
bool

True if this iteration is a checkpoint and the loop has

bool

converged, False otherwise.

status()

Hand the loop's final diagnostics over without synchronizing.

Returns:

Type Description
ConvergenceStatus

A ConvergenceStatus wrapping the convergence flag and the

ConvergenceStatus

number of iterations performed before convergence, still as

ConvergenceStatus

device tensors. Converting them to host values is deferred to

ConvergenceStatus

whoever reads them, so an inner solve nobody inspects costs no

ConvergenceStatus

synchronization at all.

validate_check_interval(check_interval)

Validate a ConvergenceTracker checkpoint interval.

Parameters:

Name Type Description Default
check_interval int

Number of iterations between host reads of the convergence flag.

required

Returns:

Type Description
int

check_interval, guaranteed positive.

Raises:

Type Description
ValueError

If check_interval is not positive.