feat: moe_tcu_dispatch.cpp — C++ MoE expert loop via torch::mm (TCU kernel)
torch profiler confirmed: torch.mm launches Gemm_tcu_bi_kernel::gemm_h_h_tcu_25 which is BI-V100 TCU (Tensor Compute Unit) hardware-accelerated GEMM. 0.58ms per call vs our custom kernel 7.7ms — TCU is 13x faster. Python for-loop overhead measured: 0.892 ms/expert = 7.1 ms for 8 experts. This C++ dispatch eliminates that overhead while using the same TCU kernel. Three entry points: - moe_decode: full MoE forward (FC1 + SiLU*mul + FC2) for decode - moe_prefill: group-by-expert MoE forward for prefill - moe_expert_gemm_tcu: raw GEMM loop for benchmarking
This commit is contained in:
160
ex_engine/csrc/build_test_moe_tcu.sh
Executable file
160
ex_engine/csrc/build_test_moe_tcu.sh
Executable file
@@ -0,0 +1,160 @@
|
||||
#!/bin/bash
|
||||
# build_test_moe_tcu.sh — Build and test moe_tcu_dispatch.cpp
|
||||
set -eo pipefail
|
||||
|
||||
echo "=== Compile moe_tcu_dispatch ==="
|
||||
python3 -c "
|
||||
import torch.utils.cpp_extension as ext
|
||||
import os, shutil, glob
|
||||
|
||||
name = 'moe_tcu_dispatch'
|
||||
build_dir = 'ex_engine/csrc/build/tmp_' + name
|
||||
os.makedirs(build_dir, exist_ok=True)
|
||||
|
||||
mod = ext.load(
|
||||
name=name,
|
||||
sources=['ex_engine/csrc/moe_tcu_dispatch.cpp'],
|
||||
extra_cflags=['-O2', '-std=c++17'],
|
||||
build_directory=build_dir,
|
||||
verbose=True,
|
||||
)
|
||||
|
||||
built = glob.glob(build_dir + '/' + name + '*.so')
|
||||
if built:
|
||||
dst = 'ex_engine/csrc/build/' + name + '.so'
|
||||
os.makedirs('ex_engine/csrc/build', exist_ok=True)
|
||||
shutil.copy2(built[0], dst)
|
||||
print(f'[build] SUCCESS: {dst}')
|
||||
"
|
||||
|
||||
echo ""
|
||||
echo "=== Test ==="
|
||||
python3 << 'PYTEST'
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import sys, os, glob, time, importlib.util
|
||||
|
||||
build_dir = 'ex_engine/csrc/build'
|
||||
so = glob.glob(f'{build_dir}/tmp_moe_tcu_dispatch/moe_tcu_dispatch*.so')
|
||||
if not so:
|
||||
print("SKIP: .so not found")
|
||||
sys.exit(0)
|
||||
spec = importlib.util.spec_from_file_location("moe_tcu_dispatch", so[0])
|
||||
mod = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(mod)
|
||||
print(f"Loaded: {so[0]}")
|
||||
|
||||
# ============================================================
|
||||
# Test 1: moe_decode correctness
|
||||
# ============================================================
|
||||
print("\n--- moe_decode correctness ---")
|
||||
K, I = 128, 256
|
||||
E = 8
|
||||
top_k = 4
|
||||
hidden = torch.randn(1, K, dtype=torch.float16, device='cuda')
|
||||
w13 = torch.randn(E, 2*I, K, dtype=torch.float16, device='cuda') * 0.01
|
||||
w2 = torch.randn(E, K, I, dtype=torch.float16, device='cuda') * 0.01
|
||||
expert_ids = torch.tensor([0, 3, 5, 7], dtype=torch.int64, device='cuda')
|
||||
expert_weights = torch.tensor([0.3, 0.25, 0.25, 0.2], dtype=torch.float32, device='cuda')
|
||||
|
||||
# C++ result
|
||||
out_cpp = mod.moe_decode(hidden, w13, w2, expert_ids, expert_weights)
|
||||
|
||||
# Python reference
|
||||
out_py = torch.zeros_like(hidden)
|
||||
for k in range(top_k):
|
||||
eid = expert_ids[k].item()
|
||||
w = expert_weights[k].item()
|
||||
gate_up = F.linear(hidden, w13[eid])
|
||||
gate = torch.silu(gate_up[:, :I])
|
||||
up = gate_up[:, I:]
|
||||
act = gate * up
|
||||
expert_out = F.linear(act, w2[eid])
|
||||
out_py += w * expert_out
|
||||
|
||||
diff = (out_cpp.float() - out_py.float()).abs().max().item()
|
||||
print(f" max_diff={diff:.6f} {'PASS' if diff < 1.0 else 'FAIL'}")
|
||||
|
||||
# ============================================================
|
||||
# Test 2: moe_expert_gemm_tcu correctness
|
||||
# ============================================================
|
||||
print("\n--- moe_expert_gemm_tcu correctness ---")
|
||||
num_experts = 4
|
||||
K, N = 128, 256
|
||||
expert_counts = torch.tensor([8, 0, 16, 4], dtype=torch.int64, device='cuda')
|
||||
total = expert_counts.sum().item()
|
||||
inp = torch.randn(total, K, dtype=torch.float16, device='cuda') * 0.1
|
||||
weights = torch.randn(num_experts, N, K, dtype=torch.float16, device='cuda') * 0.1
|
||||
|
||||
out_cpp = mod.moe_expert_gemm_tcu(inp, weights, expert_counts)
|
||||
|
||||
# Python reference
|
||||
out_py = torch.zeros(total, N, dtype=torch.float16, device='cuda')
|
||||
off = 0
|
||||
for e in range(num_experts):
|
||||
cnt = expert_counts[e].item()
|
||||
if cnt == 0: continue
|
||||
out_py[off:off+cnt] = F.linear(inp[off:off+cnt], weights[e])
|
||||
off += cnt
|
||||
|
||||
diff = (out_cpp.float() - out_py.float()).abs().max().item()
|
||||
print(f" max_diff={diff:.6f} {'PASS' if diff < 0.5 else 'FAIL'}")
|
||||
|
||||
# ============================================================
|
||||
# Test 3: Performance — Python loop vs C++ loop
|
||||
# ============================================================
|
||||
print("\n--- Performance: decode (1 token, 8 experts) ---")
|
||||
K, I = 4096, 11008
|
||||
E, top_k = 64, 8
|
||||
hidden = torch.randn(1, K, dtype=torch.float16, device='cuda')
|
||||
w13 = torch.randn(E, 2*I, K, dtype=torch.float16, device='cuda') * 0.001
|
||||
w2 = torch.randn(E, K, I, dtype=torch.float16, device='cuda') * 0.001
|
||||
expert_ids = torch.tensor([0,5,10,20,30,40,50,60], dtype=torch.int64, device='cuda')
|
||||
expert_weights = torch.ones(top_k, dtype=torch.float32, device='cuda') / top_k
|
||||
|
||||
# Warmup
|
||||
for _ in range(3):
|
||||
mod.moe_decode(hidden, w13, w2, expert_ids, expert_weights)
|
||||
torch.cuda.synchronize()
|
||||
|
||||
# C++ loop
|
||||
t0 = time.time()
|
||||
for _ in range(100):
|
||||
mod.moe_decode(hidden, w13, w2, expert_ids, expert_weights)
|
||||
torch.cuda.synchronize()
|
||||
ms_cpp = (time.time() - t0) / 100 * 1000
|
||||
|
||||
# Python loop
|
||||
for _ in range(3):
|
||||
out_py = torch.zeros_like(hidden)
|
||||
for k in range(top_k):
|
||||
eid = expert_ids[k].item()
|
||||
w = expert_weights[k].item()
|
||||
gate_up = F.linear(hidden, w13[eid])
|
||||
gate = torch.silu(gate_up[:, :I])
|
||||
up = gate_up[:, I:]
|
||||
act = gate * up
|
||||
out_py += w * F.linear(act, w2[eid])
|
||||
torch.cuda.synchronize()
|
||||
|
||||
t0 = time.time()
|
||||
for _ in range(100):
|
||||
out_py = torch.zeros_like(hidden)
|
||||
for k in range(top_k):
|
||||
eid = expert_ids[k].item()
|
||||
w = expert_weights[k].item()
|
||||
gate_up = F.linear(hidden, w13[eid])
|
||||
gate = torch.silu(gate_up[:, :I])
|
||||
up = gate_up[:, I:]
|
||||
act = gate * up
|
||||
out_py += w * F.linear(act, w2[eid])
|
||||
torch.cuda.synchronize()
|
||||
ms_py = (time.time() - t0) / 100 * 1000
|
||||
|
||||
print(f" C++ loop: {ms_cpp:.2f} ms")
|
||||
print(f" Python loop: {ms_py:.2f} ms")
|
||||
print(f" Speedup: {ms_py/ms_cpp:.2f}x")
|
||||
print(f" Saved: {ms_py-ms_cpp:.2f} ms per forward")
|
||||
|
||||
print("\n=== DONE ===")
|
||||
PYTEST
|
||||
191
ex_engine/csrc/moe_tcu_dispatch.cpp
Normal file
191
ex_engine/csrc/moe_tcu_dispatch.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
// moe_tcu_dispatch.cpp — MoE expert GEMM via torch::mm (walks Gemm_tcu_bi_kernel)
|
||||
//
|
||||
// Replaces Python for-loop over experts with C++ loop.
|
||||
// torch::mm on corex launches Gemm_tcu_bi_kernel::gemm_h_h_tcu_25 (TCU hardware).
|
||||
// Probe confirmed: Python loop overhead = 0.892 ms/expert = 7.1 ms for 8 experts.
|
||||
// This C++ dispatch eliminates that overhead.
|
||||
//
|
||||
// No custom GEMM kernel. No ixformer API dependency. Just torch::mm in C++.
|
||||
|
||||
#include <torch/extension.h>
|
||||
#include <vector>
|
||||
|
||||
// ============================================================================
|
||||
// Decode path: single token, top_k experts
|
||||
// ============================================================================
|
||||
// hidden: (1, K)
|
||||
// gate_up_weights: (num_experts, 2*intermediate, K) — pre-loaded expert weights
|
||||
// down_weights: (num_experts, K, intermediate)
|
||||
// expert_ids: (top_k,) int64 — selected expert indices
|
||||
// expert_weights: (top_k,) float — gating weights
|
||||
//
|
||||
// For each expert:
|
||||
// gate_up = hidden @ gate_up_weights[eid].t() → (1, 2*I)
|
||||
// gate = silu(gate_up[:, :I])
|
||||
// up = gate_up[:, I:]
|
||||
// act = gate * up → (1, I)
|
||||
// out = act @ down_weights[eid].t() → (1, K)
|
||||
// result += weight * out
|
||||
|
||||
torch::Tensor moe_decode(
|
||||
torch::Tensor hidden, // (1, K)
|
||||
torch::Tensor gate_up_weights, // (E, 2*I, K)
|
||||
torch::Tensor down_weights, // (E, K, I)
|
||||
torch::Tensor expert_ids, // (top_k,) int64
|
||||
torch::Tensor expert_weights // (top_k,) float/half
|
||||
) {
|
||||
auto top_k = expert_ids.size(0);
|
||||
auto K = hidden.size(1);
|
||||
auto inter2 = gate_up_weights.size(1);
|
||||
auto inter = inter2 / 2;
|
||||
|
||||
auto result = torch::zeros_like(hidden); // (1, K)
|
||||
|
||||
for (int64_t k = 0; k < top_k; ++k) {
|
||||
auto eid = expert_ids[k].item<int64_t>();
|
||||
auto w = expert_weights[k].item<float>();
|
||||
|
||||
// FC1: gate_up = hidden @ w13[eid]^T → (1, 2*I)
|
||||
auto gate_up = torch::mm(hidden, gate_up_weights[eid].t());
|
||||
|
||||
// SiLU and mul
|
||||
auto gate = torch::silu(gate_up.slice(1, 0, inter));
|
||||
auto up = gate_up.slice(1, inter, inter2);
|
||||
auto act = gate * up; // (1, I)
|
||||
|
||||
// FC2: expert_out = act @ w2[eid]^T → (1, K)
|
||||
auto expert_out = torch::mm(act, down_weights[eid].t());
|
||||
|
||||
// Weighted accumulate
|
||||
result.add_(expert_out, w);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Prefill path: multiple tokens, grouped by expert
|
||||
// ============================================================================
|
||||
// hidden: (T, K)
|
||||
// gate_up_weights: (E, 2*I, K)
|
||||
// down_weights: (E, K, I)
|
||||
// topk_ids: (T, top_k) int64 — expert indices per token
|
||||
// topk_weights: (T, top_k) float — gating weights per token
|
||||
//
|
||||
// Strategy: group tokens by expert, batch the GEMM per expert.
|
||||
|
||||
torch::Tensor moe_prefill(
|
||||
torch::Tensor hidden, // (T, K)
|
||||
torch::Tensor gate_up_weights, // (E, 2*I, K)
|
||||
torch::Tensor down_weights, // (E, K, I)
|
||||
torch::Tensor topk_ids, // (T, top_k) int64
|
||||
torch::Tensor topk_weights // (T, top_k) float/half
|
||||
) {
|
||||
auto T = hidden.size(0);
|
||||
auto K = hidden.size(1);
|
||||
auto num_experts = gate_up_weights.size(0);
|
||||
auto inter2 = gate_up_weights.size(1);
|
||||
auto inter = inter2 / 2;
|
||||
auto top_k = topk_ids.size(1);
|
||||
|
||||
auto result = torch::zeros({T, K}, hidden.options());
|
||||
|
||||
// Flatten topk_ids to find tokens per expert
|
||||
auto flat_ids = topk_ids.reshape(-1); // (T*top_k,)
|
||||
auto flat_weights = topk_weights.reshape(-1); // (T*top_k,)
|
||||
|
||||
// Token index for each (token, k) pair
|
||||
auto token_idx = torch::arange(T, topk_ids.options())
|
||||
.unsqueeze(1).expand({T, top_k}).reshape(-1); // (T*top_k,)
|
||||
|
||||
for (int64_t eid = 0; eid < num_experts; ++eid) {
|
||||
// Find which entries in flat_ids match this expert
|
||||
auto mask = flat_ids.eq(eid);
|
||||
auto count = mask.sum().item<int64_t>();
|
||||
if (count == 0) continue;
|
||||
|
||||
// Gather token indices and weights for this expert
|
||||
auto indices = mask.nonzero().squeeze(1); // (count,)
|
||||
auto tok_indices = token_idx.index_select(0, indices); // (count,)
|
||||
auto weights = flat_weights.index_select(0, indices); // (count,)
|
||||
|
||||
// Gather hidden states
|
||||
auto tokens = hidden.index_select(0, tok_indices); // (count, K)
|
||||
|
||||
// FC1: gate_up = tokens @ w13[eid]^T → (count, 2*I)
|
||||
auto gate_up = torch::mm(tokens, gate_up_weights[eid].t());
|
||||
|
||||
// SiLU and mul
|
||||
auto gate = torch::silu(gate_up.slice(1, 0, inter));
|
||||
auto up = gate_up.slice(1, inter, inter2);
|
||||
auto act = gate * up; // (count, I)
|
||||
|
||||
// FC2: expert_out = act @ w2[eid]^T → (count, K)
|
||||
auto expert_out = torch::mm(act, down_weights[eid].t());
|
||||
|
||||
// Weighted scatter-add
|
||||
auto weighted = expert_out * weights.unsqueeze(1);
|
||||
result.index_add_(0, tok_indices, weighted.to(result.dtype()));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Simple expert GEMM only (no activation, for benchmarking)
|
||||
// ============================================================================
|
||||
// input: (total_tokens, K)
|
||||
// weights: (num_experts, N, K)
|
||||
// expert_counts: (num_experts,) int64
|
||||
// Returns: (total_tokens, N)
|
||||
|
||||
torch::Tensor moe_expert_gemm_tcu(
|
||||
torch::Tensor input,
|
||||
torch::Tensor weights,
|
||||
torch::Tensor expert_counts
|
||||
) {
|
||||
auto total_tokens = input.size(0);
|
||||
auto K = input.size(1);
|
||||
auto num_experts = weights.size(0);
|
||||
auto N = weights.size(1);
|
||||
|
||||
auto output = torch::zeros({total_tokens, N}, input.options());
|
||||
|
||||
int64_t offset = 0;
|
||||
for (int64_t e = 0; e < num_experts; ++e) {
|
||||
auto count = expert_counts[e].item<int64_t>();
|
||||
if (count == 0) continue;
|
||||
|
||||
auto tokens = input.slice(0, offset, offset + count); // (count, K)
|
||||
auto w = weights[e]; // (N, K)
|
||||
|
||||
// torch::mm → Gemm_tcu_bi_kernel on BI-V100
|
||||
auto out_e = torch::mm(tokens, w.t()); // (count, N)
|
||||
output.slice(0, offset, offset + count).copy_(out_e);
|
||||
|
||||
offset += count;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
||||
m.def("moe_decode", &moe_decode,
|
||||
"MoE decode: C++ loop over experts via torch::mm (TCU kernel)",
|
||||
py::arg("hidden"), py::arg("gate_up_weights"),
|
||||
py::arg("down_weights"), py::arg("expert_ids"),
|
||||
py::arg("expert_weights"));
|
||||
|
||||
m.def("moe_prefill", &moe_prefill,
|
||||
"MoE prefill: group-by-expert via torch::mm (TCU kernel)",
|
||||
py::arg("hidden"), py::arg("gate_up_weights"),
|
||||
py::arg("down_weights"), py::arg("topk_ids"),
|
||||
py::arg("topk_weights"));
|
||||
|
||||
m.def("moe_expert_gemm_tcu", &moe_expert_gemm_tcu,
|
||||
"MoE expert GEMM only via torch::mm (TCU kernel, for benchmarking)",
|
||||
py::arg("input"), py::arg("weights"), py::arg("expert_counts"));
|
||||
}
|
||||
Reference in New Issue
Block a user