Repository navigation
[CUDA] Pass Concat per-input metadata by value - #32119
Merged
Merged
Conversation
The ragged path (inputs with unequal sizes along the concat axis) uploaded four CudaAsyncBuffers to the device on every Run: concat_sizes, concat_sizes_range, input_ptr, and axis_dimension_input_output_mapping -- the last of which is sized by the output axis dimension. Recover the input index by scanning the prefix sums instead of reading the output-sized mapping table, which makes it possible to pass the metadata by value in TArrays for up to 32 inputs, matching what the equal-size path already does. The buffer-based path is kept for wider concats. Measured on a DeepSeek-V4-Flash decode step (8xH200, tensor parallel, CUDA graph): host-to-device copies fall from 417 to 1 per step, worth 0.46 ms of transfer plus 1.21 ms of the stalls that trailed them. Step time 20.2 -> 18.8 ms, batch-1 decode 49.49 -> 53.21 tok/s. MMLU-Pro 800 unchanged at 0.67000; the kernel is bit-exact against the CPU EP over a 48-case sweep.
Contributor
There was a problem hiding this comment.
Pull request overview
Optimizes CUDA Concat for ragged inputs by passing metadata directly to kernels when there are at most 32 inputs.
Changes:
- Adds a by-value ragged Concat path.
- Recovers input indices by scanning prefix sums.
- Retains the existing buffer path for larger fan-in.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
concat.cc |
Selects and prepares the by-value path. |
concat_impl.cu |
Implements the new CUDA kernel and launcher. |
concat_impl.h |
Declares the new launcher overload. |
π‘ Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Resolve the conflict with #32410 by dispatching the ragged by-value path before the pinned CudaAsyncBuffer is allocated, so neither by-value path touches pinned host memory.
Baiju Meswani (baijumeswani)
left a comment
Contributor
There was a problem hiding this comment.
Thanks for the fix. I tested a Qwen 3.8 DFlash2 model with CUDA graphs enabled: generation now works, and the drafter runs without errors. The inline notes are suggestions to clarify the supported range and make it easier to keep this working; they are not blockers for this model.
Replace the per-element linear scan with an input-major kernel when every per-input row is long enough to coalesce, and a shared-memory prefix-sum search otherwise. Return a clear error when more than 32 inputs are concatenated during CUDA Graph capture, and test the 32-input cutoff under capture and replay.
Baiju Meswani (baijumeswani)
approved these changes
Sep 23, 2026
Tianlei Wu (tianleiwu)
enabled auto-merge (squash)
September 23, 2026 23:31
Tianlei Wu (tianleiwu)
deleted the
tlwu/20260816/cuda_concat_by_value
branch
September 24, 2026 01:23
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.
Description
CUDA
Concat's ragged path (inputs of unequal size along the concat axis) uploaded fourCudaAsyncBuffers to the device on everyRun, one of which is sized by the output axis dimension. This change recovers the input index by scanning prefix sums instead, which makes it possible to pass the per-input metadata by value inTArrays β matching what the equal-size path already does.The ragged by-value path is dispatched before any
CudaAsyncBufferis created, like the equal-size path after #32410. So for up to 32 inputs, Concat allocates no pinned host memory at all. That matters under CUDA graph capture:cudaHostAllocis not allowed while a stream is capturing, and the failed allocation surfaced as a misleadingalloc failed(seen with a DFlash2 drafter graph that concatenates[1, H]and[N - 1, H]slices).The result is also a large reduction in host-to-device traffic for graphs that concatenate many small tensors, which is common in decode-time LLM workloads.
Summary of Changes
onnxruntime/core/providers/cuda/tensor/concat.cconnxruntime/core/providers/cuda/tensor/concat_impl.cuonnxruntime/core/providers/cuda/tensor/concat_impl.honnxruntime/test/providers/cpu/tensor/concat_op_test.cconnxruntime/test/python/onnxruntime_test_python_cudagraph.pyNotes:
Testing
concat_op_test.cc:CudaRagged32InputsUsesByValueMetadata,CudaRagged33InputsUsesGpuMetadataFallback.onnxruntime_test_python_cudagraph.py:test_concat_unequal_sizes_cuda_graph_replay(all 6 tests in the file pass on H200, CUDA 13.0).Motivation and Context
Measured on a DeepSeek-V4-Flash decode step (8xH200, tensor parallel, CUDA graph):
The saving is 0.46 ms of transfer plus 1.21 ms of the stalls that trailed those transfers. The benefit is general: any model whose graph concatenates a moderate number of tensors per step pays this cost today.
Checklist