Skip to content

Commit f3ebc67

Browse files
authored
Add iterative refinement to the GPU ADAT solve path (#1680)
Adds iterative refinement to the GPU ADAT (Schur-complement) solve path in the barrier solver **Changes:** - Implement a simple ADAT matvec (gpu_adat_multiply_simple) that does not rely on pre-allocated cuSPARSE descriptors - Apply GMRES-based iterative refinement after the ADAT Cholesky solve (Hiverge proposed Richardson-style; GMRES chosen based on our experiments for better robustness) - Improves accuracy when the diagonal scaling D becomes ill-conditioned near convergence - Previously only the augmented-KKT path had iterative refinement; the ADAT path had none **Motivation:** As the barrier parameter shrinks, D spans huge magnitude ranges and the direct Cholesky solve alone can degrade. Refining the ADAT solution improves robustness on ill-conditioned LP/QP instances without affecting the SOCP path (which does not use the Schur-complement formulation). **Benchmarks:** - **LP Barrier (10 min)**: Optimal 32 → 34, suboptimal 3 → 4, unsolved 14 → 11. 9% geometric mean speedup. - **QP (Maros):** All 136 problems solved (131 optimal, 5 suboptimal); previously 130 optimal, 2 suboptimal, 4 failures. 27% geometric mean speedup. - **QCQP:** bdry3 goes from suboptimal → optimal. No performance change. - **SOCP:** Slight differences possibly from code perturbation; this change is inactive for SOCP (Schur-complement path only). **Acknowledgment:** This improvement was proposed by the [Hiverge](https://www.hiverge.ai/) AI discovery engine with experiments by [@kerry-hiverge](https://github.com/kerry-hiverge). Authors: - Rajesh Gandham (https://github.com/rg20) Approvers: - Chris Maes (https://github.com/chris-maes) URL: #1680
1 parent bd4b8f6 commit f3ebc67

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

benchmarks/linear_programming/run_mps_files.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ else
360360
mapfile -t mps_files < <(find "$MPS_DIR" -type f \( -name "*.mps" -o -name "*.MPS" -o -name "*.SIF" \) | sort)
361361
else
362362
# Gather .mps/.MPS and .SIF files in the directory
363-
mapfile -t mps_files < <(ls "$MPS_DIR"/*.mps "$MPS_DIR"/*.MPS "$MPS_DIR"/*.SIF 2>/dev/null)
363+
mapfile -t mps_files < <(ls "$MPS_DIR"/*.mps "$MPS_DIR"/*.MPS "$MPS_DIR"/*.SIF "$MPS_DIR"/*.mps.gz 2>/dev/null)
364364
fi
365365

366366
echo "Found ${#mps_files[@]} .mps and .SIF files in $MPS_DIR"

cpp/src/barrier/barrier.cu

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,26 @@ class iteration_data_t {
17151715
cusparse_view.spmv(alpha, cusparse_u, beta, cusparse_v);
17161716
}
17171717

1718+
// v = alpha * A * Dinv * A^T * y + beta * v. Simple interface (plain device vectors,
1719+
// no pre-built cusparse descriptors) so it can be used as the `a_multiply` callback of
1720+
// the generic iterative-refinement operator for the ADAT (non-augmented) solve path.
1721+
void gpu_adat_multiply_simple(f_t alpha,
1722+
const rmm::device_uvector<f_t>& y,
1723+
f_t beta,
1724+
rmm::device_uvector<f_t>& v)
1725+
{
1726+
const i_t n = A.n;
1727+
rmm::device_uvector<f_t> u(n, stream_view_);
1728+
cusparse_view_.transpose_spmv(1.0, y, 0.0, u);
1729+
cub::DeviceTransform::Transform(cuda::std::make_tuple(u.data(), d_inv_diag.data()),
1730+
u.data(),
1731+
u.size(),
1732+
cuda::std::multiplies<>{},
1733+
stream_view_.value());
1734+
RAFT_CHECK_CUDA(stream_view_);
1735+
cusparse_view_.spmv(alpha, u, beta, v);
1736+
}
1737+
17181738
// v = alpha * A * Dinv * A^T * y + beta * v
17191739
void adat_multiply(f_t alpha,
17201740
const dense_vector_t<i_t, f_t>& y,
@@ -2947,6 +2967,37 @@ i_t barrier_solver_t<i_t, f_t>::gpu_compute_search_direction(iteration_data_t<i_
29472967
settings.log.printf("Linear solve failed\n");
29482968
return -1;
29492969
}
2970+
2971+
// Iterative refinement on the ADAT (Schur-complement) system using GMRES.
2972+
// The direct Cholesky solve can degrade in accuracy on ill-conditioned D near
2973+
// convergence, as the diagonal D can span many orders of magnitude with small
2974+
// barrier parameter. In this case, we launch a GMRES-based iterative refinement
2975+
// loop for added robustness in the Schur-complement (ADAT) approach.
2976+
// GMRES can handle large, potentially ill-conditioned systems better than simple Richardson
2977+
// or classical iterative refinement, at the potential cost of higher computational work and
2978+
// memory. This is only used on the pure Schur-complement (n_dense_columns == 0).
2979+
if (settings.barrier_iterative_refinement && data.n_dense_columns == 0) {
2980+
struct adat_op_t {
2981+
adat_op_t(iteration_data_t<i_t, f_t>& data) : data_(data) {}
2982+
iteration_data_t<i_t, f_t>& data_;
2983+
void a_multiply(f_t alpha,
2984+
const rmm::device_uvector<f_t>& x,
2985+
f_t beta,
2986+
rmm::device_uvector<f_t>& y) const
2987+
{
2988+
data_.gpu_adat_multiply_simple(alpha, x, beta, y);
2989+
}
2990+
void solve(rmm::device_uvector<f_t>& b, rmm::device_uvector<f_t>& x) const
2991+
{
2992+
data_.gpu_solve_adat(b, x);
2993+
}
2994+
} adat_op(data);
2995+
const f_t adat_solve_err =
2996+
iterative_refinement<i_t, f_t, adat_op_t>(adat_op, data.d_h_, data.d_dy_);
2997+
if (adat_solve_err > 1e-1) {
2998+
settings.log.printf("||ADAT*dy - h|| %e after IR\n", adat_solve_err);
2999+
}
3000+
}
29503001
} // Close NVTX range
29513002

29523003
// y_residual <- ADAT*dy - h

0 commit comments

Comments
 (0)