GPU-Accelerated Optimization with GAMS and NVIDIA cuOpt

Posted on: 01 Sep, 2025 Last updated: 20 Oct, 2026 GAMS Solvers

Update (October 2026): The article has been updated for the most recent release of the GAMS/cuOpt solver link (v0.0.9 ) for NVIDIA cuOpt 26.10 . Since our last update, GAMS/cuOpt has learned to solve convex QCP and RMIQCP models and MIPs with semi-continuous variables, can run PDLP on several GPUs at once, offers the new primal simplex method and many new options for LP, barrier and MIP, and comes with an installer script. GAMS/cuOpt now requires GAMS 54 or newer.

Update (April 2026): The article has been extended for the most recent release of the GAMS/cuOpt solver link (v0.0.6 ) for NVIDIA cuOpt 26.04 which added the pdlp_precision option, and new and updated options for MIP, especially options to fine tune heuristics.


For a long time, GPUs were essential for various AI applications and other high-performance compute applications but had limited impact on mathematical optimization. That’s changing.

The paper “Practical Large-Scale Linear Programming using Primal-Dual Hybrid Gradient ” by Applegate et al. (2021) ignited considerable interest with its primal-dual linear programming (PDLP) implementation, proving the method’s effectiveness on real-world problems. This led many solvers to incorporate GPU-accelerated PDLP implementations into their algorithmic offerings. Public interest has further intensified with the open-sourcing of NVIDIA’s own GPU-accelerated LP/MIP and VRP solver, cuOpt .

At GAMS we’re deeply committed to improving computational efficiency for large-scale linear programming, such as those that occur in energy systems modeling. This commitment is demonstrated through projects like PEREGRINE , which develops and utilizes innovative solver technology such as PIPS-IPM++ , a parallel, high-performance computing (HPC) capable interior point method tailored for block-structured LPs, which often arise naturally in large-scale problems that have repetitive substructures. Our ongoing efforts also include exploring cutting-edge technologies like GPU-accelerated optimization with PDLP, to effectively address the challenges of modern optimization.

We are therefore happy to announce the GAMS/cuOpt Link , developed in collaboration with NVIDIA. This offering allows you to solve GAMS and GAMSPy models using cuOpt and further expands our collection of GPU-accelerated solvers, which already features COPT and HiGHS (both utilizing a GPU-accelerated implementation of PDLP, COPT also has a GPU-accelerated Barrier) as well as Gurobi and Xpress. The latter two also have GPU-accelerated PDHG solvers. COPT remains the only commercial one with a GPU-accelerated barrier.

This blog post focuses on the practical application of GAMS/cuOpt rather than extensive benchmarking. It aims to address:

  • What model types does cuOpt support
  • What hardware is needed
  • How can cuOpt be utilized through GAMS and GAMSPy

What model types does cuOpt support?

NVIDIA cuOpt offers a flexible set of solver methods and features for optimizing Linear Programming (LP), Mixed-Integer Linear Programming (MILP), Quadratic Programming (QP) , and Vehicle Routing Problems (VRP)1. This versatility allows users to tailor its operation to specific problem characteristics and desired solution qualities.

cuOpt offers five LP methods. The default concurrent method runs PDLP (GPU), Barrier (GPU), and Dual Simplex (CPU) simultaneously, returning the faster solution. The GPU-based PDLP method is designed for large-scale memory-intensive LPs and can work on model instances that often run out of memory with the Barrier method. NVIDIA introduced multi-GPU PDLP in cuOpt 26.08 and GAMS/cuOpt v0.0.9 exposes this functionality with cuOpt 26.10. Dual Simplex is a classic CPU-based method suited for small to medium-sized LPs, particularly when a high-quality basic solution is required. cuOpt 26.10 added a Primal Simplex method as a fifth option. Starting with version 26.02, cuOpt comes with the powerful LP presolve PSLP that allows dual preserving presolve for very large LPs.

Previously, cuOpt relied solely on the PaPILO presolver. While effective for medium-sized Mixed-Integer Programming (MIP) problems, PaPILO was not architected for large-scale Linear Programming (LP). Attempting to use it for such tasks often resulted in rapid memory exhaustion and time-outs. Furthermore, PaPILO lacked the ability to postsolve dual solutions for critical steps like aggregation, limiting its overall effectiveness. Recognizing that presolving is vital for performance, cuOpt 26.02 has introduced PSLP hence making it a robust, high-performance solution for optimization pipelines involving exceptionally large-scale linear programs.

For MIP, NVIDIA cuOpt uses a hybrid GPU/CPU method, running primal heuristics on the GPU and improving the dual bound on the CPU.

What hardware is needed?

cuOpt requires an NVIDIA GPU with a Volta architecture or newer for CUDA 12, and a Turing architecture or newer for CUDA 13. Powerful GPUs such as the NVIDIA V100, A100, H100/H200, and the latest Blackwell series B100/B200 (as seen in benchmarks ) can be deployed on-premise but the seamless integration of GAMS/cuOpt into GAMS Engine SaaS makes these capabilities more accessible to those without GPU-accelerated infrastructure. This cloud-based solution provides users with access to the precise hardware needed for their optimization problems, eliminating the need for substantial upfront investment.

How can cuOpt be utilized through GAMS and GAMSPy?

GAMS/cuOpt can be set up to run locally or it can be run on the cloud via GAMS Engine SaaS.

Installing and using cuOpt locally with GAMS and GAMSPy

Before you begin, ensure your system meets the following system requirements:

  • Operating System: Linux, Windows 11 through WSL2
  • CPU architecture: x86_64, arm64
  • GAMS: Version 54 or newer
  • GAMSPy: Version 1.12.1 or newer
  • NVIDIA GPU: Volta architecture or better with CUDA 12, Turing architecture or better with CUDA 13
  • CUDA Runtime Libraries: 12 or 13
  • System libraries: OpenSSL 3 and zlib

The script fetch-cuoptlink.py downloads the solver link (and optionally the matching CUDA runtime libraries) from the latest GitHub release , installs it into your GAMS system directory, adds cuOpt to the gamsconfig.yaml file there without overwriting your other settings, and verifies the installation by solving a small LP. The script has no dependencies besides Python. For example, to install GAMS/cuOpt into the GAMS system directory of GAMSPy2:

curl -O https://raw.githubusercontent.com/GAMS-dev/cuoptlink-builder/main/fetch-cuoptlink.py
python fetch-cuoptlink.py install -g "$(gamspy show base)" --cuda-runtime

For a standalone GAMS system, pass its directory to -g instead. Run python fetch-cuoptlink.py without arguments for an interactive installation that detects your GAMS system and CUDA version, and python fetch-cuoptlink.py uninstall to remove the solver link again.

Manual installation

  1. Download and unpack cuopt-link-release-cu{12,13}-{arm64,x86_64}.zip (for CUDA 12 or 13 and 64-bit CPU instruction set ARM or x86) from the latest GitHub release : Unpack the contents of cuopt-link-release-cu*-*.zip into your GAMS system directory2. The archive contains a file gamsconfig_cuopt.yaml with a solverConfig section that enables cuOpt for use with GAMS. If your GAMS system directory has no gamsconfig.yaml yet, copy gamsconfig_cuopt.yaml to gamsconfig.yaml. Otherwise add its solverConfig entry to your existing gamsconfig.yaml.

  2. Download and unpack cu{12,13}-runtime-{arm64,x86_64}.zip from the latest GitHub release (recommended): Even if your machine already has CUDA runtime libraries, we recommend to unpack cu*-runtime-*.zip into the same GAMS system directory in order to provide the precise library versions the link was built against.

Hint: The GAMSPy/cuOpt example notebooks automate these steps and allow you to get started right away.

Running GAMS/cuOpt and GAMSPy/cuOpt locally

After installation, you can run a GAMS or GAMSPY model with cuOpt.


GAMS

To load and solve the trnsport model from the GAMS model library, execute:

gamslib trnsport
gams trnsport lp=cuopt

GAMSPy

To use cuOpt with GAMSPy (e.g. with the transport.py example), set cuOpt as solver in the solve function and set option gamspy.set_options({"SOLVER_VALIDATION": 0}).

[...] 
transport.solve(solver="cuopt")

Running cuOpt in the cloud via GAMS Engine SaaS

An alternative to a local installation is to use cuOpt via GAMS Engine SaaS. This cloud-based platform offers a centralized solution for submitting GAMS jobs. It supports various client applications such as GAMS Studio , GAMS MIRO , the GAMS Engine Web UI , and custom scripts in Python and other languages. This flexibility allows users to integrate cuOpt into existing GAMS and GAMSPy workflows and lowers the barrier to entry by avoiding the need for significant on-premise GPU investments. Users gain access to necessary hardware configurations without substantial upfront capital expenditure, making GPU-accelerated optimization more accessible and scalable.

Not yet a GAMS/Engine SaaS user? Request your free test account at sales@gams.com and let the team know you want to try GAMS/cuOpt.

Running GAMS/cuOpt on Engine SaaS through GAMS Studio

The process of running GAMS/cuOpt on Engine SaaS through GAMS Studio offers a user experience akin to executing GAMS jobs locally within Studio.

Hint: Are you a Windows or Mac user working with GAMS Studio? No Problem. While cuOpt is only available for Linux, you can submit your GAMS/cuOpt jobs from GAMS Studio under Windows and Mac as well.3

  1. Select “Run GAMS Engine” Remember to configure cuOpt as your LP solver, either through the command-line parameter (as demonstrated below) or by including an appropriate option statement within your GAMS code.
  1. Should you not already be logged in, the GAMS Engine Login Dialog will appear. Please choose your preferred sign-in method to proceed with logging in.
  1. The Submit Job dialog box will appear. Select the desired Namespace and instance for submission. Optionally, assign a tag to the job before clicking OK.
  1. Your job compiles locally and is then submitted to Engine SaaS for execution. You can monitor its progress in the Process Log. Upon completion, the results and all output files are downloaded and become accessible on your local system.
--- Job indus89.gms Start 06/23/25 22:07:03 50.1.0 12b75dde WEX-WEI x86 64bit/MS Windows  
[...]  
--- Starting compilation  
--- indus89.gms(3485) 4 Mb  
*** Status: Normal completion  
--- Job indus89.gms Stop 06/23/25 22:07:03 elapsed 0:00:00.038  
 adding: indus89.gms (164 bytes security) (stored 0%)  
 adding: indus89.g00 (164 bytes security) (deflated 5%)

--- GAMS Engine at https://engine.gams.com:443/api  
--- switch LOG to indus89-server.lst  
TOKEN: b88ca408-f993-42ff-afe6-972c2ac1fae0

--- Job queued (80 sec)  
--- Job indus89.gms Start 06/23/25 20:10:18 49.6.1 55d34574 LEX-LEG x86 64bit/Linux  
--- Applying:  
   /home/jail/opt/gams/gmsprmun.txt  
   /home/jail/opt/gams/gamsconfig.yaml  
--- GAMS Parameters defined  
   LP cuopt  
   Restart /home/gfreeman/indus89.g0?  
   Input /home/gfreeman/indus89.gms  
[...]  
--- Reset Solvelink = 1  
---   2,726 rows  6,570 columns  39,489 non-zeroes  
--- Range statistics (absolute non-zero finite values)  
--- RHS       [min, max] : [ 8.438E-04, 2.170E+04] - Zero values observed as well  
--- Bound     [min, max] : [ 5.000E-04, 2.713E+03] - Zero values observed as well  
--- Matrix    [min, max] : [ 4.104E-04, 1.000E+06]  
--- Executing CUOPT (Solvelink=1): elapsed 0:00:00.117  
GAMS/cuOpt link was built against cuOpt version: 26.02.00, git hash: f73da24defcfc405317c0ae409a965fcaf7f58d9
Setting parameter log_file to /home/gfreeman/225a/cuopt.dat
cuOpt version: 26.2.0, git hash: f73da24d, host arch: x86_64, device archs: 70-real,75-real,80-real,86-real,90a-real,100f-real,120a-real,120
CPU: Intel(R) Xeon(R) Platinum 8481C CPU @ 2.70GHz, threads (physical/logical): 13/26, RAM: 227.96 GiB
CUDA 12.9, device: NVIDIA H100 80GB HBM3 (ID 0), VRAM: 79.11 GiB
CUDA device UUID: 3c527f11-4f7e-0effffff95-5758-ffffff

Solving a problem with 2725 constraints, 6569 variables (0 integers), and 36535 nonzeros
Problem scaling:
Objective coefficents range:          [2e-01, 1e+06]
Constraint matrix coefficients range: [4e-04, 1e+04]
Constraint rhs / bounds range:        [0e+00, 2e+04]
Variable bounds range:                [5e-04, 3e+03]
Warning: input problem contains a large range of coefficients: consider reformulating to avoid numerical difficulties.

Third-party presolve is disabled, skipping
Objective offset -0.000000 scaling_factor -1.000000
Running concurrent

Dual simplex finished in 0.72 seconds, total time 3.62
Barrier finished in 3.95 seconds
   Iter    Primal Obj.      Dual Obj.    Gap        Primal Res.  Dual Res.   Time
      0 -0.00000000e+00 -0.00000000e+00  0.00e+00   4.05e+04     2.50e+07   5.511s
PDLP finished
Concurrent time:  2.609s, total time 5.513s
Solved with dual simplex
Status: Optimal   Objective: 1.14873646e+05  Iterations: 6608  Time: 5.513s
--- Reading solution for model wsisn[LS2:2523]  
--- Executing after solve: elapsed 0:01:00.859[LS2:12126]  
--- GDX File C:\Users\ffian\Documents\GAMS\Studio\workspace\indus89.gdx  
*** Status: Normal completion[LS2:12142]  
--- Job indus89.gms Stop 06/23/25 20:11:19 elapsed 0:01:00.869  
Archive:  solver-output.zip  
 inflating: indus89.lst               
 inflating: indus89.lxi               
--- extracting: .\indus89-temp\indus89.gms  
 inflating: solver.log                
 inflating: indus89.gdx               
 inflating: indus89.g00               
*** Local file updated: indus89.gdx  
*** Local file updated: indus89-server.lst  
*** Local file updated: indus89-server.lxi  
*** Local file updated: indus89-solver.log

Running GAMSPy/cuOpt on Engine SaaS

In order to submit your GAMSPy model to Engine SaaS for solving, you need to define the GAMS Engine configuration by importing EngineClient and creating an instance, which can then be passed to the solve method with the backend specified as ’engine'.

To adapt the GAMSPy model library’s mexss model for use with cuOpt under Engine, some modifications are necessary.

[...]
from gamspy import (
    Container,
    Equation,
    [...],
    EngineClient
)

[...]

#create EngineClient instance. Submit to namespace gpu_tests and select an instance with NVIDIA GPU
client = EngineClient(
    host="https://engine.gams.com/api",
    username=os.environ["ENGINE_USER"],
    password=os.environ["ENGINE_PASSWORD"],
    namespace="gpu_tests",
    engine_options={"labels": "instance=g6.4xlarge"}
)  

#solve with solver cuopt and engine backend
mexss.solve(solver="cuopt", backend="engine", client=client, output=sys.stdout)
[...]

The Terminal output will look as follows:

(gamspy) PS C:\Users\ffian> & C:/Users/ffian/.conda/envs/gamspy/python.exe c:/Users/ffian/Documents/projects/gamspy-examples/models/mexss/mexss.py  
[ENGINE - INFO] Job status is queued...  
[ENGINE - INFO] Job status is queued...  
[ENGINE - INFO] Job status is queued...  
--- Job __AcZmhoFQyKC4_Jc_7zWpQ.gms Start 06/30/25 11:36:04 49.6.1 55d34574 LEX-LEG x86 64bit/Linux  
--- Applying:  
   /home/jail/opt/gams/gmsprmun.txt  
   /home/jail/opt/gams/gamsconfig.yaml  
--- GAMS Parameters defined  
   LP cuopt  
[...]     
--- Generating LP model mexss  
--- __AcZmhoFQyKC4_Jc_7zWpQ.gms(459) 4 Mb  
--- Reset Solvelink = 1  
---   74 rows  78 columns  230 non-zeroes  
--- Range statistics (absolute non-zero finite values)  
--- RHS       [min, max] : [ 5.600E-01, 4.011E+00] - Zero values observed as well  
--- Bound     [min, max] : [        NA,        NA] - Zero values observed as well  
--- Matrix    [min, max] : [ 1.200E-01, 1.500E+02]  
--- Executing CUOPT (Solvelink=1): elapsed 0:00:00.002  
GAMS/cuOpt link was built against cuOpt version: 26.02.00, git hash: f73da24defcfc405317c0ae409a965fcaf7f58d9
Setting parameter log_file to /home/gfreeman/225a/cuopt.dat
cuOpt version: 26.2.0, git hash: f73da24d, host arch: x86_64, device archs: 70-real,75-real,80-real,86-real,90a-real,100f-real,120a-real,120
CPU: Intel(R) Xeon(R) Platinum 8481C CPU @ 2.70GHz, threads (physical/logical): 13/26, RAM: 228.04 GiB
CUDA 12.9, device: NVIDIA H100 80GB HBM3 (ID 0), VRAM: 79.11 GiB
CUDA device UUID: 3c527f11-4f7e-0effffff95-5758-ffffff

Solving a problem with 73 constraints, 77 variables (0 integers), and 225 nonzeros
Problem scaling:
Objective coefficents range:          [1e+00, 1e+00]
Constraint matrix coefficients range: [1e-01, 2e+02]
Constraint rhs / bounds range:        [0e+00, 4e+00]
Variable bounds range:                [0e+00, 0e+00]

Third-party presolve is disabled, skipping
Objective offset 0.000000 scaling_factor 1.000000
Running concurrent

Dual simplex finished in 0.09 seconds, total time 2.81
Barrier finished in 2.95 seconds
   Iter    Primal Obj.      Dual Obj.    Gap        Primal Res.  Dual Res.   Time
      0 +0.00000000e+00 +0.00000000e+00  0.00e+00   4.70e+00     2.00e+00   4.621s
PDLP finished
Concurrent time:  1.901s, total time 4.623s
Solved with dual simplex
Status: Optimal   Objective: 5.38811204e+02  Iterations: 56  Time: 4.623s
--- Reading solution for model mexss  
--- Executing after solve: elapsed 0:00:01.397  
--- __AcZmhoFQyKC4_Jc_7zWpQ.gms(518) 4 Mb  
--- GDX File /home/gfreeman/__AcZmhoFQyKC4_Jc_7zWpQout.gdx  
*** Status: Normal completion  
--- Job __AcZmhoFQyKC4_Jc_7zWpQ.gms Stop 06/30/25 11:36:06 elapsed 0:00:01.398  
[ENGINE - INFO] Results have been extracted to your working directory: C:\Users\ffian\AppData\Local\Temp\tmpizp77f7a.

Key considerations when working with GAMS/cuOpt:

cuOpt offers the prospect of considerable speedup, particularly for large-scale linear programming problems. However, discussions have arisen regarding the accuracy of solutions obtained by leveraging first-order methods like PDLP. In a recent webinar , Gurobi Co-Founder and Chairman Ed Rothberg noted that while PDLP can offer faster solve times, it may struggle to deliver high-precision solutions when compared to interior point methods. Julian Hall expanded on this in the HiGHS Newsletter 25.0 , where he presented experimental comparisons between cuPDLP-C and the HiGHS interior point solver. His findings showed that although PDLP was faster on several instances, some solutions exhibited notable deviations from true optimality.

These observations underscore a critical trade-off: while first-order methods can unlock substantial speedups, especially for very large models, users must weigh this against the level of accuracy required for their use case. Understanding these trade-offs and using solvers and algorithms thoughtfully is essential for making informed decisions when selecting a solver. Carefully comparing GAMS/cuOpt with solvers you’re already using can be a smart move. In the following section, we’ll briefly describe which GAMS model types cuOpt can solve, explore the solver options GAMS/cuOpt offers, and demonstrate how the GAMS tool Examiner can be effectively used to draw conclusions about solution quality.

Supported problem types and limitations

cuOpt can solve the following GAMS problem types:

  • LP (Linear Programming)
  • MIP (Mixed Integer Programming)
  • RMIP (Relaxed Mixed Integer Programming)
  • QCP (Quadratically Constrained Programming, convex only)
  • RMIQCP (Relaxed Mixed Integer Quadratically Constrained Programming, convex only)

Mixed-integer quadratic models (MIQCP) are not supported, since cuOpt’s MIP solver only handles linear objectives and constraints.

Note on Discrete Variables: The type of discrete variables is limited to binaries, integers and semi-continuous variables. SOS1, SOS2 and semi-integer variables are not supported.

cuOpt parameters

GAMS/cuOpt options can be set through a GAMS solver options file . The various GAMS/cuOpt options are listed here by category (click a category to expand it), with a few words about each to indicate its function.

For a more detailed documentation of the available parameters, consult the official NVIDIA cuOpt user guide .4

General Options

Option Description Type Default
num_cpu_threads Number of CPU threads used by the LP and MIP solvers (default: GAMS Threads). integer -1
presolve Presolver used for presolve reductions.
  • -1: Automatic: PSLP for LP, Papilo for MIP (default)
  • 0: Off
  • 1: Papilo
  • 2: PSLP
enumint -1
mip_probing Enables the probing cache in cuOpt’s own MIP presolve. boolean 1
time_limit Time limit in seconds after which the solver stops and returns the current solution (default: GAMS ResLim). double 1e10
work_limit Work limit (a machine-independent measure of solver effort) after which the MIP solver stops and returns the current solution. double 1e10
node_limit Maximum number of branch-and-bound nodes the MIP solver explores before it stops and returns the best feasible solution found (if any). integer maxint
prob_read Reads the problem from an MPS file instead of the GAMS model. No solution is reported back to GAMS in this case. string
solution_file Name of a file to which cuOpt writes the solution. string
user_problem_file Name of a file to which cuOpt writes the problem as it receives it (MPS format). string
presolve_file Name of a file to which cuOpt writes the presolved problem. string
random_seed Random seed used by the solver (-1: automatic). A fixed seed gives reproducible results in deterministic mode. integer -1

Linear Programming Options

Option Description Type Default
num_gpus Number of GPUs used for LP solves. In the default concurrent mode, up to 2 GPUs are used to run PDLP and barrier on separate GPUs. With method 1 (PDLP), -1 (all visible GPUs) or a value greater than 1 runs multi-GPU PDLP, which distributes the LP across the GPUs (the problem must currently fit into the memory of a single GPU). integer 1
multigpu_pdlp_partitioner How multi-GPU PDLP splits the problem across GPUs.
  • 0: Automatic: round robin on 1 GPU, KaMinPar otherwise (default)
  • 1: KaMinPar: multi-threaded graph partitioner, better balanced at the cost of extra partitioning time
  • 2: Round robin: no partitioning graph is built
enumint 0
dual_postsolve Enables dual postsolve when Papilo presolve is used for LPs. boolean 1
pdlp_solver_mode Parameter preset for PDLP.
  • 0: Stable1 (legacy)
  • 1: Stable2 (legacy)
  • 2: Methodical1: slower individual steps, but fewer of them
  • 3: Fast1: fastest mode, but converges less often
  • 4: Stable3: best overall balance of speed and convergence (default)
enumint 4
method Algorithm used to solve LPs.
  • 0: Concurrent: PDLP, barrier and dual simplex in parallel (default)
  • 1: PDLP
  • 2: Dual simplex
  • 3: Barrier
  • 4: Primal simplex
enumint 0
pdlp_precision Floating-point precision used by PDLP.
  • -1: Default: native precision of the problem (FP64)
  • 0: Single: run PDLP in FP32. Uses half the memory and makes iterations about twice as fast on average, but may need more iterations. Works with crossover and concurrent mode
  • 1: Double: explicitly run in FP64
  • 2: Mixed: store the constraint matrix in FP32 for faster matrix-vector products while vectors and computations stay in FP64. Convergence checks always use the FP64 matrix
enumint -1
iteration_limit Iteration limit after which the solver stops and returns the current solution (default: GAMS IterLim). integer maxint
infeasibility_detection Enables infeasibility detection in PDLP. boolean 0
strict_infeasibility Enables strict infeasibility mode in PDLP. boolean 0
crossover Enables crossover from the PDLP solution to a basic solution. boolean 0
large_nnz_threshold Number of nonzeros (after presolve) at or above which concurrent mode skips barrier and dual simplex and PDLP uses column reduction where supported (-1: disables both). integer 50000000
primal_simplex_pricing Pricing rule of the primal simplex method.
  • 0: Dantzig
  • 1: Devex (default)
enumint 1
dual_simplex_initial_perturbation Whether the simplex method perturbs the problem initially (-1: automatic, 0: off, 1: on). integer -1
dual_simplex_remove_perturbation Whether the simplex method removes the perturbation again (-1: automatic, 0: off, 1: on). integer -1
pdlp_hyper_enable_curtis_reid_scaling Enables Curtis-Reid scaling in PDLP before Ruiz and Pock-Chambolle scaling. boolean 1
pdlp_hyper_reduced_matrix PDLP column reduction, which iterates on a reduced matrix and periodically refreshes with the full problem (-1: automatic, i.e. at or above large_nnz_threshold, 0: off, 1: on). integer -1
pdlp_hyper_project_initial_primal Whether PDLP projects the initial primal solution (the GAMS levels) onto the variable bounds (-1: as set by pdlp_solver_mode, 0: off, 1: on). integer -1
save_best_primal_so_far Makes PDLP keep the best primal solution found so far. boolean 0
first_primal_feasible Makes PDLP stop at the first primal feasible solution. boolean 0
per_constraint_residual Makes PDLP compute primal and dual residuals per constraint instead of globally. boolean 0
absolute_primal_tolerance Absolute primal tolerance of PDLP’s primal feasibility check. double 0.0001
relative_primal_tolerance Relative primal tolerance of PDLP’s primal feasibility check. double 0.0001
absolute_dual_tolerance Absolute dual tolerance of PDLP’s dual feasibility check. double 0.0001
relative_dual_tolerance Relative dual tolerance of PDLP’s dual feasibility check. double 0.0001
absolute_gap_tolerance Absolute tolerance of PDLP’s duality gap check. double 0.0001
relative_gap_tolerance Relative tolerance of PDLP’s duality gap check. double 0.0001

Barrier Solver Options

Option Description Type Default
folding Whether to fold the LP. Folding can reduce the problem size by exploiting symmetry.
  • -1: Automatic (default)
  • 0: Off
  • 1: On
enumint -1
dualize Whether to dualize the LP in presolve. Dualizing can speed up problems with inequality constraints that have more constraints than variables.
  • -1: Automatic (default)
  • 0: Off
  • 1: On
enumint -1
ordering Ordering algorithm used by cuDSS for sparse factorizations. The ordering can have a large impact on run time.
  • -1: Automatic (default)
  • 0: cuDSS default ordering
  • 1: AMD (approximate minimum degree)
enumint -1
augmented Linear system solved in each barrier iteration.
  • -1: Automatic (default)
  • 0: Normal equations (ADAT system)
  • 1: Augmented system
enumint -1
eliminate_dense_columns Eliminates dense columns from the constraint matrix. This can reduce fill-in during factorization, but needs extra solves in each iteration. boolean 1
cudss_deterministic Runs cuDSS in deterministic mode, which gives reproducible results but may be slower. boolean 0
barrier_dual_initial_point Method used to compute the dual initial point of the barrier method. The initial point affects the number of barrier iterations.
  • -1: Automatic (default)
  • 0: Heuristic of Lustig, Marsten and Shanno, “On Implementing Mehrotra’s Predictor-Corrector Interior-Point Method for Linear Programming” (SIAM J. Optimization, 1992)
  • 1: Least squares problem that minimizes the norms of the dual variables and reduced costs subject to the dual equality constraints
  • 2: Sturm/SeDuMi-style primal and dual initial point
enumint -1
barrier_iterative_refinement Applies iterative refinement after each barrier iteration to improve accuracy. boolean 1
qcqp_hyper_ruiz_equilibration Ruiz equilibration when scaling QCQPs for barrier (-1: automatic, 0: off, 1: on). integer -1
barrier_adaptive_regularization Adaptive regularization in the barrier method (-1: automatic, 0: off, 1: on). integer -1
barrier_primal_regularization Initial primal regularization of the augmented system (-1: automatic). double -1
barrier_dual_regularization Initial dual regularization of the augmented system (-1: automatic). double -1
barrier_initial_point_safeguard Margin by which the barrier initial point is pushed into the interior of the nonnegative orthant or second-order cone. double 10
barrier_presolve_bound_free_variables Whether barrier presolve adds bounds to free variables (-1: automatic, 0: off, 1: on). integer -1
barrier_hyper_gpu_ruiz_nnz_threshold Number of nonzeros (in A and Q) at or above which barrier runs Ruiz equilibration on the GPU instead of the CPU. integer 500000
barrier_step_scale Scaling factor applied to the barrier step size. double 0.9
postsolve_info Makes barrier log extra diagnostics after the solve, such as unscaled residual checks and postsolve messages (-1: automatic, currently off, 0: off, 1: on). integer -1

Mixed Integer Linear Programming Options

Option Description Type Default
miptrace Name of a MIP trace file that logs the incumbent and best bound over time. string
mipstart Tries to use the initial variable levels as an initial MIP solution. boolean 0
mip_heuristics_only Runs only the GPU heuristics. boolean 0
mip_scaling Scaling applied to the MIP problem.
  • 0: Off
  • 1: Objective and rows
  • 2: Rows only, no objective scaling (default)
enumint 2
mip_symmetry Symmetry detection and handling in the MIP solver.
  • -1: Automatic (default)
  • 0: Off
  • 1: Orbital fixing
  • 2: Orbital fixing and lexicographic reduction
enumint -1
mip_absolute_tolerance MIP absolute tolerance. double 1e-06
mip_relative_tolerance MIP relative tolerance. double 1e-12
mip_integrality_tolerance MIP integrality tolerance. double 1e-05
mip_absolute_gap Absolute gap at which the MIP solve terminates (default: GAMS OptCA). double 1e-10
mip_relative_gap Relative gap at which the MIP solve terminates (default: GAMS OptCR, at most 0.1). double 1e-4
mip_cut_passes Number of cut passes (0: no cuts). integer 10
mip_mixed_integer_rounding_cuts Mixed integer rounding cuts (-1: automatic, 0: off, 1: on). integer -1
mip_mixed_integer_gomory_cuts Mixed integer Gomory cuts (-1: automatic, 0: off, 1: on). integer -1
mip_strong_chvatal_gomory_cuts Strong Chvátal-Gomory cuts (-1: automatic, 0: off, 1: on). integer -1
mip_knapsack_cuts Knapsack cuts (-1: automatic, 0: off, 1: on). integer -1
mip_flow_cover_cuts Flow cover cuts.
  • -1: Automatic (default)
  • 0: Off
  • 1: On
enumint -1
mip_implied_bound_cuts Implied bound cuts (-1: automatic, 0: off, 1: on). integer -1
mip_clique_cuts Clique cuts (-1: automatic, 0: off, 1: on). integer -1
mip_zero_half_cuts Zero-half cuts (-1: automatic, 0: off, 1: on). integer -1
mip_rins (Recursive) RINS heuristic (relaxation induced neighborhood search) (-1: automatic, 0: off, 1: on). integer -1
mip_rens RENS heuristic (relaxation enforced neighborhood search) (-1: automatic, 0: off, 1: on). integer -1
mip_mutation Mutation heuristic, which fixes a random subset of the integer variables to their incumbent values (-1: automatic, 0: off, 1: on). integer -1
mip_determinism_mode Whether the MIP solver runs in opportunistic or deterministic mode.
  • 0: Opportunistic: results may vary between runs due to parallelism (default)
  • 1: Deterministic (experimental): reproducible results for the same number of threads
enumint 0
mip_batch_pdlp_strong_branching Use of batched PDLP for strong branching at the root.
  • 0: Dual simplex only (default)
  • 1: Dual simplex and batched PDLP together
  • 2: Batched PDLP only
enumint 0
mip_batch_pdlp_reliability_branching Use of batched PDLP for reliability branching.
  • 0: Dual simplex only (default)
  • 1: Dual simplex and batched PDLP together
  • 2: Batched PDLP only
enumint 0
mip_objective_step Detects and exploits a discrete step structure in the objective to tighten the dual bound. boolean 1
mip_cut_change_threshold Minimum dual bound improvement per cut pass (-1: no threshold). double -1
mip_cut_min_orthogonality Minimum orthogonality a cut needs to be added to the LP relaxation. double 0.5
mip_reduced_cost_strengthening Reduced cost strengthening (-1: automatic, 0: off, 1: root only, 2: root and branching). integer -1
mip_dual_degenerate_feasibility_pump Dual degenerate feasibility pump (-1: automatic, currently off, 0: off, 1: on). integer -1
mip_primal_degenerate_pivots Primal degenerate pivots to improve reduced costs (-1: automatic, currently on, 0: off, 1: on). integer -1
mip_dual_degenerate_pivots Dual degenerate pivots to remove fractional integer variables (-1: automatic, currently off, 0: off, 1: root and dives, 2: everywhere). integer -1
mip_reliability_branching Reliability branching mode. integer -1
mip_strong_branching_simplex_iteration_limit Simplex iteration limit for strong branching. integer -1
mip_hyper_heuristic_population_size Maximum number of solutions in the heuristics’ solution pool. integer 32
mip_hyper_heuristic_num_cpufj_threads Number of parallel CPU feasibility jump climbers. integer 8
mip_hyper_heuristic_root_lp_time_ratio Fraction of the total time spent on the root LP. double 0.1
mip_hyper_heuristic_root_lp_max_time Maximum time in seconds spent on the root LP. double 15
mip_hyper_heuristic_rins_time_limit Time limit in seconds per RINS sub-MIP. double 3
mip_hyper_heuristic_rins_max_time_limit Upper limit in seconds of the adaptive RINS time budget. double 20
mip_hyper_heuristic_rins_fix_rate Fraction of variables fixed by RINS. double 0.5
mip_hyper_heuristic_stagnation_trigger Feasibility pump loops without improvement before recombination starts. integer 3
mip_hyper_heuristic_max_iterations_without_improvement Depth of the diversification step after stagnation. integer 8
mip_hyper_heuristic_initial_infeasibility_weight Initial penalty weight for constraint violations. double 1000
mip_hyper_heuristic_n_of_minimums_for_exit Number of local minima after which feasibility jump exits. integer 7000
mip_hyper_heuristic_enabled_recombiners Bitmask of enabled recombiners (1: bound propagation, 2: feasibility pump, 4: line segment, 8: sub-MIP). integer 15
mip_hyper_heuristic_cycle_detection_length Length of the buffer used to detect cycles in the feasibility pump. integer 30
mip_hyper_heuristic_relaxed_lp_time_limit Base time limit in seconds for relaxed LPs solved by the heuristics. double 1
mip_hyper_heuristic_related_vars_time_limit Time limit in seconds for detecting related variables. double 2
mip_hyper_diving_line_search Line search diving (-1: automatic, 0: off, 1: on). integer -1
mip_hyper_diving_pseudocost Pseudocost diving (-1: automatic, 0: off, 1: on). integer -1
mip_hyper_diving_guided Guided diving (-1: automatic, 0: off, 1: on). integer -1
mip_hyper_diving_coefficient Coefficient diving (-1: automatic, 0: off, 1: on). integer -1
mip_hyper_diving_farkas Farkas diving (-1: automatic, 0: off, 1: on). integer -1
mip_hyper_diving_vector_length Vector length diving (-1: automatic, 0: off, 1: on). integer -1
mip_hyper_diving_node_limit Maximum number of nodes per dive. integer 500
mip_hyper_diving_iteration_limit_factor Simplex iterations allowed per dive, as a fraction of the best-first search iterations. double 0.05
mip_hyper_diving_backtrack_limit Maximum number of backtracks per dive. integer 5
mip_hyper_submip_base_target_fixrate Base target fix rate of the RINS neighborhood. double 0.6
mip_hyper_submip_min_fixrate Minimum fix rate needed to accept a RINS neighborhood. double 0.25
mip_hyper_submip_min_fixrate_cap Upper limit for the minimum fix rate needed to solve a sub-MIP. double 0.1
mip_hyper_submip_target_mip_gap Target MIP gap of a sub-MIP. double 0.01
mip_hyper_submip_max_level Maximum sub-MIP recursion depth. integer 10
mip_hyper_submip_iteration_limit_ratio Simplex iteration limit of a sub-MIP, as a fraction of the parent branch-and-bound iterations. double 0.8
mip_hyper_submip_enable_cpufj Runs CPU feasibility jump on the sub-MIP. boolean 1
mip_hyper_submip_node_limit_offset Base node limit of a sub-MIP. integer 200
mip_hyper_submip_iteration_limit_offset Base simplex iteration limit of a sub-MIP in the root heuristics. integer 10000
mip_hyper_submip_round_close_ratio Share of still-unfixed integer variables left for later neighborhood rounds (0: reach the target fix rate in one round). double 0.8
mip_hyper_block_bve Eliminates blocks of binary variables in cuOpt’s MIP presolve (requires mip_probing). boolean 1
mip_hyper_presolve_indicator_strengthening Adds implied indicator rows and lifts capacity rows by their indicator before Papilo presolve. boolean 1
mip_hyper_heuristic_presolve_max_rounds Maximum number of Papilo presolve rounds in the heuristics (-1: derived from the problem, 0: PaPILO default). integer -1
mip_hyper_heuristic_papilo_probing_max_badgesize Upper limit for Papilo’s probing badge size in MIP presolve (-1: derived from the problem, 0: no limit). integer -1
mip_hyper_diving_show_type Shows in the log which diving heuristic found a new incumbent. boolean 0
mip_semi_continuous_big_m Big-M coefficient used to linearize semi-continuous variables. double 1e10

Multi-GPU PDLP

With GAMS/cuOpt v0.0.9 and cuOpt 26.10 or newer, LPs can be solved with PDLP on several GPUs at once. This requires both method 1 (PDLP) and num_gpus -1 (all visible GPUs) or num_gpus greater than 1 in the GAMS/cuOpt option file:

method 1
num_gpus -1

Without method 1, i.e. in the default concurrent mode, num_gpus 2 instead runs PDLP and barrier in parallel on two GPUs. The option multigpu_pdlp_partitioner selects how the problem is split across the GPUs, and the GPUs used can be restricted with the environment variable CUDA_VISIBLE_DEVICES. Currently, the whole problem still has to fit into the memory of a single GPU, starting values (levels and marginals) are not passed to multi-GPU PDLP, and convergence can differ from single-GPU PDLP.

Solution quality

No single algorithm is universally optimal for all problem types. Within cuOpt, PDLP is well suited for large-scale, memory-intensive problems due to its ability to avoid explicit matrix factorizations. In contrast, Dual Simplex is better suited for smaller to medium-sized problems, where it can provide high-quality basic solutions.

Users can customize operations based on specific problem characteristics and desired solution qualities, thanks to a wide array of available options. GAMS/Examiner further aids users by providing an unbiased assessment of solution quality, verifying the validity of a solver’s reported optimal solution by checking for primal feasibility, dual feasibility, and optimality.

GAMS/Examiner is used “like a solver” and can be parameterized to use any solver available with GAMS and GAMSPy as a subsolver.

Running Examiner with cuOpt as subsolver

The Examiner options subsolver and subsolveropt allow to specify a subsolver and activate its corresponding option file. For the Examiner example we utilize cuOpt options method 1 and crossover 0.

The following code snippets illustrate one way to set these solver options in both GAMS and GAMSPy.


GAMS

[...]  
* Write GAMS/Examiner option file examiner.opt and instruct to use cuOpt as subsolver with a GAMS/cuOpt option file*  
file     opt_examiner / examiner.opt /;  
putclose opt_examiner 'subsolver cuopt' /  
                      'subsolveropt 1'  ;

* Write GAMS/cuOpt option file cuopt.opt and instruct to use*  
* PDLP without crossover*  
file opt_cuopt / cuopt.opt /;  
putclose opt_cuopt 'method 1'    /  
                   'crossover 0' ;      

option solver =examiner, optFile=1;  
solve myModel maximizing myObjective using lp;  
[...]

GAMSPy

[...]
#Write cuOpt option file in the container's working directory
myContainer.writeSolverOptions("cuopt", {"method": 1, "crossover": 0})

#set examiner as solver and define examiner options
myModel.solve(solver="examiner", solver_options={"subsolver": "cuopt", "subsolveropt": "1"})
[...]

When solving the indus89 model from the GAMS model library using Examiner and cuOpt, Examiner analyzes the solver’s solution and generates the following report. Note that we asked cuOpt to solve this problem with the default tolerance settings of 1e-4, which are likely to generate constraint violations due to the accuracy level resulting from this tolerance value. To reduce or completely eliminate these violations, we will also discuss additional steps and features provided by cuOpt below.

[...]
Status: Optimal   Objective: 1.15093789e+05  Iterations: 673080  Time: 42.146s
Subsolver cuopt returns modstat 1, solstat 1.


SolvPoint - solver-provided levels & marginals:
Scale range (observed but not applied): [1,999999]
Maximum element: row=objn, col=artwater(nwfp,fresh,jan): Aij = 999999
Primal variable bounds satisfied (tol = 1e-06)
Dual variable bounds satisfied (tol = 1e-06)
Primal infeasible w.r.t. constraints (tol = 1e-06):
  Max violation: watalcz.l(srws,saline,sep): 0 <= 1.70757 <= 0
  2-Norm of violation: 5.54794
Dual constraints satisfied (tol = 1e-06)
Primal CS is nonzero (tol = 1e-07):
  Max violation: canaldiv.l(44-ful,apr): 0.1021 <= 0.106234 <= 0.894
                 canaldiv.m(44-ful,apr): -INF <= 2.561 <= +INF
Dual CS is nonzero (tol = 1e-07):
  Max violation: watalcz.m(srws,saline,sep): -INF <= 0.00933685 <= +INF
                 watalcz.l(srws,saline,sep): 0 <= 1.70757 <= 0
Model attributes OK (tol = 1e-06)

As expected, the returned primal solution reveals significant constraint violations (max violation 1.70757 and 2-Norm of violations 5.54794). If this is unacceptable, mitigation strategies could for example be to reduce the relative primal tolerance by setting GAMS/cuOpt option ‘relative_primal_tolerance 1e-6’ (default 1e-4) or to enable crossover by setting GAMS/cuOpt option ‘crossover 1’.

With relative_primal_tolerance 1e-6 Examiner returns

[...]
Status: Optimal   Objective: 1.14889661e+05  Iterations: 898280  Time: 61.183s
Subsolver cuopt returns modstat 1, solstat 1.


SolvPoint - solver-provided levels & marginals:
Scale range (observed but not applied): [1,999999]
Maximum element: row=objn, col=artwater(nwfp,fresh,jan): Aij = 999999
Primal variable bounds satisfied (tol = 1e-06)
Dual variable bounds satisfied (tol = 1e-06)
Primal infeasible w.r.t. constraints (tol = 1e-06):
  Max violation: demnat.l(prw,buff-milk): 0 <= -0.057 <= 1e+299
  2-Norm of violation: 0.0958988
Dual constraints satisfied (tol = 1e-06)
Primal CS is nonzero (tol = 1e-07):
  Max violation: x.l(psw,saline,sc-mill,bullock,standard,standard): 0 <= 216.779 <= +INF
                 x.m(psw,saline,sc-mill,bullock,standard,standard): -INF <= -0.158928 <= 0
Dual CS is nonzero (tol = 1e-07):
  Max violation: demnat.m(prw,basmati): -INF <= -2.4166 <= 0
                 demnat.l(prw,basmati): 0 <= 0.0619937 <= +INF
Model attributes OK (tol = 1e-06)

As expected, we can observe that a tighter tolerance results in a smaller violation but also in a higher run time.

With ‘crossover 1’ all violations are gone and Examiner returns

[...]
Crossover time 0.36 seconds
Total time 42.69 seconds
Crossover status Optimal
Subsolver cuopt returns modstat 1, solstat 1.


SolvPoint - solver-provided levels & marginals:
Scale range (observed but not applied): [1,999999]
Maximum element: row=objn, col=artwater(nwfp,fresh,jan): Aij = 999999
Primal variable bounds satisfied (tol = 1e-06)
Dual variable bounds satisfied (tol = 1e-06)
Primal constraints satisfied (tol = 1e-06)
Dual constraints satisfied (tol = 1e-06)
Primal CS is zero (tol = 1e-07)
Dual CS is zero (tol = 1e-07)
Model attributes OK (tol = 1e-06)

Note: All experiments were conducted on an AWS EC g6.4xlarge instance, featuring an NVIDIA L4 Tensor Core GPU (even though this is not officially supported). Other instances supporting NVIDIA GPUs V100, A100, H100, and H200 are available upon request. This post aims to guide users on leveraging GAMS/cuOpt for their own experiments, rather than offering an exhaustive performance benchmark.

Conclusion

The new GAMS/cuOpt link empowers users to integrate cuOpt directly into their existing GAMS and GAMSPy models. Whether through local installation or the scalable GAMS Engine SaaS cloud solution, users can leverage cutting-edge GPU performance without the burden of major on-premise GPU investments.

Crucially, GAMS’ robust diagnostic tools—such as Examiner—are integral to this process, providing essential checks on solution feasibility and quality. This ensures that the benefits of GPU acceleration are complemented by the precision required for practical applications.

We invite you to explore the potential of cuOpt with GAMS and GAMSPy for your most challenging optimization tasks. To get started with cuOpt (or any other solver) please contact sales@gams.com .

Your feedback and experiences are vital as we continue advancing large-scale decision-making. Please reach out to support@gams.com or join forum.gams.com with any questions, issues, or success stories you’d like to share.


  1. The cuOpt VRP Solver is not available through GAMS and GAMSPy ↩︎

  2. Run gamspy show base to identify the GAMS system directory for GAMSPy. ↩︎ ↩︎

  3. For GAMS versions prior to 51.2.0, please add a “dummy” solverConfig section to the gamsconfig.yaml file:

    [...]  
    
    solverConfig:  
       - cuopt:  
         minVersion: 49  
         scriptName: dummy  
         executableName: dummy  
         modelTypes:  
           - LP  
           - MIP  
           - RMIP
    
     ↩︎
  4. Note that GAMS/cuOpt options omit the “CUOPT_” prefix from the original parameter name. For example, “CUOPT_METHOD” in cuOpt documentation becomes simply ‘method’ in a GAMS/cuOpt solver options file. ↩︎