Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4d2ec82
port fast mps parser tp tree
aliceb-nv Jun 3, 2026
68daf3d
thread count cap
aliceb-nv Jun 3, 2026
eb0e285
fix crashes, more opti
aliceb-nv Jun 5, 2026
91742cd
improved iee754 compliant float parsing, warn on nnz > INT_MAX
aliceb-nv Jun 5, 2026
be97a05
decode performance metrics
aliceb-nv Jun 8, 2026
1e4d7c9
lots of cleanup
aliceb-nv Jun 10, 2026
8e01e28
moved perf counters
aliceb-nv Jun 10, 2026
94bfbc7
extend the lz4 decompression to the regular parser, more cleanup and …
aliceb-nv Jun 11, 2026
62c8dcd
further cleanup
aliceb-nv Jun 11, 2026
79e958e
cleanup for clarity
aliceb-nv Jun 12, 2026
2614137
more cleanup, fix som eedge case failures
aliceb-nv Jun 12, 2026
9185d7a
ai review comments
aliceb-nv Jun 12, 2026
a1e14d5
ai review
aliceb-nv Jun 12, 2026
fe0aa31
Comments on the build flags
aliceb-nv Jun 12, 2026
cfaccc3
gate O_DIRECT behind non-nfs, add missing license notices
aliceb-nv Jun 13, 2026
7220838
fix bitwise comps, more cleanup and comments
aliceb-nv Jun 15, 2026
1990c06
AI review comments
aliceb-nv Jun 15, 2026
d7358f6
fix sloppy fix
aliceb-nv Jun 15, 2026
b1abd6f
Merge branch 'main' into fast-mps-parser-final
aliceb-nv Jun 15, 2026
38e1d59
Merge branch 'main' into fast-mps-parser-final
aliceb-nv Jun 23, 2026
225ae33
review comments
aliceb-nv Jun 23, 2026
3f8f9fb
hopefully fix wheel CI builds
aliceb-nv Jun 23, 2026
57e48a2
wheel fix
aliceb-nv Jun 23, 2026
2c5fec2
wheel fix, hopefully
aliceb-nv Jun 23, 2026
aa9248a
wheel build fixes
aliceb-nv Jul 1, 2026
5fec3ab
wheel build fixes
aliceb-nv Jul 1, 2026
9d23c2a
fix build
aliceb-nv Jul 1, 2026
634a549
style
aliceb-nv Jul 1, 2026
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
11 changes: 10 additions & 1 deletion ci/build_wheel_libcuopt.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,16 @@ fi
# Install Protobuf + gRPC (protoc + grpc_cpp_plugin)
bash ci/utils/install_protobuf_grpc.sh

export SKBUILD_CMAKE_ARGS="-DCUOPT_BUILD_WHEELS=ON;-DDISABLE_DEPRECATION_WARNING=ON"
# The fast MPS parser requires OpenMP 5 symbols from the active GCC toolset. Pin FindOpenMP to
# that compiler's libgomp instead of the older system runtime on Rocky/RHEL wheel builders.
CXX_COMPILER="${CXX:-g++}"
GOMP_LIBRARY="$("${CXX_COMPILER}" -print-file-name=libgomp.so)"
if [[ "${GOMP_LIBRARY}" != /* || ! -f "${GOMP_LIBRARY}" ]]; then
echo "Could not resolve libgomp from ${CXX_COMPILER}: '${GOMP_LIBRARY}'" >&2
exit 1
fi

export SKBUILD_CMAKE_ARGS="-DCUOPT_BUILD_WHEELS=ON;-DDISABLE_DEPRECATION_WARNING=ON;-DOpenMP_gomp_LIBRARY:FILEPATH=${GOMP_LIBRARY}"

# OpenSSL 3 hints for libcuopt's own find_package(OpenSSL).
#
Expand Down
57 changes: 43 additions & 14 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,6 @@ list(APPEND CUOPT_CUDA_FLAGS -Xfatbin=-compress-all)
if (CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 12.9 AND CMAKE_CUDA_COMPILER_VERSION VERSION_LESS 13.0)
list(APPEND CUOPT_CUDA_FLAGS -Xfatbin=--compress-level=3)
endif ()
list(APPEND CUOPT_CUDA_FLAGS -fopenmp)

# Add jobserver flags for parallel compilation if PARALLEL_LEVEL is set
if (PARALLEL_LEVEL AND NOT "${PARALLEL_LEVEL}" STREQUAL "")
Expand All @@ -198,13 +197,14 @@ if (PARALLEL_LEVEL AND NOT "${PARALLEL_LEVEL}" STREQUAL "")
endif ()
endif ()

# The MIP solver requires OpenMP to work
find_package(OpenMP REQUIRED)
# The MIP solver requires OpenMP for both C++ and CUDA host code.
rapids_find_package(OpenMP REQUIRED COMPONENTS CXX CUDA BUILD_EXPORT_SET cuopt-exports)
message(VERBOSE "cuOpt: OpenMP found in ${OpenMP_CXX_INCLUDE_DIRS}")

# MPS/QPS parser supports compressed inputs via bzip2 and zlib
# MPS/QPS parser supports compressed inputs via bzip2, zlib and lz4
option(CUOPT_PARSER_WITH_BZIP2 "Build MPS parser with bzip2 decompression" ON)
option(CUOPT_PARSER_WITH_ZLIB "Build MPS parser with zlib decompression" ON)
option(CUOPT_PARSER_WITH_LZ4 "Build experimental fast MPS parser with LZ4 decompression" ON)
if (CUOPT_PARSER_WITH_BZIP2)
find_package(BZip2 REQUIRED)
add_compile_definitions(MPS_PARSER_WITH_BZIP2)
Expand All @@ -213,6 +213,10 @@ if (CUOPT_PARSER_WITH_ZLIB)
find_package(ZLIB REQUIRED)
add_compile_definitions(MPS_PARSER_WITH_ZLIB)
endif ()
if (CUOPT_PARSER_WITH_LZ4)
# No headers or link target needed; the experimental reader loads one liblz4 symbol at runtime.
add_compile_definitions(MPS_PARSER_WITH_LZ4)
endif ()

# Debug options
if (CMAKE_BUILD_TYPE MATCHES Debug)
Expand Down Expand Up @@ -250,6 +254,20 @@ else ()
find_package(RAFT REQUIRED)
endif ()

rapids_cpm_find(simde 0.8.2
CPM_ARGS
GIT_REPOSITORY https://github.com/simd-everywhere/simde.git
GIT_TAG v0.8.2
GIT_SHALLOW TRUE
DOWNLOAD_ONLY TRUE
)

if (NOT TARGET simde::simde)
add_library(simde::simde INTERFACE IMPORTED GLOBAL)
set_target_properties(simde::simde
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${simde_SOURCE_DIR}")
endif ()

FetchContent_Declare(
papilo
GIT_REPOSITORY "https://github.com/scipopt/papilo.git"
Expand Down Expand Up @@ -436,6 +454,7 @@ if (BUILD_TESTS)
endif ()

set(CUOPT_SRC_FILES)
set(MPS_FAST_SRC_FILES)
add_subdirectory(src)

# nvcc 13.0.3 ICE (signal 11) compiling sliding_window.cu with 7 GPU architectures;
Expand All @@ -445,14 +464,24 @@ set_source_files_properties(
PROPERTIES COMPILE_OPTIONS "--split-compile=0")

if (HOST_LINEINFO)
set_source_files_properties(${CUOPT_SRC_FILES} DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTIES COMPILE_OPTIONS "-g1")
set_source_files_properties(${CUOPT_SRC_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTIES COMPILE_OPTIONS "-g1")
endif ()

# Needed for the fast MPS parser, available on all x86-64-v3 compliant x86 CPUs (essentially since Haswell ~2013)
if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64|amd64)$" AND
CMAKE_CXX_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
set_property(SOURCE ${MPS_FAST_SRC_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
APPEND PROPERTY COMPILE_OPTIONS "-mbmi2;-mavx2;-msse4.2")
endif ()

# TODO: figure out a set of flags for ARM that fits the range of CPUs we wish to support (neoverse?)
# NEON should be universal on aarch64 and enough for our purposes (parsing) though

# Apply -UNDEBUG only to solver source files (not gRPC infrastructure).
# Must happen before gRPC files are appended to CUOPT_SRC_FILES.
# Uses APPEND to preserve any existing per-file options (e.g. -g1 from HOST_LINEINFO).
if (DEFINE_ASSERT)
set_property(SOURCE ${CUOPT_SRC_FILES} DIRECTORY ${CMAKE_SOURCE_DIR}
set_property(SOURCE ${CUOPT_SRC_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
APPEND PROPERTY COMPILE_OPTIONS "-UNDEBUG")
endif ()

Expand All @@ -477,7 +506,7 @@ if (NOT SKIP_GRPC_BUILD)
# The conda-forge abseil shared library is built with NDEBUG and does not
# export that symbol (abseil-cpp#1624). Without this, Debug builds fail
# at runtime with "undefined symbol: absl::…::Mutex::Dtor".
set_property(SOURCE ${GRPC_INFRA_FILES} DIRECTORY ${CMAKE_SOURCE_DIR}
set_property(SOURCE ${GRPC_INFRA_FILES} DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
APPEND PROPERTY COMPILE_OPTIONS "-DNDEBUG")
endif (NOT SKIP_GRPC_BUILD)

Expand Down Expand Up @@ -559,8 +588,7 @@ add_dependencies(cuopt PSLP)
set(CUOPT_PRIVATE_CUDA_LIBS
CUDA::curand
CUDA::cusolver
TBB::tbb
OpenMP::OpenMP_CXX)
TBB::tbb)

list(PREPEND CUOPT_PRIVATE_CUDA_LIBS CUDA::cublasLt)

Expand Down Expand Up @@ -603,11 +631,16 @@ target_link_libraries(cuopt
${CUDSS_LIB_FILE}
PRIVATE
${CUOPT_PRIVATE_CUDA_LIBS}
simde::simde
OpenMP::OpenMP_CXX
OpenMP::OpenMP_CUDA
$<$<BOOL:${CUOPT_ENABLE_GRPC}>:protobuf::libprotobuf>
$<$<BOOL:${CUOPT_ENABLE_GRPC}>:gRPC::grpc++>
INTERFACE
"$<BUILD_INTERFACE:OpenMP::OpenMP_CXX>"
"$<BUILD_INTERFACE:OpenMP::OpenMP_CUDA>"
)


# ##################################################################################################
# - generate tests --------------------------------------------------------------------------------
if (BUILD_TESTS)
Expand Down Expand Up @@ -744,7 +777,6 @@ if (NOT BUILD_LP_ONLY)
target_link_libraries(cuopt_cli
PUBLIC
cuopt
OpenMP::OpenMP_CXX
${CUDSS_LIBRARIES}
TBB::tbb
PRIVATE
Expand Down Expand Up @@ -786,7 +818,6 @@ if (BUILD_MIP_BENCHMARKS AND NOT BUILD_LP_ONLY)
target_link_libraries(solve_MIP
PUBLIC
cuopt
OpenMP::OpenMP_CXX
PRIVATE
)
if (NOT DEFINED INSTALL_TARGET OR "${INSTALL_TARGET}" STREQUAL "")
Expand Down Expand Up @@ -816,7 +847,6 @@ if (BUILD_LP_BENCHMARKS)
target_link_libraries(solve_LP
PUBLIC
cuopt
OpenMP::OpenMP_CXX
PRIVATE
)
if (NOT DEFINED INSTALL_TARGET OR "${INSTALL_TARGET}" STREQUAL "")
Expand Down Expand Up @@ -869,7 +899,6 @@ if (NOT SKIP_GRPC_BUILD)
target_link_libraries(cuopt_grpc_server
PUBLIC
cuopt
OpenMP::OpenMP_CXX
PRIVATE
protobuf::libprotobuf
gRPC::grpc++
Expand Down
25 changes: 21 additions & 4 deletions cpp/cuopt_cli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,13 @@ inline cuopt::init_logger_t dummy_logger(
* .mps/.qps and their .gz/.bz2 variants → MPS parser;
* anything else is rejected.
* @param initial_solution_file Path to initial solution file in SOL format
* @param mps_reader MPS reader implementation selected by the CLI
* @param settings Merged solver settings (config file loaded in main, then CLI overrides applied)
*/
int run_single_file(const std::string& file_path,
const std::string& initial_solution_file,
bool solve_relaxation,
cuopt::mathematical_optimization::io::mps_reader_type_t mps_reader,
cuopt::mathematical_optimization::solver_settings_t<int, double>& settings)
{
cuopt::init_logger_t log(settings.get_parameter<std::string>(CUOPT_LOG_FILE),
Expand All @@ -108,7 +110,8 @@ int run_single_file(const std::string& file_path,
{
CUOPT_LOG_INFO("Reading file %s", base_filename.c_str());
try {
mps_data_model = cuopt::mathematical_optimization::io::read<int, double>(file_path);
mps_data_model =
cuopt::mathematical_optimization::io::read<int, double>(file_path, mps_reader);
} catch (const std::logic_error& e) {
CUOPT_LOG_ERROR("Parser exception: %s", e.what());
parsing_failed = true;
Expand Down Expand Up @@ -287,8 +290,8 @@ int main(int argc, char* argv[])
program.add_argument("filename")
.help(
"input problem file; format dispatched by extension (case-insensitive). "
"Supported: .lp, .mps, .qps and their .gz / .bz2 compressed variants "
"(e.g. .lp.gz, .mps.bz2, .qps.gz)")
"Supported: .lp, .mps, .qps and their .gz / .bz2 / .lz4 compressed variants "
"(e.g. .lp.gz, .mps.bz2, .qps.lz4).")
.nargs(1)
.required();

Expand All @@ -306,6 +309,14 @@ int main(int argc, char* argv[])
.help("path to parameter config file (key = value format, supports all parameters)")
.default_value(std::string(""));

program.add_argument("--mps-reader")
.help(
"MPS reader implementation: default uses the production parser; experimental-fast uses the "
"experimental SIMD parser for free-format LP/MIP/QP/QCQP (SOCP) .mps/.qps files and their "
".gz/.bz2/.lz4 compressed variants")
.default_value(std::string("default"))
.choices("default", "experimental-fast");

program.add_argument("--dump-hyper-params")
.help("print hyper-parameters only in config file format and exit")
.default_value(false)
Expand Down Expand Up @@ -406,6 +417,12 @@ int main(int argc, char* argv[])
const auto initial_solution_file = program.get<std::string>("--initial-solution");
const auto solve_relaxation = program.get<bool>("--relaxation");
const auto params_file = program.get<std::string>("--params-file");
const auto mps_reader_arg = program.get<std::string>("--mps-reader");

auto mps_reader = cuopt::mathematical_optimization::io::mps_reader_type_t::default_reader;
if (mps_reader_arg == "experimental-fast") {
mps_reader = cuopt::mathematical_optimization::io::mps_reader_type_t::fast_experimental;
}

cuopt::mathematical_optimization::solver_settings_t<int, double> settings;
try {
Expand Down Expand Up @@ -435,5 +452,5 @@ int main(int argc, char* argv[])
RAFT_CUDA_TRY(cudaSetDevice(0));
}

return run_single_file(file_name, initial_solution_file, solve_relaxation, settings);
return run_single_file(file_name, initial_solution_file, solve_relaxation, mps_reader, settings);
}
82 changes: 69 additions & 13 deletions cpp/include/cuopt/mathematical_optimization/io/parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,26 @@

#include <algorithm>
#include <cctype>
#include <cstring>
#include <stdexcept>
#include <string>
#include <string_view>

namespace cuopt::mathematical_optimization::io {

/**
* @brief Selects which MPS reader implementation should be used by dispatching entry points.
*
* The experimental fast reader is intentionally opt-in. It supports the same free-format
* MPS/QPS scope as read_mps(): LP, MIP, QP (QUADOBJ/QMATRIX), and QCQP/SOCP (QCMATRIX).
*/
enum class mps_reader_type_t { default_reader, fast_experimental };
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* @brief Reads the equation from an MPS or QPS file.
*
* The input file can be a plain text file in MPS-/QPS-format or a compressed MPS/QPS
* file (.mps.gz or .mps.bz2).
* file (.mps.gz, .mps.bz2, or .mps.lz4).
*
* Read this link http://lpsolve.sourceforge.net/5.5/mps-format.htm for more
* details on both free and fixed MPS format.
Expand All @@ -32,8 +41,8 @@ namespace cuopt::mathematical_optimization::io {
* - QMATRIX: Full symmetric quadratic objective matrix (alternative to QUADOBJ)
* - QCMATRIX: Symmetric quadratic terms for a named constraint row (QCQP)
*
* Note: Compressed MPS files .mps.gz, .mps.bz2 can only be read if the compression
* libraries zlib or libbzip2 are installed, respectively.
* Note: Compressed MPS files .mps.gz, .mps.bz2, and .mps.lz4 can only be read if
* zlib, libbzip2, or liblz4 are installed, respectively.
*
* @param[in] mps_file_path Path to MPS/QPSfile.
* @param[in] fixed_mps_format If MPS/QPS file should be parsed as fixed, false by default
Expand All @@ -43,6 +52,19 @@ template <typename i_t, typename f_t>
mps_data_model_t<i_t, f_t> read_mps(const std::string& mps_file_path,
bool fixed_mps_format = false);

/**
* @brief Reads an MPS/QPS problem with the experimental SIMD-optimized reader.
*
* Supports the same free-format LP/MIP/QP/QCQP (SOCP-relevant QCMATRIX) scope as read_mps().
* Fixed MPS format forcing is not supported. Accepts .mps/.qps and their .gz/.bz2/.lz4 variants
* (compression is detected from the file path, same as read_mps()).
*
* @param[in] mps_file_path Path to a raw or compressed .mps or .qps file.
* @return mps_data_model_t A fully formed LP/MIP/QP problem which represents the given file.
*/
template <typename i_t, typename f_t>
mps_data_model_t<i_t, f_t> read_mps_fast_experimental(const std::string& mps_file_path);

/**
* @brief Reads an MPS problem from in-memory file contents.
*
Expand Down Expand Up @@ -111,38 +133,72 @@ mps_data_model_t<i_t, f_t> read_lp_from_string(std::string_view lp_contents);
* @brief Reads an optimization problem from a file, dispatching on the file
* extension. Extension matching is case-insensitive.
*
* Routing:
* - .mps, .mps.gz, .mps.bz2, .qps, .qps.gz, .qps.bz2 → read_mps()
* - .lp, .lp.gz, .lp.bz2 → read_lp()
* Routing (case-insensitive extensions):
* - .mps, .mps.gz, .mps.bz2, .mps.lz4, .qps, .qps.gz, .qps.bz2, .qps.lz4
* → read_mps() when mps_reader == default_reader, or read_mps_fast_experimental()
* when mps_reader == fast_experimental (fixed_mps_format must be false)
* - .lp, .lp.gz, .lp.bz2, .lp.lz4 → read_lp()
* - anything else → std::logic_error
*
* This is the entry point of choice for user-facing tools (CLI, C API) that
* want both formats to "just work" without an explicit format flag.
*
* @param[in] path Path to the input file.
* @param[in] mps_reader Selects the MPS reader implementation for MPS/QPS inputs.
* @param[in] fixed_mps_format If the MPS/QPS reader should use fixed format;
* ignored for LP inputs. False by default.
* @return mps_data_model_t The parsed problem.
*/
template <typename i_t, typename f_t>
inline mps_data_model_t<i_t, f_t> read(const std::string& path, bool fixed_mps_format = false)
inline mps_data_model_t<i_t, f_t> read(const std::string& path,
mps_reader_type_t mps_reader,
bool fixed_mps_format = false)
{
std::string lower(path);
std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
if (lower.ends_with(".mps") || lower.ends_with(".mps.gz") || lower.ends_with(".mps.bz2") ||
lower.ends_with(".qps") || lower.ends_with(".qps.gz") || lower.ends_with(".qps.bz2")) {
return read_mps<i_t, f_t>(path, fixed_mps_format);
for (const char* compression_suffix : {".bz2", ".gz", ".lz4"}) {
if (lower.ends_with(compression_suffix)) {
lower.resize(lower.size() - std::strlen(compression_suffix));
break;
}
}
if (lower.ends_with(".lp") || lower.ends_with(".lp.gz") || lower.ends_with(".lp.bz2")) {
return read_lp<i_t, f_t>(path);
if (lower.ends_with(".mps") || lower.ends_with(".qps")) {
if (mps_reader == mps_reader_type_t::fast_experimental) {
if (fixed_mps_format) {
throw std::logic_error(
"experimental fast MPS reader does not support fixed MPS format forcing");
}
return read_mps_fast_experimental<i_t, f_t>(path);
}
return read_mps<i_t, f_t>(path, fixed_mps_format);
}
if (lower.ends_with(".lp")) { return read_lp<i_t, f_t>(path); }
throw std::logic_error(
"read: unrecognized input file extension. Supported (case-insensitive): "
".mps, .mps.gz, .mps.bz2, .qps, .qps.gz, .qps.bz2, .lp, .lp.gz, .lp.bz2. "
".mps, .mps.gz, .mps.bz2, .mps.lz4, .qps, .qps.gz, .qps.bz2, .qps.lz4, "
".lp, .lp.gz, .lp.bz2, .lp.lz4. "
"Given path: " +
path);
}

/**
* @brief Reads an optimization problem from a file, dispatching on the file
* extension. Extension matching is case-insensitive.
*
* Uses the default MPS reader. See the 3-argument read() overload for routing
* details and supported extensions.
*
* @param[in] path Path to the input file.
* @param[in] fixed_mps_format If the MPS/QPS reader should use fixed format;
* ignored for LP inputs. False by default.
* @return mps_data_model_t The parsed problem.
*/
template <typename i_t, typename f_t>
inline mps_data_model_t<i_t, f_t> read(const std::string& path, bool fixed_mps_format = false)
{
return read<i_t, f_t>(path, mps_reader_type_t::default_reader, fixed_mps_format);
}

} // namespace cuopt::mathematical_optimization::io
1 change: 1 addition & 0 deletions cpp/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ add_subdirectory(branch_and_bound)
add_subdirectory(cuts)

set(CUOPT_SRC_FILES ${CUOPT_SRC_FILES} ${UTIL_SRC_FILES} PARENT_SCOPE)
set(MPS_FAST_SRC_FILES ${MPS_FAST_SRC_FILES} PARENT_SCOPE)
Loading
Loading