Skip to content

Execute fused QKVG attention projections - #699

Open
xhjkl wants to merge 4 commits into
trymirai:mainfrom
xhjkl:qkvg-fuse
Open

Execute fused QKVG attention projections#699
xhjkl wants to merge 4 commits into
trymirai:mainfrom
xhjkl:qkvg-fuse

Conversation

@xhjkl

@xhjkl xhjkl commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Consumes Lalamo's fused QKVG projection directly. This eliminates the separate gate-projection dispatch and the gated-input scratch copy while retaining explicit packed-row strides for attention preparation, Q/K/V normalization, and post-attention sigmoid gating.

Benchmarks

  • BF16: decode improved from 169.48 to 186.74 tokens/s (+10.18%); long-prompt prefill throughput regressed 1.29%; greedy output was identical; peak memory increased by 4 MiB.
  • MLX 4-bit: decode improved from 283.35 to 310.99 tokens/s (+9.75%); long-prompt prefill throughput regressed 0.49%; decode peak memory was unchanged; long-context peak memory decreased by 16 MiB.

Validation

  • cargo test -p backend-uzu qkv_norm -- --nocapture
  • cargo test -p backend-uzu attention_prepare -- --nocapture
  • cargo test -p backend-uzu sigmoid_gate -- --nocapture
  • cargo clippy -p backend-uzu --lib --tests -- -D warnings

Dependency

Requires trymirai/lalamo#340 and a Lalamo release containing it.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@uuuvn uuuvn 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.

Thank you very much for taking a time to do this!

One thing is that we shouldn't name variables "qkv" when they are in fact "qkvg", other than that looks good, will merge after trymirai/lalamo#340 is merged and a lalamo version is released with it.

@xhjkl
xhjkl requested a review from uuuvn August 12, 2026 17:37
#[uzu_config(super::TokenMixerConfig)]
pub struct AttentionConfig {
pub qkv_projection_config: LinearConfig,
/// Unified QKVG projection; ungated attention omits the G output segment.

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.

Useless llm comment

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.

Ha, I wrote it myself 🤭 Just wanted to emphasize the "G" part may be skipped. But I'll drop this comment in the subsequent update if you feel that way.

q_dim
};
let projection_dim = if config.has_gate {
let qkvg_dim = if config.has_gate {

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.

Why change "projection" to "qkvg"?

let key_norm_config = (!is_kv_sharing).then(|| config.key_norm_config.clone()).flatten();
let value_norm_config = (!is_kv_sharing).then(|| config.value_norm_config()).flatten();
let packed_projection_heads = projection_dim / head_dim;
let qkvg_heads = qkvg_dim / head_dim;

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.

There is no gate heads, qkvg_heads is wrong here

};

pub(super) struct LinearProjection<B: Backend> {
pub(super) struct QKVGProjection<B: Backend> {

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.

Why rename linear?

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.

Purest misfire on my end, as I'm still getting ramped up with the codebase knowledge. Now fixed.

@uuuvn uuuvn 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.

LGTM, will merge when lalamo part merges and releases, thanks

@gearonixx

gearonixx commented Aug 17, 2026

Copy link
Copy Markdown

@xhjkl

In older versions, the kernel computed the offset internally

// older qkv_norm.rs kernel

let qkv_stride = total_heads * head_dim; 

let offset =
    (token_index * qkv_stride)
    + HEAD_START_OFFSET;

After these changes, the stride is passed explicitly as input_row_stride

self.encode_packed(
    key_value,
    batch_dim,
    0,
    2 * self.num_kv_heads, // this is the number of heads. should be the number of elements in the token
    encoder,
)

should be:

-        self.encode_packed(key_value, batch_dim, 0, 2 * self.num_kv_heads, encoder)
+        self.encode_packed(key_value, batch_dim, 0, 2 * self.num_kv_heads * self.head_dim, encoder)

* self.head_dim is missing

The kernel still computes:

let offset =
    (batch * input_row_stride) // batch * number_of_elements (not number of heads)
    + HEAD_START_OFFSET;

input_row_stride needs to be the number of elements per token

this path only happens when DFlash synchronizes accepted speculative tokens through DFlash::encode_accept

@xhjkl

xhjkl commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Great catch, @gearonixx! Now fixed ✨

@xhjkl
xhjkl force-pushed the qkvg-fuse branch 5 times, most recently from e68e8a4 to a4071d1 Compare August 20, 2026 14:47
@xhjkl
xhjkl force-pushed the qkvg-fuse branch 2 times, most recently from 8379eb4 to 28f66e9 Compare August 26, 2026 15:43
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