Repository navigation
Decode LoRA kernels are optimized - #550
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Debug-test collection can fail without an XPU, and the synchronization-free claim needs qualification.
2 open findings
What changed in this PR
Optimizes Intel XPU LoRA decode by replacing separate gather, matrix multiplication, and scatter steps with gather-fused DPAS kernels, while preserving Python signatures.
Changes:
- Adds optimized shrink and expand kernels with alignment and tail handling.
- Moves value-level validation behind
USE_DEBUG_CHECKS. - Extends shape tests and benchmarks, and updates API documentation.
| File | Description |
|---|---|
| tests/test_sgemm_lora_b_fwd.py | Marks debug-only validation cases. |
| tests/test_sgemm_lora_a_fwd.py | Marks debug-only validation cases. |
| tests/test_qkv_lora_b_fwd.py | Marks debug-only validation cases. |
| tests/test_lora_debug_checks.py | Adds shared validation tests. |
| tests/test_gate_up_lora_b_fwd.py | Marks debug-only validation cases. |
| tests/test_chunked_sgmv_lora_shrink_fwd.py | Tests tails and misaligned inputs. |
| tests/test_chunked_sgmv_lora_expand_fwd.py | Tests irregular slices and alignment. |
| tests/conftest.py | Registers and skips debug-check tests. |
| src/sycl/SGEMMLoraBFwd.cpp | Uses conditional validation helpers. |
| src/sycl/SGEMMLoraAFwd.cpp | Uses conditional validation helpers. |
| src/sycl/QKVLoraBFwd.cpp | Gates value and offset checks. |
| src/sycl/kernels/lora/device/chunked_sgmv_lora_shrink_fwd_types.hpp | Defines split-K tile settings. |
| src/sycl/kernels/lora/device/chunked_sgmv_lora_shrink_fwd_runner.hpp | Launches alignment-aware shrink kernels. |
| src/sycl/kernels/lora/device/chunked_sgmv_lora_shrink_fwd_kernel.hpp | Implements gather-fused shrink. |
| src/sycl/kernels/lora/device/chunked_sgmv_lora_shrink_fwd_dispatch.hpp | Updates shrink launch declarations. |
| src/sycl/kernels/lora/device/chunked_sgmv_lora_expand_fwd_types.hpp | Defines wide tile settings. |
| src/sycl/kernels/lora/device/chunked_sgmv_lora_expand_fwd_runner.hpp | Launches alignment-aware expand kernels. |
| src/sycl/kernels/lora/device/chunked_sgmv_lora_expand_fwd_kernel.hpp | Implements gather-fused expand. |
| src/sycl/kernels/lora/device/chunked_sgmv_lora_expand_fwd_dispatch.hpp | Updates expand launch declarations. |
| src/sycl/kernels/lora/common/lora_dpas_primitives.hpp | Adds shared DPAS and memory helpers. |
| src/sycl/kernels/lora/common/lora_debug_checks.hpp | Centralizes conditional value checks. |
| src/sycl/GateUpLoraBFwd.cpp | Uses conditional validation helpers. |
| src/sycl/EmbeddingLoraAFwd.cpp | Gates validation; retains launch-sizing sync. |
| src/sycl/ChunkedSgmvLoraShrinkFwd.cpp | Dispatches fused shrink without sorted copies. |
| src/sycl/ChunkedSgmvLoraExpandFwd.cpp | Dispatches fused expand without sorted copies. |
| src/sycl/chunked_sgmv_lora_shrink_fwd_kernel.cpp.in | Updates generated shrink launchers. |
| src/sycl/chunked_sgmv_lora_expand_fwd_kernel.cpp.in | Updates generated expand launchers. |
| src/sgl_kernel_debug_config.h.in | Defines generated validation flag. |
| src/CMakeLists.txt | Adds debug-check build option. |
| src/ChunkedSgmvLoraShrinkFwdXe20.cmake | Registers split-K tile. |
| src/ChunkedSgmvLoraExpandFwdXe20.cmake | Registers wide tile. |
| python/sgl_kernel/lora.py | Documents fused kernels and coalesced segments. |
| benchmark/bench_chunked_sgmv_lora_shrink_fwd.py | Adds cases and adjusts Triton baseline. |
| benchmark/bench_chunked_sgmv_lora_expand_fwd.py | Adds cases and adjusts Triton baseline. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
0b81027 to
12fd7b7
Compare
| """Debug-only value checks of the LoRA kernels (USE_DEBUG_CHECKS=ON builds). | ||
|
|
There was a problem hiding this comment.
why this test file is needed ?
There was a problem hiding this comment.
I have kept all of the checks which require device-host copies under DEBUG build mode. This test file tests those cases when the repo is built under DEBUG MODE ON. I placed those debug checks related to all lora kernels in a separate file.
There was a problem hiding this comment.
I think for checking data(configs) under debug mode flag is okay, but no need to create test file for that, if the debug mode flag is used in workload enablement then still it is fine, otherwise it will be cluttered, and maintaining in CI will be a burden, I think it need to be removed.
There was a problem hiding this comment.
Done. The file is removed as per the recommendation.
12fd7b7 to
454ee5d
Compare
| endif() | ||
| # Delivered as a generated header, not a global -D: only sources that include it | ||
| # (via lora_debug_checks.hpp) recompile when USE_DEBUG_CHECKS is toggled. | ||
| set(SGL_KERNEL_DEBUG_CHECKS ${USE_DEBUG_CHECKS}) |
There was a problem hiding this comment.
Instead of creating new debug flags, could you use RelWithDebInfo, which is currently available and you will enable your checks by simply add SKBUILD_CMAKE_BUILD_TYPE=RelWithDebInfo with your build cmd.
There was a problem hiding this comment.
Done. Removed USE_DEBUG_CHECKS and the generated config header. The value checks are now compiled in when CMAKE_BUILD_TYPE is RelWithDebInfo.
454ee5d to
25bac18
Compare
airMeng
left a comment
There was a problem hiding this comment.
Requesting changes to fix the SGL_WERROR=ON build regression described in the inline comment.
25bac18 to
2a195fb
Compare
airMeng
left a comment
There was a problem hiding this comment.
Re-reviewed the latest revision. The earlier embedding API compatibility and license-comment/warnings-as-errors findings are fixed; fresh targeted Release and RelWithDebInfo builds both pass with -Werror. Requesting changes for the newly reproduced default embedding launch-bound regression described inline. The PR's existing 16,384-token, one-token-per-segment test fails in both modes on B60/Xe20 with icpx 2026.1 and Torch 2.14.0+xpu; the same input succeeds with an explicit tight bound. Verification used isolated targeted source builds rather than a complete production wheel.
85d2f31 to
c9efb9a
Compare
| template <typename T> | ||
| struct ToCuteElementType; | ||
| template <> | ||
| struct ToCuteElementType<at::BFloat16> { | ||
| using type = cute::bfloat16_t; | ||
| }; | ||
| template <> | ||
| struct ToCuteElementType<at::Half> { | ||
| using type = cute::half_t; | ||
| }; |
There was a problem hiding this comment.
Could we reuse the existing at::native::xpu::ToCutlassElementType<T> from common/group_gemm_types.hpp:79–93 instead of introducing a second dtype mapper here? The two fp16/bf16 specializations are identical: sycl-tla’s cute/numeric/numeric_types.hpp imports cutlass::half_t and cutlass::bfloat16_t into cute, so ToCuteElementType does not provide different types or behavior. Reusing the existing trait would keep the Torch-to-kernel dtype mapping in one place. If importing the full prefill type-bundle header creates undesirable dependencies, the shared trait can be factored into a lightweight common header.
There was a problem hiding this comment.
Done. The two specializations were identical (cute::half_t / bfloat16_t are the cutlass types), so ToCutlassElementType now lives in a small shared header, common/lora_element_type.hpp, used by both the prefill and decode type bundles. I didn't include group_gemm_types.hpp directly to avoid pulling the prefill epilogue/mainloop headers into the decode TUs.
conflicts resolved
…20 extension in file names as these files are not specific to only Xe20
c9efb9a to
519cb51
Compare
| sgk, | ||
| n_groups); | ||
| const int64_t local = 16 * sgk; | ||
| sycl_kernel_submit(row_tiles * n_groups * local, local, queue, kernel); |
There was a problem hiding this comment.
use launch<kerenl,grf_size> instead of sycl_kernel_submit
| template <class FrgTensorC> | ||
| CUTLASS_DEVICE static void fixup(Params const&, WorkTileInfo const&, FrgTensorC&, uint32_t, uint32_t) {} |
| CUTLASS_HOST_DEVICE static uint32_t get_work_k_tile_start(WorkTileInfo const&) { | ||
| return 0u; | ||
| } |
| tSrB(i) = ElementB::bitcast(n < p.N && k < p.K ? p.b[static_cast<int64_t>(n) * p.K + k] : uint16_t(0)); | ||
| } | ||
| load_a(c); | ||
| cute::gemm(mma, tSrA, tSrB, accum); |
There was a problem hiding this comment.
try to use prefetch before gemm to get more performance. though you can incorporate in your next PR.

Summary
Rewrites the chunked-SGMV LoRA decode kernels (
chunked_sgmv_lora_shrink_fwd,chunked_sgmv_lora_expand_fwd) as gather / scatter grouped GEMMs on sycl-tla'sGemmUniversalAdapter/
GemmUniversal, with custom LoRA collectives (gathered-A mainloop, scatter epilogues, on-devicesegment scheduler). The value-level input checks of all LoRA ops now run only in
RelWithDebInfobuilds.
Effective bandwidth goes from ~47 GB/s to ~220 GB/s (shrink) and ~270 GB/s (expand). Both ops beat the
Triton csgmv kernels (sglang's deployed XPU defaults) by 2.7–4.8x. In a default build, no LoRA op issues a device-to-host sync per call, except
embedding_lora_a_fwdwhenmax_lenis omitted.Performance
Intel Arc B580,
triton.testing.do_bench(L2 flushed between runs),benchmark/bench_chunked_sgmv_lora_{shrink,expand}_fwd.py. Triton uses sglang's deployed XPU csgmvdefaults (chunk 16; shrink BLOCK_N=16 / BLOCK_K=256, expand BLOCK_N=64 / BLOCK_K=16).
Mean effective bandwidth (GB/s; 9 cases, tok = 50 … 8192):
* The one loss is the script's first-timed case (tok=50). Timed on its own, it is 1.5x faster than
Triton.
Behaviour changes
seg_indptr, index range,permutation or offset table is undefined behaviour (wrong output or a GPU fault) instead of a
TORCH_CHECKerror. Build withRelWithDebInfoto validate inputs.segments. This requirement is unchanged.
max_lenonembedding_lora_a_fwd.Testing
against a
RelWithDebInfobuild withSGL_KERNEL_DEBUG_CHECKS=1.starts, and misaligned pointers.
debug_checkspytest marker (tests/conftest.py): the existing prefill value-check casescarry it.