Skip to content

[XPU][FMHA] Add reverse-odd-heads Q-tile scheduler for causal prefill#363

Open
pengzhao-intel wants to merge 5 commits into
mainfrom
reverse_scheduler
Open

[XPU][FMHA] Add reverse-odd-heads Q-tile scheduler for causal prefill#363
pengzhao-intel wants to merge 5 commits into
mainfrom
reverse_scheduler

Conversation

@pengzhao-intel

@pengzhao-intel pengzhao-intel commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Motivation

Thanks cong provides the optimization idea.

In FA2 chunk-prefill on Xe2, the default XeFHMAIndividualTileScheduler dispatches Q-tiles in the same order for every head. For causal prefill this creates a per-head load imbalance:

  • Tile (head=h, q_blk=0) only attends the diagonal block of KV — light work.
  • Tile (head=h, q_blk=last) attends nearly the full KV history — heavy work.

Because all heads start from q_blk=0, the first wave is uniformly light and the last wave is uniformly heavy. XE cores idle waiting for the heaviest wave to drain.

Change

Introduce XeFHMAIndividualReverseOrderTileScheduler, a drop-in scheduler variant that reverses the Q-tile order for odd-indexed heads. Adjacent heads now mix light and heavy tiles in the same wave, smoothing per-wave occupancy.

Selection is per-call via a new bool on the args struct:

struct chunk_prefill_args_t {
  ...
  bool reverse_q_order = false;   // new
};

The FA2 op sets it from env var VLLM_XPU_FA_REVERSE_ODD_HEADS:

Value Behavior
unset / auto Shape-based heuristic (default)
1 / on / true Force enabled
0 / off / false Force disabled

Auto heuristic:

reverse = is_causal && !is_local && !is_sink && num_heads_q >= 2;

Performance evidence

Intel Arc B580, FA2 varlen, MQA q=2 / kv=1, head_size=128, fp16, causal. Numbers are FlashAttention_Kernel_Time(us) speedup of on over off.

Threshold sweep (bs ∈ {1, 2, 4}, per-seq qlen ∈ [2048, 32768])

Shape family Range Median
bs=1, qlen 8k–32k +7 % … +50 % ~+20 %
bs=2, qlen 4k–10k +8 % … +44 % ~+27 %
bs=4, qlen 2k–4k -0.7 % … +22 % ~+15 %

Long-context shapes (total kvlen up to 262k)

Shape Speedup
bs=1, qlen 32k–131k (paged & non-paged) +1.6 % … +9.5 %
bs=2 / bs=6, qlen 10k (multi-batch short prefill) +5 % … +22 %
bs=1 / bs=2, qlen 8k + cached_kv 57k +2 % … +3 %

Worst observed: -0.7 % on bs=4, qlen=4k non-paged — within run-to-run noise. Every other config across 28 measured shape variants showed a speedup.

API / backwards compatibility

  • reverse_q_order defaults to false, so callers that construct chunk_prefill_args_t without going through fmha_xe2.cpp are unaffected.
  • Env-var default is auto, i.e. enabled for causal multi-head prefill. To restore strict pre-PR behavior set VLLM_XPU_FA_REVERSE_ODD_HEADS=off.

Testing

  • All existing chunk_prefill code paths are unchanged (head_size, block_size, mask, sink, paged, varlen, fp8 KV). The new scheduler is only instantiated when the new bool is true.
  • New benchmark configs are automatically included in benchmark_cutlass_flash_attn_varlen.py.
  • Recommended pre-merge check: run pytest tests/flash_attn/test_flash_attn_varlen_func.py with both VLLM_XPU_FA_REVERSE_ODD_HEADS=on and off to confirm numeric equivalence.

Copilot AI review requested due to automatic review settings May 21, 2026 22:40

Copilot AI 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.

Pull request overview

This PR adds an alternative Q-tile scheduling strategy for Xe2 FlashAttention2 chunk-prefill to reduce per-wave load imbalance in causal prefill by reversing Q-tile dispatch order for odd-indexed heads, with runtime selection controlled via a new reverse_q_order argument and an env-var/heuristic in the Xe2 entrypoint.

Changes:

  • Add XeFHMAIndividualReverseOrderTileScheduler that flips BlockIdxY for odd heads to mix light/heavy tiles across heads.
  • Add chunk_prefill_args_t::reverse_q_order and dispatch logic to select the new scheduler when enabled.
  • Parse VLLM_XPU_FA_REVERSE_ODD_HEADS (with an “auto” heuristic) and add additional benchmark shape configs for MQA prefill/long-context scenarios.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
csrc/xpu/attn/xe_2/fmha_xe2.cpp Adds env-var parsing + heuristic to set args.reverse_q_order for selecting the new scheduler.
csrc/xpu/attn/xe_2/collective/chunk_prefill_scheduler.hpp Introduces the reverse-order per-head Q-tile scheduler implementation.
csrc/xpu/attn/xe_2/chunk_prefill.hpp Adds reverse_q_order flag to args and selects scheduler in kernel_dispatch.
benchmark/src/get_model_config.py Adds benchmark configs covering MQA causal prefill and long-context shapes.

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

Comment thread csrc/xpu/attn/xe_2/fmha_xe2.cpp Outdated
Comment on lines +186 to +190
static const char* env = std::getenv("VLLM_XPU_FA_REVERSE_ODD_HEADS");
std::string mode = "auto";
if (env != nullptr) {
mode.assign(env);
for (auto& c : mode) c = static_cast<char>(std::tolower(c));
Comment on lines +193 to +203
bool reverse;
if (mode == "1" || mode == "on" || mode == "true" || mode == "yes") {
reverse = true;
} else if (mode == "0" || mode == "off" || mode == "false" ||
mode == "no") {
reverse = false;
} else { // "auto" or anything unrecognized
reverse = is_causal && !is_local && !is_sink && num_heads_q >= 2;
}
args.reverse_q_order = reverse;
}
@pengzhao-intel

Copy link
Copy Markdown
Collaborator Author

@copilot fix the comment issues

Copilot AI commented May 21, 2026

Copy link
Copy Markdown
Contributor

@copilot fix the comment issues

Addressed in commit e9e8ac0.

  • Fixed VLLM_XPU_FA_REVERSE_ODD_HEADS parsing to call std::getenv() on each invocation (removed static pointer caching) and made tolower safe via unsigned char cast.
  • Added test_varlen_reverse_odd_heads_env_on_off_equivalence to validate numerical equivalence when forcing env off vs on.

@wuxun-zhang

Copy link
Copy Markdown
Contributor

I think later we can also try longest processing time scheduling (process longer request firstly from all heads), even for single head, it could help avoid load imbalance issue.

@pengzhao-intel

Copy link
Copy Markdown
Collaborator Author

I think later we can also try longest processing time scheduling (process longer request firstly from all heads), even for single head, it could help avoid load imbalance issue.

Yes, I think current implementation is only one way and we may provide different scheduler for different situation.

@xinyu-intel

Copy link
Copy Markdown
Collaborator

@jikunshang we may need a more proper way to handle env variables.

@baodii baodii left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM. @YizhouZ pls comment.

@YizhouZ

YizhouZ commented May 26, 2026

Copy link
Copy Markdown
Collaborator

Let's wait for benchmark to finally merge this PR.

@pengzhao-intel

Copy link
Copy Markdown
Collaborator Author

Let's wait for benchmark to finally merge this PR.

Let me know when the benchmark is done

pengzhao-intel and others added 4 commits June 15, 2026 04:33
Add XeFHMAIndividualReverseOrderTileScheduler, a variant of the
XeFHMAIndividualTileScheduler that flips the Q-tile dispatch order
for odd-indexed heads. This mitigates the causal-prefill load
imbalance where head 0's first wave is dominated by the lightly
loaded upper-triangle tiles, by letting odd heads consume the heavy
lower-triangle tiles in parallel with the remaining light tiles of
even heads.

Selection is runtime-controlled by chunk_prefill_args_t::reverse_q_order,
set from the FA2 op based on VLLM_XPU_FA_REVERSE_ODD_HEADS:

  unset / 'auto'        : shape-based heuristic (default)
  '1' / 'on' / 'true'   : force enabled
  '0' / 'off' / 'false' : force disabled

The 'auto' heuristic enables the scheduler whenever the kernel is
running causal prefill with num_heads_q >= 2 and no local/sink
masking. An FA2 varlen MQA sweep on Arc B580 across batch in
{1,2,4} and per-seq qlen in [2048, 131072] showed only a single
near-noise (-0.7%) result and speedups up to +50% kernel time, so
no workload-size guard is required.

Also adds a set of MQA(q=2, kv=1) chunked-prefill and long-context
shapes to gen_cutlass_flash_attn_varlen_perf_configs() so the new
scheduler can be regression-tested via benchmark_cutlass_flash_attn_varlen.

Signed-off-by: pengzhao-intel <patric.zhao@intel.com>
Signed-off-by: pengzhao-intel <patric.zhao@intel.com>
Use a generic mix of MHA / GQA-4:1 / GQA-8:1 prefill shapes (head=128,
fp16, causal) at round token counts up to 16k instead of the previous
MQA(q=2, kv=1) long-context set. Same intent (exercise the reverse
odd-heads scheduler) but covers a broader, more representative shape
mix and runs faster.

Signed-off-by: pengzhao-intel <patric.zhao@intel.com>
For MHA (num_heads_q == num_heads_kv), reversing Q-tile order for odd
heads scatters memory accesses across the sequence, causing L2 cache
thrashing when the GPU is fully saturated. GQA/MQA workloads are immune
because KV is shared across Q-heads regardless of access order.

Tighten the auto-heuristic: always enable for GQA/MQA, but for MHA only
enable when the GPU is under-saturated (total_wgs < 4 * sm_count).

Fixes regression on Wan-AI (40,40) diffusion model config: 1624us -> ~948us.

Signed-off-by: Patric Zhao <patric.zhao@intel.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: pengzhao-intel <patric.zhao@intel.com>
Signed-off-by: Kunshang Ji <kunshang.ji@intel.com>
@jikunshang jikunshang mentioned this pull request Jun 22, 2026
3 tasks
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.

8 participants