Skip to content

implement batching - #80

Open
simeonschaub wants to merge 65 commits into
JuliaDecisionFocusedLearning:mainfrom
simeonschaub:sds/batching
Open

implement batching#80
simeonschaub wants to merge 65 commits into
JuliaDecisionFocusedLearning:mainfrom
simeonschaub:sds/batching

Conversation

@simeonschaub

Copy link
Copy Markdown
Collaborator

An initial implementation of batched linear solves, as described in https://openreview.net/pdf?id=A4qADJulVa

@gdalle, I know we discussed just making all scalars vectors and all vectors matrices, but I ended up going with a parametric approach, since at least for MILP, we do need to actually be able to tell what is batched and what stays the same across batches and it made sense to go with this dual approach for things like the errors and restart stats as well. My hope is that this also keeps downstream breakage to a minimum, since other than additional type parameters, a lot of the structs actually stay the same.

Simeon SCHAUB and others added 12 commits July 6, 2026 14:47
Solutions, scratch space and MILP data go from Vector to Matrix, while
the quantities derived from them go from Scalar to Vector, one entry per
column of the batch. `BatchedNumber` covers both cases, so unbatched
problems keep scalar errors and step sizes.

Restart candidates are selected column by column, but the decisions to
restart or terminate are reduced over the batch, since the whole batch
shares a single state.

The per-iteration loop stays allocation free in both cases: `Scratch`
gains two per-column buffers and `prog_showvalues` is only evaluated when
the progress bar prints it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`KKTErrors` becomes mutable and is filled in place by `kkt_errors!`, which
now takes its destination as an argument: with batching the per-column
buffers are reused, without it the scalar fields are simply overwritten.
Column-wise reductions write into a `1 × nbatch` alias of a scratch
buffer, since `sum!` only avoids allocating with a preallocated
destination of the reduced shape.

`RestartStats` keeps the absolute errors it compares, which also removes
the repeated `absolute` calls in `should_restart`, and holds the buffer
for the candidate that gets discarded.

Termination and restart checks are now allocation free as well, for both
batched and unbatched problems, which a new test guards against.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Instead of waiting for every column to satisfy the restart criteria, apply
the three usual conditions to the mean of the per-column absolute KKT
errors, so a single decision covers the whole batch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Use `instance` for anything addressing a single instance of the batch
(`instance(x, i)`, `EachInstance`, `instance_vec`, `instance_num`, ...) and
`batched` for operations spanning all of them (`batched_apply!`,
`batched_all`, `batched_mean`, `batched_select!`, ...). The instance count
becomes `nbinstances`, in line with `nbvar` and `nbcons`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a backend-parameterized `test_batching(matrix_type, backend)` next to
`test_moi`, checking instance iteration, per-instance KKT errors, iterates
and full solves of a batch against the same problems solved one at a time.
Run it in the CUDA group for both `GPUSparseMatrixCSR` and
`cuSPARSE.CuSparseMatrixCSR`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.58333% with 13 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/algorithms/common.jl 81.81% 2 Missing ⚠️
src/problems/milp.jl 93.93% 2 Missing ⚠️
src/problems/solution.jl 91.30% 2 Missing ⚠️
src/utils/batching.jl 88.23% 2 Missing ⚠️
src/utils/mat_csr.jl 90.47% 2 Missing ⚠️
src/algorithms/pdlp.jl 96.77% 1 Missing ⚠️
src/components/errors.jl 96.66% 1 Missing ⚠️
src/utils/linalg.jl 92.85% 1 Missing ⚠️
Files with missing lines Coverage Δ
src/CoolPDLP.jl 100.00% <ø> (ø)
src/algorithms/pdhg.jl 100.00% <100.00%> (+10.00%) ⬆️
src/components/restart.jl 95.45% <100.00%> (+1.01%) ⬆️
src/components/scratch.jl 100.00% <100.00%> (ø)
src/components/step_size.jl 97.56% <100.00%> (+0.89%) ⬆️
src/components/termination.jl 90.90% <100.00%> (+0.90%) ⬆️
src/algorithms/pdlp.jl 98.85% <96.77%> (-1.15%) ⬇️
src/components/errors.jl 96.15% <96.66%> (+0.80%) ⬆️
src/utils/linalg.jl 95.16% <92.85%> (+1.41%) ⬆️
src/algorithms/common.jl 92.30% <81.81%> (-5.66%) ⬇️
... and 4 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@simeonschaub

simeonschaub commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Hmm, allocation tests are always finicky, especially with coverage involved. I'll see whether it's real or whether we can work around it somehow.

OpenCL failure is genuinely interesting. It only fails on 1.12 and not on CUDA, I wonder whether there's any miscompilation going on there? Will try to track it down

@gdalle

gdalle commented Jul 24, 2026

Copy link
Copy Markdown
Member

Thanks, I'll take a look next week!
Note that downstream breakage is not a relevant concern at this point, literally no one is using the package yet ;)

simeonschaub and others added 2 commits July 24, 2026 15:04
The CSR kernels compute `c[i] = α * s + β * c[i]`, so they read the
destination even when β is zero. `kkt_errors!` passed a float `zero(T)`
into a `scratch.x` freshly obtained from `similar`, and `0.0 * NaN` is
NaN, so uninitialized memory leaked into `c_At_y`. Under pocl the
recycled host pages regularly do contain NaN bit patterns, which made the
batched OpenCL tests fail on Julia 1.12.

The Bool `false` is a strong zero, so it discards the garbage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@simeonschaub

This comment was marked as resolved.

Comment thread src/components/errors.jl Outdated
simeonschaub and others added 10 commits July 28, 2026 10:43
written by claude
Cover every subset of the four field groups that can vary across a batch
(objective, variable bounds, constraint matrix, constraint bounds).

Combinations with a batched constraint matrix are marked broken where they
currently fail: slicing a `BatchedGPUSparseMatrixCSR` on the CPU, and
preconditioning inside `solve`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`PrimalDualSolution(milp)` copied the shape of `lv` and `lc`, so a MILP batched
only through its objective (or only through its constraint matrix) produced an
unbatched starting point. Take the number of columns from `nbinstances` instead,
and reuse the constructor in the two-argument `solve`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`view(A, :, :, k)` hands the k-th column of `nzval` to `GPUSparseMatrixCSR`,
which required a `DenseVector`. GPU array types return one of their own from a
contiguous view, but on the CPU the slice is a `SubArray`, so `instance`,
`EachInstance` and `spectral_norm` all failed on a batched constraint matrix.
Accept any `AbstractVector` of nonzeros and relax the matching `mul!` methods.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A `MILP` carries a single pair of scalings `D1` and `D2` for the whole batch, so
`pdlp_preconditioner` cannot handle a batched constraint matrix. Say so instead
of failing with a `MethodError` from the `ConstraintMatrix` constructor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`batched_zeros` picked between a vector and a matrix from the number of
instances, so `PrimalDualSolution(milp)` returned a `Union`. Whether a MILP is
batched is a property of its type, unlike how many instances it holds: expose
that as `isbatched` and pass it down as a `Val`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The `isbatched` docstring cross-references `nbinstances`, which had no docstring
of its own, so Documenter could not resolve the `@ref` and the build failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
simeonschaub and others added 6 commits August 3, 2026 13:26
Upstream moved the GPU test files under `test/gpu/<backend>/`, so the CUDA
batching testset and the OpenCL one follow the CUDA and OpenCL runners there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Upstream gave the sparse matrix-vector kernels a strong zero, which the batched
matrix-matrix ones need just as much: `kkt_errors!` multiplies into the
uninitialized `scratch.x`, so a float zero would read whatever NaN garbage lives
there. With every CSR path honoring it, the `false` that stood in for `zero(T)`
can go.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Metal only supports single precision, so `test_batching` takes the float type to
solve in. Two iterates of a batch drift apart much faster in `Float32`, hence the
looser tolerance on the comparison against the single solves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`runtests.jl` includes every test file into `Main`, so the eight files including
`fixtures.jl` each redefined the same handful of methods and buried the CI log
under overwritten-method warnings. Include them once, up front.

`fixtures.jl` loads CoolPDLP, so it has to come after every `set_preferences!`
call: the per-group setup moves above it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only the batch and the solver called it, always with a one α and a zero β, so
the three other branches of the batched matrix-vector method went untested. Walk
through all four, and refuse a batch whose instances disagree on the pattern.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@simeonschaub

simeonschaub commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author

Ok, I think this PR is ready! I looked into the missing coverage codecov is explaining about, but it seems to be an upstream issue with Julia. The function bodies all show up as hit, only the first line of the function definition is sometimes marked as uncovered

Edit: coverage issue should be resolved by #85

@simeonschaub
simeonschaub marked this pull request as ready for review August 3, 2026 15:14
@simeonschaub simeonschaub changed the title [WIP] batching implement batching Aug 3, 2026
@gdalle

gdalle commented Aug 3, 2026

Copy link
Copy Markdown
Member

I'll take a detailed look on Wednesday, thanks! You could use a break from batching anyway, to look at more flow-specific stuff

@gdalle gdalle left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mostly reviewed the code in src, didn't look at the tests in detail. For me, the main question is whether we want to allow other batched matrix types or force users to pick our own batched CSR.

Comment thread src/utils/linalg.jl Outdated
kwargs...
)
return map(axes(K, 3)) do k
spectral_norm(view(K, :, :, k), view(Kᵀ, :, :, k); kwargs...)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be really slow unless the batched sparse matrices have a specialized view method.
I'm not sure it's safe to assume that users will always rely on our own batched matrix implementations?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not unreasonable to assume that batched constraint matrices should be nicely splittable along batches, but that probably falls under the larger discussion to be had whether to keep batched matrix support for now and if so, what we want to impose upon them

Comment thread src/utils/eachinstance.jl Outdated
Comment thread src/utils/batched_diagonal.jl Outdated
Base.eltype(::Type{<:BatchedDiagonal{T}}) where {T} = T
Base.size(D::BatchedDiagonal) = (size(D.diag, 1), size(D.diag, 1), size(D.diag, 2))
Base.size(D::BatchedDiagonal, i::Integer) = i <= 3 ? size(D)[i] : 1
LinearAlgebra.diag(D::BatchedDiagonal) = D.diag

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this coherent with the definition of diag?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, a lot of these overloads are a little bit iffy, since Julia doesn't normally define multiplication for 3d arrays

Comment thread src/problems/milp.jl
@@ -20,29 +20,35 @@ $(TYPEDFIELDS)
"""
struct MILP{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring needs to be updated too

Comment thread src/problems/milp.jl Outdated
Comment on lines +30 to +31
M <: AbstractArray{T},
Mt <: AbstractArray{T},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit too vague, we could impose Union{AbstractMatrix{T},AbstractArray{T,3}} for readability

Comment thread src/problems/solution.jl

function is_feasible(
x, milp::MILP;
x::AbstractVector, milp::MILP;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Batching at this level would probably be more efficient, even though we don't call this function often

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you're right. I wasn't sure whether it was worth the extra complexity since it's not really performance critical. I think we can leave it as a later TODO for now, or do you think it's important to fix before merging?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can leave this for later

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a good way to track this would be to try to eliminate all usages of instance

Comment thread src/components/errors.jl
Comment thread src/utils/mat_csr.jl Outdated
"""
BatchedGPUSparseMatrixCSR(A, nbinstances)

Repeat `A` into a batch of `nbinstances` identical matrices.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would this ever be necessary? In that case, can't we just keep A as a matrix?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's mostly used for testing

Comment thread src/utils/mat_csr.jl Outdated
@inline batchval(v::AbstractVector, k, ::Integer) = v[k]
@inline batchval(m::AbstractMatrix, k, batch_idx::Integer) = m[k, batch_idx]

@kernel function spmm_csr!(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is the one which is much slower than cuSPARSE's SpMM?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be worth reading up on SpMM kernels

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't benchmark this one in detail, I was mostly talking about my other matrix zoo PR before

Comment thread src/utils/mat_csr.jl Outdated
Comment on lines +226 to +234
if α_is_one && β_is_zero
kernel!(c, A.rowptr, A.colval, A.nzval, b, One(), Zero(); ndrange = size(c))
elseif α_is_one
kernel!(c, A.rowptr, A.colval, A.nzval, b, One(), β; ndrange = size(c))
elseif β_is_zero
kernel!(c, A.rowptr, A.colval, A.nzval, b, α, Zero(); ndrange = size(c))
else
kernel!(c, A.rowptr, A.colval, A.nzval, b, α, β; ndrange = size(c))
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of duplication for this 4-way split, can we avoid it?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agree. We should probably just use LinearAlgebra's MulAddMul, I ended up reinventing the wheel here a little

@gdalle
gdalle requested a review from klamike August 5, 2026 17:38
@gdalle

gdalle commented Aug 5, 2026

Copy link
Copy Markdown
Member

Since it will be verified in most of our applications and since it would allow considerable code simplifications, what would you think of stashing the batched matrix additions on a branch (to avoid losing them) and trimming the current PR by assuming that A is constant across batches? It would remove a lot of complexity and edge cases, so I think it might allow us to get this merged before JuliaCon

@simeonschaub

Copy link
Copy Markdown
Collaborator Author

Yes, you're probably right. It bugs me a little to not get that in as well, but I agree it's probably the right call for now. There are too many open questions for how to handle 3d arrays representing batched matrices and operations like matmul and inv properly.

Comment thread src/components/restart.jl Outdated
necessary = absolute(err_candidate, ω) <= necessary_decay * absolute(err_restart, ω)
no_local_progress = absolute(err_candidate, ω) > absolute(err_candidate_last, ω)
long_inner_loop = inner >= artificial_decay * total
candidate = batched_mean(abs_candidate)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parametrizable aggregation function here instead of mean?

Comment thread test/gpu/batching.jl
end
end

broken && @test_broken kkt_errors!(KKTErrors(sol_dev), Scratch(sol_dev), sol_dev, milp_dev) isa KKTErrors

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Broken tests?

@gdalle

gdalle commented Aug 6, 2026

Copy link
Copy Markdown
Member

simeonschaub and others added 5 commits August 6, 2026 11:03
Every instance of a batch now shares a single A, so BatchedGPUSparseMatrixCSR,
the per-instance preconditioning, and BatchedDiagonal go away, and MILP is back
to accepting plain matrices as A and At. Batching of the objective and the
bounds stays.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
should_restart reduces the per-instance absolute KKT errors to one number
before applying the three restart criteria; the reduction is now a
batch_aggregation parameter (the mean by default) instead of being
hard-coded, and Algorithm carries the restart parameters as a type
parameter so the aggregation stays concretely typed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@klamike klamike left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Just did a first pass mostly looking at src. I like the parametric approach, the code is very elegant. LGTM, just some questions.

Thanks also for all the small fixes throughout!

Comment thread src/utils/eachinstance.jl Outdated
Comment thread src/utils/batching.jl
Comment thread test/perf.jl

function iteration_allocations(state, milp, algo)
run_iterations!(state, milp, algo, 5)
return @allocated run_iterations!(state, milp, algo, 20)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can actually guarantee this as is. For example on CUDA mapreduce may allocate if it decides to use multiple blocks: https://github.com/JuliaGPU/CUDA.jl/blob/959f3caa51f31846f8986554990af6d281e05928/CUDACore/src/mapreduce.jl#L266

It is why I had to implement the BatchQuadraticModels.batch_mapreduce!: see https://github.com/klamike/BatchQuadraticModels.jl/blob/main/src/batch_mapreduce.jl and CUDA overloads https://github.com/klamike/BatchQuadraticModels.jl/blob/main/ext/cuda/mapreduce.jl (namely the use of CUDA.reduce_block)

I don't think there is a way to do it while staying device agnostic though..

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only run this test on the CPU, you are right that I believe all the GPU backends do rely on dynamic allocations for kernel launches. I think it's a good check though that at least the CPU case doesn't allocate to avoid any accidental allocations somewhere in the main loop.

Btw, do you know about GPUArrays.mapreducedim!? It allows you to reduce into an existing array. I think that could be used instead of your hand-rolled implementation and will probably be more optimized.

@klamike klamike Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not the kernel launch allocations I am talking about (these were actually fixed/reduced recently I believe, I remember seeing a PR)

it is that the mapreducedim! implementation (sometimes) uses temporaries instead of warp reduction. Note the first link I sent is to the CUDA implementation of GPUArrays.mapreducedim!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, sorry, I totally missed your first link, you're right! It might be nice if mapreducedim! allowed preallocating a scratch buffer that doesn't need to be reallocated every time, at least if the dimensions stayed the same, but I guess that's a more general issue and can always be addressed later

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that keeping a no-allocation check on the CPU is a necessary safeguard, even if it fails on GPUs

Comment thread src/components/restart.jl

Decide whether the whole batch restarts, based on an aggregate of the per-column fixed-point residuals.

Since every instance of the batch restarts at the same time, the three usual criteria (sufficient decay, necessary decay without local progress, long inner loop) are applied to `params.batch_aggregation` (the mean by default) of the per-column absolute KKT errors instead of requiring each column to agree.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you have a sense of how invasive it would be to make termination/restart decisions on a per-instance basis? I guess we'd have to introduce some masking? (future work for sure; I understand you're following the nvidia paper on this.)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We definitely talked about it, I don't think it would be too invasive. I believe what the paper does is permute the columns, so that the non-converged columns come first and then only run the iteration on those first n columns, which is basically the same as your masking idea

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we split the batch_aggregation reduction depending on whether we're considering restarts or termination? IIRC, the NVIDIA paper uses the mean for restarts and the max for termination, so perhaps we need two parameters with slightly more specific names than batch_aggregation?

Comment thread src/problems/solution.jl

function is_feasible(
x, milp::MILP;
x::AbstractVector, milp::MILP;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a good way to track this would be to try to eliminate all usages of instance

Comment thread src/utils/batching.jl
Extract the value of `val` for the `i`-th instance of the batch.
"""
instance_num(val::Number, ::Int) = val
instance_num(val::AbstractVector, i::Int) = val[i]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will raise scalar indexing with gpu arrays right?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it would need an @allowscalar annotation at least. I think you typically will want to avoid calling instance with a GPU array anyways

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that mean the batched solver is not compatible with GPU? I believe I saw some calls to instance that happen in the loop.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There shouldn't be any. I just grepped for it again and didn't find any calls that happened inside the loop

Comment thread src/algorithms/common.jl
rel_primal = primal / primal_scale
rel_dual = dual / dual_scale
rel_gap = gap / gap_scale
rel_primal = primal ./ primal_scale

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps we should reduce these to scalars instead? how does the progbar look with this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's not a bad idea, especially with larger batches. Should we just take the average of the relative quantities? Though the maximum might be more interesting, maybe we show both?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think maximum is better since I believe the termination is currently based on maximum

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also a counter of how many instances are still violating the termination condition as well.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Termination is, but restart uses the mean by default, so displaying both might make sense?

Comment thread src/algorithms/pdhg.jl
@simeonschaub

Copy link
Copy Markdown
Collaborator Author

Thanks for the thorough review, very much appreciated! I left some replies in-line

simeonschaub and others added 3 commits August 7, 2026 10:03
One value per column gets unreadable past a few instances, so the progress
lines now show the maximum and the mean of the relative errors instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reusing format_error keeps mean at the same column on every row, and matches
how the final stats print their errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@simeonschaub simeonschaub reopened this Aug 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants