Repository navigation
refactor(cpu-moe): split the executor into weight formats and gpu sync backends - #670
Conversation
…c backends
- move each expert weight format into kernels/<fmt>.cpp behind the MoeFormat
interface in format.h; the executor keeps the threads, the work split and
the pass 2 route sum
- pass each format its banks and integer options by name; MoeParams keeps only
the shared shape and activation fields
- key the formats by name in one table that weight_formats() exports, replacing
the integer format ids and the fp8_block_isa_name hook
- replace the per-(layer, batch size) flag slots, pinned IO buffers and task
table with one doorbell word MoeTask{layer, num_tokens} and one shared pinned
IO block of max_tokens rows
- move the GPU/CPU handshake behind GpuSync with a CUDA and a HIP backend picked
by setup.py; HIP waits on signal memory and adds explicit batch-memop graph
nodes where the runtime does not record the native memops under capture
- raise on graph capture when the handshake is off and host functions are not
replay-safe
- allocate format scratch on 64-byte boundaries
- size max_tokens by the explicit cuda_graph_bs as well
Co-authored-by: zihaomu <zihaomu@outlook.com>
|
Pushed two perf commits on top of the refactor:
Decode throughput on H100 80GB HBM3 and Xeon Platinum 8480+,
Output text sha1 matches 51afda3 in every run. The numbers come from a build with only the |
|
I found a HIP probe failure-recovery issue while validating this PR on AMD Radeon AI PRO R9700 (
The branch adds exactly one commit after Failure and runtime scope If This is a fault-injection recovery issue, not evidence that normal ROCm 10 capture is broken. Normal ROCm 10 native WRITE/WAIT capture passed. ROCm 7.14 naturally selects the explicit-node compatibility path; reproducing the dependency-update failure on ROCm 10 requires forcing that compatibility path. I also checked the actual loaded HIP libraries and found no runtime-version mix-up. Fix logic
Validation Single visible R9700; host CPU: dual AMD EPYC 9334. Separate rebuilt native extensions were used for both environments:
The three new tests cover dependency-update failure, an EndCapture failure that sets a real HIP last-error, and a pre-existing caller error. They verify eager numerical results on changed inputs, balanced capture cleanup, node rollback, rejection of unsafe graph capture, and preservation of the caller's earlier error.
The original combined-runtime fault/disable matrix improved from 19/22 to 22/22 passed. On the HIP-only repair of After updating to To apply just this fix: git fetch https://github.com/zihaomu/FreeToken.git fix/pr670-hip-probe-recovery-20261014
git cherry-pick FETCH_HEADThe targeted regression entry point, after rebuilding the HIP extension in the configured environment, is: python setup.py build_ext --inplace
python -m pytest -q tests/moe/test_cpu_moe.py -k test_cpu_moe_hip_probe_error_recovery |
Summary
cpu_moe_ext.cppheld the CPU MoE executor, the kernels of all six weight formats and the CUDA-only GPU/CPU handshake in one 2.4k-line file. This PR splits it so that a weight format, an ISA tier or a GPU runtime can change without touching the executor. It also moves the GPU side of the handshake into two kernels on CUDA, and the coordinator skips tasks without routed experts (see Kernel IO).Layout of
kernel/csrc/cpu_moe/:cpu_moe_ext.cppformat.hMoeFormatinterface,MoeParams(shared shape and activation fields),FormatArgs(banks and options by name)kernels/<fmt>.cppMoeFormatclass (bf16, nvfp4, mxfp4_triton, ds_fp4, q4_0, fp8_block)common.hCPU_MOE_TARGET, numeric helpers, 64-byte aligned scratchgpu_sync.h,gpu_sync_cuda.cpp,gpu_sync_hip.cppsetup.pybuilds the backend of the installed torchgpu_sync_io.h,gpu_sync_io.cufreetoken/hip_compat.hFormat interface
gate_up(layer, e, tok, i0, i1, x, w_in, g)for a block of intermediate rows anddown(layer, h0, h1, routes, n, out)for a block of output rows over all of a token's routes at once. Visiting the routes' rows together keeps the down GEMV at DRAM bandwidth.prep_input,prep_interandrounds_routesare optional hooks with defaults.MoeParamsholds only the fields every format shares.kFormats;_cpu_moe.weight_formats()exports the names. This replaces the integer format ids kept in sync between C++ and Python.Handshake
MoeTask{layer:32, num_tokens:32}into a singlereadyword and waits on a singledoneword. Inputs and outputs go through one shared pinned IO block ofmax_tokensrows. This replaces the per-(layer, batch size) flag slots, pinned IO buffers and task table. It holds because one task is in flight: decode runs on one stream and every layer waits before the next one submits.GpuSyncstates two contracts a backend keeps in eager mode and in graph replay: submit (GPU to CPU) and wait (CPU to GPU).cuStreamWriteValue64/cuStreamWaitValue64path.--cuda-graph-max-bs 0instead of producing wrong outputs on replay. Refs [ROCm] CpuMoeExecutor decode is silently wrong under CUDA-graph replay (rel err 1.36, no exception) #350.The HIP handshake and its probe are adapted from #378 and #667 by @zihaomu.
Kernel IO
KernelIo::publishwrites the step's x / ids / w into the pinned IO block through its mapped address, then writes done = 0 and the doorbell word. When none of the step's routes goes to the CPU it writes done = 1 and rings nothing, so the coordinator is not woken and the wait passes at once.KernelIo::combinefollows the wait. It reads y from the mapped block and adds it into the GPU partial in place (hybrid) or writes it out (cpu backend).gpu_sync.hstates the skip, andGpuSyncgivesKernelIothe device addresses of its two words.gpu_sync_io.cuis the only device source in_cpu_moe, which stays aCppExtension. On CUDA it is built as SASS for sm_75, sm_80, sm_90, sm_100 and sm_120 (each also runs on the newer minors of its major) pluscompute_75PTX that the driver builds for any other GPU. Naming the archs also keeps torch from picking them, which fails on a build machine without a GPU. On ROCm torch adds its offload architectures.kernel_io_probe). If that fails, the executor logs a warning and keeps the memop doorbell with copies. ROCm builds the kernels but keeps the memop path until they are checked on AMD GPUs. The executor-ready log line names the handshake in use (kernel,memoporhost-func).Behavior changes
apply_router_weight_on_inputwithmxfp4_tritonords_fp4experts raises; both apply the router weight on the output. No current model sets it.top_k > 64raises.max_tokensraises. The engine sizesmax_tokensto cover explicitcuda_graph_bsas well.weight_format,banksandoptions;memop_submit/memop_sync,set_act_block/set_input_prequant,fp8_block_isa_nameandmax_weight_format_idare removed;enable_kernel_io,flag_publish,flag_combineandkernel_io_probeare added.Validation
Refactor commit (51afda3)
Intel Xeon Platinum 8559C, RTX PRO 6000 Blackwell Server Edition, torch 2.14.1.
tests/moe tests/engine tests/kernels/test_swiglu_clamp.py tests/models/deepseek_v41/test_engram.py: 276 passed, 6 skipped. The two fewer than main are the fp8_block avx512bf16 cases, which ran the same kernel as avx512. Graph-replay tests also pass withFREETOKEN_CPU_MOE_FLAG_SYNC=0.bench_decode_moe.py --greedy --decode 512 --cache-rate 0.25, ABBA against main, tok/s:Output tokens match main in every run.
tests/moe/test_cpu_moe.py::test_cpu_moe_decode_cuda_graph_replayon ROCm 10 and on ROCm 7.14 would cover both handshake paths.Kernel IO and coordinator skip (the two perf commits)
Intel Xeon Platinum 8480+, H100 80GB HBM3, torch 2.14.1+cu130.
tests/moe tests/engine/test_moe_cpu_layers.py tests/kernels/test_swiglu_clamp.py: 157 passed, 6 skipped, the same as 51afda3 on this machine.tests/), one executor switched between kernel IO and the copies:CUDA_CACHE_DISABLE=1 CUDA_DISABLE_PTX_JIT=1the executor decodes withhandshake=kernelfrom the SASS; withCUDA_CACHE_DISABLE=1 CUDA_FORCE_PTX_JIT=1it decodes withhandshake=kernelfrom the PTX.compute_75PTX: withCUDA_CACHE_DISABLE=1 CUDA_DISABLE_PTX_JIT=1the probe fails ("publish launch: PTX JIT compilation was disabled"), the executor logs the warning and decodes withhandshake=memop.rocm/pytorch:rocm10.1.0_ubuntu24.04_py3.12_pytorch_release_2.14.0image,setup.py build_extwithPYTORCH_ROCM_ARCH="gfx942;gfx1100;gfx1201"builds all three extensions, hipcc buildsgpu_sync_io.cufor the three targets, and_cpu_moeimports. Not run on AMD hardware.bench_decode_moe.py --greedy --decode 512 --cache-rate 0.25, ABBA against 51afda3, tok/s, measured on the build with only thecompute_75PTX (same sources; the SASS list was added after):Output text sha1 matches 51afda3 in every run.