refactor: make solver settings constructible without CUDA - #1803
Draft
ramakrishnap-nv wants to merge 1 commit into
Draft
refactor: make solver settings constructible without CUDA#1803ramakrishnap-nv wants to merge 1 commit into
ramakrishnap-nv wants to merge 1 commit into
Conversation
Two things forced CUDA on anything that merely constructed or inspected solver settings, even when it never touched a device. 1. pdlp_solver_settings_t held pdlp_warm_start_data_t by value. That type owns nine rmm::device_uvector, and its default constructor is out-of-line in a CUDA translation unit because device_uvector has no default ctor -- it needs a stream, and building even a zero-size one calls cudaGetDevice. So constructing settings pulled in libcuopt. It is now held by shared_ptr, allocated lazily via ensure_pdlp_warm_start_data(). shared_ptr rather than unique_ptr specifically: shared_ptr type-erases its deleter into the control block at construction, so a host-only translation unit can copy and destroy the member without the complete type. unique_ptr would only move the problem from the constructor to the destructor. The ~88 device-side uses inside set_pdlp_warm_start_data() are unchanged; a local reference alias keeps that code reading as before. 2. populate_from_data_model_view() inlined both the GPU and CPU warm-start paths in one if/else. The GPU direction is only reachable when handle != nullptr, but the compiler instantiated both branches into every translation unit including the header -- dragging convert_to_gpu_warmstart, convert_to_cpu_warmstart and pdlp_warm_start_data_t(view, stream) along with it. Split into apply_warmstart_gpu_target() (declared in the header, defined in optimization_problem.cu) and apply_warmstart_cpu_target() (host-only, inline), selected by a kHostOnly template parameter dispatched with `if constexpr`. The compile-time dispatch is the point: a host-only caller never *instantiates* the GPU branch, so it emits no reference to it. A runtime `if` would not help. Also moves the warm-start accessors that need no allocation into solver_settings_accessors.cpp, leaving the CUDA TU with only members that do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Ramakrishna Prabhu <ramakrishnap@nvidia.com>
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
This was referenced Aug 25, 2026
Collaborator
Author
|
/ok to test |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
3 of 4 toward a CUDA-free client library. Stacked on #1802.
This is the subtlest PR of the four — two small changes, each resting on a specific language guarantee. Worth reading closely; that's why it's separated out.
1. Warm-start data behind a
shared_ptrpdlp_solver_settings_theldpdlp_warm_start_data_tby value. That type owns ninermm::device_uvector, and its default constructor is out-of-line in a CUDA TU —device_uvectorhas no default ctor (it needs a stream), and constructing even a zero-size one callscudaGetDevice. So merely constructing settings pulled inlibcuopt.Now held by
shared_ptr, allocated lazily.Why
shared_ptrand notunique_ptr:shared_ptrtype-erases its deleter into the control block at construction, so a host-only TU can copy and destroy the member without the complete type.unique_ptrbakes the deleter into the type, which would just move the problem from the constructor to the destructor.The ~88 device-side uses inside
set_pdlp_warm_start_data()are untouched — a local reference alias keeps that code reading exactly as before.2. Compile-time opt-out of the GPU warm-start path
populate_from_data_model_view()inlined both directions in oneif/else. The GPU branch is only reachable whenhandle != nullptr, but the compiler instantiated both into every TU including the header, dragging inconvert_to_gpu_warmstart,convert_to_cpu_warmstartandpdlp_warm_start_data_t(view, stream).Split into
apply_warmstart_gpu_target()(declared in the header, defined inoptimization_problem.cu) andapply_warmstart_cpu_target()(host-only, inline), selected by akHostOnlytemplate parameter dispatched withif constexpr:Why
if constexprand not a runtimeif: the goal is that a host-only caller never instantiates the GPU branch, so it emits no reference to it. A runtime branch would still instantiate both and leave the undefined symbols. This one change removed 5 of the 6 GPU references from the client.Also moves the warm-start accessors that need no allocation into
solver_settings_accessors.cpp, leaving the CUDA TU with only members that do.Testing
Full build + 126 test binaries, 0 errors. 111/125 pass; the 14 failures are
cudaErrorUnknownfrom a locally wedgednvidia_uvm, identical on unmodifiedmain.🤖 Generated with Claude Code