Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/cuopt/source/_static/large-rubric.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* Rubric styled closer to an H2/H3 for in-page subheads that must stay out of the TOC. */
p.rubric.large-rubric {
font-size: 1.4em;
font-weight: 600;
margin-top: 1.75rem;
margin-bottom: 0.75rem;
}
6 changes: 5 additions & 1 deletion docs/cuopt/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,11 @@
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_css_files = ["swagger-nvidia.css", "install-selector.css"]
html_css_files = [
"swagger-nvidia.css",
"install-selector.css",
"large-rubric.css",
]
html_js_files = ["cuopt-install-version.js", "install-selector.js"]
html_extra_path = ["versions1.json"]

Expand Down
100 changes: 91 additions & 9 deletions docs/cuopt/source/cuopt-grpc/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,18 @@ These variables apply when the container **entrypoint** builds a ``cuopt_grpc_se

The REST server path in the same image still uses ``CUOPT_SERVER_PORT`` for HTTP in other docs; that is separate from the gRPC defaults above.

Bundled Remote Client (Python, C API, ``cuopt_cli``)
----------------------------------------------------
Integrated Remote Client (Python, C API, ``cuopt_cli``)
-------------------------------------------------------

Remote mode is active when **both** ``CUOPT_REMOTE_HOST`` and ``CUOPT_REMOTE_PORT`` are set. A **custom** gRPC client does not read these automatically; it must configure the channel and protos itself (see :doc:`api`).
These variables apply to **remote execution**: the client integrated into the
Python solver APIs, the C API (``cuOptSolve``), and ``cuopt_cli``. Remote mode
is active when **both** ``CUOPT_REMOTE_HOST`` and ``CUOPT_REMOTE_PORT`` are set.

The :doc:`Python async gRPC client <python-async-client>` does **not** use
``CUOPT_REMOTE_HOST`` / ``CUOPT_REMOTE_PORT``; you pass host and port to
``Client(...)``. See *Python async gRPC client* below for variables that apply
to that client. A **custom** gRPC client must configure the channel itself
(see :doc:`api`).

.. list-table::
:header-rows: 1
Expand All @@ -82,7 +90,7 @@ Remote mode is active when **both** ``CUOPT_REMOTE_HOST`` and ``CUOPT_REMOTE_POR
* - ``CUOPT_REMOTE_HOST``
- For remote
- —
- Server hostname or IP
- GPU server hostname or IP
* - ``CUOPT_REMOTE_PORT``
- For remote
- —
Expand Down Expand Up @@ -116,6 +124,48 @@ Remote mode is active when **both** ``CUOPT_REMOTE_HOST`` and ``CUOPT_REMOTE_POR
- ``0``
- Non-zero: extra gRPC client logging

Python Async gRPC Client (``cuopt.grpc``)
-----------------------------------------

``Client(host, port, tls=...)`` takes the server address in code. It does
**not** read ``CUOPT_REMOTE_HOST`` or ``CUOPT_REMOTE_PORT``.

When ``tls`` is omitted (``None``), the client honors the same ``CUOPT_TLS_*``
variables as remote execution. Pass ``tls=False`` for plain TCP, or
``tls=TlsConfig(...)`` for explicit PEM paths (see :doc:`python-async-client`).

.. list-table::
:header-rows: 1
:widths: 26 14 18 42

* - Variable
- Required
- Default
- Description
* - ``CUOPT_TLS_ENABLED``
- No
- ``0``
- Used when ``tls=None``; non-zero enables TLS
* - ``CUOPT_TLS_ROOT_CERT``
- If TLS
- —
- PEM path to verify the **server** certificate
* - ``CUOPT_TLS_CLIENT_CERT``
- mTLS
- —
- Client certificate PEM
* - ``CUOPT_TLS_CLIENT_KEY``
- mTLS
- —
- Client private key PEM
* - ``CUOPT_GRPC_DEBUG``
- No
- ``0``
- Non-zero: extra gRPC client logging

``CUOPT_CHUNK_SIZE`` and ``CUOPT_MAX_MESSAGE_BYTES`` also apply to this client
when set (same defaults as the integrated remote client).

Usage
=====

Expand Down Expand Up @@ -259,14 +309,14 @@ Bypass the entrypoint:
Client Environment (Examples)
------------------------------

**Required** for remote (see *Bundled remote client* table for all variables):
**Remote execution** — required host/port (see *Integrated remote client* table):

.. code-block:: bash

export CUOPT_REMOTE_HOST=<server-hostname>
export CUOPT_REMOTE_PORT=5001

**TLS** (optional):
**TLS** (optional; also used by the Python async gRPC client when ``tls=None``):

.. code-block:: bash

Expand All @@ -280,13 +330,44 @@ For mTLS, also:
export CUOPT_TLS_CLIENT_CERT=client.crt
export CUOPT_TLS_CLIENT_KEY=client.key

**Python async gRPC client** — pass host and port to ``Client(...)`` (not
``CUOPT_REMOTE_*``). With ``tls=None`` (default), the same ``CUOPT_TLS_*``
variables above apply. For explicit PEM paths, use ``TlsConfig``:

.. code-block:: python

from cuopt.grpc.linear_programming import Client, TlsConfig

# TLS: verify the server with a CA (or omit root_certs for the system trust store)
client = Client(
"server.example.com",
5001,
tls=TlsConfig(root_certs="ca.crt"),
)

# mTLS: also present a client certificate
client = Client(
"server.example.com",
5001,
tls=TlsConfig(
root_certs="ca.crt",
client_cert="client.crt",
client_key="client.key",
),
)

# Plain TCP (ignore CUOPT_TLS_* even if set)
client = Client("localhost", 5001, tls=False)

See :doc:`python-async-client` for the full job API.

Limitations and Scope
=====================

* **Problem types** — **LP**, **MILP**, and **QP** are supported on the gRPC remote path. **Routing** (VRP, TSP, PDP) is **not** supported yet; use the :doc:`REST self-hosted server <../cuopt-server/index>` for remote routing until a future release adds routing over ``CuOptRemoteService``.
* **Problem types** — **LP**, **MIP**, and **QP** are supported on the gRPC remote path. **Routing** (VRP, TSP, PDP) is **not** supported yet; use the :doc:`REST self-hosted server <../cuopt-server/index>` for remote routing until a future release adds routing over ``CuOptRemoteService``.
* **Message size** — Large problems use chunking; very large models can still hit gRPC max message / timeout limits. Tune ``CUOPT_CHUNK_SIZE``, ``CUOPT_MAX_MESSAGE_BYTES``, server ``--max-message-mb``, and solver ``time_limit`` as needed.
* **``CUOPT_GRPC_ARGS``** — Parsed on whitespace only; arguments containing spaces are awkward unless you invoke ``cuopt_grpc_server`` directly.
* **CRL / OCSP** — Not handled by the bundled gRPC TLS stack; use a private CA rotation strategy or a TLS-terminating proxy if you need revocation workflows.
* **CRL / OCSP** — Not handled by the integrated gRPC TLS stack; use a private CA rotation strategy or a TLS-terminating proxy if you need revocation workflows.

Troubleshooting
===============
Expand All @@ -310,5 +391,6 @@ Further Reading
===============

* :doc:`quick-start` — Plain TCP quick path.
* :doc:`examples` — Links to Python, C, and CLI example sections (use with ``CUOPT_REMOTE_*`` on the client).
* :doc:`examples` — Links to Python, C, and CLI example sections (use with ``CUOPT_REMOTE_*`` on the client for remote execution).
* :doc:`python-async-client` — Explicit Python gRPC client.
* :doc:`grpc-server-architecture` — Process model and job behavior (operator overview).
29 changes: 24 additions & 5 deletions docs/cuopt/source/cuopt-grpc/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,16 @@ The **CuOptRemoteService** gRPC API is defined in Protocol Buffers under the ``c
* ``cpp/src/grpc/cuopt_remote_service.proto`` — service and job/chunk/log RPCs
* ``cpp/src/grpc/cuopt_remote.proto`` — LP/MIP problem, settings, and result messages
Comment thread
tmckayus marked this conversation as resolved.

Most users do **not** call these RPCs directly: the NVIDIA cuOpt **Python** API, **C API**, and **cuopt_cli** submit jobs using solver APIs plus :doc:`environment variables <advanced>`. **Custom** clients call ``CuOptRemoteService`` over gRPC using these definitions. This page summarizes the service for custom integrators and debugging.
Most users do **not** call these RPCs directly:

* **Remote execution** — Python, C (``cuOptSolve``), and ``cuopt_cli`` forward
solves when ``CUOPT_REMOTE_HOST`` and ``CUOPT_REMOTE_PORT`` are set
(:doc:`quick-start`, :doc:`advanced`).
* **Python async gRPC client** — ``cuopt.grpc.linear_programming.Client``
(:doc:`python-async-client`).

**Custom** clients call ``CuOptRemoteService`` over gRPC using these definitions.
This page summarizes the service for custom integrators and debugging.

Service: ``CuOptRemoteService``
================================
Expand All @@ -26,7 +35,7 @@ Asynchronous Jobs
* - RPC
- Purpose
* - ``SubmitJob``
- Submit an LP or MILP job in one message (within gRPC message size limits).
- Submit an LP or MIP job in one message (within gRPC message size limits).
* - ``CheckStatus``
- Poll job status by ``job_id``.
* - ``GetResult``
Expand Down Expand Up @@ -82,17 +91,27 @@ Streaming and Callbacks
* - ``StreamLogs``
- Server-streaming solver log lines for a job.
* - ``GetIncumbents``
- MILP incumbent solutions since a given index.
- MIP incumbent solutions since a given index (only if the job was
submitted with ``enable_incumbents``; otherwise the list is empty).

Messages and Constraints
========================

* **Problem types** — LP and MILP in the enum; the problem payload can include quadratic objective data for **QP**-style solves where the client API supports it. **Routing** over this gRPC service is **not** available yet; it is planned for an **upcoming** release (use REST for remote routing today).
* **Problem types** — Wire categories are LP/QP or MIP. QP is submitted as
``lp_request`` (``SolveLPRequest``) with quadratic fields on
``OptimizationProblem``. **Routing** over this gRPC service is **not**
available yet (planned; use REST for remote routing today).
* **Solver settings** — Carried as ``PDLPSolverSettings`` or ``MIPSolverSettings`` inside the request or chunked header, aligned with the NVIDIA cuOpt solver options documentation.
* **Errors** — gRPC status codes carry failures (see comments at the end of ``cuopt_remote_service.proto``).
* **Errors** — Transport failures use gRPC status codes. Some outcomes use
``Status::OK`` with response fields: ``CheckStatus`` reports unknown jobs as
``job_status=NOT_FOUND``; ``GetResult`` uses transport ``NOT_FOUND`` /
``UNAVAILABLE`` (not ready) and ``status=ERROR_SOLVE_FAILED`` for failed
solves; ``DeleteResult`` / ``CancelJob`` report outcomes in the response.
See ``cuopt_remote_service.proto``.

Further Reading
===============

* :doc:`python-async-client` / :doc:`python-async-client-api` — Python job client (``cuopt.grpc``) built on these RPCs.
* :doc:`grpc-server-architecture` — Server process model and job lifecycle (overview); :doc:`advanced` for ``cuopt_grpc_server`` flags. Contributor details: ``cpp/docs/grpc-server-architecture.md``.
* :doc:`advanced` — TLS, Docker, client environment variables, and limitations.
42 changes: 32 additions & 10 deletions docs/cuopt/source/cuopt-grpc/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@
Examples
========

gRPC remote execution uses the same **Python**, **C API**, and **cuopt_cli** entry points as a local solve. After you start ``cuopt_grpc_server`` on the GPU host (:doc:`quick-start`), set the client environment and run **any** of the examples below **unchanged** — no code edits are required.
**Remote execution** uses the same **Python**, **C API**, and **cuopt_cli**
entry points as a local solve. After you start ``cuopt_grpc_server`` on the
GPU server (:doc:`quick-start`), set the client environment and run the
integrated examples below **unchanged** — no code edits are required. The
:ref:`Python async gRPC client <cuopt-grpc-examples-async-client>` section is
separate: it uses ``Client(host, port)`` and does not read ``CUOPT_REMOTE_*``.

On the **client** host, before running the example commands or scripts:
On the **client** machine, before running the example commands or scripts:

.. code-block:: bash

export CUOPT_REMOTE_HOST=<gpu-hostname-or-ip>
export CUOPT_REMOTE_HOST=<gpu-server-hostname-or-ip>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
export CUOPT_REMOTE_PORT=5001

Add TLS or tuning variables from :doc:`advanced` if your deployment uses them.
Expand All @@ -24,19 +29,21 @@ Add TLS or tuning variables from :doc:`advanced` if your deployment uses them.
Where to Find Examples
======================

Python (LP / QP / MILP)
Python (LP / QP / MIP)
-----------------------

* :doc:`../cuopt-python/convex/convex-examples` — runnable Python samples (LP, QP). With ``CUOPT_REMOTE_HOST`` and ``CUOPT_REMOTE_PORT`` set on the client, solves go to the remote server automatically.
* :doc:`../cuopt-python/mip/mip-examples` — runnable Python samples (MILP). With ``CUOPT_REMOTE_HOST`` and ``CUOPT_REMOTE_PORT`` set on the client, solves go to the remote server automatically.
* :doc:`../cuopt-python/mip/mip-examples` — runnable Python samples (MIP). With ``CUOPT_REMOTE_HOST`` and ``CUOPT_REMOTE_PORT`` set on the client, solves go to the remote server automatically.

C API (LP / QP / MILP)
C API (LP / QP / MIP)
----------------------

* :doc:`../cuopt-c/convex/convex-examples` — LP and QP C examples.
* :doc:`../cuopt-c/mip/mip-examples` — MILP C examples.
* :doc:`../cuopt-c/mip/mip-examples` — MIP C examples.

Compile and run these programs with the same exports in the shell; ``solve_lp`` / ``solve_mip`` use gRPC when both remote variables are set (see :doc:`../cuopt-c/convex/convex-c-api` for API reference).
Compile and run these programs with the same exports in the shell;
``cuOptSolve`` uses gRPC when both remote variables are set (see
:doc:`../cuopt-c/convex/convex-c-api` for API reference).

``cuopt_cli``
-------------
Expand All @@ -46,15 +53,30 @@ C API (LP / QP / MILP)
Minimal Demos (This Section)
----------------------------

Bundled with the gRPC docs source for a quick copy-paste path (also walked through in :doc:`quick-start`):
Included with the gRPC docs source for a quick copy-paste path (also walked through in :doc:`quick-start`):

* :download:`remote_lp_demo.py <examples/remote_lp_demo.py>`
* :download:`remote_lp_demo.mps <examples/remote_lp_demo.mps>`

Python Async gRPC Client
------------------------

.. _cuopt-grpc-examples-async-client:

For explicit job control (submit / wait / cancel / stream logs or incumbents)
without ``CUOPT_REMOTE_*``, use ``cuopt.grpc.linear_programming.Client``:

* :doc:`python-async-client` — overview
* :doc:`python-async-client-examples` — log streaming and incumbent streaming
* :doc:`python-async-client-api` — API reference

Custom gRPC Client
------------------

Integrations that do **not** use the bundled Python / C / CLI stack should speak ``CuOptRemoteService`` directly. See :doc:`api`, :doc:`grpc-server-architecture`, and ``cpp/docs/grpc-server-architecture.md`` in the repository for protos and server behavior.
Integrations that do **not** use remote execution or the Python async gRPC
client should speak ``CuOptRemoteService`` directly. See :doc:`api`,
:doc:`grpc-server-architecture`, and ``cpp/docs/grpc-server-architecture.md``
in the repository for protos and server behavior.

More Samples
============
Expand Down
55 changes: 55 additions & 0 deletions docs/cuopt/source/cuopt-grpc/examples/incumbent_stream_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""MIP incumbent streaming via the Python async gRPC client.

Same ``set_mip_callback`` registration as a local solve, plus
``start_incumbent_stream`` so those callbacks fire while the remote job runs.

Start the server first::

cuopt_grpc_server --port 5001 --workers 1

Then::

python incumbent_stream_demo.py
"""

from cuopt.grpc.linear_programming import Client, JobStatus
from cuopt.linear_programming.internals import GetSolutionCallback
from cuopt.linear_programming.problem import INTEGER, MAXIMIZE, Problem
from cuopt.linear_programming.solver_settings import SolverSettings


class IncumbentPrinter(GetSolutionCallback):
def get_solution(self, solution, solution_cost, solution_bound, user_data):
print(
f"incumbent cost={float(solution_cost[0]):.4f} "
f"values={solution.tolist()}",
flush=True,
)
Comment thread
tmckayus marked this conversation as resolved.


problem = Problem("incumbent_stream_demo")
x = problem.addVariable(lb=0, ub=10, vtype=INTEGER, name="x")
y = problem.addVariable(lb=0, ub=10, vtype=INTEGER, name="y")
problem.addConstraint(x + y <= 10, name="c1")
problem.addConstraint(x - y >= 0, name="c2")
problem.setObjective(x + 2 * y, sense=MAXIMIZE)

settings = SolverSettings()
settings.set_mip_callback(IncumbentPrinter(), None)
settings.set_parameter("time_limit", 30)

client = Client("localhost", 5001)
job_id = client.submit(problem, settings)
try:
client.start_incumbent_stream(job_id, settings=settings)
if client.wait(job_id, timeout=120) != JobStatus.COMPLETED:
raise RuntimeError("job did not complete")
client.join_incumbent_stream(job_id)
names = [v.getVariableName() for v in problem.getVariables()]
solution = client.result(job_id, variable_names=names)
print(solution.get_termination_reason(), solution.get_primal_objective())
finally:
client.delete(job_id)
Comment thread
tmckayus marked this conversation as resolved.
18 changes: 12 additions & 6 deletions docs/cuopt/source/cuopt-grpc/examples/remote_lp_demo.mps
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
NAME good-1
NAME good-1
OBJSENSE
MAXIMIZE
ROWS
N COST
L ROW1
L ROW2
COLUMNS
VAR1 COST -0.2
VAR1 ROW1 3 ROW2 2.7
VAR2 COST 0.1
VAR2 ROW1 4 ROW2 10.1
VAR1 COST 0.2
VAR1 ROW1 3.0 ROW2 2.7
VAR2 COST 0.1
VAR2 ROW1 4.0 ROW2 10.1
RHS
RHS1 ROW1 5.4 ROW2 4.9
RHS1 ROW1 5.4 ROW2 4.9
BOUNDS
LO BND1 VAR1 0.0
UP BND1 VAR1 2.0
LO BND1 VAR2 0.0
ENDATA
Loading
Loading