Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
126 commits
Select commit Hold shift + click to select a range
8be551d
update the code
a32543254 Jul 27, 2026
ff76f39
opt 16 bit group gemm
a32543254 Jul 30, 2026
68bb067
perf: dedicated w4a16 tiles for int4 moe prefill + widen decode gemv
Copilot Jul 31, 2026
6cda6c8
fix: avoid illegal sycl::vec<uint16_t,32> in int4 MoE decode kernel
Copilot Jul 31, 2026
ed2c78f
perf: widen int8 MoE decode to 32-wide vectorized ladder
Copilot Jul 31, 2026
488fa34
perf: widen int2 MoE decode to 32-wide vectorized ladder
Copilot Jul 31, 2026
2284e77
perf: widen fp8 MoE decode to 32-wide vectorized ladder
Copilot Jul 31, 2026
a039ad9
perf: hoist per-group/per-expert scale out of fp8 MoE decode inner loop
Copilot Jul 31, 2026
f2c4739
perf: defer per-group scale fold in native fp8 MoE prefill sym mainloop
Copilot Jul 31, 2026
ec61621
merge: integrate opt_moe_kernel MoE kernel optimizations (WIP)
Copilot Jul 31, 2026
90ba433
fix: resolve S4 MoE dispatch conflict (opt_moe env-policy + our w4a16…
Copilot Jul 31, 2026
f4efc89
fix: restore INT4 MoE perf by defaulting S4 direct DPAS path ON
Copilot Jul 31, 2026
5466928
revert: restore int4 MoE to original base implementation
Copilot Jul 31, 2026
515f839
Changes before error encountered
Copilot Jul 31, 2026
93cde8c
fix: restore fp8 per-expert MoE prefill large-M tile policy threshold
Copilot Aug 3, 2026
f887763
fix: restore fp8 per-group MoE prefill large-M tile policy threshold
Copilot Aug 3, 2026
c8374a8
revert: undo fp8 per-group MoE prefill threshold change
Copilot Aug 3, 2026
3b3d714
fix: switch ark.moe auto dispatch to total-token threshold
Copilot Aug 3, 2026
ff20e3a
Merge branch 'main' into copilot/optimize-int4-moe-performance
a32543254 Aug 4, 2026
6c3d26f
feat: route int4-sym MoE decode to shared S4 DPAS grouped GEMM
Copilot Aug 4, 2026
0c94659
test: add int4-sym decode DPAS vs scalar perf comparison; docs
Copilot Aug 4, 2026
ffd2926
fix: split orphaned int8 block out of int4-sym dpas perf test
Copilot Aug 4, 2026
87b617e
fix: lower default MoE auto-decode threshold 256 -> 32
Copilot Aug 4, 2026
23ba0db
test: add int4-sym DPAS vs scalar token-count sweep for MoE decode th…
Copilot Aug 5, 2026
a1480af
perf: hoist per-group scale and split accumulators in int4 MoE decode…
Copilot Aug 5, 2026
c20c0b2
perf: coalesce int4 MoE decode weight loads via on-device N-tiled repack
Copilot Aug 5, 2026
8c732ed
perf: block tokens-per-expert in coalesced int4 MoE decode GEMV
Copilot Aug 5, 2026
cc939f8
fix: parameterize MoEDecodeRepackInt4 kernel name to avoid duplicate …
Copilot Aug 5, 2026
590ce59
perf: gate coalesced int4 decode on repack amortization to fix tiny-b…
Copilot Aug 5, 2026
d80a9a1
fix: qualify TOKEN_BLOCK with moe_decode_detail namespace
Copilot Aug 5, 2026
7db3d5f
perf: hoist routing filter out of int4 coalesced decode hot loop
Copilot Aug 5, 2026
c8ef1b8
feat: add dedicated w4a16_policy_m_8 decode DPAS dispatch for S4 sym
Copilot Aug 5, 2026
a3c82ce
docs+test: document decode S4 DPAS path and add A/B parity assertion
Copilot Aug 5, 2026
3396082
perf: reuse persistent atomic buffer in S4 DPAS dispatch
Copilot Aug 6, 2026
ba2c18d
perf: trim device-timeline overhead on S4 DPAS decode hot path
Copilot Aug 6, 2026
5b88c2e
perf(ark): use A_avg_M tile ladder for int4 moe sym decode (match vll…
Copilot Aug 6, 2026
1f7b631
perf(ark): route decode-sized int4-sym moe through the asym scalar GEMV
Copilot Aug 6, 2026
211e95f
perf(ark): decode int4-sym nibbles through the asym unsigned path (^0…
Copilot Aug 6, 2026
e4d25c5
perf(ark): vectorize int4 decode GEMV loads, hoist activation sums, p…
Copilot Aug 6, 2026
4ec9d7f
test,docs: cover the new int4 decode paths and document the tuning knobs
Copilot Aug 6, 2026
610a005
perf: speed up int4-sym MoE decode with word-native nibble decode
Copilot Aug 6, 2026
a282794
docs: update int4 decode perf notes for word-native nibble decode
Copilot Aug 6, 2026
6cd8192
perf: word-native FP8 decode + decode-tuned FP8 DPAS dispatch
Copilot Aug 6, 2026
3423da8
test: FP8 decode-mode + DPAS parity/perf coverage; docs: FP8 decode s…
Copilot Aug 6, 2026
c6f67a1
fix: restore test_perf_fp8_per_tensor definition; make fp8_decode_mod…
Copilot Aug 6, 2026
7102f0e
perf: add K-split lane-parallel FP8 MoE decode GEMV
Copilot Aug 6, 2026
e43b98f
docs: describe the K-split FP8 decode GEMV (EN + CN)
Copilot Aug 6, 2026
51328f3
perf: N-block the FP8 K-split decode GEMV and drop the per-call routi…
Copilot Aug 7, 2026
03ca877
perf: raise the MoE auto-dispatch decode cutoff from 32 to 128 tokens
Copilot Aug 7, 2026
353543d
test: add qwen3-moe int4-sym prefill and decode perf benchmarks
Copilot Aug 11, 2026
42949b7
docs: document Qwen3-MoE int4-sym prefill/decode perf tests
Copilot Aug 11, 2026
05ad2e1
Merge branch 'copilot/optimize-int4-moe-performance' into W4A8 MoE work
Copilot Aug 11, 2026
ef97754
feat: add W4A8 (int4 weight / int8 compute) ARK XPU MoE kernel
Copilot Aug 11, 2026
f789819
feat: add W4A8 MoE Python API and perf/accuracy benchmark script
Copilot Aug 11, 2026
dc5ac50
docs: add EN + CN README for the W4A8 MoE benchmark script
Copilot Aug 11, 2026
3e969ce
fix: pin source tensors in the W4A8 prepack cache to prevent address-…
Copilot Aug 11, 2026
8cb7034
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 11, 2026
1ff5679
perf(ark): W4A8 MoE decode K-split mapping + roofline-aware benchmark
Copilot Aug 12, 2026
22a7a65
docs(ark): document the W4A8 roofline, decode K-split mapping and new…
Copilot Aug 12, 2026
bb7bdce
fix(ark): compare the two decode mappings by SNR, not bit-equality
Copilot Aug 12, 2026
ed2e3ca
perf(ark): widen the prefill tile to 256x256 and make the decode chun…
Copilot Aug 12, 2026
725b2c8
docs(ark): document the prefill tile / decode chunk knobs, MiniMax sh…
Copilot Aug 12, 2026
35e89ae
fix(ark): carry the 32-byte W4A8 decode chunk as 16-byte sycl::vec su…
Copilot Aug 12, 2026
d112a37
perf(ark): top the W4A8 prefill tile ladder at 128x128 from the measu…
Copilot Aug 12, 2026
210c9ad
perf(ark): widen W4A8 act-quant messages and drop the float C shadow …
Copilot Aug 13, 2026
1197fe2
test(ark): sweep the W4A8 act-quant message width and assert bit-iden…
Copilot Aug 13, 2026
95e7853
perf(ark): add the measured 256x128 prefill rung and an interior-tile…
Copilot Aug 13, 2026
fba1656
perf(ark): take the 256-row prefill tile everywhere and batch the act…
Copilot Aug 13, 2026
a6b07c5
docs(ark): record the new sweep and the act-quant unroll knob (EN + CN)
Copilot Aug 13, 2026
6d16b00
docs(ark): pin the W4A8 roofline to the Arc Pro B60 it was measured o…
Copilot Aug 13, 2026
d8f3e29
docs(ark): note the 24 GB B60 ceiling on the W4A8 prepack cache (EN +…
Copilot Aug 13, 2026
b56f301
perf(ark): 2D block store epilogue + single-pass activation quantizer…
Copilot Aug 13, 2026
3660ad4
docs: document the single-pass quantizer, the 2D store and the correc…
Copilot Aug 13, 2026
d7b09b1
docs: correct the D-vs-weight byte figures for the 384 rows/expert batch
Copilot Aug 13, 2026
038f02e
perf: re-tune the W4A8 prefill tile ladder on the 384 rows/expert sweep
Copilot Aug 13, 2026
a561aa0
docs: refresh the W4A8 prefill tuning tables from the 384 rows/expert…
Copilot Aug 13, 2026
a1d2ab5
test: add 8K-prompt prefill case and tile sweep to W4A8 perf harness
Copilot Aug 13, 2026
4318efa
perf: retire the W4A8 prefill 256-row tile rung from the auto ladder
Copilot Aug 13, 2026
fb2fca6
perf: cut W4A8 prefill traffic with int8-in and fused top-k reduce
Copilot Aug 14, 2026
dce6713
docs: document the W4A8 prefill call contracts and correct the A-tile…
Copilot Aug 14, 2026
a3b1241
fix: pass prefetch_dist and reduce through the W4A8 prefill launch macro
Copilot Aug 14, 2026
49307cd
fix: bound the W4A8 pre-quantized contract test to one ULP instead of…
Copilot Aug 14, 2026
064b3cf
fix: build W4A8 pre-quantized contract cases on the int8 grid
Copilot Aug 14, 2026
81aa4e6
Merge branch 'main' into copilot/copilotoptimize-int4-moe-performance
Copilot Aug 31, 2026
1649b92
style: fix continuation indent on sycl_tla_moe_decode_fill_expert_id …
Copilot Aug 31, 2026
ff1ca3e
fix: emit ark::moe_decode_release_scratch from generated int4 decode TU
Copilot Aug 31, 2026
90d69ee
merge: bring branch up to date with main, scope PR to W4A8 only
Copilot Sep 4, 2026
170be37
docs: point W4A8 declarations at MOE_SOURCE_MODE 21, not 19
Copilot Sep 4, 2026
03a0019
Merge branch 'main' into copilot/copilotoptimize-int4-moe-performance
a32543254 Sep 6, 2026
d7b1e0f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 6, 2026
a5f0f75
refactor: split W4A8 MoE kernels across translation units to cut comp…
Copilot Sep 7, 2026
2ac5d4f
docs: describe the W4A8 three-header / 19-TU source layout
Copilot Sep 7, 2026
dcf4b24
chore: drop stray .orig_moe_w4a8.hpp scratch backup
Copilot Sep 7, 2026
f2c43e6
fix: include utils.hpp for DeviceMemoryPool in the W4A8 helpers header
Copilot Sep 7, 2026
53ff079
refactor: move W4A8 scratch pools into their own TU to keep utils.hpp…
Copilot Sep 7, 2026
c9adeed
docs: document the W4A8 scratch TU split in both READMEs and the head…
Copilot Sep 7, 2026
aa9f118
Merge branch 'main' into copilot/copilotoptimize-int4-moe-performance
a32543254 Sep 7, 2026
53351da
Merge branch 'main' into copilot/copilotoptimize-int4-moe-performance
a32543254 Sep 7, 2026
4c1fef5
test: sweep W4A8 prefill prefetch depth over 1-8 at the long-prompt r…
Copilot Sep 7, 2026
f98a776
docs: record the measured prefetch sweep and the real prefill limiter
Copilot Sep 7, 2026
6d17d0a
fix: make the W4A8 perf sweeps robust to clock drift
Copilot Sep 7, 2026
a72cd6d
Changes before error encountered
Copilot Sep 7, 2026
4591c4b
fix: charge the caller-side reduction to unfused rows in the contract…
Copilot Sep 7, 2026
86b6400
Merge branch 'main' into copilot/copilotoptimize-int4-moe-performance
a32543254 Sep 7, 2026
463dcbe
test: isolate the int8-in contract and stop fusing the up projection
Copilot Sep 7, 2026
69b44e2
fix: make the bandwidth probe sound and record the measured contract-…
Copilot Sep 7, 2026
a4dbb54
perf: deduplicate MoE prefill activation quantization for up/gate
Copilot Sep 7, 2026
f2d1b0b
fix: attribute the dedup benchmark's stages and measure the fused qua…
Copilot Sep 7, 2026
271c77e
docs: record the measured 1.46x for deduplicated activation quantization
Copilot Sep 7, 2026
3318b69
perf: issue the w4a8 prefill tile claim before the GEMM instead of af…
Copilot Sep 8, 2026
a7a5678
docs: record the early tile claim in both READMEs
Copilot Sep 8, 2026
93b84d8
docs: claim_early measured flat on B70, and the per-tile cost model t…
Copilot Sep 8, 2026
b70ac5e
perf: make the w4a8 prefill GRF budget a property of the tile policy
Copilot Sep 8, 2026
c289f7b
test: sweep the prefill register budget and assert it is bit-identical
Copilot Sep 8, 2026
b239108
docs: record the per-policy GRF budget and the occupancy sweep in bot…
Copilot Sep 8, 2026
4ec0465
perf: default the w4a8 prefill small-GRF budget off — B70 measured it…
Copilot Sep 8, 2026
e49b7dc
perf: make the act-quant work-group shape a knob and time the pass di…
Copilot Sep 8, 2026
4851405
docs: document the act-quant work-group knob and direct timing (EN + CN)
Copilot Sep 8, 2026
e433471
fix: cost the dedup path with the direct quant measurement, not diffe…
Copilot Sep 8, 2026
10cf6a2
fix: repair the dead guard that made the dedup path silently fall bac…
Copilot Sep 8, 2026
c77f80b
docs: record the settled 8192-row quantization measurement
Copilot Sep 8, 2026
5f7b754
Merge branch 'main' into copilot/copilotoptimize-int4-moe-performance
a32543254 Sep 10, 2026
7d32f35
Merge branch 'main' into copilot/copilotoptimize-int4-moe-performance
a32543254 Sep 10, 2026
8b04e40
Merge branch 'main' into copilot/copilotoptimize-int4-moe-performance
a32543254 Sep 11, 2026
2bd0bdc
Merge branch 'main' into copilot/copilotoptimize-int4-moe-performance
a32543254 Sep 15, 2026
588a53d
Merge branch 'main' into copilot/copilotoptimize-int4-moe-performance
a32543254 Sep 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
490 changes: 490 additions & 0 deletions auto_round_extension/ark/auto_round_kernel/__init__.py

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions auto_round_extension/ark/auto_round_kernel/ark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,42 @@ static void moe_gemm_prefill_int_dpas_wrapper(torch_ptr stream, torch_ptr activa
(int*)num_tokens_per_expert, num_experts, total_tokens);
}

// W4A8 MoE: int4 weights re-scaled to int8 (AUTO_S8), int8 DPAS compute,
// per-token dynamically quantized int8 activations. `weights_s8` is
// [E, N, K] int8 and `wscales` is [E, N, K/rescale_block] FP32, both produced
// by `moe_w4a8_prepack`. STATUS: NEEDS-HARDWARE-VALIDATION.
static void moe_w4a8_prepack_wrapper(torch_ptr stream, torch_ptr weights_s4, torch_ptr scales,
torch_ptr weights_s8, torch_ptr wscales, int act_dtype, int num_experts,
int N, int K, int group_size, int rescale_group_size) {
ark::moe_w4a8_prepack((sycl::queue*)stream, (void*)weights_s4, (void*)scales, (void*)weights_s8,
(void*)wscales, (BTLA_DTYPE)(act_dtype), num_experts, N, K, group_size,
rescale_group_size);
}

// `qact`/`ascale` are optional pre-quantized activations (0 = quantize in the
// call); `row_to_token`/`routing_weights`/`fused_out` are the optional fused
// top-k reduction (0 = write the unreduced `[T, N]` output).
static void moe_gemm_w4a8_wrapper(torch_ptr stream, torch_ptr activations, torch_ptr weights_s8,
torch_ptr wscales, torch_ptr outputs, int act_dtype, int N, int K,
int rescale_block_size, torch_ptr num_tokens_per_expert, int num_experts,
int total_tokens, int phase, torch_ptr qact, torch_ptr ascale,
torch_ptr row_to_token, torch_ptr routing_weights, torch_ptr fused_out,
int fused_batch) {
ark::moe_gemm_w4a8((sycl::queue*)stream, (void*)activations, (void*)weights_s8, (void*)wscales, (void*)outputs,
(BTLA_DTYPE)(act_dtype), N, K, rescale_block_size, (int*)num_tokens_per_expert, num_experts,
total_tokens, phase, (const void*)qact, (const float*)ascale, (const int*)row_to_token,
(const float*)routing_weights, (float*)fused_out, fused_batch);
}

// Standalone per-token activation quantization -- the pass `moe_gemm_w4a8`
// runs internally, exposed so it can be timed on its own and so a caller can
// pre-quantize once and hand the result back as `qact`/`ascale`.
static void moe_w4a8_quant_act_wrapper(torch_ptr stream, torch_ptr activations, torch_ptr qact, torch_ptr ascale,
int act_dtype, int total_tokens, int K) {
ark::moe_w4a8_quant_act((sycl::queue*)stream, (const void*)activations, (void*)qact, (void*)ascale,
(BTLA_DTYPE)(act_dtype), total_tokens, K);
}

static void sage_dynamic_quant(torch_ptr stream, torch_ptr input, torch_ptr bias, torch_ptr output, torch_ptr scale_out,
int num_rows, int head_dim, int block_size) {
auto* q = (sycl::queue*)stream;
Expand Down Expand Up @@ -1430,6 +1466,11 @@ PYBIND11_MODULE(PY_NAME, m) {
m.def("moe_gemm_prefill", &ark::moe_gemm_prefill_wrapper);
m.def("moe_gemm_prefill_fp8_dpas", &ark::moe_gemm_prefill_fp8_dpas_wrapper);
m.def("moe_gemm_prefill_int_dpas", &ark::moe_gemm_prefill_int_dpas_wrapper);
m.def("moe_w4a8_prepack", &ark::moe_w4a8_prepack_wrapper);
m.def("moe_gemm_w4a8", &ark::moe_gemm_w4a8_wrapper);
m.def("moe_w4a8_quant_act", &ark::moe_w4a8_quant_act_wrapper);
m.def("moe_w4a8_rescale_block_size", &ark::moe_w4a8_rescale_block_size);
m.def("moe_w4a8_release_scratch", &ark::moe_w4a8_release_scratch);
m.def("matmul_sycl_tla", &ark::matmul_sycl_tla);
#endif // ARK_SYCL_TLA
#if !defined(ARK_XPU)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,68 @@ set(MOE_SOURCE_MODE 16)
generate_sycl_tla_source(sycl_tla_moe.cpp.in sycl_tla_moe_prefill_s4.cpp)
set(MOE_SOURCE_MODE 17)
generate_sycl_tla_source(sycl_tla_moe.cpp.in sycl_tla_moe_prefill_native_fp8.cpp)
# W4A8 (int4 weights re-scaled to int8, int8 activations) prepack + GEMM.
#
# This used to be a single `sycl_tla_moe_w4a8.cpp`, which instantiated all 52
# W4A8 kernels -- 12 grouped DPAS GEMMs, 14 decode GEMVs, 22 activation-quant
# and 4 prepack kernels -- and peaked at ~4.2 GB of compiler RSS. It is now
# fanned out the same way the S4 prefill is (see MOE_SOURCE_MODE 8): one TU per
# variant, with the dispatcher left holding no kernels at all.
#
# mode 21 dispatcher 0 kernels, cutlass-free
# mode 22 prefill, dtype x tile 1 DPAS kernel each (12 TUs)
# mode 23 decode, dtype 7 kernels each (2 TUs, cutlass-free)
# mode 24 act quant, dtype 11 kernels each (2 TUs, cutlass-free)
# mode 25 prepack, dtype 2 kernels each (2 TUs, cutlass-free)
#
# Only the 12 prefill TUs include the CuTe stack; the other seven take
# `sycl_tla_moe_w4a8_kernels.hpp` / `..._helpers.hpp`, which stop at plain SYCL.
set(MOE_SOURCE_MODE 21)
generate_sycl_tla_source(sycl_tla_moe.cpp.in sycl_tla_moe_w4a8.cpp)

# Prefill tile ladder: the policy names must match `W4A8PrefillTile` /
# `moe_w4a8_prefill_select_tile` in `sycl_tla_moe_w4a8_helpers.hpp`, and the
# function names the `prefill_*` declarations there.
set(_w4a8_tiles m8 m64 m128 m128n256 m256n128 large)
foreach(_w4a8_dtype IN ITEMS f16 bf16)
if(_w4a8_dtype STREQUAL "f16")
set(MOE_SCALAR_TYPE sycl::half)
else()
set(MOE_SCALAR_TYPE sycl::ext::oneapi::bfloat16)
endif()

set(MOE_SOURCE_MODE 22)
foreach(_w4a8_tile IN LISTS _w4a8_tiles)
if(_w4a8_tile STREQUAL "m8")
set(MOE_POLICY_NAME w4a8_policy_m_8)
elseif(_w4a8_tile STREQUAL "m64")
set(MOE_POLICY_NAME w4a8_policy_m_64)
elseif(_w4a8_tile STREQUAL "m128")
set(MOE_POLICY_NAME w4a8_policy_m_128)
elseif(_w4a8_tile STREQUAL "m128n256")
set(MOE_POLICY_NAME w4a8_policy_m_128_n256)
elseif(_w4a8_tile STREQUAL "m256n128")
set(MOE_POLICY_NAME w4a8_policy_m_256_n128)
else()
set(MOE_POLICY_NAME w4a8_policy_large)
endif()
set(MOE_FUNCTION_NAME prefill_${_w4a8_dtype}_${_w4a8_tile})
generate_sycl_tla_source(sycl_tla_moe.cpp.in
sycl_tla_moe_w4a8_prefill_${_w4a8_dtype}_${_w4a8_tile}.cpp)
endforeach()

set(MOE_SOURCE_MODE 23)
set(MOE_FUNCTION_NAME decode_${_w4a8_dtype})
generate_sycl_tla_source(sycl_tla_moe.cpp.in sycl_tla_moe_w4a8_decode_${_w4a8_dtype}.cpp)

set(MOE_SOURCE_MODE 24)
set(MOE_FUNCTION_NAME quant_${_w4a8_dtype})
generate_sycl_tla_source(sycl_tla_moe.cpp.in sycl_tla_moe_w4a8_quant_${_w4a8_dtype}.cpp)

set(MOE_SOURCE_MODE 25)
set(MOE_FUNCTION_NAME prepack_${_w4a8_dtype})
generate_sycl_tla_source(sycl_tla_moe.cpp.in sycl_tla_moe_w4a8_prepack_${_w4a8_dtype}.cpp)
endforeach()

foreach(_moe_dtype IN ITEMS f16 bf16)
if(_moe_dtype STREQUAL "f16")
Expand Down
110 changes: 110 additions & 0 deletions auto_round_extension/ark/auto_round_kernel/sycl_tla_moe.cpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,116 @@ void dispatch_@MOE_DISPATCH_DTYPE@_@MOE_TILE_SUFFIX@(sycl::queue* q, const void*
} // namespace moe_gemm_detail
} // namespace ark

#elif @MOE_SOURCE_MODE@ == 21

// W4A8 dispatcher. Deliberately includes only the cutlass-free front end: the
// tile choice and the public entry points are host code, and every kernel
// lives in one of the per-variant TUs generated by modes 22-25. This TU
// therefore instantiates no kernels at all.
#include "sycl_tla_common.hpp"
#include "sycl_tla_moe_w4a8_helpers.hpp"

namespace ark {

void moe_w4a8_prepack(sycl::queue* q, void* weights_s4, void* scales, void* weights_s8, void* wscales,
BTLA_DTYPE act_dtype, int num_experts, int N, int K, int group_size,
int rescale_group_size) {
moe_w4a8_detail::moe_w4a8_prepack(q, weights_s4, scales, weights_s8, wscales, act_dtype, num_experts, N, K,
group_size, rescale_group_size);
}

void moe_gemm_w4a8(sycl::queue* q, void* activations, void* weights_s8, void* wscales, void* outputs,
BTLA_DTYPE act_dtype, int N, int K, int rescale_block_size, int* num_tokens_per_expert,
int num_experts, int total_tokens, int phase, const void* qact_in, const float* ascale_in,
const int* row_to_token, const float* routing_weights, float* fused_out, int fused_batch) {
moe_w4a8_detail::moe_gemm_w4a8(q, activations, weights_s8, wscales, outputs, act_dtype, N, K, rescale_block_size,
num_tokens_per_expert, num_experts, total_tokens, phase, qact_in, ascale_in,
row_to_token, routing_weights, fused_out, fused_batch);
}

int moe_w4a8_rescale_block_size(int K, int group_size, int rescale_group_size) {
return moe_w4a8_detail::moe_w4a8_rescale_block_size(K, group_size, rescale_group_size);
}

void moe_w4a8_quant_act(sycl::queue* q, const void* activations, void* qact, void* ascale, BTLA_DTYPE act_dtype,
int total_tokens, int K) {
moe_w4a8_detail::moe_w4a8_quant_act(q, activations, qact, ascale, act_dtype, total_tokens, K);
}

void moe_w4a8_release_scratch() { moe_w4a8_detail::moe_w4a8_release_scratch(); }

} // namespace ark

#elif @MOE_SOURCE_MODE@ == 22

// W4A8 prefill, one DPAS policy per translation unit.
//
// The single TU this replaced instantiated 52 kernels -- 12 of them grouped
// DPAS GEMMs -- and peaked at ~4.2 GB of compiler RSS. Fanning the tile ladder
// out over (dtype x tile) leaves exactly one DPAS kernel here, mirroring how
// `sycl_tla_moe_prefill_s4_*.cpp` splits the S4 prefill.
#include "sycl_tla_moe_w4a8.hpp"

namespace ark {
namespace moe_w4a8_detail {

void @MOE_FUNCTION_NAME@(const W4A8PrefillParams& params) {
moe_w4a8::moe_w4a8_prefill_launch<moe_w4a8::@MOE_POLICY_NAME@, @MOE_SCALAR_TYPE@>(params);
}

} // namespace moe_w4a8_detail
} // namespace ark

#elif @MOE_SOURCE_MODE@ == 23

// W4A8 decode GEMV, one dtype per translation unit (7 kernels: the legacy
// GEMV plus the six K-split (NCOLS, CH) specializations). Takes the
// cutlass-free kernel header, so this TU never parses CuTe.
#include "sycl_tla_moe_w4a8_kernels.hpp"

namespace ark {
namespace moe_w4a8_detail {

void @MOE_FUNCTION_NAME@(const W4A8DecodeParams& params) {
moe_w4a8::moe_w4a8_decode_launch<@MOE_SCALAR_TYPE@>(params);
}

} // namespace moe_w4a8_detail
} // namespace ark

#elif @MOE_SOURCE_MODE@ == 24

// W4A8 per-token activation quantization, one dtype per translation unit
// (11 kernels: the message-width x unroll grid, the single-pass variants and
// the scalar fallback). Cutlass-free.
#include "sycl_tla_moe_w4a8_kernels.hpp"

namespace ark {
namespace moe_w4a8_detail {

void @MOE_FUNCTION_NAME@(const W4A8QuantParams& params) {
moe_w4a8::moe_w4a8_quant_launch<@MOE_SCALAR_TYPE@>(params);
}

} // namespace moe_w4a8_detail
} // namespace ark

#elif @MOE_SOURCE_MODE@ == 25

// W4A8 one-shot AUTO_S8 prepack, one dtype per translation unit (2 kernels:
// the per-block scale reduction and the int4 -> int8 re-scale). Cutlass-free.
#include "sycl_tla_moe_w4a8_kernels.hpp"

namespace ark {
namespace moe_w4a8_detail {

void @MOE_FUNCTION_NAME@(const W4A8PrepackParams& params) {
moe_w4a8::moe_w4a8_prepack_launch<@MOE_SCALAR_TYPE@>(params);
}

} // namespace moe_w4a8_detail
} // namespace ark

#endif

#endif // ARK_XPU && ARK_SYCL_TLA
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Device scratch management for the W4A8 MoE path.
//
// The bookkeeping below is deliberately defined here rather than in a header so
// that the module holds exactly one instance of it, and so that `utils.hpp`
// (and the bestla JIT headers behind it) stays out of the cutlass-free W4A8
// translation units. See `sycl_tla_moe_w4a8_scratch.hpp` for the rationale.
//
// Copyright (C) 2026 Intel Corporation
// SPDX-License-Identifier: Apache-2.0

#include "sycl_tla_moe_w4a8_scratch.hpp"

#if defined(ARK_XPU) && defined(ARK_SYCL_TLA)

#include <map>
#include <mutex>
#include <stdexcept>
#include <utility>
#include <vector>

#include "utils.hpp"

namespace ark {
namespace moe_w4a8 {

namespace {

struct W4A8ScratchState {
std::mutex mu;
// Device key (`DeviceMemoryPool::get_device_key`) -> a queue handle on that
// device, held *by value*: a `sycl::queue` is a reference-counted handle, so
// keeping a copy guarantees the queue outlives the memory allocated against
// it.
std::map<size_t, sycl::queue> queues;
};

// Intentionally leaked, see the header.
W4A8ScratchState& w4a8_scratch_state() {
static W4A8ScratchState* s = new W4A8ScratchState();
return *s;
}

// Acquire a slab from the shared pool, synchronizing first when the request
// grows it: `DeviceMemoryPool` frees the old pointer in place when it grows a
// slot, and in-flight kernels may still be reading the old slab, so the wait
// has to happen before the call rather than after.
//
// The caller must hold `W4A8ScratchState::mu`.
void* acquire_w4a8_slab(sycl::queue* q, size_t bytes, size_t buf_loc) {
auto* pool = DeviceMemoryPool::Instance();
const size_t held = pool->get_scratch_size(buf_loc, q);
if (held != 0 && held < bytes) {
q->wait();
}
void* ptr = pool->get_scratch_mem(bytes, buf_loc, q);
if (ptr == nullptr) {
// The pool records the slot before checking the result, so a failed
// allocation leaves a {bytes, nullptr} entry behind that would satisfy
// every later request of this size or smaller without ever retrying.
// Drop it so the next call allocates again.
pool->detach_scratch_mem(buf_loc, q);
throw std::runtime_error("moe_gemm_w4a8: failed to allocate device scratch buffer");
}
auto& st = w4a8_scratch_state();
const size_t key = pool->get_device_key(q);
if (st.queues.find(key) == st.queues.end()) {
st.queues.emplace(key, *q);
}
return ptr;
}

} // namespace

uint8_t* acquire_qact_scratch(sycl::queue* q, size_t bytes) {
if (q == nullptr) {
throw std::invalid_argument("moe_gemm_w4a8: device scratch requires a non-null SYCL queue");
}
if (bytes == 0) return nullptr;
auto& st = w4a8_scratch_state();
std::lock_guard<std::mutex> lock(st.mu);
return static_cast<uint8_t*>(acquire_w4a8_slab(q, bytes, kW4A8QactScratchLoc));
}

int* acquire_expert_map_scratch(sycl::queue* q, size_t bytes) {
if (q == nullptr) {
throw std::invalid_argument("moe_gemm_w4a8: device scratch requires a non-null SYCL queue");
}
if (bytes == 0) return nullptr;
auto& st = w4a8_scratch_state();
std::lock_guard<std::mutex> lock(st.mu);
return static_cast<int*>(acquire_w4a8_slab(q, bytes, kW4A8ExpertMapScratchLoc));
}

void moe_w4a8_release_scratch() {
auto& st = w4a8_scratch_state();

// Detach everything under the lock, then drop the lock before the device sync
// and the frees: `wait()` blocks for an unbounded time and must not be held
// across. Because the slabs are already out of the pool's tables, an acquire
// that races in behind us allocates fresh ones instead of handing back a
// pointer we are about to free.
std::vector<std::pair<sycl::queue, void*>> pending;
{
std::lock_guard<std::mutex> lock(st.mu);
auto* pool = DeviceMemoryPool::Instance();
for (auto& kv : st.queues) {
sycl::queue q = kv.second;
for (size_t loc : {kW4A8QactScratchLoc, kW4A8ExpertMapScratchLoc}) {
void* ptr = pool->detach_scratch_mem(loc, &q);
if (ptr != nullptr) pending.emplace_back(q, ptr);
}
}
st.queues.clear();
}

for (auto& item : pending) {
item.first.wait();
sycl::free(item.second, item.first);
}
}

} // namespace moe_w4a8
} // namespace ark

#endif // ARK_XPU && ARK_SYCL_TLA
Loading
Loading