feat: W4A8 ARK XPU MoE kernel (int4 weight / int8 compute) with prefill + decode#2143
Conversation
Signed-off-by: Dong, Bo1 <bo1.dong@intel.com>
Signed-off-by: Dong, Bo1 <bo1.dong@intel.com>
Merge ec61621 accidentally widened the dpas_w8a16_policy_m_32 bucket in the fp8 per-tensor (per-expert) prefill dispatch from A_avg_M <= 32 to <= 512, routing large-M prefill through the small 32x64 tile instead of the large-M 128x128 default tile and regressing performance. Restore the <= 32 threshold.
Merge ec61621 also widened the dpas_w8a16_policy_m_32 bucket in the fp8 per-group prefill dispatch from A_avg_M <= 32 to <= 512, regressing large-M group-size prefill for the same reason as the per-tensor path. Restore the <= 32 threshold so large-M prefill uses the 128x128 default tile.
Migrate the auto-dispatch logic from branch copilot/update-phase-auto-dispatch-logic (commit 9605fe4): phase="auto" now dispatches to decode when activations.shape[0] <= threshold (total tokens) instead of inspecting num_tokens_per_expert.max(), avoiding a host-device sync. Adds ARK_MOE_AUTO_DECODE_MAX_TOKENS env override (default 256) and updates test_moe_unified.py accordingly.
Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
The shared S4 DPAS grouped-GEMM (prefill path) already beats the scalar GEMV decode kernel by ~2x at 256 tokens (bs32) and only loses at the single-stream bs1 (8-token) extreme. Routing 256-token batches to decode was leaving ~2x on the table, so lower the auto-dispatch default and update coupled unified-dispatch tests and perf-test notes. Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
…reshold tuning Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
… GEMV Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
…mangled names Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
…atch regression Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
… fusion claim Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
… bit-identity Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
|
@copilot resolve the merge conflicts in this pull request |
Resolve conflicts with main's SYCL-TLA translation-unit refactor, which
moved kernel entry points out of the headers into CMake-generated TUs
(sycl_tla_moe.cpp.in + MOE_SOURCE_MODE).
Textual conflicts:
- ark.cpp: keep main's include list (kernel headers are no longer pulled
into the pybind TU).
- sycl_tla_common.hpp: keep both sides -- main's dense-GEMM / igemm
declarations plus this branch's W4A8 declarations.
- sycl_tla_moe.hpp / sycl_tla_moe_decode.hpp: drop the header-resident
ark::moe_gemm and ark::moe_gemm_decode definitions (main now emits them
non-inline from modes 1 and 3; keeping the header copies would be an ODR
violation). The moe_detail / moe_decode_detail internals are unchanged.
Semantic ports (not flagged by git, since the files differ):
- mode 2 called moe_gemm_launcher<'R','R',Element,...>, but this branch
changed that template to take WGTile/SGLayout and added
moe_gemm_dispatch<Element> for tile selection -- switch mode 2 over.
- MoeDecodeParams gains num_tokens_per_expert / num_experts; the int4-sym
and FP8 DPAS fast paths are driven by per-expert row counts, not the
per-token expert map.
- New shared predicates sycl_tla_moe_decode_{int4,fp8}_dpas_fastpath so
mode 3 can skip the fill_expert_id launch on exactly the paths that
never read it, without the skip and the routing decision diverging.
- mode 5 (fp8): add the FP8 DPAS fast path and route the fallback through
launch_fp8_by_mode.
- new mode 18: int4 decode gets its own TU (mode 6 is shared with
int2/int8) carrying the S4 DPAS fast path and the coalesced GEMV.
- new mode 19: the four W4A8 entry points. The header versions move into
namespace moe_w4a8_detail and mode 19 emits the external ark:: symbols,
since pybind takes their address and inline definitions would leave the
symbols unemitted.
Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
…decl Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
Merged in This wasn't purely textual. Textual conflicts (5 regions, 4 files)
Semantic breakages git did not flag (the files differ, so no conflict was reported)
Verification — no SYCL compiler in this environment, so checks were static: every namespaced symbol in the template resolves against the merged headers; exactly one definition site per entry point; all call arities and decl/defn signatures match; namespace and per-mode brace balance clean; and the auto-merged The kernel still needs the hardware validation called out in the description — that's unchanged by this merge. |
Co-authored-by: a32543254 <53296245+a32543254@users.noreply.github.com>
|
/azp run Unit-Test-CUDA-AutoRound |
|
Azure Pipelines could not run because the pipeline triggers exclude this branch/path. |
Description
Adds a W4A8 MoE kernel to the ARK XPU backend covering both prefill and decode: int4 symmetric weights, int8 compute dtype, activations dynamically quantized per token to int8. Follows the W4A8 weight-only GEMM in
zhenzhong/woqgemm_s8_update, and uses ARK'sAUTO_S8trick — re-scaling int4 group=32 weights into int8 group=-1 so the K loop needs a single full-width int32 accumulation instead of per-group folding.Built on top of
copilot/optimize-int4-moe-performance.Numerics
AUTO_S8 re-scale, ported from
packscale/unpackqinxpu_wrapper.hpp:Default block = K (
group=-1) →blks == 1. Since_pack_int4_symdivides by 7.0,max|w8| = 7 * 127/8 ≈ 111 ≤ 127, so the conversion never clips. Epilogue isout = acc_s32 * scale_b[col] * scale_a[row].Kernel —
wrapper/include/sycl_tla_moe_w4a8.hpp(new)moe_w4a8_prepack:[E,N,K/2]int4 →[E,N,K]int8 +[E,N,blks]fp32 scales. CostsE*N*Kbytes (2× the packed int4).x_s8andscale_a.XE_DPAS_TT<8, int32_t, int8_t, int8_t>, tile ladder mirroring the reference (m<16 → 8x128,m<128 → 64x128,m<=1024 → 128x128, else256x128).num_tokens_per_expertis a device tensor, so it reuses the existing persistent work-stealing scheduler.SG_SIZE=16/N_TILE=16, one output column per lane.ARK_MOE_W4A8_AUTO_S8overrides the rescale block size; invalid values fall back to K. Shape gate:N % 16 == 0,K % 64 == 0,group_size % 8 == 0,K % group_size == 0.Plumbing
sycl_tla_common.hpp: 4 public declarations.ark.cpp: include, 2 wrappers, 4m.defregistrations.auto_round_kernel/__init__.py: Python API plus a prepack cache. The cache key includes device type/index and the entry pins the source tensors, so a freed-and-reallocated weight buffer can't alias another layer's int8 weights.Benchmark —
test/test_moe_w4a8_perf.py(new)Standalone perf + accuracy harness, runnable under pytest or directly. Qwen3-MoE shapes (
E=128, hidden=2048, inter=768, top_k=8, group_size=32). Reports SNR/cosine/max-rel-err against a torch bf16 baseline and latency/TFLOPS/speedup against the W4A16 kernel, for prefill across batch×seq and decode across batch.Type of Change
New feature (Performance)
Checklist Before Submitting
/azp run Unit-Test-CUDA-AutoRound.Important
Needs hardware validation. No XPU or SYCL compiler was available, so the kernel is unbuilt and has not run on device; the header carries the same
STATUS: NEEDS-HARDWARE-VALIDATIONmarker as the sibling MoE DPAS headers. What was checked offline: a numpy replay of the full AUTO_S8 + int8-activation path (no clipping, ~38.5 dB end-to-end output SNR vs. the script's 20 dB gate), Python↔C++ parity ofmoe_w4a8_rescale_block_sizeacross 11 cases, pybind argument order against the Python call sites, and offset overflow atE=128/N=2048/K=2048.Worth a close look on hardware: CuTe tile/policy tuning, and the 128-token cutoff for decode auto-dispatch.
Docs:
test/README_MOE_W4A8.md+README_MOE_W4A8_CN.md.