mirror of
https://github.com/linkedin/Liger-Kernel.git
synced 2026-10-12 02:17:47 +00:00
## Summary - Add exact valid-row metadata and skip fully padded MLP3/MLP4 K blocks. This is the only intentional new runtime feature. - Fix SM100 fused-backward hangs and non-finite gradients by replacing invalid partial-CTA synchronization with pair-scoped DSMEM barriers, separating X/dY TMA mbarriers, and draining overlapped TMA pipelines before shared-memory phase reuse. - Fix SM90 TM64 subtile semaphore scheduling, cross-configuration buffer sizing, and per-forward routing-count lifetime. - Retain only validated SM100 backward templates and regenerate single-/multi-GPU tuning tables. - Add exhaustive dispatch and PyTorch-autograd comparisons for every unique tuned template. ## Details Same-host communication remains TMA-based; NVSHMEM getmem is used only for cross-host/IB traffic. SM100 communication remains M128/NC4; the rejected M256 experiments are not enabled. Backward performance versus mainline (which is not a multi-GPU correctness oracle because its backward produces non-finite gradients): | Architecture | 1K | 4K | |---|---:|---:| | SM90 | 3.849 ms (+0.7%) | 6.222 ms (-4.2%) | | SM100 | 2.092 ms (+5.4%) | 3.249 ms (+9.0%) | ## Testing Done - Hardware Type: H100 (SM90) and B200 (SM100) - [ ] run `make test` to ensure correctness (replaced by the targeted distributed GPU suites below) - [x] run `make checkstyle` to ensure code style - [ ] run `make test-convergence` to ensure convergence (not applicable to the fused C++ MoE path) - Full distributed MoE integration suite: 8/8 passed on both SM90 and SM100. - Every unique tuned template compared with a PyTorch autograd reference: - SM90: 9 forward / 23 backward templates. - SM100: 8 forward / 3 backward templates. - C++ dispatch-table coverage passed on both architectures. - SM100 repeated-cycle stress: 100 single-GPU cycles and 30 eight-GPU cycles. - Full SM100 tuning completed without non-finite/CUDA failures: - single-GPU: 346 rows. - multi-GPU: 437 rows. Co-authored-by: jkolehma <[email protected]> Co-authored-by: Copilot <[email protected]>
624 lines
21 KiB
C++
624 lines
21 KiB
C++
#include <gtest/gtest.h>
|
|
#include <cuda_runtime.h>
|
|
|
|
#include <dlpack/dlpack.h>
|
|
#include <tvm/ffi/container/tensor.h>
|
|
#include <tvm/ffi/extra/module.h>
|
|
#include <tvm/ffi/function.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "moe_dispatch_configs_sm90.cuh"
|
|
#include "moe_dispatch_configs_sm100.cuh"
|
|
#include "moe_fwd_bwd_tuning_configs.cuh"
|
|
|
|
namespace {
|
|
|
|
namespace ffi = tvm::ffi;
|
|
|
|
#ifndef LIGER_CUTE_TEST_MODULE_PATH
|
|
#define LIGER_CUTE_TEST_MODULE_PATH "libliger_cute_kernels.so"
|
|
#endif
|
|
|
|
constexpr int kTokens = 256;
|
|
constexpr int kHiddenDim = 2048;
|
|
constexpr int kIntermediateDim = 768;
|
|
constexpr int kNumExperts = 16;
|
|
constexpr int kTopK = 1;
|
|
constexpr int kCommTileM = 128;
|
|
constexpr int kBwdRepeats = 3;
|
|
|
|
DLDataType bf16_dtype() { return DLDataType{kDLBfloat, 16, 1}; }
|
|
DLDataType i32_dtype() { return DLDataType{kDLInt, 32, 1}; }
|
|
DLDataType i64_dtype() { return DLDataType{kDLInt, 64, 1}; }
|
|
DLDevice cuda_device(int device_id) { return DLDevice{kDLCUDA, device_id}; }
|
|
DLDevice cpu_device() { return DLDevice{kDLCPU, 0}; }
|
|
|
|
struct FwdConfig {
|
|
int compute;
|
|
int tn1, tk1, s1, ec1, tn2, tk2, s2, ec2, zb, cs, tm, gtm;
|
|
};
|
|
|
|
struct BwdConfig {
|
|
int compute;
|
|
int ns2, tn1, tk1, s1, tm3, tn3, tk3, s3, en1, en25, en34, cs, tm, gtm;
|
|
};
|
|
|
|
#define FWD_CONFIG_C(C, TN1, TK1, S1, EC1, TN2, TK2, S2, EC2, ZB, CS, TM) \
|
|
FwdConfig{C, TN1, TK1, S1, EC1, TN2, TK2, S2, EC2, ZB, CS, TM, TM},
|
|
#define FWD_CONFIG_G_C(C, TN1, TK1, S1, EC1, TN2, TK2, S2, EC2, ZB, CS, TM, GTM) \
|
|
FwdConfig{C, TN1, TK1, S1, EC1, TN2, TK2, S2, EC2, ZB, CS, TM, GTM},
|
|
#define FWD_CONFIG_SM90(...) FWD_CONFIG_C(90, __VA_ARGS__)
|
|
#define FWD_CONFIG_G_SM90(...) FWD_CONFIG_G_C(90, __VA_ARGS__)
|
|
#define FWD_CONFIG_SM100(...) FWD_CONFIG_C(100, __VA_ARGS__)
|
|
#define FWD_CONFIG_G_SM100(...) FWD_CONFIG_G_C(100, __VA_ARGS__)
|
|
|
|
std::vector<FwdConfig> all_fwd_configs() {
|
|
return {
|
|
#if LIGER_CUTE_DISPATCH_COMPUTE == 0 || LIGER_CUTE_DISPATCH_COMPUTE == 90
|
|
LIGER_MOE_FWD_DISPATCH_CONFIGS_SM90(FWD_CONFIG_SM90, FWD_CONFIG_G_SM90)
|
|
#endif
|
|
#if LIGER_CUTE_DISPATCH_COMPUTE == 0 || LIGER_CUTE_DISPATCH_COMPUTE == 100
|
|
LIGER_MOE_FWD_DISPATCH_CONFIGS_SM100(FWD_CONFIG_SM100, FWD_CONFIG_G_SM100)
|
|
#endif
|
|
};
|
|
}
|
|
|
|
#undef FWD_CONFIG_SM100
|
|
#undef FWD_CONFIG_G_SM100
|
|
#undef FWD_CONFIG_SM90
|
|
#undef FWD_CONFIG_G_SM90
|
|
#undef FWD_CONFIG_C
|
|
#undef FWD_CONFIG_G_C
|
|
|
|
#define BWD_CONFIG_C(C, NS2, TN1, TK1, S1, TM3, TN3, TK3, S3, EN1, EN25, EN34, CS, TM) \
|
|
BwdConfig{C, NS2, TN1, TK1, S1, TM3, TN3, TK3, S3, EN1, EN25, EN34, CS, TM, TM},
|
|
#define BWD_CONFIG_G_C(C, NS2, TN1, TK1, S1, TM3, TN3, TK3, S3, EN1, EN25, EN34, CS, TM, GTM) \
|
|
BwdConfig{C, NS2, TN1, TK1, S1, TM3, TN3, TK3, S3, EN1, EN25, EN34, CS, TM, GTM},
|
|
#define BWD_CONFIG_SM90(...) BWD_CONFIG_C(90, __VA_ARGS__)
|
|
#define BWD_CONFIG_G_SM90(...) BWD_CONFIG_G_C(90, __VA_ARGS__)
|
|
#define BWD_CONFIG_SM100(...) BWD_CONFIG_C(100, __VA_ARGS__)
|
|
#define BWD_CONFIG_G_SM100(...) BWD_CONFIG_G_C(100, __VA_ARGS__)
|
|
|
|
std::vector<BwdConfig> all_bwd_configs() {
|
|
return {
|
|
#if LIGER_CUTE_DISPATCH_COMPUTE == 0 || LIGER_CUTE_DISPATCH_COMPUTE == 90
|
|
LIGER_MOE_BWD_DISPATCH_CONFIGS_SM90(BWD_CONFIG_SM90, BWD_CONFIG_G_SM90)
|
|
#endif
|
|
#if LIGER_CUTE_DISPATCH_COMPUTE == 0 || LIGER_CUTE_DISPATCH_COMPUTE == 100
|
|
LIGER_MOE_BWD_DISPATCH_CONFIGS_SM100(BWD_CONFIG_SM100, BWD_CONFIG_G_SM100)
|
|
#endif
|
|
};
|
|
}
|
|
|
|
#undef BWD_CONFIG_SM100
|
|
#undef BWD_CONFIG_G_SM100
|
|
#undef BWD_CONFIG_SM90
|
|
#undef BWD_CONFIG_G_SM90
|
|
#undef BWD_CONFIG_C
|
|
#undef BWD_CONFIG_G_C
|
|
|
|
std::string force_string(const FwdConfig& c) {
|
|
std::ostringstream os;
|
|
os << c.tn1 << ',' << c.tk1 << ',' << c.s1 << ',' << c.ec1
|
|
<< ',' << c.tn2 << ',' << c.tk2 << ',' << c.s2 << ',' << c.ec2
|
|
<< ',' << c.zb << ',' << c.cs << ',' << c.tm << ',' << c.gtm;
|
|
return os.str();
|
|
}
|
|
|
|
std::string force_string(const BwdConfig& c) {
|
|
std::ostringstream os;
|
|
os << c.ns2 << ',' << c.tn1 << ',' << c.tk1 << ',' << c.s1
|
|
<< ',' << c.tm3 << ',' << c.tn3 << ',' << c.tk3 << ',' << c.s3
|
|
<< ',' << c.en1 << ',' << c.en25 << ',' << c.en34 << ',' << c.cs
|
|
<< ',' << c.tm << ',' << c.gtm;
|
|
return os.str();
|
|
}
|
|
|
|
bool matches(const BwdConfig& dispatch, const liger::TunedConfigFwdBwd& tuned) {
|
|
return dispatch.ns2 == tuned.Bwd_NSplit2 &&
|
|
dispatch.tn1 == tuned.Bwd_TileN1 &&
|
|
dispatch.tk1 == tuned.Bwd_TileK1 &&
|
|
dispatch.s1 == tuned.Bwd_Stages1 &&
|
|
dispatch.tm3 == tuned.Bwd_TileM3 &&
|
|
dispatch.tn3 == tuned.Bwd_TileN3 &&
|
|
dispatch.tk3 == tuned.Bwd_TileK3 &&
|
|
dispatch.s3 == tuned.Bwd_Stages3 &&
|
|
dispatch.en1 == tuned.Bwd_EpiChunkN1 &&
|
|
dispatch.en25 == tuned.Bwd_EpiChunkN25 &&
|
|
dispatch.en34 == tuned.Bwd_EpiChunkN34 &&
|
|
dispatch.cs == tuned.Bwd_CommNumStages &&
|
|
dispatch.tm == tuned.Bwd_TileM &&
|
|
dispatch.gtm == tuned.Bwd_TileM;
|
|
}
|
|
|
|
void expect_tuned_rows_dispatchable(
|
|
int compute,
|
|
const liger::TunedConfigFwdBwdTable* tables,
|
|
int table_count) {
|
|
const auto configs = all_bwd_configs();
|
|
for (int table_index = 0; table_index < table_count; ++table_index) {
|
|
const auto& table = tables[table_index];
|
|
if (table.Compute != compute) continue;
|
|
for (int i = 0; i < table.count; ++i) {
|
|
const auto& tuned = table.configs[i];
|
|
EXPECT_TRUE(std::any_of(
|
|
configs.begin(), configs.end(), [&](const BwdConfig& dispatch) {
|
|
return dispatch.compute == compute && matches(dispatch, tuned);
|
|
})) << "missing dispatch row for tuned BWD config at index " << i;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool table_contains(
|
|
const BwdConfig& dispatch,
|
|
int compute,
|
|
const liger::TunedConfigFwdBwdTable* tables,
|
|
int table_count) {
|
|
for (int table_index = 0; table_index < table_count; ++table_index) {
|
|
const auto& table = tables[table_index];
|
|
if (table.Compute != compute) continue;
|
|
for (int i = 0; i < table.count; ++i)
|
|
if (matches(dispatch, table.configs[i])) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void expect_dispatch_rows_tuned(int compute) {
|
|
for (const auto& dispatch : all_bwd_configs()) {
|
|
if (dispatch.compute != compute) continue;
|
|
EXPECT_TRUE(
|
|
table_contains(
|
|
dispatch, compute, liger::kTunedConfigTablesSingle,
|
|
liger::kNumTunedConfigTablesSingle) ||
|
|
table_contains(
|
|
dispatch, compute, liger::kTunedConfigTablesMulti,
|
|
liger::kNumTunedConfigTablesMulti))
|
|
<< "untuned backward dispatch row: " << force_string(dispatch);
|
|
}
|
|
}
|
|
|
|
int compute_dispatch_key() {
|
|
int dev = 0;
|
|
cudaDeviceProp prop{};
|
|
if (cudaGetDevice(&dev) != cudaSuccess || cudaGetDeviceProperties(&prop, dev) != cudaSuccess)
|
|
return 0;
|
|
if (prop.major == 9) return 90;
|
|
if (prop.major == 10) return 100;
|
|
return 0;
|
|
}
|
|
|
|
int initial_device() {
|
|
int count = 0;
|
|
if (cudaGetDeviceCount(&count) != cudaSuccess || count <= 0) return 0;
|
|
const char* rank_envs[] = {
|
|
"LOCAL_RANK", "SLURM_LOCALID", "OMPI_COMM_WORLD_LOCAL_RANK", "PMI_RANK",
|
|
};
|
|
for (const char* name : rank_envs) {
|
|
if (const char* value = std::getenv(name)) {
|
|
return std::atoi(value) % count;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void check_cuda(cudaError_t status, const char* what) {
|
|
ASSERT_EQ(status, cudaSuccess) << what << " failed: " << cudaGetErrorString(status);
|
|
}
|
|
|
|
ffi::Module& ffi_module() {
|
|
static ffi::Module module = ffi::Module::LoadFromFile(LIGER_CUTE_TEST_MODULE_PATH);
|
|
return module;
|
|
}
|
|
|
|
ffi::Function module_func(const char* name) {
|
|
auto opt = ffi_module()->GetFunction(ffi::String(name));
|
|
EXPECT_TRUE(opt.has_value()) << "missing TVM FFI function: " << name;
|
|
return opt.value();
|
|
}
|
|
|
|
struct TensorArg {
|
|
DLTensor dl{};
|
|
std::vector<int64_t> shape;
|
|
std::vector<int64_t> strides;
|
|
|
|
TensorArg() = default;
|
|
TensorArg(void* data, std::vector<int64_t> dims, DLDataType dtype, DLDevice device)
|
|
: shape(std::move(dims)) {
|
|
strides.resize(shape.size());
|
|
int64_t stride = 1;
|
|
for (size_t i = shape.size(); i-- > 0;) {
|
|
strides[i] = stride;
|
|
stride *= shape[i];
|
|
}
|
|
dl.data = data;
|
|
dl.device = device;
|
|
dl.ndim = static_cast<int32_t>(shape.size());
|
|
dl.dtype = dtype;
|
|
dl.shape = shape.data();
|
|
dl.strides = strides.data();
|
|
dl.byte_offset = 0;
|
|
}
|
|
|
|
ffi::TensorView view() const { return ffi::TensorView(&dl); }
|
|
};
|
|
|
|
template <typename T>
|
|
struct HostBuffer {
|
|
std::vector<T> data;
|
|
explicit HostBuffer(size_t n) : data(n) {}
|
|
TensorArg tensor(DLDataType dtype) {
|
|
return TensorArg(data.data(), {static_cast<int64_t>(data.size())}, dtype, cpu_device());
|
|
}
|
|
};
|
|
|
|
struct DeviceBuffer {
|
|
void* ptr = nullptr;
|
|
size_t bytes = 0;
|
|
|
|
DeviceBuffer() = default;
|
|
explicit DeviceBuffer(size_t nbytes) : bytes(nbytes) {
|
|
check_cuda(cudaMalloc(&ptr, bytes), "cudaMalloc");
|
|
check_cuda(cudaMemset(ptr, 0, bytes), "cudaMemset");
|
|
}
|
|
~DeviceBuffer() {
|
|
if (ptr) cudaFree(ptr);
|
|
}
|
|
DeviceBuffer(const DeviceBuffer&) = delete;
|
|
DeviceBuffer& operator=(const DeviceBuffer&) = delete;
|
|
};
|
|
|
|
std::vector<uint16_t> copy_bf16(const DeviceBuffer& buffer) {
|
|
std::vector<uint16_t> host(buffer.bytes / sizeof(uint16_t));
|
|
check_cuda(cudaMemcpy(
|
|
host.data(), buffer.ptr, buffer.bytes, cudaMemcpyDeviceToHost),
|
|
"cudaMemcpy finite check");
|
|
return host;
|
|
}
|
|
|
|
bool all_finite_bf16(const std::vector<uint16_t>& values) {
|
|
return std::all_of(values.begin(), values.end(), [](uint16_t value) {
|
|
return (value & UINT16_C(0x7f80)) != UINT16_C(0x7f80);
|
|
});
|
|
}
|
|
|
|
float bf16_to_float(uint16_t value) {
|
|
uint32_t bits = static_cast<uint32_t>(value) << 16;
|
|
float result;
|
|
std::memcpy(&result, &bits, sizeof(result));
|
|
return result;
|
|
}
|
|
|
|
bool mean_relative_close(
|
|
const std::vector<uint16_t>& actual,
|
|
const std::vector<uint16_t>& expected) {
|
|
if (actual.size() != expected.size() || actual.empty()) return false;
|
|
double relative_error = 0.0;
|
|
for (size_t i = 0; i < actual.size(); ++i) {
|
|
const float actual_value = bf16_to_float(actual[i]);
|
|
const float expected_value = bf16_to_float(expected[i]);
|
|
relative_error += std::abs(actual_value - expected_value) /
|
|
std::max(std::abs(expected_value), 1.0e-3f);
|
|
}
|
|
return relative_error / actual.size() < 0.15;
|
|
}
|
|
|
|
struct MoeBuffers {
|
|
int n_pes;
|
|
int num_experts;
|
|
int experts_per_pe;
|
|
int max_total_slots;
|
|
int tile_id_slots;
|
|
int device;
|
|
|
|
DeviceBuffer X;
|
|
DeviceBuffer dY;
|
|
DeviceBuffer expert_indices;
|
|
DeviceBuffer expert_weights;
|
|
DeviceBuffer all_B;
|
|
DeviceBuffer all_C;
|
|
DeviceBuffer all_A;
|
|
DeviceBuffer Y;
|
|
DeviceBuffer token_expert_slots;
|
|
DeviceBuffer tile_expert_ids;
|
|
DeviceBuffer dX;
|
|
DeviceBuffer dB;
|
|
DeviceBuffer dC;
|
|
DeviceBuffer dA;
|
|
DeviceBuffer dW;
|
|
HostBuffer<int64_t> symm_meta;
|
|
|
|
MoeBuffers(int pes, int device_id)
|
|
: n_pes(pes),
|
|
num_experts(kNumExperts),
|
|
experts_per_pe(num_experts / n_pes),
|
|
max_total_slots(kTokens * kTopK + num_experts * kCommTileM),
|
|
tile_id_slots(max_total_slots / kCommTileM),
|
|
device(device_id),
|
|
X(sizeof(uint16_t) * kTokens * kHiddenDim),
|
|
dY(sizeof(uint16_t) * kTokens * kHiddenDim),
|
|
expert_indices(sizeof(int32_t) * kTokens * kTopK),
|
|
expert_weights(sizeof(uint16_t) * kTokens * kTopK),
|
|
all_B(sizeof(uint16_t) * experts_per_pe * kIntermediateDim * kHiddenDim),
|
|
all_C(sizeof(uint16_t) * experts_per_pe * kIntermediateDim * kHiddenDim),
|
|
all_A(sizeof(uint16_t) * experts_per_pe * kHiddenDim * kIntermediateDim),
|
|
Y(sizeof(uint16_t) * kTokens * kHiddenDim),
|
|
token_expert_slots(sizeof(int32_t) * max_total_slots),
|
|
tile_expert_ids(sizeof(int32_t) * ((max_total_slots + kCommTileM - 1) / kCommTileM)),
|
|
dX(sizeof(uint16_t) * kTokens * kHiddenDim),
|
|
dB(sizeof(uint16_t) * experts_per_pe * kIntermediateDim * kHiddenDim),
|
|
dC(sizeof(uint16_t) * experts_per_pe * kIntermediateDim * kHiddenDim),
|
|
dA(sizeof(uint16_t) * experts_per_pe * kHiddenDim * kIntermediateDim),
|
|
dW(sizeof(uint16_t) * kTokens * kTopK),
|
|
symm_meta(17) {
|
|
std::vector<int32_t> h_indices(kTokens * kTopK);
|
|
for (int t = 0; t < kTokens; ++t) h_indices[t] = t % num_experts;
|
|
check_cuda(cudaMemcpy(expert_indices.ptr, h_indices.data(),
|
|
h_indices.size() * sizeof(int32_t), cudaMemcpyHostToDevice),
|
|
"cudaMemcpy expert_indices");
|
|
check_cuda(cudaMemset(X.ptr, 0x3f, X.bytes), "initialize X");
|
|
check_cuda(cudaMemset(dY.ptr, 0x3f, dY.bytes), "initialize dY");
|
|
check_cuda(cudaMemset(expert_weights.ptr, 0x3f, expert_weights.bytes),
|
|
"initialize expert_weights");
|
|
check_cuda(cudaMemset(all_B.ptr, 0x3f, all_B.bytes), "initialize B");
|
|
check_cuda(cudaMemset(all_C.ptr, 0x3f, all_C.bytes), "initialize C");
|
|
check_cuda(cudaMemset(all_A.ptr, 0x3f, all_A.bytes), "initialize A");
|
|
}
|
|
|
|
DLDevice cuda_dev() const { return cuda_device(device); }
|
|
|
|
TensorArg X_tensor() { return TensorArg(X.ptr, {kTokens, kHiddenDim}, bf16_dtype(), cuda_dev()); }
|
|
TensorArg dY_tensor() { return TensorArg(dY.ptr, {kTokens, kHiddenDim}, bf16_dtype(), cuda_dev()); }
|
|
TensorArg expert_indices_tensor() {
|
|
return TensorArg(expert_indices.ptr, {kTokens, kTopK}, i32_dtype(), cuda_dev());
|
|
}
|
|
TensorArg expert_weights_tensor() {
|
|
return TensorArg(expert_weights.ptr, {kTokens, kTopK}, bf16_dtype(), cuda_dev());
|
|
}
|
|
TensorArg B_tensor() {
|
|
return TensorArg(all_B.ptr, {experts_per_pe, kIntermediateDim, kHiddenDim}, bf16_dtype(), cuda_dev());
|
|
}
|
|
TensorArg C_tensor() {
|
|
return TensorArg(all_C.ptr, {experts_per_pe, kIntermediateDim, kHiddenDim}, bf16_dtype(), cuda_dev());
|
|
}
|
|
TensorArg A_tensor() {
|
|
return TensorArg(all_A.ptr, {experts_per_pe, kHiddenDim, kIntermediateDim}, bf16_dtype(), cuda_dev());
|
|
}
|
|
TensorArg Y_tensor() { return TensorArg(Y.ptr, {kTokens, kHiddenDim}, bf16_dtype(), cuda_dev()); }
|
|
TensorArg token_expert_slots_tensor() {
|
|
return TensorArg(token_expert_slots.ptr, {max_total_slots}, i32_dtype(), cuda_dev());
|
|
}
|
|
TensorArg tile_expert_ids_tensor() {
|
|
return TensorArg(tile_expert_ids.ptr, {tile_id_slots}, i32_dtype(), cuda_dev());
|
|
}
|
|
TensorArg dX_tensor() { return TensorArg(dX.ptr, {kTokens, kHiddenDim}, bf16_dtype(), cuda_dev()); }
|
|
TensorArg dB_tensor() {
|
|
return TensorArg(dB.ptr, {experts_per_pe, kIntermediateDim, kHiddenDim}, bf16_dtype(), cuda_dev());
|
|
}
|
|
TensorArg dC_tensor() {
|
|
return TensorArg(dC.ptr, {experts_per_pe, kIntermediateDim, kHiddenDim}, bf16_dtype(), cuda_dev());
|
|
}
|
|
TensorArg dA_tensor() {
|
|
return TensorArg(dA.ptr, {experts_per_pe, kHiddenDim, kIntermediateDim}, bf16_dtype(), cuda_dev());
|
|
}
|
|
TensorArg dW_tensor() { return TensorArg(dW.ptr, {kTokens, kTopK}, bf16_dtype(), cuda_dev()); }
|
|
TensorArg symm_meta_tensor() { return symm_meta.tensor(i64_dtype()); }
|
|
};
|
|
|
|
void run_fwd(MoeBuffers& b, int64_t team, const ffi::Function& fwd_fn) {
|
|
auto X = b.X_tensor();
|
|
auto ei = b.expert_indices_tensor();
|
|
auto ew = b.expert_weights_tensor();
|
|
auto B = b.B_tensor();
|
|
auto C = b.C_tensor();
|
|
auto A = b.A_tensor();
|
|
auto Y = b.Y_tensor();
|
|
auto slots = b.token_expert_slots_tensor();
|
|
auto tile_ids = b.tile_expert_ids_tensor();
|
|
auto meta = b.symm_meta_tensor();
|
|
fwd_fn(X.view(), ei.view(), ew.view(), B.view(), C.view(), A.view(),
|
|
static_cast<int64_t>(b.num_experts), static_cast<int64_t>(kTopK), team,
|
|
Y.view(), slots.view(), tile_ids.view(), meta.view());
|
|
}
|
|
|
|
void run_bwd(MoeBuffers& b, int64_t team, const ffi::Function& bwd_fn) {
|
|
auto dY = b.dY_tensor();
|
|
auto meta = b.symm_meta_tensor();
|
|
auto slots = b.token_expert_slots_tensor();
|
|
auto tile_ids = b.tile_expert_ids_tensor();
|
|
auto ei = b.expert_indices_tensor();
|
|
auto ew = b.expert_weights_tensor();
|
|
auto B = b.B_tensor();
|
|
auto C = b.C_tensor();
|
|
auto A = b.A_tensor();
|
|
auto dX = b.dX_tensor();
|
|
auto dB = b.dB_tensor();
|
|
auto dC = b.dC_tensor();
|
|
auto dA = b.dA_tensor();
|
|
auto dW = b.dW_tensor();
|
|
bwd_fn(dY.view(), meta.view(), slots.view(), tile_ids.view(), ei.view(), ew.view(),
|
|
B.view(), C.view(), A.view(),
|
|
static_cast<int64_t>(b.num_experts), static_cast<int64_t>(kTopK), team,
|
|
dX.view(), dB.view(), dC.view(), dA.view(), dW.view());
|
|
}
|
|
|
|
struct NvshmemSession {
|
|
bool initialized = false;
|
|
~NvshmemSession() {
|
|
if (initialized) {
|
|
module_func("finalize")();
|
|
}
|
|
}
|
|
};
|
|
|
|
void run_dispatch_coverage_for_compute(int target_compute) {
|
|
int dev_count = 0;
|
|
if (cudaGetDeviceCount(&dev_count) != cudaSuccess || dev_count <= 0)
|
|
GTEST_SKIP() << "requires CUDA";
|
|
const int device = initial_device();
|
|
check_cuda(cudaSetDevice(device), "cudaSetDevice");
|
|
|
|
const int compute = compute_dispatch_key();
|
|
if (compute != 90 && compute != 100)
|
|
GTEST_SKIP() << "requires Hopper-family or Blackwell-family GPU";
|
|
if (compute != target_compute)
|
|
GTEST_SKIP() << "dispatch compute " << target_compute
|
|
<< " is not supported by this GPU family (detected "
|
|
<< compute << ")";
|
|
|
|
auto uniqueid_nbytes = module_func("uniqueid_nbytes");
|
|
auto get_uniqueid = module_func("get_uniqueid");
|
|
auto init_with_uniqueid = module_func("init_with_uniqueid");
|
|
auto n_pes_fn = module_func("n_pes");
|
|
auto team_world_fn = module_func("team_world");
|
|
auto configure_fn = module_func("moe_configure_symmetric");
|
|
auto pop_fwd_fn = module_func("moe_pop_fwd");
|
|
auto pool_clear_fn = module_func("pool_clear_all");
|
|
auto fwd_fn = module_func("moe_fused_fwd_bf16");
|
|
auto bwd_fn = module_func("moe_fused_bwd_bf16");
|
|
|
|
HostBuffer<int64_t> uid_nbytes_buf(1);
|
|
auto uid_nbytes_tensor = uid_nbytes_buf.tensor(i64_dtype());
|
|
uniqueid_nbytes(uid_nbytes_tensor.view());
|
|
std::vector<unsigned char> uid(static_cast<size_t>(uid_nbytes_buf.data[0]));
|
|
get_uniqueid(static_cast<int64_t>(reinterpret_cast<uintptr_t>(uid.data())));
|
|
NvshmemSession nvshmem;
|
|
init_with_uniqueid(static_cast<int64_t>(0), static_cast<int64_t>(1),
|
|
static_cast<int64_t>(reinterpret_cast<uintptr_t>(uid.data())));
|
|
nvshmem.initialized = true;
|
|
|
|
HostBuffer<int32_t> n_pes_buf(1);
|
|
auto n_pes_tensor = n_pes_buf.tensor(i32_dtype());
|
|
n_pes_fn(n_pes_tensor.view());
|
|
const int n_pes = n_pes_buf.data[0];
|
|
|
|
HostBuffer<int64_t> team_buf(1);
|
|
auto team_tensor = team_buf.tensor(i64_dtype());
|
|
team_world_fn(team_tensor.view());
|
|
const int64_t team = team_buf.data[0];
|
|
|
|
MoeBuffers buffers(n_pes, device);
|
|
configure_fn(static_cast<int64_t>(kTokens), static_cast<int64_t>(kHiddenDim),
|
|
static_cast<int64_t>(buffers.num_experts), static_cast<int64_t>(kTopK),
|
|
static_cast<int64_t>(n_pes), static_cast<int64_t>(1), static_cast<int64_t>(n_pes));
|
|
|
|
std::vector<FwdConfig> fwd_configs;
|
|
for (const auto& c : all_fwd_configs())
|
|
if (c.compute == target_compute) fwd_configs.push_back(c);
|
|
std::vector<BwdConfig> bwd_configs;
|
|
for (const auto& c : all_bwd_configs())
|
|
if (c.compute == target_compute) bwd_configs.push_back(c);
|
|
ASSERT_FALSE(fwd_configs.empty());
|
|
ASSERT_FALSE(bwd_configs.empty());
|
|
|
|
std::vector<uint16_t> reference_y;
|
|
unsetenv("LIGER_MOE_BWD_FORCE_CONFIG");
|
|
for (const auto& cfg : fwd_configs) {
|
|
const std::string force = force_string(cfg);
|
|
setenv("LIGER_MOE_FORCE_CONFIG", force.c_str(), 1);
|
|
SCOPED_TRACE("fwd " + force);
|
|
run_fwd(buffers, team, fwd_fn);
|
|
check_cuda(cudaDeviceSynchronize(), "fwd sync");
|
|
auto y = copy_bf16(buffers.Y);
|
|
EXPECT_TRUE(all_finite_bf16(y)) << "non-finite forward output";
|
|
if (reference_y.empty())
|
|
reference_y = y;
|
|
else
|
|
EXPECT_TRUE(mean_relative_close(y, reference_y))
|
|
<< "forward output differs from the reference template";
|
|
pop_fwd_fn();
|
|
}
|
|
|
|
const std::string fwd_force = force_string(fwd_configs.front());
|
|
setenv("LIGER_MOE_FORCE_CONFIG", fwd_force.c_str(), 1);
|
|
std::array<std::vector<uint16_t>, 5> reference_grads;
|
|
bool have_reference_grads = false;
|
|
const char* grad_names[] = {"dX", "dB", "dC", "dA", "dW"};
|
|
for (const auto& cfg : bwd_configs) {
|
|
const std::string force = force_string(cfg);
|
|
setenv("LIGER_MOE_BWD_FORCE_CONFIG", force.c_str(), 1);
|
|
SCOPED_TRACE("bwd " + force);
|
|
for (int repeat = 0; repeat < kBwdRepeats; ++repeat) {
|
|
SCOPED_TRACE("repeat " + std::to_string(repeat));
|
|
run_fwd(buffers, team, fwd_fn);
|
|
run_bwd(buffers, team, bwd_fn);
|
|
check_cuda(cudaDeviceSynchronize(), "bwd sync");
|
|
std::array<std::vector<uint16_t>, 5> grads = {
|
|
copy_bf16(buffers.dX),
|
|
copy_bf16(buffers.dB),
|
|
copy_bf16(buffers.dC),
|
|
copy_bf16(buffers.dA),
|
|
copy_bf16(buffers.dW),
|
|
};
|
|
for (size_t i = 0; i < grads.size(); ++i) {
|
|
EXPECT_TRUE(all_finite_bf16(grads[i]))
|
|
<< "non-finite " << grad_names[i];
|
|
if (have_reference_grads) {
|
|
EXPECT_TRUE(mean_relative_close(grads[i], reference_grads[i]))
|
|
<< grad_names[i]
|
|
<< " differs from the reference backward template";
|
|
}
|
|
}
|
|
if (!have_reference_grads) {
|
|
reference_grads = std::move(grads);
|
|
have_reference_grads = true;
|
|
}
|
|
pop_fwd_fn();
|
|
}
|
|
}
|
|
|
|
unsetenv("LIGER_MOE_FORCE_CONFIG");
|
|
unsetenv("LIGER_MOE_BWD_FORCE_CONFIG");
|
|
pool_clear_fn();
|
|
module_func("finalize")();
|
|
nvshmem.initialized = false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST(MoeApiDispatchSm90, RunsEveryForwardAndBackwardDispatchEntry) {
|
|
run_dispatch_coverage_for_compute(90);
|
|
}
|
|
|
|
TEST(MoeApiDispatchSm100, RunsEveryForwardAndBackwardDispatchEntry) {
|
|
run_dispatch_coverage_for_compute(100);
|
|
}
|
|
|
|
#if LIGER_CUTE_DISPATCH_COMPUTE == 0 || LIGER_CUTE_DISPATCH_COMPUTE == 90
|
|
TEST(MoeApiDispatchSm90, EveryTunedBackwardRowIsDispatchable) {
|
|
expect_tuned_rows_dispatchable(
|
|
90, liger::kTunedConfigTablesSingle,
|
|
liger::kNumTunedConfigTablesSingle);
|
|
expect_tuned_rows_dispatchable(
|
|
90, liger::kTunedConfigTablesMulti,
|
|
liger::kNumTunedConfigTablesMulti);
|
|
expect_dispatch_rows_tuned(90);
|
|
}
|
|
#endif
|
|
|
|
#if LIGER_CUTE_DISPATCH_COMPUTE == 0 || LIGER_CUTE_DISPATCH_COMPUTE == 100
|
|
TEST(MoeApiDispatchSm100, EveryTunedBackwardRowIsDispatchable) {
|
|
expect_tuned_rows_dispatchable(
|
|
100, liger::kTunedConfigTablesSingle,
|
|
liger::kNumTunedConfigTablesSingle);
|
|
expect_tuned_rows_dispatchable(
|
|
100, liger::kTunedConfigTablesMulti,
|
|
liger::kNumTunedConfigTablesMulti);
|
|
expect_dispatch_rows_tuned(100);
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, char** argv) {
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
return RUN_ALL_TESTS();
|
|
}
|