Skip to content

Batched device memcpy for CUDA/HIP - #791

Open
devreal wants to merge 4 commits into
ICLDisco:masterfrom
devreal:batched-memcpy
Open

Batched device memcpy for CUDA/HIP#791
devreal wants to merge 4 commits into
ICLDisco:masterfrom
devreal:batched-memcpy

Conversation

@devreal

@devreal devreal commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Since CUDA 12.8 we can use batched memory copy to perform multiple operations in a single CUDA call. For tasks with multiple stage in/out flows this can be beneficial. Prominent example
are eviction tasks. There is no reason to order pushouts of a single task.

In practice, I am observing higher bandwidth for small batched data (9x2GB/s vs 1x12GB/s for 9 8KB chunks).

Since CUDA 12.8 we can use batched memory copy to perform multiple
operations in a single CUDA call. For tasks with multiple stage in/out
flows this can be beneficial. Prominent example
are eviction tasks. There is no reason to order pushouts
of a single task.

In practice, I am observing higher bandwidth for small batched data
(9x2GB/s vs 1x12GB/s for 9 8KB chunks).

Signed-off-by: Joseph Schuchart <joseph.schuchart@stonybrook.edu>
@devreal
devreal requested a review from Copilot July 23, 2026 13:05
@devreal
devreal requested a review from a team as a code owner July 23, 2026 13:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new multi-item GPU memcpy interface and updates the default GPU stage-in/stage-out paths to batch multiple transfers into a single backend call when possible (notably leveraging CUDA 12.8’s cudaMemcpyBatchAsync) to improve bandwidth for workloads with multiple in/out flows (e.g., evictions).

Changes:

  • Adds a new memcpy_multi_async function pointer to the GPU device module interface, with a generic fallback that issues multiple memcpy_async calls.
  • Updates default GPU stage-in/stage-out logic to aggregate per-flow copies and issue one combined multi-copy operation.
  • Implements a CUDA 12.8+ native backend using cudaMemcpyBatchAsync, and wires Level Zero to the generic fallback.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
parsec/mca/device/level_zero/device_level_zero_module.c Wires Level Zero GPU device to use the generic multi-memcpy fallback.
parsec/mca/device/device_gpu.h Adds the memcpy_multi_async API + documents semantics and declares the generic fallback.
parsec/mca/device/device_gpu.c Implements the generic fallback and updates default stage-in/stage-out and push/pop paths to batch transfers.
parsec/mca/device/cuda/device_cuda_module.c Adds CUDA 12.8+ native batched memcpy implementation and selects it at init time.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread parsec/mca/device/cuda/device_cuda_module.c

@bosilca bosilca left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good idea, but the scope of the current implementation is narrow and only benefit to tasks with small data and only if the copy latency is important, which means for tasks that cannot keep the GPU busy (aka. not compute intensive). It also work per task, so at best it batches MAX_PARAM items.

If we can merge this with the task batching we could have a much stronger support for small compute-trivial tasks.

{
int ret, rc = PARSEC_SUCCESS;

for(int i = 0; i < nb_items; i++) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop is too carefree. I know that if any error is returned everything will fail, but if we try to expose the faults at least we should do it properly. Here if anything fails we return the error but we continue to submit all the others copies. However, once we return from this function with an error we call parsec_device_kernel_push_release_readers_on_failure directly, releasing the readers for the entire mask (and that's one problem), and then return without enquing an event (which allow the data use right away on another stream).

The interface needs either atomic submission semantics, a returned submitted mask, or the old per-flow path for backends without native batching.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bailing out on error now. It's hard to reliably report which operation failed though (the new CUDA API doesn't report it anymore). I'm not sure it's important to know that. Repeating all transfers would do no harm in terms of correctness.

Comment thread parsec/mca/device/device_gpu.c Outdated
parsec_device_module_t *src_dev_mod = parsec_mca_device_get(src->device_index);
if( (NULL == src_dev_mod) || !PARSEC_DEV_IS_GPU(src_dev_mod->type) ) continue;
int readers = parsec_gpu_data_copy_release_reader((parsec_device_gpu_module_t*)src_dev_mod, src, 1);
assert(readers >= 0);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

releases a GPU reader without locking its original or coordinating with the source GPU manager. When this removes the last reader, parsec_gpu_data_copy_release_reader() can relink the copy into a source-device LRU.
The normal D2D completion path serializes with the source manager, locks the original, and updates data_avail_epoch. The failure path should reuse that protocol.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Factored out the d2d release and used it here. Please recheck

The same code is used in several places so factor it out
and use it in parsec_device_kernel_push_release_readers_on_failure.

Signed-off-by: Joseph Schuchart <joseph.schuchart@stonybrook.edu>
Constness is complicated and PaRSEC's internal device API has not cared
about it.

Signed-off-by: Joseph Schuchart <joseph.schuchart@stonybrook.edu>
Bail out if an error occurs. There is no portable reliable way to report
which transfer succeeds (e.g., in the new CUDA API). If a transfer fails
we repeat it (if we wish to recover) without loss of information.

Signed-off-by: Joseph Schuchart <joseph.schuchart@stonybrook.edu>
@devreal

devreal commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

If we can merge this with the task batching we could have a much stronger support for small compute-trivial tasks.

I'm torn on this. I agree that batching memcpy for batched tasks could be helpful. However, for tasks with large inputs we don't gain much (since a single transfer can saturate the bandwidth) already and in fact may harm performance because the tasks are delayed collectively waiting for larger transfers.

For small transfers and kernels this would be helpful, although we probably want to cap the maximum number of batched transfers (for the same reason as above).

I would suggest we take what we have here as a first step and explore more batching later. Small kernel are a problem that is hitting us in MADNESS so it is something I'm looking at. If we can benefit from task-level batched inputs I will explore that further.

@devreal

devreal commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

One way this would be helpful for us is if we could transfer the ring of batched tasks from execute to pushout and batch their outputs. Not sure if #792 covers that.

Alternatively, we could just collect a bounded number of PARSEC_DEV_CHORE_ALLOW_BATCH tasks in the pushout and combine them that way. That should be easy to do but runs the risk of delaying successor releases because of large transfers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants