diff --git a/ci/build_wheel_libcuopt.sh b/ci/build_wheel_libcuopt.sh index 5d6adcef0..a12f41e9b 100755 --- a/ci/build_wheel_libcuopt.sh +++ b/ci/build_wheel_libcuopt.sh @@ -29,6 +29,22 @@ bash ci/utils/install_protobuf_grpc.sh export SKBUILD_CMAKE_ARGS="-DCUOPT_BUILD_WHEELS=ON;-DDISABLE_DEPRECATION_WARNING=ON" +# OpenSSL 3 hints for libcuopt's own find_package(OpenSSL). +# +# install_protobuf_grpc.sh links gRPC against OpenSSL 3 (see that script for +# rationale). libcuopt then re-resolves OpenSSL via find_package because +# gRPC's imported targets propagate it transitively. On Rocky/RHEL 8 the +# EPEL openssl3-devel package installs in non-default paths, so we have to +# point CMake at them; on Rocky/RHEL 9+ and Ubuntu 22.04+ the default +# OpenSSL is already 3.x and no hints are needed. +if [ -f /etc/os-release ]; then + . /etc/os-release + if [[ "$ID" == "rocky" || "$ID" == "centos" || "$ID" == "rhel" || "$ID" == "fedora" ]] && \ + [[ "${VERSION_ID%%.*}" == "8" ]]; then + SKBUILD_CMAKE_ARGS="${SKBUILD_CMAKE_ARGS};-DOPENSSL_INCLUDE_DIR=/usr/include/openssl3;-DOPENSSL_SSL_LIBRARY=/usr/lib64/openssl3/libssl.so;-DOPENSSL_CRYPTO_LIBRARY=/usr/lib64/openssl3/libcrypto.so" + fi +fi + # For pull requests we are enabling assert mode. if [ "$RAPIDS_BUILD_TYPE" = "pull-request" ]; then echo "Building in assert mode" @@ -72,6 +88,14 @@ EXCLUDE_ARGS=( --exclude "libnvJitLink*" --exclude "librapids_logger.so" --exclude "librmm.so" + # OpenSSL 3 is intentionally NOT bundled. Resolving libssl.so.3 / libcrypto.so.3 + # at runtime via the host (or container image) keeps libcrypto and the FIPS + # provider (system or mounted) byte-version-matched, which is required for + # the FIPS provider's HMAC integrity check and avoids loading two libcrypto.so.3 + # in the same process. Hosts must provide libssl.so.3 / libcrypto.so.3 (Ubuntu + # 22.04+, RHEL/Rocky 9+, manylinux_2_28+ with openssl3, Debian 12+). + --exclude "libssl.so.3" + --exclude "libcrypto.so.3" ) ci/build_wheel.sh libcuopt ${package_dir} diff --git a/ci/test_wheel_cuopt.sh b/ci/test_wheel_cuopt.sh index d620c0d90..b4830ec3e 100755 --- a/ci/test_wheel_cuopt.sh +++ b/ci/test_wheel_cuopt.sh @@ -9,6 +9,11 @@ set -euo pipefail # so those constraints will affect all future 'pip install' calls source rapids-init-pip +# Make sure libssl.so.3 / libcrypto.so.3 are on the linker path before any +# 'import cuopt'. cuopt wheels link OpenSSL 3 and don't bundle it; on Rocky 8 +# the runtime needs to come from EPEL (no-op on distros that already ship it). +bash "$(dirname "$(realpath "${BASH_SOURCE[0]}")")/utils/install_openssl3_runtime.sh" + # Download the packages built in the previous step RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")" CUOPT_SH_CLIENT_WHEELHOUSE=$(RAPIDS_PY_WHEEL_NAME="cuopt_sh_client" RAPIDS_PY_WHEEL_PURE="1" rapids-download-wheels-from-github python) diff --git a/ci/test_wheel_cuopt_server.sh b/ci/test_wheel_cuopt_server.sh index 29ce0fac3..72a800187 100755 --- a/ci/test_wheel_cuopt_server.sh +++ b/ci/test_wheel_cuopt_server.sh @@ -7,6 +7,11 @@ set -eou pipefail source rapids-init-pip +# Make sure libssl.so.3 / libcrypto.so.3 are on the linker path before any +# 'import cuopt'. cuopt wheels link OpenSSL 3 and don't bundle it; on Rocky 8 +# the runtime needs to come from EPEL (no-op on distros that already ship it). +bash "$(dirname "$(realpath "${BASH_SOURCE[0]}")")/utils/install_openssl3_runtime.sh" + # Download the packages built in the previous step RAPIDS_PY_CUDA_SUFFIX="$(rapids-wheel-ctk-name-gen "${RAPIDS_CUDA_VERSION}")" CUOPT_SERVER_WHEELHOUSE=$(RAPIDS_PY_WHEEL_NAME="cuopt_server_${RAPIDS_PY_CUDA_SUFFIX}" RAPIDS_PY_WHEEL_PURE="1" rapids-download-wheels-from-github python) diff --git a/ci/utils/install_openssl3_runtime.sh b/ci/utils/install_openssl3_runtime.sh new file mode 100755 index 000000000..db8707a1d --- /dev/null +++ b/ci/utils/install_openssl3_runtime.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +# Install OpenSSL 3 runtime libraries when the host distro doesn't ship them +# by default. cuopt wheels DT_NEEDED libssl.so.3 / libcrypto.so.3 and do NOT +# bundle them (see docs/openssl3-runtime-requirements.md), so the test image +# has to provide them at runtime. +# +# Coverage: +# - Rocky/RHEL/Alma 8: needs openssl3 from EPEL (default OpenSSL is 1.0.x). +# - Other distros (Ubuntu 22.04+, Debian 12+, RHEL/Rocky 9+, Fedora 36+): +# already ship libssl.so.3 / libcrypto.so.3 — no-op. +# +# Safe to run unconditionally; only takes action where needed. + +set -euo pipefail + +if [ ! -f /etc/os-release ]; then + exit 0 +fi + +. /etc/os-release + +case "${ID:-}" in + rocky|rhel|centos|almalinux) + if [[ "${VERSION_ID%%.*}" == "8" ]]; then + echo "==> Installing OpenSSL 3 runtime from EPEL (Rocky/RHEL/Alma 8)" + if ! rpm -q epel-release >/dev/null 2>&1; then + dnf install -y -q epel-release + fi + # 'openssl3' is the runtime package; it drops libssl.so.3 / + # libcrypto.so.3 into /usr/lib64 and ldconfig picks them up + # automatically. ('openssl3-devel' is what the build host uses.) + dnf install -y -q openssl3 + ldconfig_out="$(ldconfig -p)" + if ! grep -q "libssl\.so\.3" <<<"${ldconfig_out}" || \ + ! grep -q "libcrypto\.so\.3" <<<"${ldconfig_out}"; then + echo "ERROR: libssl.so.3 / libcrypto.so.3 still not on linker path" >&2 + exit 1 + fi + fi + ;; + *) + # Ubuntu 22.04+, Debian 12+, etc. ship OpenSSL 3 by default; nothing to do. + ;; +esac diff --git a/ci/utils/install_protobuf_grpc.sh b/ci/utils/install_protobuf_grpc.sh index 74a837b9f..a334d7f96 100755 --- a/ci/utils/install_protobuf_grpc.sh +++ b/ci/utils/install_protobuf_grpc.sh @@ -90,6 +90,16 @@ echo " Build dir: ${BUILD_DIR}" echo " Skip deps: ${SKIP_DEPS}" echo "==============================================" +# OpenSSL 3 hints for CMake's find_package(OpenSSL). +# +# On Rocky/RHEL 8 the default 'openssl-devel' is still 1.1.1k, so we install +# 'openssl3-devel' from EPEL. That package installs in non-default paths +# (/usr/include/openssl3 and /usr/lib64/openssl3) so we have to point CMake +# at them explicitly. On Rocky/RHEL 9+ and Ubuntu 22.04+ the default OpenSSL +# devel package is already 3.x, so no special handling is needed. +OPENSSL_INCLUDE_DIR_HINT="" +OPENSSL_LIB_DIR_HINT="" + # Install system dependencies if not skipped if [ "${SKIP_DEPS}" = false ]; then echo "" @@ -100,11 +110,35 @@ if [ "${SKIP_DEPS}" = false ]; then # Enable PowerTools (Rocky 8) or CRB (Rocky 9) for some packages if [[ "${VERSION_ID%%.*}" == "8" ]]; then dnf config-manager --set-enabled powertools || dnf config-manager --set-enabled PowerTools || true + # EPEL provides 'openssl3-devel' in parallel with the system OpenSSL 1.1.x. + dnf install -y epel-release + dnf install -y git cmake ninja-build gcc gcc-c++ openssl3-devel zlib-devel c-ares-devel + OPENSSL_INCLUDE_DIR_HINT="/usr/include/openssl3" + OPENSSL_LIB_DIR_HINT="/usr/lib64/openssl3" elif [[ "${VERSION_ID%%.*}" == "9" ]]; then dnf config-manager --set-enabled crb || true + dnf install -y git cmake ninja-build gcc gcc-c++ openssl-devel zlib-devel c-ares-devel + elif [[ "$ID" == "fedora" ]]; then + # Fedora 36+ ships OpenSSL 3.x as the default 'openssl-devel'. + dnf install -y git cmake ninja-build gcc gcc-c++ openssl-devel zlib-devel c-ares-devel + else + echo "ERROR: ${PRETTY_NAME:-$ID $VERSION_ID} is not a supported RHEL-family release for OpenSSL 3 builds." >&2 + echo "Supported: Rocky/RHEL/CentOS/Alma 8 or 9, or Fedora. Re-run with --skip-deps to bypass." >&2 + exit 1 fi - dnf install -y git cmake ninja-build gcc gcc-c++ openssl-devel zlib-devel c-ares-devel elif [[ "$ID" == "ubuntu" || "$ID" == "debian" ]]; then + # The default 'libssl-dev' package is OpenSSL 3.x on Ubuntu 22.04+ + # and Debian 12+. Older releases (Ubuntu 20.04, Debian 11) ship + # OpenSSL 1.1.1, which we deliberately do not link against (see top + # of file). Refuse to proceed there rather than silently regress. + DISTRO_MAJOR="${VERSION_ID%%.*}" + if [[ "$ID" == "ubuntu" && "${DISTRO_MAJOR}" -lt 22 ]] || \ + [[ "$ID" == "debian" && "${DISTRO_MAJOR}" -lt 12 ]]; then + echo "ERROR: ${PRETTY_NAME:-$ID $VERSION_ID} ships OpenSSL 1.1; cuopt requires OpenSSL 3." >&2 + echo "Upgrade to Ubuntu 22.04+ / Debian 12+, or install OpenSSL 3 manually" >&2 + echo "(e.g. via PPA or backport) and re-run this script with --skip-deps." >&2 + exit 1 + fi apt-get update apt-get install -y git cmake ninja-build g++ libssl-dev zlib1g-dev libc-ares-dev else @@ -200,6 +234,17 @@ cmake --install "${PROTOBUF_BUILD}" echo "" echo "Building gRPC (using installed Abseil and Protobuf)..." +# When building on Rocky/RHEL 8, the EPEL openssl3-devel package installs +# headers in /usr/include/openssl3 and libs in /usr/lib64/openssl3. CMake's +# FindOpenSSL.cmake doesn't know to look there, so pass explicit hints. +GRPC_OPENSSL_HINTS=() +if [[ -n "${OPENSSL_INCLUDE_DIR_HINT}" && -n "${OPENSSL_LIB_DIR_HINT}" ]]; then + GRPC_OPENSSL_HINTS=( + "-DOPENSSL_INCLUDE_DIR=${OPENSSL_INCLUDE_DIR_HINT}" + "-DOPENSSL_SSL_LIBRARY=${OPENSSL_LIB_DIR_HINT}/libssl.so" + "-DOPENSSL_CRYPTO_LIBRARY=${OPENSSL_LIB_DIR_HINT}/libcrypto.so" + ) +fi cmake -S "${GRPC_SRC}" -B "${GRPC_BUILD}" -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_POSITION_INDEPENDENT_CODE=ON \ @@ -214,6 +259,7 @@ cmake -S "${GRPC_SRC}" -B "${GRPC_BUILD}" -G Ninja \ -DgRPC_SSL_PROVIDER=package \ -DgRPC_ZLIB_PROVIDER=package \ -DgRPC_CARES_PROVIDER=package \ + "${GRPC_OPENSSL_HINTS[@]}" \ -DCMAKE_PREFIX_PATH="${PREFIX}" \ -DCMAKE_INSTALL_PREFIX="${PREFIX}" cmake --build "${GRPC_BUILD}" --parallel diff --git a/cpp/src/mip_heuristics/mip_constants.hpp b/cpp/src/mip_heuristics/mip_constants.hpp index 34a4b07b2..f50c58194 100644 --- a/cpp/src/mip_heuristics/mip_constants.hpp +++ b/cpp/src/mip_heuristics/mip_constants.hpp @@ -21,3 +21,7 @@ #define CUOPT_MIP_RINS_REQUIRED_THREAD_COUNT 4 #define CUOPT_MIP_BATCH_PDLP_REQUIRED_THREAD_COUNT 3 #define CUOPT_MIP_CLIQUE_CUTS_REQUIRED_THREAD_COUNT 3 + +// MIP-only gate: skip the concurrent barrier when fewer threads are available than this +// (1 PDLP + 1 dual simplex + 1 barrier). Stand-alone LP always runs all three. +#define CUOPT_CONCURRENT_LP_BARRIER_REQUIRED_THREAD_COUNT 3 diff --git a/cpp/src/pdlp/solve.cu b/cpp/src/pdlp/solve.cu index 43d507b2f..076952811 100644 --- a/cpp/src/pdlp/solve.cu +++ b/cpp/src/pdlp/solve.cu @@ -37,6 +37,7 @@ #include #include +#include #include #include @@ -56,11 +57,12 @@ #include +#include + #include #include #include #include -#include #include #define CUOPT_LOG_CONDITIONAL_INFO(condition, ...) \ @@ -1526,10 +1528,18 @@ optimization_problem_solution_t run_concurrent( // Make sure allocations are done on the original stream problem.handle_ptr->sync_stream(); + // Stand-alone LP always runs all three concurrently. MIP gates the barrier so we don't + // overshoot num_cpu_threads (need 1 PDLP + 1 dual simplex + 1 barrier). + const int available_threads = omp_in_parallel() ? omp_get_num_threads() : omp_get_max_threads(); + const bool enable_barrier = + !settings.inside_mip || available_threads >= CUOPT_CONCURRENT_LP_BARRIER_REQUIRED_THREAD_COUNT; + if (settings.num_gpus > 1) { int device_count = raft::device_setter::get_device_count(); - CUOPT_LOG_CONDITIONAL_INFO( - !settings.inside_mip, "Running PDLP and Barrier on %d GPUs", device_count); + CUOPT_LOG_CONDITIONAL_INFO(!settings.inside_mip, + "Running PDLP%s on %d GPUs", + enable_barrier ? " and Barrier" : "", + device_count); cuopt_expects( device_count > 1, error_type_t::RuntimeError, "Multi-GPU mode requires at least 2 GPUs"); } @@ -1539,82 +1549,114 @@ optimization_problem_solution_t run_concurrent( // capture off dual_simplex::user_problem_t dual_simplex_problem = cuopt_problem_to_user_problem(problem.handle_ptr, problem); - // Create a thread for dual simplex + // Dual simplex / barrier results — written by tasks, read after the taskgroup barrier. std::unique_ptr< std::tuple, dual_simplex::lp_status_t, f_t, f_t, f_t>> sol_dual_simplex_ptr; - std::thread dual_simplex_thread; std::exception_ptr dual_simplex_exception; auto request_concurrent_halt = [&settings_pdlp]() { if (settings_pdlp.concurrent_halt != nullptr) { settings_pdlp.concurrent_halt->store(1); } }; - if (!settings.inside_mip) { - dual_simplex_thread = std::thread([&]() { - try { - run_dual_simplex_thread( - dual_simplex_problem, settings_pdlp, sol_dual_simplex_ptr, timer); - } catch (...) { - dual_simplex_exception = std::current_exception(); - request_concurrent_halt(); - } - }); - } - // Create a thread for barrier. - // The barrier handle is owned here so that its destructor runs on the - // main thread after PDLP finishes. cublasDestroy internally calls cudaDeviceSynchronize, which - // is globally forbidden while any stream is in graph capture mode. + // Owned at parent scope so its destructor runs on the dispatching thread after the taskgroup + // joins every spawned task — cublasDestroy internally calls cudaDeviceSynchronize, which is + // globally forbidden while any stream is in graph capture mode. Construction happens inside + // the barrier task body below: capture invalidation caused by another thread's first-use + // library init is now recovered by manual_cuda_graph_t::run, so the previous main-thread + // preflight (eager handle construction + cuDSS warmup) is no longer needed. std::unique_ptr barrier_handle_ptr; + if (!enable_barrier) { + CUOPT_LOG_DEBUG("MIP: skipping concurrent barrier, %d threads available < %d required.", + available_threads, + CUOPT_CONCURRENT_LP_BARRIER_REQUIRED_THREAD_COUNT); + } + + // Dispatch barrier + dual simplex as OMP tasks (not std::threads) so they consume slots from + // the upstream MIP OMP team and respect num_cpu_threads. PDLP runs synchronously on the + // dispatching thread; the taskgroup implicit barrier joins the tasks. std::unique_ptr< std::tuple, dual_simplex::lp_status_t, f_t, f_t, f_t>> sol_barrier_ptr; std::exception_ptr barrier_exception; - auto barrier_thread = std::thread([&]() { - try { - auto call_barrier_thread = [&]() { - rmm::cuda_stream_view barrier_stream = rmm::cuda_stream_per_thread; - barrier_handle_ptr = std::make_unique(barrier_stream); - auto barrier_problem = dual_simplex_problem; - barrier_problem.handle_ptr = barrier_handle_ptr.get(); - - run_barrier_thread(barrier_problem, settings_pdlp, sol_barrier_ptr, timer); - }; + std::exception_ptr pdlp_exception; + optimization_problem_solution_t sol_pdlp{pdlp_termination_status_t::NumericalError, + problem.handle_ptr->get_stream()}; + + auto dispatch_concurrent_solvers = [&]() { +#pragma omp taskgroup + { + // Barrier task — always on for stand-alone LP, gated on enable_barrier for MIP. + if (enable_barrier) { +#pragma omp task default(shared) + { + try { + auto call_barrier_thread = [&]() { + rmm::cuda_stream_view barrier_stream = rmm::cuda_stream_per_thread; + barrier_handle_ptr = std::make_unique(barrier_stream); + auto barrier_problem = dual_simplex_problem; + barrier_problem.handle_ptr = barrier_handle_ptr.get(); + run_barrier_thread(barrier_problem, settings_pdlp, sol_barrier_ptr, timer); + }; + if (settings.num_gpus > 1) { + problem.handle_ptr->sync_stream(); + raft::device_setter device_setter(1); // Scoped variable + CUOPT_LOG_DEBUG("Barrier device: %d", device_setter.get_current_device()); + call_barrier_thread(); + } else { + call_barrier_thread(); + } + } catch (...) { + barrier_exception = std::current_exception(); + request_concurrent_halt(); + } + } + } + + // Dual simplex task — skipped from MIP (B&B already drives it separately). + if (!settings.inside_mip) { +#pragma omp task default(shared) + { + try { + run_dual_simplex_thread( + dual_simplex_problem, settings_pdlp, sol_dual_simplex_ptr, timer); + } catch (...) { + dual_simplex_exception = std::current_exception(); + request_concurrent_halt(); + } + } + } + if (settings.num_gpus > 1) { - problem.handle_ptr->sync_stream(); - raft::device_setter device_setter(1); // Scoped variable - CUOPT_LOG_DEBUG("Barrier device: %d", device_setter.get_current_device()); - call_barrier_thread(); - } else { - call_barrier_thread(); + CUOPT_LOG_DEBUG("PDLP device: %d", raft::device_setter::get_current_device()); } - } catch (...) { - barrier_exception = std::current_exception(); - request_concurrent_halt(); - } - }); - if (settings.num_gpus > 1) { - CUOPT_LOG_DEBUG("PDLP device: %d", raft::device_setter::get_current_device()); - } + // PDLP runs synchronously on the dispatcher, concurrently with the queued tasks. + try { + sol_pdlp = run_pdlp(problem, settings_pdlp, timer, is_batch_mode); + } catch (...) { + pdlp_exception = std::current_exception(); + request_concurrent_halt(); + } + // Implicit taskgroup barrier joins all spawned tasks below. + } + }; - // Run pdlp in the main thread. - // Must join all spawned threads before leaving this scope, even on exception, - // because destroying a joinable std::thread calls std::terminate(). - std::exception_ptr pdlp_exception; - optimization_problem_solution_t sol_pdlp{pdlp_termination_status_t::NumericalError, - problem.handle_ptr->get_stream()}; - try { - sol_pdlp = run_pdlp(problem, settings_pdlp, timer, is_batch_mode); - } catch (...) { - pdlp_exception = std::current_exception(); - request_concurrent_halt(); + if (omp_in_parallel()) { + // Reuse the upstream OMP team (e.g. solve_mip's outer parallel region). + dispatch_concurrent_solvers(); + } else { + // Stand-alone LP: stand up a local team sized for 1 dispatcher + 1 per spawned task. + const int num_workers = 1 + (settings.inside_mip ? 0 : 1) + (enable_barrier ? 1 : 0); +#pragma omp parallel num_threads(num_workers) default(shared) + { +#pragma omp single + { + dispatch_concurrent_solvers(); + } + } } - // Wait for dual simplex thread to finish - if (dual_simplex_thread.joinable()) { dual_simplex_thread.join(); } - - if (barrier_thread.joinable()) { barrier_thread.join(); } - // At this point, it is safe to destroy the barrier context since we're outside of any PDLP graph - // capture. + // Destroy on the dispatching thread, post-join: cublasDestroy → cudaDeviceSynchronize must + // not fire during any graph capture. barrier_handle_ptr.reset(); if (pdlp_exception) { std::rethrow_exception(pdlp_exception); } @@ -1634,14 +1676,17 @@ optimization_problem_solution_t run_concurrent( : optimization_problem_solution_t{pdlp_termination_status_t::ConcurrentLimit, problem.handle_ptr->get_stream()}; - // copy the barrier solution to the device - auto sol_barrier = convert_dual_simplex_sol(problem, - std::get<0>(*sol_barrier_ptr), - std::get<1>(*sol_barrier_ptr), - std::get<2>(*sol_barrier_ptr), - std::get<3>(*sol_barrier_ptr), - std::get<4>(*sol_barrier_ptr), - method_t::Barrier); + // copy the barrier solution to the device (sentinel when the barrier task was skipped). + auto sol_barrier = enable_barrier ? convert_dual_simplex_sol(problem, + std::get<0>(*sol_barrier_ptr), + std::get<1>(*sol_barrier_ptr), + std::get<2>(*sol_barrier_ptr), + std::get<3>(*sol_barrier_ptr), + std::get<4>(*sol_barrier_ptr), + method_t::Barrier) + : optimization_problem_solution_t{ + pdlp_termination_status_t::ConcurrentLimit, + problem.handle_ptr->get_stream()}; f_t end_time = timer.elapsed_time(); CUOPT_LOG_CONDITIONAL_INFO(!settings.inside_mip, "Concurrent time: %.3fs", end_time); @@ -1739,9 +1784,9 @@ optimization_problem_solution_t solve_qp(optimization_problem_t solve_lp( } try { - if (!settings_const.inside_mip) print_version_info(); - pdlp_solver_settings_t settings(settings_const); // Create log stream for file logging and add it to default logger init_logger_t log(settings.log_file, settings.log_to_console); + if (!settings_const.inside_mip) print_version_info(); + // Init libraries before to not include it in solve time // This needs to be called before pdlp is initialized init_handler(op_problem.get_handle_ptr()); diff --git a/cpp/src/pdlp/utilities/cython_solve.cu b/cpp/src/pdlp/utilities/cython_solve.cu index 738b36d4d..ec3cc2862 100644 --- a/cpp/src/pdlp/utilities/cython_solve.cu +++ b/cpp/src/pdlp/utilities/cython_solve.cu @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -258,6 +259,11 @@ std::pair>, double> call_batch_solve( return solve_batch_remote(data_models, solver_settings); } + // Hold the logger configuration for the whole batch so that worker-local + // init_logger_t instances inside solve_lp() reuse it. + init_logger_t batch_log(solver_settings->get_pdlp_settings().log_file, + solver_settings->get_pdlp_settings().log_to_console); + const std::size_t size = data_models.size(); std::vector> list(size); diff --git a/docs/cuopt/source/faq.rst b/docs/cuopt/source/faq.rst index 015a56d4f..69a34dc17 100644 --- a/docs/cuopt/source/faq.rst +++ b/docs/cuopt/source/faq.rst @@ -81,6 +81,27 @@ General FAQ #. The complete round-trip solve time might be more than what was set. +.. dropdown:: Why am I getting "libssl.so.3: cannot open shared object file" or "libcrypto.so.3: cannot open shared object file" when importing cuOpt? + + The cuOpt wheel links against OpenSSL 3 and intentionally does not bundle ``libssl.so.3`` / ``libcrypto.so.3``; the host must provide them. This keeps libcrypto and any FIPS provider (system or mounted) byte-version-matched at runtime. + + Most modern Linux distributions provide OpenSSL 3 out of the box: Ubuntu 22.04+, Debian 12+, RHEL/Rocky/Alma 9+, and Fedora 36+. For older distributions: + + - **RHEL / Rocky / Alma / CentOS 8**: install the ``openssl3`` runtime package from EPEL: + + .. code-block:: bash + + dnf install -y epel-release + dnf install -y openssl3 + # Verify + ldconfig -p | grep -E 'libssl\.so\.3|libcrypto\.so\.3' + + - **Ubuntu 20.04**: install OpenSSL 3 from a PPA or backport, or use the cuOpt container image (Ubuntu 22.04 base) instead. + + - **Other distributions**: install your distribution's OpenSSL 3 development/runtime package, or use the cuOpt container. + + After installing, re-run ``pip install`` (or restart the Python process) and the import should succeed. + .. dropdown:: Why am I getting "libcuopt.so: cannot open shared object file: No such file or directory" error? This error indicates that the cuOpt shared library is not found. Please check the following: diff --git a/docs/cuopt/source/system-requirements.rst b/docs/cuopt/source/system-requirements.rst index bbc37f26e..26dcf84b2 100644 --- a/docs/cuopt/source/system-requirements.rst +++ b/docs/cuopt/source/system-requirements.rst @@ -28,6 +28,12 @@ Dependencies are installed automatically when using the pip and Conda installati * Python: - >= 3.11.* and <= 3.14.* + * OpenSSL: + - 3.x runtime (``libssl.so.3`` and ``libcrypto.so.3``) must be present on the host + - Shipped by default on Ubuntu 22.04+, Debian 12+, RHEL/Rocky/Alma 9+, Fedora 36+ + - On RHEL/Rocky/Alma 8, install the ``openssl3`` package from EPEL (see :doc:`faq`) + - On Ubuntu 20.04, install OpenSSL 3 from a PPA/backport or use the cuOpt container + * NVIDIA drivers: - 525.60.13+ (Linux) - 527.41+ (Windows)