Skip to content

Add Paged Attention Op for CUDA SM80 support #24595

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

aciddelgado
Copy link
Contributor

Description

Adds Paged Attention Op which enables of Paged KV Cache. Inputs to this op are unpadded (packed / varlen) so Cumulative Sequence Lengths are a required input.

Motivation and Context

Adding this op to ONNXRuntime is necessary to allow the GenAI team to enable a continuous batching server API.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

You can commit the suggested changes from lintrunner.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

You can commit the suggested changes from lintrunner.

}

ONNX_MS_OPERATOR_SET_SCHEMA(
PagedAttention, 1,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we name the input KV caches as past_key and past_value + the output KV caches as present_key and present_value to match the names used in the other attention ops?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since the kv cache inputs are always updated in place in this op, and the kv cache outputs are optional, I think it would be innacurate to name them like that

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

You can commit the suggested changes from lintrunner.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

You can commit the suggested changes from lintrunner.

Comment on lines 1358 to 1375
"cumulative_sequence_length",
"A tensor with shape (batch_size + 1). It specifies the cumulative sequence lengths between the packed "
"entries in Q/K/V.",
"M")
.Input(6,
"seqlens",
"A tensor with shape (batch_size). It specifies the past lengths of cached sequence in the KV cache.",
"M")
.Input(7,
"max_query_len",
"Scalar tensor equivalent to the maximum length of all sequences in the query tensor (max new seqlen). "
"This input is allocated on CPU.",
"M")
.Input(8,
"max_seq_len",
"Scalar tensor equivalent to the maximum length of all sequences in the KV Cache after appending new "
"tokens (max total seqlen). This input is allocated on CPU.",
"M")
Copy link
Contributor

Choose a reason for hiding this comment

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

The interface is designed for the convenience of current implementation, that might not be good for other EP (like WebGPU, CPU etc).

A generic design is to have q_seq_lens and kv_seq_lens, then you can deduce these four inputs. For example, for CPU, it is very easy to compute max seq length, so there is no need to have max_query_len and max_seq_len inputs.

I guess that the extra latency caused by such interface change might be tiny.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We take max query length and total sequence length as inputs because in CUDA we would need to copy the value from device to host within the op for however many layers are in the model. If you think this might be a tiny performance penalty then I'll change it; I agree that the amount of inputs is slightly bloated.

Copy link
Contributor

@tianleiwu tianleiwu May 2, 2025

Choose a reason for hiding this comment

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

Since padding has been removed in the input, the input is treated as a long sequence. My understanding is that there is no much use of max query length as long as you know the total length of the long sequence.

For max kv length, it is only used to check the optional input cos/sin cache of rotary. But, there is no guarantee that the max kv length itself is correct. To verify max length is correct, we will have to reduce from length per sequence.

If we do not check max lengths in CheckInputs. For security reason, we can let rotary kernel outputs NaNs when total length is too large, and add a check to GenAI to make sure total length is within the limit of rotary cache length.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Primarily, max seqlen q and k were included as input because these values are passed to flash attention. However, I believe we can remove these dependencies at a kernel level.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

You can commit the suggested changes from lintrunner.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

You can commit the suggested changes from lintrunner.

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