Repository navigation
fix: keep host sources alive for async route-view copies (#1756) #1759
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
07a50d0
ce25ffc
9b8360d
b0b31ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,11 +93,10 @@ void solution_t<i_t, f_t, REQUEST>::add_route(route_t<i_t, f_t, REQUEST>&& route | |
| route.n_nodes.set_value_async(n_nodes, sol_handle->get_stream()); | ||
| route.route_id.set_value_async(route_id, sol_handle->get_stream()); | ||
| sol_handle->sync_stream(); | ||
| i_t route_slot = route_id_to_idx[route_id]; | ||
| routes[route_slot] = std::move(route); | ||
| const auto route_view = routes[route_slot].view(); | ||
| i_t route_slot = route_id_to_idx[route_id]; | ||
| routes[route_slot] = std::move(route); | ||
| cuopt_assert(route_id < (int)routes_view.size(), "route id should be in range"); | ||
| routes_view.set_element_async(route_id, route_view, sol_handle->get_stream()); | ||
| set_route_views(route_id, route_id + 1); | ||
| if (max_nodes_per_route < get_route(route_id).max_nodes_per_route()) { | ||
| resize_routes(raft::alignTo(get_route(route_id).max_nodes_per_route(), base_route_size)); | ||
| } | ||
|
|
@@ -122,6 +121,14 @@ void solution_t<i_t, f_t, REQUEST>::add_routes( | |
| n_routes += added_routes; | ||
|
|
||
| check_and_allocate_routes(n_routes); | ||
| // Host sources of the async copies below must stay valid and unmodified until the | ||
| // sync_stream() after this loop, so per-iteration locals cannot be used. vehicle_id and | ||
| // route are references into new_routes and already outlive this function; the scalars | ||
| // are staged here, reserved up front so no reallocation can invalidate a pending copy. | ||
| std::vector<i_t> h_n_nodes; | ||
| std::vector<i_t> h_route_ids; | ||
| h_n_nodes.reserve(new_routes.size()); | ||
| h_route_ids.reserve(new_routes.size()); | ||
| for (const auto& [vehicle_id, route] : new_routes) { | ||
| // depot for the beginning and the end | ||
| i_t new_route_size = route.size() + 2; | ||
|
|
@@ -131,26 +138,22 @@ void solution_t<i_t, f_t, REQUEST>::add_routes( | |
| std::max(max_nodes_per_route, raft::alignTo(new_route_size, base_route_size)); | ||
| resize_routes(new_route_size); | ||
| } | ||
| std::vector<NodeInfo<i_t>> node_info_h; | ||
| for (size_t x = 0; x < route.size(); ++x) { | ||
| // Dummy, will be overriden in set_nodes_data_of_route | ||
| node_info_h.emplace_back(route[x]); | ||
| } | ||
| // skip depot | ||
| // Values are overridden in set_nodes_data_of_route; copy straight out of new_routes, | ||
| // which outlives this function, rather than through a temporary. Skip the depot. | ||
| raft::copy(d_route.dimensions.requests.node_info.data() + 1, | ||
| node_info_h.data(), | ||
| node_info_h.size(), | ||
| route.data(), | ||
| route.size(), | ||
| sol_handle->get_stream()); | ||
| const i_t n_nodes = route.size() + 1; | ||
| const i_t& n_nodes = h_n_nodes.emplace_back(route.size() + 1); | ||
| d_route.n_nodes.set_value_async(n_nodes, sol_handle->get_stream()); | ||
| d_route.route_id.set_value_async(route_id, sol_handle->get_stream()); | ||
| const i_t& stable_route_id = h_route_ids.emplace_back(route_id); | ||
| d_route.route_id.set_value_async(stable_route_id, sol_handle->get_stream()); | ||
| d_route.vehicle_id.set_value_async(vehicle_id, sol_handle->get_stream()); | ||
| i_t route_slot = route_id_to_idx[route_id]; | ||
| const auto route_view = routes[route_slot].view(); | ||
| cuopt_assert(route_id < (int)routes_view.size(), "route id should be in range"); | ||
| routes_view.set_element_async(route_id, route_view, sol_handle->get_stream()); | ||
| ++route_id; | ||
| } | ||
| // Publish once, through the single path that owns the lifetime rule. | ||
| set_route_views(prev_route_size, n_routes); | ||
| set_nodes_data_of_new_routes(added_routes, prev_route_size); | ||
| sol_handle->sync_stream(); | ||
| } | ||
|
|
@@ -286,18 +289,32 @@ void solution_t<i_t, f_t, REQUEST>::set_routes_to_search() | |
| } | ||
|
|
||
| template <typename i_t, typename f_t, request_t REQUEST> | ||
| void solution_t<i_t, f_t, REQUEST>::set_route_views() | ||
| void solution_t<i_t, f_t, REQUEST>::set_route_views(i_t start, i_t end) | ||
| { | ||
| raft::common::nvtx::range fun_scope("set_route_views"); | ||
| if (routes.size() > routes_view.size()) { | ||
| // reserve with the max size | ||
| routes_view.resize(routes.size(), sol_handle->get_stream()); | ||
| } | ||
|
|
||
| for (size_t i = 0; i < routes.size(); ++i) { | ||
| const auto route_view = get_route(i).view(); | ||
| routes_view.set_element_async(i, route_view, sol_handle->get_stream()); | ||
| if (end < 0) { end = (i_t)routes.size(); } | ||
| cuopt_assert(start >= 0 && end <= (i_t)routes.size(), "route view range out of bounds"); | ||
| if (end <= start) { return; } | ||
|
|
||
| // Single point where route views are published to the device. The host source of an async | ||
| // copy must stay valid and unmodified until the stream is synchronized -- rmm's | ||
| // memcpy_async uses cudaMemcpySrcAccessOrderStream on CUDA 13, so the bytes are read when | ||
| // the copy runs, not when it is enqueued. Staging in a member buffer and synchronizing here | ||
| // keeps that rule in one place instead of at every call site. | ||
| h_routes_view.clear(); | ||
| h_routes_view.reserve(end - start); | ||
| for (i_t i = start; i < end; ++i) { | ||
| h_routes_view.push_back(get_route(i).view()); | ||
| } | ||
| raft::copy(routes_view.data() + start, | ||
| h_routes_view.data(), | ||
| h_routes_view.size(), | ||
| sol_handle->get_stream()); | ||
| sol_handle->sync_stream(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am worried about this synchronization. I think we can remove that, I expect the call sites handle the synchronization pretty well. It was not synhronized before, it shouldn't be synchonized now.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to push back on this one, or at least lay out why I think it is load-bearing. The unsynchronized version is what caused #1756. Without a sync inside set_route_views(route_id, route_id + 1); // copy A enqueued, reads h_routes_view
if (max_nodes_per_route < ...) resize_routes(...); // republishes -> overwrites h_routes_viewWith the sync removed, On "the call sites handle the synchronization pretty well" -- that was the assumption that did not hold. rmm sets That said, your perf concern is fair and I do not want a blocking sync in a hot path either. The principled way to get both is to record a Two mitigations already in place:
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @akifcorduk What is your suggestion on this #1759 (comment)
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. okay makes sense. we can keep it. |
||
| } | ||
|
|
||
| // init the rotues with the desired number of routes | ||
|
|
@@ -374,14 +391,17 @@ void solution_t<i_t, f_t, REQUEST>::resize_routes(i_t new_size) | |
| { | ||
| raft::common::nvtx::range fun_scope("resize_routes"); | ||
|
|
||
| bool any_resized = false; | ||
| for (i_t i = 0; i < n_routes; ++i) { | ||
| auto& route_i = get_route(i); | ||
| if (route_i.max_nodes_per_route() < new_size) { | ||
| route_i.resize(new_size); | ||
| const auto route_view = get_route(i).view(); | ||
| routes_view.set_element_async(i, route_view, sol_handle->get_stream()); | ||
| any_resized = true; | ||
| } | ||
| } | ||
| // A resize reallocates the route buffers, so the device-side views are stale. Republish | ||
| // through set_route_views rather than copying each changed view from a local here. | ||
| if (any_resized) { set_route_views(); } | ||
| max_nodes_per_route = std::max(max_nodes_per_route, new_size); | ||
| } | ||
|
|
||
|
|
@@ -512,10 +532,8 @@ void solution_t<i_t, f_t, REQUEST>::copy_device_solution(solution_t<i_t, f_t, RE | |
| if (!src_sol.n_routes) { return; } | ||
| check_and_allocate_routes(src_sol.n_routes); | ||
|
|
||
| for (i_t i = n_routes; i < src_sol.n_routes; ++i) { | ||
| const auto route_view = get_route(i).view(); | ||
| routes_view.set_element_async(i, route_view, sol_handle->get_stream()); | ||
| } | ||
| // copy_routes below reads these entries, so they must be published before it launches. | ||
| set_route_views(n_routes, src_sol.n_routes); | ||
|
|
||
| n_routes = src_sol.n_routes; | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.