项目文件夹

文件
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

1752 行
64 KiB
C++

#include "common.h"
#include "gemm.h"
#include "vec.h"
#include "vec_pack.h"
namespace {
// [NOTE] GDN Optimizations on AMX CPU
// * intra loop: fuse `kkt_solve` and `recompute_w_u` so as to avoid materialize `A`.
// * inter loop: fuse `recompute_w_u` and `update_v` so as to avoid materialize `h` and `v_new`.
// * intra loop parallel on H instead of Hv, remove duplicated key @ key.T
// * fuse format pack with elemwise OP as much as possible.
// * update state (FP32) with amx-bf16 where C(FP32) += A(BF16) * B(BF16)
// * compile time mask out upper triangular part in decay mask and tril solve, reduce fma needed.
// * convert to vnni format, expect contiguous input and output
// from [K/2, 2, N] FP32 to [K/2, N, 2] BF16
// * update src = src * exp(g_last)
template <typename scalar_t, int K, int N>
void pack_vnni2(scalar_t* __restrict__ dst, float* __restrict__ src, const float g_last, int ld_src, int ld_dst) {
static_assert(K % 32 == 0);
static_assert(N % 32 == 0);
const float scale = std::exp(g_last);
#if defined(CPU_CAPABILITY_AVX512)
constexpr int KB = K / 2;
constexpr int NB = N / 32;
__m512i s0, s1, d0, d1;
__m512 vd = _mm512_set1_ps(scale);
const auto trans = [&](auto i) {
constexpr int kb = i / NB;
constexpr int nb = i % NB;
// [K/2, 2, N/32, 32] -> [K/2, N/32, 32, 2]
constexpr int k0 = kb * 2 + 0;
constexpr int k1 = kb * 2 + 1;
__m512 v00 = _mm512_loadu_ps(src + k0 * ld_src + nb * 32);
__m512 v01 = _mm512_loadu_ps(src + k0 * ld_src + nb * 32 + 16);
__m512 v10 = _mm512_loadu_ps(src + k1 * ld_src + nb * 32);
__m512 v11 = _mm512_loadu_ps(src + k1 * ld_src + nb * 32 + 16);
s0 = (__m512i)_mm512_cvtne2ps_pbh(v01, v00);
s1 = (__m512i)_mm512_cvtne2ps_pbh(v11, v10);
std::tie(d0, d1) = transpose_2x32_16bit(s0, s1);
_mm512_storeu_si512(dst + kb * ld_dst * 2 + nb * 32 * 2, d0);
_mm512_storeu_si512(dst + kb * ld_dst * 2 + nb * 32 * 2 + 32, d1);
// update src = src * exp(g_last)
_mm512_storeu_ps(src + k0 * ld_src + nb * 32, _mm512_mul_ps(v00, vd));
_mm512_storeu_ps(src + k0 * ld_src + nb * 32 + 16, _mm512_mul_ps(v01, vd));
_mm512_storeu_ps(src + k1 * ld_src + nb * 32, _mm512_mul_ps(v10, vd));
_mm512_storeu_ps(src + k1 * ld_src + nb * 32 + 16, _mm512_mul_ps(v11, vd));
};
Unroll<KB * NB>{}(trans);
#else
// [K/2, 2, N] -> [K/2, N, 2]
for (int k = 0; k < K; k += 2) {
for (int n = 0; n < N; ++n) {
const float v0 = src[(k + 0) * ld_src + n];
const float v1 = src[(k + 1) * ld_src + n];
dst[(k >> 1) * ld_dst * 2 + n * 2 + 0] = static_cast<scalar_t>(v0);
dst[(k >> 1) * ld_dst * 2 + n * 2 + 1] = static_cast<scalar_t>(v1);
src[(k + 0) * ld_src + n] = v0 * scale;
src[(k + 1) * ld_src + n] = v1 * scale;
}
}
#endif
}
template <typename scalar_t, int SIZE>
inline void fill_stub(scalar_t* __restrict__ out, float val) {
using Vec = at::vec::Vectorized<scalar_t>;
constexpr int kVecSize = Vec::size();
static_assert(SIZE % kVecSize == 0);
const Vec data_vec = Vec(static_cast<scalar_t>(val));
#pragma GCC unroll 8
for (int d = 0; d < SIZE; d += kVecSize) {
data_vec.store(out + d);
}
}
template <typename scalar_t, int D, bool has_scale>
struct l2norm_kernel {
static inline void apply(scalar_t* __restrict__ out, const scalar_t* __restrict__ input, float eps) {
TORCH_CHECK(false, "l2norm_kernel: scalar path not implemented!");
}
};
#if defined(CPU_CAPABILITY_AVX512)
template <int D, bool has_scale>
struct l2norm_kernel<at::BFloat16, D, has_scale> {
static inline void apply(at::BFloat16* __restrict__ out, const at::BFloat16* __restrict__ input, float eps) {
static_assert(D % 32 == 0);
constexpr int COLS = D / 32;
__m512bh va[COLS];
__m512 vrscale;
constexpr float scale = 1.f / std::sqrt(D);
__m512 vscale = _mm512_set1_ps(scale);
// step 1: load input and do reduce with avx512-bf16
__m512 vsum = _mm512_set1_ps(0.f);
auto reduce = [&](auto col) {
va[col] = (__m512bh)(_mm512_loadu_si512(input + col * 32));
vsum = _mm512_dpbf16_ps(vsum, va[col], va[col]);
};
Unroll<COLS>{}(reduce);
float sqsum = _mm512_reduce_add_ps(vsum);
float rscale = 1.f / std::sqrt(sqsum + eps);
vrscale = _mm512_set1_ps(rscale);
// step 2: apply scale to output
auto map = [&](auto col) {
__m512i a16 = (__m512i)va[col];
__m512 va0 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 0));
__m512 va1 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 1));
va0 = _mm512_mul_ps(va0, vrscale);
va1 = _mm512_mul_ps(va1, vrscale);
// keep the mul order same as torch code:
// query = l2norm(query) * scale
if constexpr (has_scale) {
va0 = _mm512_mul_ps(va0, vscale);
va1 = _mm512_mul_ps(va1, vscale);
}
_mm512_storeu_si512(out + col * 32, (__m512i)(_mm512_cvtne2ps_pbh(va1, va0)));
};
Unroll<COLS>{}(map);
}
};
#endif
template <typename scalar_t, int CHUNK_SIZE, int BLOCK_H>
struct cumsum_kernel {
static inline void apply(
scalar_t* __restrict__ out,
const scalar_t* __restrict__ input,
int mb_size,
int hb_size,
int ld_src,
int ld_dst) {
TORCH_CHECK(false, "cumsum_kernel: scalar path not implemented!");
}
};
#if defined(CPU_CAPABILITY_AVX512)
template <int CHUNK_SIZE, int BLOCK_H>
struct cumsum_kernel<float, CHUNK_SIZE, BLOCK_H> {
static inline void
apply(float* __restrict__ out, const float* __restrict__ input, int mb_size, int hb_size, int ld_src, int ld_dst) {
// vector length of fp32 for avx512
static_assert(BLOCK_H == 16);
TORCH_CHECK(hb_size > 0 && hb_size <= BLOCK_H);
const __mmask16 vmask = static_cast<__mmask16>((1u << hb_size) - 1u);
__m512i va[16];
__m512 vsum = _mm512_set1_ps(0.f);
for (int i = 0; i < CHUNK_SIZE; i += 16) {
// load input data
Unroll<16>{}([&](auto j) {
__m512 v;
if (i + j < mb_size) {
v = _mm512_maskz_loadu_ps(vmask, input + (i + j) * ld_src);
} else {
v = _mm512_setzero_ps();
}
vsum = _mm512_add_ps(vsum, v);
va[j] = _mm512_castps_si512(vsum);
});
// transpose
transpose_16x16_32bit(va);
// store output data
Unroll<16>{}([&](auto j) {
if (j < hb_size) {
_mm512_storeu_si512(out + j * ld_dst + i, va[j]);
}
});
}
}
};
#endif
template <typename scalar_t, int CHUNK_SIZE>
struct decay_mask_kernel {
static inline void apply(scalar_t* __restrict__ out, const scalar_t* __restrict__ input) {
TORCH_CHECK(false, "decay_mask_kernel: scalar path not implemented!");
}
};
#if defined(CPU_CAPABILITY_AVX512)
template <int CHUNK_SIZE>
struct decay_mask_kernel<float, CHUNK_SIZE> {
static inline void apply(float* __restrict__ out, const float* __restrict__ input) {
static_assert(CHUNK_SIZE % 16 == 0);
constexpr int ROWS = CHUNK_SIZE;
constexpr int COLS = CHUNK_SIZE / 16;
__m512 va;
__m512 vb[COLS];
// step 1: load g[j]
auto loadb = [&](auto i) { vb[i] = _mm512_loadu_ps(input + i * 16); };
Unroll<COLS>{}(loadb);
// step2: exp(g[i] - g[j])
auto compute = [&](auto i) {
constexpr int row = i / COLS;
constexpr int col = i % COLS;
if constexpr (col == 0) {
va = _mm512_set1_ps(input[row]);
}
// mask vb[col] (already loaded in step 1) for the lower-triangular region
constexpr int len = std::max(0, std::min(row + 1 - col * 16, 16));
__m512 vc;
if constexpr (len == 16) {
vc = _mm512_fexp_u20_ps(va - vb[col]);
} else if constexpr (len == 0) {
vc = _mm512_setzero_ps();
} else {
vc = _mm512_fexp_u20_ps(va - vb[col]);
// do mask for vc
constexpr __mmask16 vmask = (1 << len) - 1;
vc = _mm512_mask_blend_ps(vmask, _mm512_setzero_ps(), vc);
}
_mm512_storeu_ps(out + row * CHUNK_SIZE + col * 16, vc);
};
Unroll<ROWS * COLS>{}(compute);
}
};
#endif
template <typename scalar_t, int CHUNK_SIZE, bool has_beta>
struct apply_mask_kernel {
static inline void apply(
scalar_t* __restrict__ attn2,
const float* __restrict__ attn,
const scalar_t* __restrict__ beta,
const float* __restrict__ d,
int size,
int b_stride = 0) {
TORCH_CHECK(false, "apply_mask_kernel: scalar path not implemented!");
}
};
#if defined(CPU_CAPABILITY_AVX512)
template <int CHUNK_SIZE, bool has_beta>
struct apply_mask_kernel<at::BFloat16, CHUNK_SIZE, has_beta> {
static inline void apply(
at::BFloat16* __restrict__ attn2,
const float* __restrict__ attn,
const at::BFloat16* __restrict__ beta,
const float* __restrict__ d,
int size,
int b_stride = 0) {
static_assert(CHUNK_SIZE % 16 == 0);
constexpr int ROWS = CHUNK_SIZE;
constexpr int COLS = CHUNK_SIZE / 16;
__m512 vbeta;
// has_beta: attn2 = -attn * beta * d (strict lower)
// !has_beta: attn2 = attn * d (lower incl. diagonal)
auto compute = [&](auto i) {
constexpr int row = i / COLS;
constexpr int col = i % COLS;
constexpr int len =
has_beta ? std::max(0, std::min(row - col * 16, 16)) : std::max(0, std::min(row + 1 - col * 16, 16));
if (row < size) {
if constexpr (has_beta) {
if constexpr (col == 0) {
vbeta = _mm512_set1_ps(-static_cast<float>(beta[row * b_stride]));
}
}
__m512 vc;
if constexpr (len == 0) {
vc = _mm512_setzero_ps();
} else {
constexpr __mmask16 vmask = (1 << len) - 1;
__m512 va = _mm512_maskz_loadu_ps(vmask, attn + row * CHUNK_SIZE + col * 16);
__m512 vd = _mm512_maskz_loadu_ps(vmask, d + row * CHUNK_SIZE + col * 16);
if constexpr (has_beta) {
vc = _mm512_mul_ps(_mm512_mul_ps(va, vbeta), vd);
} else {
vc = _mm512_mul_ps(va, vd);
}
}
_mm256_storeu_si256(
reinterpret_cast<__m256i*>(attn2 + row * CHUNK_SIZE + col * 16), (__m256i)(_mm512_cvtneps_pbh(vc)));
}
};
Unroll<ROWS * COLS>{}(compute);
}
};
#endif
template <typename scalar_t, int CHUNK_SIZE>
struct solve_tril_kernel {
static inline void apply(scalar_t* __restrict__ attn2, int size) {
TORCH_CHECK(false, "solve_tril_kernel: scalar path not implemented!");
}
};
#if defined(CPU_CAPABILITY_AVX512)
template <int CHUNK_SIZE>
struct solve_tril_kernel<at::BFloat16, CHUNK_SIZE> {
static inline void apply(at::BFloat16* __restrict__ attn2, int size) {
static_assert(CHUNK_SIZE % 16 == 0);
constexpr int COLS = CHUNK_SIZE / 16;
__m512 va;
__m512 vb[COLS];
__m512 vsum[COLS];
// for len == 0 and row < size, we don't have to write back zero again
// as in `apply_mask_kernel`, we already set zero for the upper-triangular region
for (int i = 1; i < size; ++i) {
// load row attn[..., i, :i]
at::BFloat16* __restrict__ row_ptr = attn2 + i * CHUNK_SIZE;
Unroll<COLS>{}([&](auto col) {
int len = std::min(i - col * 16, 16);
if (len > 0) {
const __mmask16 vmask = (1 << len) - 1;
vsum[col] = CVT_BF16_TO_FP32(_mm256_maskz_loadu_epi16(vmask, row_ptr + col * 16));
}
});
// row = attn[..., i, :i].clone()
// sub = attn[..., :i, :i].clone()
// vsum = row + (row.unsqueeze(-1) * sub).sum(-2)
for (int k = 0; k < i; ++k) {
va = _mm512_set1_ps(static_cast<float>(row_ptr[k]));
const at::BFloat16* __restrict__ row_k_ptr = attn2 + k * CHUNK_SIZE;
Unroll<COLS>{}([&](auto col) {
int len = std::min(k - col * 16, 16);
if (len > 0) {
const __mmask16 vmask = (1 << len) - 1;
vb[col] = CVT_BF16_TO_FP32(_mm256_maskz_loadu_epi16(vmask, row_k_ptr + col * 16));
vsum[col] = _mm512_fmadd_ps(va, vb[col], vsum[col]);
}
});
}
// attn[..., i, :i] = vsum
Unroll<COLS>{}([&](auto col) {
int len = std::min(i - col * 16, 16);
if (len > 0) {
const __mmask16 vmask = (1 << len) - 1;
_mm256_mask_storeu_epi16(row_ptr + col * 16, vmask, (__m256i)(_mm512_cvtneps_pbh(vsum[col])));
}
});
}
// attn = attn + torch.eye(chunk_size)
for (int i = 0; i < size; ++i) {
attn2[i * CHUNK_SIZE + i] += 1.f;
}
}
};
#endif
template <typename scalar_t, int CHUNK_SIZE, int D, bool has_beta, bool has_g>
struct apply_beta_kernel {
static inline void apply(
scalar_t* __restrict__ out,
const scalar_t* __restrict__ input,
const scalar_t* __restrict__ beta,
const float* __restrict__ g,
int size,
int ld_src,
int ld_dst,
int b_stride) {
TORCH_CHECK(false, "apply_beta_kernel: scalar path not implemented!");
}
};
#if defined(CPU_CAPABILITY_AVX512)
template <int CHUNK_SIZE, int D, bool has_beta, bool has_g>
struct apply_beta_kernel<at::BFloat16, CHUNK_SIZE, D, has_beta, has_g> {
static inline void apply(
at::BFloat16* __restrict__ out,
const at::BFloat16* __restrict__ input,
const at::BFloat16* __restrict__ beta,
const float* __restrict__ g,
int size,
int ld_src,
int ld_dst,
int b_stride) {
static_assert(D % 32 == 0);
constexpr int COLS = D / 16;
// get g.exp() and g is padded to CHUNK_SIZE
alignas(64) float g_arr[CHUNK_SIZE];
if constexpr (has_g) {
Unroll<CHUNK_SIZE / 16>{}([&](auto col) {
__m512 vg = _mm512_loadu_ps(g + col * 16);
__m512 vg_exp = _mm512_fexp_u20_ps(vg);
_mm512_storeu_ps(g_arr + col * 16, vg_exp);
});
}
for (int i = 0; i < size; ++i) {
__m512 vbeta;
if constexpr (has_beta) {
vbeta = _mm512_set1_ps(static_cast<float>(beta[i * b_stride]));
}
__m512 vg;
if constexpr (has_g) {
vg = _mm512_set1_ps(g_arr[i]);
}
Unroll<COLS>{}([&](auto col) {
// load for 0, 2, 4, 6
if constexpr (col % 2 == 0) {
__m512i a16 = _mm512_loadu_si512(input + i * ld_src + col * 16);
__m512 va0 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 0));
__m512 va1 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(a16, 1));
if constexpr (has_beta) {
va0 = _mm512_mul_ps(va0, vbeta);
va1 = _mm512_mul_ps(va1, vbeta);
}
if constexpr (has_g) {
va0 = _mm512_mul_ps(va0, vg);
va1 = _mm512_mul_ps(va1, vg);
}
_mm512_storeu_si512(out + i * ld_dst + col * 16, (__m512i)(_mm512_cvtne2ps_pbh(va1, va0)));
}
});
}
}
};
#endif
template <typename scalar_t, int D>
struct update_kernel {
static inline void
apply(scalar_t* __restrict__ out, const float* __restrict__ input, int size, int ld_src, int ld_dst) {
TORCH_CHECK(false, "update_kernel: scalar path not implemented!");
}
};
#if defined(CPU_CAPABILITY_AVX512)
template <int D>
struct update_kernel<at::BFloat16, D> {
static inline void
apply(at::BFloat16* __restrict__ out, const float* __restrict__ input, int size, int ld_src, int ld_dst) {
static_assert(D % 32 == 0);
constexpr int COLS = D / 16;
for (int i = 0; i < size; ++i) {
Unroll<COLS>{}([&](auto col) {
if constexpr (col % 2 == 0) {
__m512 va0 = _mm512_loadu_ps(input + i * ld_src + (col + 0) * 16);
__m512 va1 = _mm512_loadu_ps(input + i * ld_src + (col + 1) * 16);
__m512i a16 = (__m512i)(_mm512_cvtne2ps_pbh(va1, va0));
_mm512_storeu_si512(out + i * ld_dst + col * 16, a16);
}
});
}
}
};
#endif
template <typename scalar_t, int D>
struct update_value_kernel {
static inline void apply(
scalar_t* __restrict__ v_prime2,
const scalar_t* __restrict__ v,
const float* __restrict__ v_prime,
int size,
int padded_size,
int v_strideT) {
TORCH_CHECK(false, "update_kernel: scalar path not implemented!");
}
};
#if defined(CPU_CAPABILITY_AVX512)
template <int D>
struct update_value_kernel<at::BFloat16, D> {
static inline void apply(
at::BFloat16* __restrict__ v_prime2,
const at::BFloat16* __restrict__ v,
const float* __restrict__ v_prime,
int size,
int padded_size,
int v_strideT) {
static_assert(D % 32 == 0);
constexpr int COLS = D / 16;
// v2' = v - v'
for (int i = 0; i < size; ++i) {
Unroll<COLS>{}([&](auto col) {
// load for 0, 2, 4, 6
if constexpr (col % 2 == 0) {
__m512i v16 = _mm512_loadu_si512(v + i * v_strideT + col * 16);
__m512 va0 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(v16, 0));
__m512 va1 = CVT_BF16_TO_FP32(_mm512_extracti32x8_epi32(v16, 1));
__m512 v_prime0 = _mm512_loadu_ps(v_prime + i * D + col * 16);
__m512 v_prime1 = _mm512_loadu_ps(v_prime + i * D + col * 16 + 16);
va0 = _mm512_sub_ps(va0, v_prime0);
va1 = _mm512_sub_ps(va1, v_prime1);
__m512i o16 = (__m512i)(_mm512_cvtne2ps_pbh(va1, va0));
_mm512_storeu_si512(v_prime2 + i * D + col * 16, o16);
}
});
}
// pad the last chunk
for (int i = size; i < padded_size; ++i) {
Unroll<COLS>{}([&](auto col) {
if constexpr (col % 2 == 0) {
__m512i v16 = _mm512_setzero_si512();
_mm512_storeu_si512(v_prime2 + i * D + col * 16, v16);
}
});
}
}
};
#endif
template <typename scalar_t, int CHUNK_SIZE, int D>
struct update_key_kernel {
static inline void apply(
scalar_t* __restrict__ k_updated,
const scalar_t* __restrict__ k,
const float* __restrict__ g,
int size,
int k_strideT) {
TORCH_CHECK(false, "update_key_kernel: scalar path not implemented!");
}
};
#if defined(CPU_CAPABILITY_AVX512)
template <int CHUNK_SIZE, int D>
struct update_key_kernel<at::BFloat16, CHUNK_SIZE, D> {
static inline void apply(
at::BFloat16* __restrict__ k_updated,
const at::BFloat16* __restrict__ k,
const float* __restrict__ g,
int size,
int k_strideT) {
static_assert(D % 32 == 0);
const int MB = div_up(size, 16);
const int KB = D / 16;
const float g_last = g[size - 1];
const __m512 vg_last = _mm512_set1_ps(g_last);
float scale_arr[16];
__m256i va[16];
// from [C, D](MB, KB) to [D, C](KB, MB)
// pad size to 16 in this kernel so that transpose can be done in one loop
for (int mb = 0; mb < MB; ++mb) {
const int mb_size = std::min(size - mb * 16, 16);
// prepare exp(g_last - g)
__m512 vg = _mm512_loadu_ps(g + mb * 16);
_mm512_storeu_ps(scale_arr, _mm512_fexp_u20_ps(_mm512_sub_ps(vg_last, vg)));
for (int kb = 0; kb < KB; ++kb) {
const at::BFloat16* __restrict__ k_ptr = k + mb * 16 * k_strideT + kb * 16;
at::BFloat16* __restrict__ k_updated_ptr = k_updated + kb * 16 * CHUNK_SIZE + mb * 16;
// load 16 regs
Unroll<16>{}([&](auto m) {
if (m < mb_size) {
__m256i v16 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(k_ptr + m * k_strideT));
__m512 v32 = _mm512_mul_ps(CVT_BF16_TO_FP32(v16), _mm512_set1_ps(scale_arr[m]));
va[m] = (__m256i)_mm512_cvtneps_pbh(v32);
} else {
va[m] = _mm256_setzero_si256();
}
});
// transpose 16x16
transpose_16x16_16bit(va);
// store 16 regs
Unroll<16>{}(
[&](auto k) { _mm256_storeu_si256(reinterpret_cast<__m256i*>(k_updated_ptr + k * CHUNK_SIZE), va[k]); });
}
}
}
};
#endif
// template head_dim here to reduce extra read
// * normal approach: read inputs 2 times:
// - reduce: 1R
// - scale: 1R + 1W
// * keep input data in register:
// - reduce: 1R
// - scale: 1W
template <typename scalar_t, int D>
void l2norm_fwd_kernel_impl(
scalar_t* __restrict__ query_norm,
scalar_t* __restrict__ key_norm,
const scalar_t* __restrict__ query,
const scalar_t* __restrict__ key,
float eps,
int64_t T,
int64_t H,
int64_t q_strideT,
int64_t q_strideH,
int64_t k_strideT,
int64_t k_strideH) {
// expected to be contuguous
int64_t qn_strideH = D;
int64_t kn_strideH = D;
// parallel on [B, T, H]
at::parallel_for(0, T * H, 0, [&](int64_t begin, int64_t end) {
int64_t t{0}, h{0};
data_index_init(begin, t, T, h, H);
for (int64_t i = begin; i < end; ++i) {
const scalar_t* __restrict__ q_ptr = query + t * q_strideT + h * q_strideH;
const scalar_t* __restrict__ k_ptr = key + t * k_strideT + h * k_strideH;
scalar_t* __restrict__ qn_ptr = query_norm + i * qn_strideH;
scalar_t* __restrict__ kn_ptr = key_norm + i * kn_strideH;
l2norm_kernel<scalar_t, D, true>::apply(qn_ptr, q_ptr, eps);
l2norm_kernel<scalar_t, D, false>::apply(kn_ptr, k_ptr, eps);
// move to the next index
data_index_step(t, T, h, H);
}
});
}
// g : [B, T, Hv]
// g_ : [B, Hv, NT, C] -> [B, NT, HB, BLOCK_H, C]
// cu_seqlens : [num_seqs + 1]
// chunk_indices : [NT * 2]
template <typename scalar_t, int CHUNK_SIZE>
void chunk_local_cumsum_kernel_impl(
scalar_t* __restrict__ g_,
const scalar_t* __restrict__ g,
const int32_t* __restrict__ cu_seqlens,
const int32_t* __restrict__ chunk_indices,
int64_t Hv,
int64_t NT) {
constexpr int BLOCK_H = 16;
int64_t HB = div_up(Hv, int64_t(BLOCK_H));
// parallel on [NT * HB] to increase parallelism
at::parallel_for(0, NT * HB, 0, [&](int64_t begin, int64_t end) {
int64_t nt{0}, hb{0};
data_index_init(begin, nt, NT, hb, HB);
for (int64_t i = begin; i < end; ++i) {
int32_t bs = chunk_indices[nt * 2 + 0];
int32_t batch_offset = cu_seqlens[bs];
int32_t seqlen = cu_seqlens[bs + 1] - cu_seqlens[bs];
int64_t mb_start = chunk_indices[nt * 2 + 1] * CHUNK_SIZE;
int64_t mb_size = std::min(seqlen - mb_start, int64_t(CHUNK_SIZE));
int64_t hb_size = std::min(Hv - hb * BLOCK_H, int64_t(BLOCK_H));
const scalar_t* __restrict__ g_ptr = g + (batch_offset + mb_start) * Hv + hb * BLOCK_H;
scalar_t* __restrict__ gsum_ptr = g_ + nt * (Hv * CHUNK_SIZE) + hb * (BLOCK_H * CHUNK_SIZE);
cumsum_kernel<scalar_t, CHUNK_SIZE, BLOCK_H>::apply(gsum_ptr, g_ptr, mb_size, hb_size, Hv, CHUNK_SIZE);
// move to the next index
data_index_step(nt, NT, hb, HB);
}
});
}
#define DECL_BUF(type, name, size_expr) alignas(64) type name[(size_expr)]
#define DECL_ZERO_BUF(type, name, size_expr) \
DECL_BUF(type, name, size_expr); \
fill_stub<type, (size_expr)>(name, 0.f)
// w : [B, T, Hv, D]
// u : [B, T, Hv, Dv]
// d : [B, NT, Hv, C, C]
// k : [B, T, H, D]
// v : [B, T, Hv, Dv]
// g : [B, NT, Hv, C]
// beta : [B, T, Hv]
// cu_seqlens : [num_seqs + 1]
// chunk_indices : [NT * 2]
template <typename scalar_t, int D, int CHUNK_SIZE>
void chunk_gated_delta_rule_fwd_intra_kernel_impl(
scalar_t* __restrict__ w,
scalar_t* __restrict__ u,
float* __restrict__ d,
const scalar_t* __restrict__ k,
const scalar_t* __restrict__ v,
const float* __restrict__ g,
const scalar_t* __restrict__ beta,
const int32_t* __restrict__ cu_seqlens,
const int32_t* __restrict__ chunk_indices,
int64_t H,
int64_t Hv,
int64_t NT,
int64_t k_strideT,
int64_t k_strideH,
int64_t v_strideT,
int64_t v_strideH) {
// head group, expect to be 1,2,4 for qwen3.5
const int64_t HG = Hv / H;
// strides
const int64_t w_strideT = Hv * D;
const int64_t w_strideH = D;
const int64_t u_strideT = Hv * D;
const int64_t u_strideH = D;
// [NB]: parallel on [NT, H]
// * parallel on num_heads and go sequential on num_heads_v,
// * avoid instantialize k_beta (beta * k)
// * compute key @ key^T * beta instead of k_beta @ key^T, same as triton impl
// * compute key @ key^T once for each k head index and reuse for v head index
at::parallel_for(0, NT * H, 0, [&](int64_t begin, int64_t end) {
int64_t nt{0}, h{0};
data_index_init(begin, nt, NT, h, H);
// thread local temp buffer
DECL_ZERO_BUF(scalar_t, tmp, CHUNK_SIZE * D);
DECL_ZERO_BUF(scalar_t, tmp2, CHUNK_SIZE * D);
DECL_ZERO_BUF(float, attn, CHUNK_SIZE* CHUNK_SIZE);
DECL_ZERO_BUF(scalar_t, attn2, CHUNK_SIZE * CHUNK_SIZE);
DECL_ZERO_BUF(float, tmp3, CHUNK_SIZE* D);
// alias
scalar_t* __restrict__ k_packed = tmp;
scalar_t* __restrict__ k_beta = tmp;
scalar_t* __restrict__ v_beta = tmp;
scalar_t* __restrict__ k_beta_packed = tmp2;
scalar_t* __restrict__ v_beta_packed = tmp2;
float* __restrict__ k_updated = tmp3;
float* __restrict__ v_updated = tmp3;
for (int64_t i = begin; i < end; ++i) {
int32_t bs = chunk_indices[nt * 2 + 0];
int32_t batch_offset = cu_seqlens[bs];
int32_t seqlen = cu_seqlens[bs + 1] - cu_seqlens[bs];
int64_t mb_start = chunk_indices[nt * 2 + 1] * CHUNK_SIZE;
int64_t mb_size = std::min(seqlen - mb_start, int64_t(CHUNK_SIZE));
// mb_size` is K in 5.c, 5.g, pad to TILE_K;
const int64_t padded_mb_size = div_up((int)mb_size, TILE_K) * TILE_K;
// step 1: decay_mask = ((g.unsqueeze(-1) - g.unsqueeze(-2)).tril().exp().float()).tril()
for (int64_t hv = h * HG; hv < h * HG + HG; ++hv) {
const float* __restrict__ g_ptr = g + nt * (Hv * CHUNK_SIZE) + hv * CHUNK_SIZE;
float* __restrict__ d_ptr = d + nt * (Hv * CHUNK_SIZE * CHUNK_SIZE) + hv * (CHUNK_SIZE * CHUNK_SIZE);
decay_mask_kernel<float, CHUNK_SIZE>::apply(d_ptr, g_ptr);
}
// step 2: attn = key @ key^T
const scalar_t* __restrict__ k_ptr = k + (batch_offset + mb_start) * k_strideT + h * k_strideH;
pack_vnni<scalar_t>(
/* dst */ k_packed,
/* src */ k_ptr,
/* N */ mb_size,
/* K */ D,
/* ld_src */ k_strideT,
/* ld_dst */ CHUNK_SIZE);
at::native::cpublas::brgemm(
/* M */ mb_size,
/* N */ mb_size,
/* K */ D,
/* lda */ k_strideT,
/* ldb */ CHUNK_SIZE,
/* ldc */ CHUNK_SIZE,
/* add_C */ false,
/* A */ k_ptr,
/* B */ k_packed,
/* C */ attn);
for (int64_t hv = h * HG; hv < h * HG + HG; ++hv) {
// step 3: attn2 = -attn * beta * d
const scalar_t* __restrict__ beta_ptr = beta + (batch_offset + mb_start) * Hv + hv;
const float* __restrict__ d_ptr = d + nt * (Hv * CHUNK_SIZE * CHUNK_SIZE) + hv * (CHUNK_SIZE * CHUNK_SIZE);
apply_mask_kernel<scalar_t, CHUNK_SIZE, true>::apply(attn2, attn, beta_ptr, d_ptr, mb_size, Hv);
// step 4: solve_tril(attn2) -> (I + L)^{-1}, L = strict-lower from step 3
// for i in 1..C-1: attn2[i, :i] += (attn2[i, :i] * attn2[:i, :i]).sum(-1)
// attn2 += eye(C)
solve_tril_kernel<scalar_t, CHUNK_SIZE>::apply(attn2, mb_size);
// step 5: recompute_w_u
// w = attn2 @ (k_beta * g.exp().unsqueeze(-1))
// u = attn2 @ value * beta.unsqueeze(-1)
const float* __restrict__ g_ptr = g + nt * (Hv * CHUNK_SIZE) + hv * CHUNK_SIZE;
const scalar_t* __restrict__ v_ptr = v + (batch_offset + mb_start) * v_strideT + hv * v_strideH;
// 5.a key = key * beta * g.exp
apply_beta_kernel<scalar_t, CHUNK_SIZE, D, true, true>::apply(
k_beta, k_ptr, beta_ptr, g_ptr, mb_size, k_strideT, D, Hv);
// 5.b pack key
pack_vnni2<scalar_t>(
/* dst */ k_beta_packed,
/* src */ k_beta,
/* K */ mb_size,
/* N */ D,
/* ld_src */ D,
/* ld_dst */ D);
// 5.c w = attn2 @ k_beta
at::native::cpublas::brgemm(
/* M */ mb_size,
/* N */ D,
/* K */ padded_mb_size, // mb_size
/* lda */ CHUNK_SIZE,
/* ldb */ D,
/* ldc */ D,
/* add_C */ false,
/* A */ attn2,
/* B */ k_beta_packed,
/* C */ k_updated);
// 5.d k_updated -> w
scalar_t* __restrict__ w_ptr = w + (batch_offset + mb_start) * w_strideT + hv * w_strideH;
update_kernel<scalar_t, D>::apply(w_ptr, k_updated, mb_size, D, w_strideT);
// 5.e value = value * beta
apply_beta_kernel<scalar_t, CHUNK_SIZE, D, true, false>::apply(
v_beta, v_ptr, beta_ptr, nullptr, mb_size, v_strideT, D, Hv);
// 5.f pack value
pack_vnni2<scalar_t>(
/* dst */ v_beta_packed,
/* src */ v_beta,
/* K */ mb_size,
/* N */ D,
/* ld_src */ D,
/* ld_dst */ D);
// 5.g u = attn2 @ v_beta
at::native::cpublas::brgemm(
/* M */ mb_size,
/* N */ D,
/* K */ padded_mb_size, // mb_size
/* lda */ CHUNK_SIZE,
/* ldb */ D,
/* ldc */ D,
/* add_C */ false,
/* A */ attn2,
/* B */ v_beta_packed,
/* C */ v_updated);
// 5.h v_updated -> u
scalar_t* __restrict__ u_ptr = u + (batch_offset + mb_start) * u_strideT + hv * u_strideH;
update_kernel<scalar_t, D>::apply(u_ptr, v_updated, mb_size, D, u_strideT);
}
// move to the next index
data_index_step(nt, NT, h, H);
}
at::native::cpublas::brgemm_release();
});
}
//
// out : [B, T, Hv, Dv]
// state : [num_seqs, Hv, Dv, D]
// q : [B, T, H, D]
// k : [B, T, H, D]
// w : [B, T, Hv, D]
// u : [B, T, Hv, Dv]
// g : [B, NT, Hv, C]
// d : [B, NT, Hv, C, C]
// cu_seqlens : [num_seqs + 1]
// chunk_offsets : [num_seqs + 1]
template <typename scalar_t, int D, int CHUNK_SIZE>
void chunk_gated_delta_rule_fwd_inter_kernel_impl(
scalar_t* __restrict__ out,
float* __restrict__ state,
const int32_t* __restrict__ indices,
const scalar_t* __restrict__ q,
const scalar_t* __restrict__ k,
const scalar_t* __restrict__ w,
const scalar_t* __restrict__ u,
const float* __restrict__ g,
const float* __restrict__ d,
const int32_t* __restrict__ cu_seqlens,
const int32_t* __restrict__ chunk_offsets,
int64_t H,
int64_t Hv,
int64_t num_seqs,
int64_t q_strideT,
int64_t q_strideH,
int64_t k_strideT,
int64_t k_strideH) {
// head group, expect to be 1,2,4 for qwen3.5
const int64_t HG = Hv / H;
// strides
const int64_t w_strideT = Hv * D;
const int64_t w_strideH = D;
const int64_t u_strideT = Hv * D;
const int64_t u_strideH = D;
const int64_t o_strideT = Hv * D;
const int64_t o_strideH = D;
// [NB]: parallel on [num_seqs, Hv]
// * choose to parallel on Hv instead of H, though this means q @ kT has duplicated compute
// * H might be 16 which is not enough to use 32C when num_seqs is small
at::parallel_for(0, num_seqs * Hv, 0, [&](int64_t begin, int64_t end) {
int64_t bs{0}, hv{0};
data_index_init(begin, bs, num_seqs, hv, Hv);
// thread local temp buffer
DECL_ZERO_BUF(scalar_t, tmp, CHUNK_SIZE * D);
DECL_ZERO_BUF(scalar_t, tmp2, D * D);
DECL_ZERO_BUF(float, tmp3, CHUNK_SIZE* D);
DECL_ZERO_BUF(scalar_t, tmp4, CHUNK_SIZE * D);
DECL_ZERO_BUF(float, attn, CHUNK_SIZE* CHUNK_SIZE);
DECL_ZERO_BUF(scalar_t, attn2, CHUNK_SIZE * CHUNK_SIZE);
// alias
scalar_t* __restrict__ k_packed = tmp;
scalar_t* __restrict__ s_packed = tmp2;
float* __restrict__ v_prime = tmp3;
scalar_t* __restrict__ v_prime2 = tmp;
float* __restrict__ attn_inter = tmp3;
scalar_t* __restrict__ qg_exp = tmp4;
scalar_t* __restrict__ v_packed = tmp4;
scalar_t* __restrict__ k_updated = tmp;
for (int64_t i = begin; i < end; ++i) {
int64_t h = hv / HG;
int32_t batch_offset = cu_seqlens[bs];
int32_t seqlen = cu_seqlens[bs + 1] - cu_seqlens[bs];
int64_t nt = chunk_offsets[bs];
for (int64_t mb_start = 0; mb_start < seqlen; mb_start += CHUNK_SIZE, ++nt) {
int64_t mb_size = std::min(seqlen - mb_start, int64_t(CHUNK_SIZE));
// mb_size` is K in 4.a, pad to TILE_K;
const int64_t padded_mb_size = div_up((int)mb_size, TILE_K) * TILE_K;
// step 1.a: attn = query @ key^T
// attn_i = (q_i @ k_i.transpose(-1, -2) * decay_mask[:, :, i]).masked_fill_(mask, 0)
const scalar_t* __restrict__ q_ptr = q + (batch_offset + mb_start) * q_strideT + h * q_strideH;
const scalar_t* __restrict__ k_ptr = k + (batch_offset + mb_start) * k_strideT + h * k_strideH;
pack_vnni<scalar_t>(
/* dst */ k_packed,
/* src */ k_ptr,
/* N */ mb_size,
/* K */ D,
/* ld_src */ k_strideT,
/* ld_dst */ CHUNK_SIZE);
at::native::cpublas::brgemm(
/* M */ mb_size,
/* N */ mb_size,
/* K */ D,
/* lda */ q_strideT,
/* ldb */ CHUNK_SIZE,
/* ldc */ CHUNK_SIZE,
/* add_C */ false,
/* A */ q_ptr,
/* B */ k_packed,
/* C */ attn);
// step 1.b: attn = attn * decay_mask.masked_fill_(mask, 0)
const float* __restrict__ d_ptr = d + nt * (Hv * CHUNK_SIZE * CHUNK_SIZE) + hv * (CHUNK_SIZE * CHUNK_SIZE);
apply_mask_kernel<scalar_t, CHUNK_SIZE, false>::apply(attn2, attn, nullptr, d_ptr, mb_size);
// step 2.a: v' = w @ state (fuse state *= exp(g_last) with packing)
float* __restrict__ s_ptr = state + indices[bs] * (Hv * D * D) + hv * (D * D);
const float* __restrict__ g_ptr = g + nt * (Hv * CHUNK_SIZE) + hv * (CHUNK_SIZE);
float g_last = g_ptr[mb_size - 1];
pack_vnni2<scalar_t, D, D>(
/* dst */ s_packed,
/* src */ s_ptr,
/* g_last */ g_last,
/* ld_src */ D,
/* ld_dst */ D);
const scalar_t* __restrict__ w_ptr = w + (batch_offset + mb_start) * w_strideT + hv * w_strideH;
at::native::cpublas::brgemm(
/* M */ mb_size,
/* N */ D,
/* K */ D,
/* lda */ w_strideT,
/* ldb */ D,
/* ldc */ D,
/* add_C */ false,
/* A */ w_ptr,
/* B */ s_packed,
/* C */ v_prime);
// step 2.b: v2' = u - v'
const scalar_t* __restrict__ u_ptr = u + (batch_offset + mb_start) * u_strideT + hv * u_strideH;
update_value_kernel<scalar_t, D>::apply(v_prime2, u_ptr, v_prime, mb_size, padded_mb_size, u_strideT);
// step 3.a: qg_exp = q * exp(g)
apply_beta_kernel<scalar_t, CHUNK_SIZE, D, false, true>::apply(
qg_exp, q_ptr, nullptr, g_ptr, mb_size, q_strideT, D, /*b_stride*/ 0);
// step 3.b: attn_inter = qg_exp @ state
at::native::cpublas::brgemm(
/* M */ mb_size,
/* N */ D,
/* K */ D,
/* lda */ D,
/* ldb */ D,
/* ldc */ D,
/* add_C */ false,
/* A */ qg_exp,
/* B */ s_packed,
/* C */ attn_inter);
// step 4.a: attn_inter += attn2 @ v2'
pack_vnni2<scalar_t>(
/* dst */ v_packed,
/* src */ v_prime2,
/* K */ padded_mb_size,
/* N */ D,
/* ld_src */ D,
/* ld_dst */ D);
at::native::cpublas::brgemm(
/* M */ mb_size,
/* N */ D,
/* K */ padded_mb_size,
/* lda */ CHUNK_SIZE,
/* ldb */ D,
/* ldc */ D,
/* add_C */ true,
/* A */ attn2,
/* B */ v_packed,
/* C */ attn_inter);
// step 4.b: write attn_inter -> out
scalar_t* __restrict__ o_ptr = out + (batch_offset + mb_start) * o_strideT + hv * o_strideH;
update_kernel<scalar_t, D>::apply(o_ptr, attn_inter, mb_size, D, o_strideT);
// step 5: update state
// state_new = state * exp(g_last) + (k * exp(g_last - g)).T @ v2'
// step 5.1 state *= exp(g_last) fused with step 2.a
// step 5.2 k' = k * exp(g_last - g).T; TODO: fuse this with 1.a
update_key_kernel<scalar_t, CHUNK_SIZE, D>::apply(k_updated, k_ptr, g_ptr, mb_size, k_strideT);
// step 5.3 state += k' @ v2'
at::native::cpublas::brgemm(
/* M */ D,
/* N */ D,
/* K */ padded_mb_size, // mb_size
/* lda */ CHUNK_SIZE,
/* ldb */ D,
/* ldc */ D,
/* add_C */ true,
/* A */ k_updated,
/* B */ v_packed,
/* C */ s_ptr);
}
// move to the next index
data_index_step(bs, num_seqs, hv, Hv);
}
at::native::cpublas::brgemm_release();
});
}
inline float softplus(float x, double threshold = 20.0) {
if (x > threshold)
return x;
else if (x < -threshold)
return std::exp(x);
else
return std::log1p(std::exp(x));
}
inline at::vec::Vectorized<float> softplus(const at::vec::Vectorized<float>& x, double threshold = 20.0) {
using Vec = at::vec::Vectorized<float>;
Vec mask_hi = x > Vec(threshold);
Vec mask_lo = x < Vec(-threshold);
Vec expx = x.exp_u20();
Vec log1pex = (expx + Vec(1.0f)).log();
return Vec::blendv(Vec::blendv(log1pex, expx, mask_lo), x, mask_hi);
}
template <typename scalar_t, typename param_t>
void fused_sigmoid_gating_delta_rule_update_kernel_impl(
const scalar_t* __restrict__ q_ptr,
const scalar_t* __restrict__ k_ptr,
const scalar_t* __restrict__ v_ptr,
const param_t* __restrict__ A_log_ptr,
const scalar_t* __restrict__ a_ptr,
const scalar_t* __restrict__ dt_bias_ptr,
const scalar_t* __restrict__ b_ptr,
const int32_t* __restrict__ indices_ptr,
float* __restrict__ state_ptr,
scalar_t* __restrict__ o_ptr,
float* __restrict__ qk_scale_buf,
int64_t seq_len,
int64_t batch_size,
int64_t num_heads,
int64_t head_dim,
int64_t v_num_heads,
int64_t v_head_dim,
int64_t q_strideB,
int64_t q_strideS,
int64_t q_strideH,
int64_t k_strideB,
int64_t k_strideS,
int64_t k_strideH,
int64_t v_strideB,
int64_t v_strideS,
int64_t v_strideH,
bool use_qk_l2norm_in_kernel,
double softplus_threshold) {
using bVec = at::vec::Vectorized<scalar_t>;
using fVec = at::vec::Vectorized<float>;
constexpr int64_t VecSize = bVec::size();
constexpr int64_t fVecSize = fVec::size();
int64_t group_size = v_num_heads / num_heads;
double scale = 1 / std::sqrt(head_dim);
fVec scale_vec = fVec(scale);
if (use_qk_l2norm_in_kernel) {
float eps = 1e-5;
at::parallel_for(0, batch_size * seq_len * num_heads, 0, [&](int64_t begin, int64_t end) {
int64_t bi{0}, si{0}, ni{0};
data_index_init(begin, bi, batch_size, si, seq_len, ni, num_heads);
for (int64_t i = begin; i < end; ++i) {
float sum_q = float(0);
float sum_k = float(0);
fVec sum_q_fvec = fVec(float(0));
fVec sum_k_fvec = fVec(float(0));
int64_t q_offset = bi * q_strideB + si * q_strideS + ni * q_strideH;
int64_t k_offset = bi * k_strideB + si * k_strideS + ni * k_strideH;
int64_t q_scale_offset = bi * seq_len * num_heads + si * num_heads + ni;
int64_t k_scale_offset = q_scale_offset + batch_size * seq_len * num_heads;
int64_t d;
#pragma GCC unroll 4
for (d = 0; d <= head_dim - VecSize; d += VecSize) {
bVec q_bvec = bVec::loadu(q_ptr + q_offset + d);
fVec q_fvec0, q_fvec1;
std::tie(q_fvec0, q_fvec1) = at::vec::convert_to_float(q_bvec);
sum_q_fvec += q_fvec0 * q_fvec0;
sum_q_fvec += q_fvec1 * q_fvec1;
bVec k_bvec = bVec::loadu(k_ptr + k_offset + d);
fVec k_fvec0, k_fvec1;
std::tie(k_fvec0, k_fvec1) = at::vec::convert_to_float(k_bvec);
sum_k_fvec += k_fvec0 * k_fvec0;
sum_k_fvec += k_fvec1 * k_fvec1;
}
#pragma GCC unroll 4
for (; d < head_dim; ++d) {
float q_val = static_cast<float>(q_ptr[q_offset + d]);
sum_q += q_val * q_val;
float k_val = static_cast<float>(k_ptr[k_offset + d]);
sum_k += k_val * k_val;
}
sum_q += vec_reduce_sum(sum_q_fvec);
sum_k += vec_reduce_sum(sum_k_fvec);
qk_scale_buf[q_scale_offset] = float(1) / std::sqrt(sum_q + eps);
qk_scale_buf[k_scale_offset] = float(1) / std::sqrt(sum_k + eps);
data_index_step(bi, batch_size, si, seq_len, ni, num_heads);
}
});
}
at::parallel_for(0, batch_size * seq_len * v_num_heads, 0, [&](int64_t begin, int64_t end) {
int64_t bi{0}, si{0}, ni{0};
data_index_init(begin, bi, batch_size, si, seq_len, ni, v_num_heads);
for (int64_t i = begin; i < end; ++i) {
int64_t cache_index = indices_ptr[bi];
int64_t state_offset = (cache_index * v_num_heads + ni) * head_dim * v_head_dim;
float g_val = -std::exp(float(A_log_ptr[ni])) *
softplus(float(a_ptr[bi * v_num_heads + ni]) + float(dt_bias_ptr[ni]), softplus_threshold);
float g_val_exp = std::exp(g_val);
fVec g_val_exp_vec = fVec(g_val_exp);
int64_t q_offset = si * q_strideS + bi * q_strideB + (ni / group_size) * q_strideH;
int64_t k_offset = si * k_strideS + bi * k_strideB + (ni / group_size) * k_strideH;
int64_t q_scale_offset = bi * seq_len * num_heads + si * num_heads + (ni / group_size);
int64_t k_scale_offset = q_scale_offset + batch_size * seq_len * num_heads;
float q_scale = use_qk_l2norm_in_kernel ? qk_scale_buf[q_scale_offset] : 1.0f;
float k_scale = use_qk_l2norm_in_kernel ? qk_scale_buf[k_scale_offset] : 1.0f;
int64_t v_offset = si * v_strideS + bi * v_strideB + ni * v_strideH;
int64_t o_offset = ((bi * seq_len + si) * v_num_heads + ni) * v_head_dim;
float beta_val = 1 / (1 + std::exp(-b_ptr[bi * v_num_heads + ni]));
fVec beta_vec = fVec(beta_val);
int64_t dvi = 0;
for (; dvi <= v_head_dim - VecSize; dvi += VecSize) {
fVec kv_mem_vec0 = fVec(float(0));
fVec kv_mem_vec1 = fVec(float(0));
for (int di = 0; di < head_dim; ++di) {
fVec k_val_vec = fVec(k_ptr[k_offset + di] * k_scale);
fVec state_vec0 = fVec::loadu(state_ptr + state_offset + di * v_head_dim + dvi);
fVec state_vec1 = fVec::loadu(state_ptr + state_offset + di * v_head_dim + dvi + fVecSize);
kv_mem_vec0 = kv_mem_vec0 + state_vec0 * g_val_exp_vec * k_val_vec;
kv_mem_vec1 = kv_mem_vec1 + state_vec1 * g_val_exp_vec * k_val_vec;
}
bVec v_bvec = bVec::loadu(v_ptr + v_offset + dvi);
fVec v_vec0, v_vec1;
std::tie(v_vec0, v_vec1) = at::vec::convert_to_float(v_bvec);
fVec dt_vec0 = (v_vec0 - kv_mem_vec0) * beta_vec;
fVec dt_vec1 = (v_vec1 - kv_mem_vec1) * beta_vec;
fVec o_vec0 = fVec(float(0));
fVec o_vec1 = fVec(float(0));
for (int di = 0; di < head_dim; ++di) {
fVec q_vec = fVec(q_ptr[q_offset + di] * q_scale);
fVec k_vec = fVec(k_ptr[k_offset + di] * k_scale);
fVec state_vec0 = fVec::loadu(state_ptr + state_offset + di * v_head_dim + dvi);
fVec state_vec1 = fVec::loadu(state_ptr + state_offset + di * v_head_dim + dvi + fVecSize);
state_vec0 = state_vec0 * g_val_exp_vec + k_vec * dt_vec0;
state_vec1 = state_vec1 * g_val_exp_vec + k_vec * dt_vec1;
o_vec0 = o_vec0 + state_vec0 * q_vec * scale_vec;
o_vec1 = o_vec1 + state_vec1 * q_vec * scale_vec;
state_vec0.store(state_ptr + state_offset + di * v_head_dim + dvi);
state_vec1.store(state_ptr + state_offset + di * v_head_dim + dvi + fVecSize);
}
bVec o_vec = at::vec::convert_from_float<scalar_t>(o_vec0, o_vec1);
o_vec.store(o_ptr + o_offset + dvi);
}
for (; dvi < v_head_dim; ++dvi) {
float kv_mem_val = 0;
for (int di = 0; di < head_dim; ++di) {
float k_val = k_ptr[k_offset + di] * k_scale;
state_ptr[state_offset + di * v_head_dim + dvi] *= g_val_exp;
kv_mem_val += state_ptr[state_offset + di * v_head_dim + dvi] * k_val;
}
float v_val = v_ptr[v_offset + dvi];
float dt_val = (v_val - kv_mem_val) * beta_val;
float o_val = 0;
for (int di = 0; di < head_dim; ++di) {
float q_val = q_ptr[q_offset + di] * q_scale;
float k_val = k_ptr[k_offset + di] * k_scale;
state_ptr[state_offset + di * v_head_dim + dvi] += k_val * dt_val;
o_val += state_ptr[state_offset + di * v_head_dim + dvi] * q_val * scale;
}
o_ptr[o_offset + dvi] = o_val;
}
data_index_step(bi, batch_size, si, seq_len, ni, v_num_heads);
}
});
}
template <typename scalar_t>
void fused_gdn_gating_kernel_impl(
float* __restrict__ A_log,
const scalar_t* __restrict__ a,
const scalar_t* __restrict__ b,
const scalar_t* __restrict__ dt_bias,
float* __restrict__ out,
scalar_t* __restrict__ beta,
int64_t batch,
int64_t num_heads) {
using bVec = at::vec::Vectorized<scalar_t>;
using fVec = at::vec::Vectorized<float>;
constexpr int vec_size = bVec::size();
constexpr int fvec_size = fVec::size();
const fVec neg_one(-1.0f);
const fVec one(1.0f);
at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
for (int64_t i = begin; i < end; ++i) {
int64_t j = 0;
for (; j < num_heads - (num_heads % vec_size); j += vec_size) {
fVec A_log_vec0 = fVec::loadu(A_log + j);
fVec A_log_vec1 = fVec::loadu(A_log + j + fvec_size);
bVec dt_bias_vec = bVec::loadu(dt_bias + j);
bVec a_bvec = bVec::loadu(a + i * num_heads + j);
bVec b_bvec = bVec::loadu(b + i * num_heads + j);
fVec a0, a1, dt_bias_vec0, dt_bias_vec1, b0, b1;
std::tie(a0, a1) = at::vec::convert_to_float(a_bvec);
std::tie(b0, b1) = at::vec::convert_to_float(b_bvec);
std::tie(dt_bias_vec0, dt_bias_vec1) = at::vec::convert_to_float(dt_bias_vec);
fVec g0 = neg_one * A_log_vec0.exp_u20() * softplus(a0 + dt_bias_vec0);
fVec g1 = neg_one * A_log_vec1.exp_u20() * softplus(a1 + dt_bias_vec1);
fVec beta0 = one / (one + (neg_one * b0).exp_u20());
fVec beta1 = one / (one + (neg_one * b1).exp_u20());
g0.store(out + i * num_heads + j);
g1.store(out + i * num_heads + j + fvec_size);
bVec beta_vec = at::vec::convert_from_float<scalar_t>(beta0, beta1);
beta_vec.store(beta + i * num_heads + j);
}
for (; j < num_heads; ++j) {
out[i * num_heads + j] = -std::exp(A_log[j]) * softplus(float(a[i * num_heads + j]) + float(dt_bias[j]));
beta[i * num_heads + j] = 1 / (1 + std::exp(-b[i * num_heads + j]));
}
}
});
}
template <typename scalar_t>
void fused_gdn_gating_kernel_impl(
scalar_t* __restrict__ A_log,
const scalar_t* __restrict__ a,
const scalar_t* __restrict__ b,
const scalar_t* __restrict__ dt_bias,
float* __restrict__ out,
scalar_t* __restrict__ beta,
int64_t batch,
int64_t num_heads) {
using bVec = at::vec::Vectorized<scalar_t>;
using fVec = at::vec::Vectorized<float>;
constexpr int vec_size = bVec::size();
constexpr int fvec_size = fVec::size();
const fVec neg_one(-1.0f);
const fVec one(1.0f);
at::parallel_for(0, batch, 0, [&](int64_t begin, int64_t end) {
for (int64_t i = begin; i < end; ++i) {
int64_t j = 0;
for (; j < num_heads - (num_heads % vec_size); j += vec_size) {
bVec A_log_bvec = bVec::loadu(A_log + j);
fVec A_log_vec0, A_log_vec1;
std::tie(A_log_vec0, A_log_vec1) = at::vec::convert_to_float(A_log_bvec);
bVec dt_bias_vec = bVec::loadu(dt_bias + j);
bVec a_bvec = bVec::loadu(a + i * num_heads + j);
bVec b_bvec = bVec::loadu(b + i * num_heads + j);
fVec a0, a1, dt_bias_vec0, dt_bias_vec1, b0, b1;
std::tie(a0, a1) = at::vec::convert_to_float(a_bvec);
std::tie(b0, b1) = at::vec::convert_to_float(b_bvec);
std::tie(dt_bias_vec0, dt_bias_vec1) = at::vec::convert_to_float(dt_bias_vec);
fVec g0 = neg_one * A_log_vec0.exp_u20() * softplus(a0 + dt_bias_vec0);
fVec g1 = neg_one * A_log_vec1.exp_u20() * softplus(a1 + dt_bias_vec1);
fVec beta0 = one / (one + (neg_one * b0).exp_u20());
fVec beta1 = one / (one + (neg_one * b1).exp_u20());
g0.store(out + i * num_heads + j);
g1.store(out + i * num_heads + j + fvec_size);
bVec beta_vec = at::vec::convert_from_float<scalar_t>(beta0, beta1);
beta_vec.store(beta + i * num_heads + j);
}
for (; j < num_heads; ++j) {
out[i * num_heads + j] = -std::exp(float(A_log[j])) * softplus(float(a[i * num_heads + j]) + float(dt_bias[j]));
beta[i * num_heads + j] = 1 / (1 + std::exp(-b[i * num_heads + j]));
}
}
});
}
} // anonymous namespace
template <int CHUNK_SIZE>
std::tuple<at::Tensor, at::Tensor> prepare_chunk_indices(const at::Tensor& cu_seqlens) {
int64_t num_seqs = cu_seqlens.size(0) - 1;
at::Tensor chunk_offsets = at::empty({num_seqs + 1}, cu_seqlens.options());
// get number of chunks and chunk offsets
const int32_t* offsets_data = cu_seqlens.data_ptr<int32_t>();
int32_t num_chunks = 0;
chunk_offsets[0] = 0;
for (int64_t row = 0; row < num_seqs; ++row) {
num_chunks += div_up(offsets_data[row + 1] - offsets_data[row], CHUNK_SIZE);
chunk_offsets[row + 1] = num_chunks;
}
// get chunk indices
at::Tensor chunk_indices = at::empty({num_chunks, 2}, cu_seqlens.options());
int32_t* indices_data = chunk_indices.data_ptr<int32_t>();
int64_t idx = 0;
for (int32_t row = 0; row < num_seqs; ++row) {
int32_t num_chunks = div_up(offsets_data[row + 1] - offsets_data[row], CHUNK_SIZE);
for (int32_t col = 0; col < num_chunks; ++col) {
indices_data[idx * 2 + 0] = row;
indices_data[idx * 2 + 1] = col;
idx++;
}
}
return std::make_tuple(chunk_indices, chunk_offsets);
}
#define DISPATCH_HEAD_DIM_CASE(launch_macro, hd) \
case hd: { \
launch_macro(hd); \
break; \
}
// [NB]: add new head_dim support here
#define DISPATCH_HEAD_DIM(dim, launch_macro) \
switch (dim) { \
DISPATCH_HEAD_DIM_CASE(launch_macro, 64) \
DISPATCH_HEAD_DIM_CASE(launch_macro, 128) \
default: \
TORCH_CHECK(false, "Unexpected head dim size, ", dim); \
}
#define LAUNCH_L2NORM_KERNEL(HD) \
l2norm_fwd_kernel_impl<scalar_t, HD>( \
query_norm.data_ptr<scalar_t>(), \
key_norm.data_ptr<scalar_t>(), \
query.data_ptr<scalar_t>(), \
key.data_ptr<scalar_t>(), \
eps, \
T, \
H, \
query.stride(1), \
query.stride(2), \
key.stride(1), \
key.stride(2));
std::tuple<at::Tensor, at::Tensor> l2norm_fwd(const at::Tensor& query, const at::Tensor& key, double eps) {
int64_t B = query.size(0);
int64_t T = query.size(1);
int64_t H = query.size(2);
int64_t D = query.size(3);
at::Tensor query_norm = at::empty_like(query);
at::Tensor key_norm = at::empty_like(key);
AT_DISPATCH_REDUCED_FLOATING_TYPES(
query.scalar_type(), "l2norm_fwd", [&] { DISPATCH_HEAD_DIM(D, LAUNCH_L2NORM_KERNEL); });
return std::make_tuple(query_norm, key_norm);
}
// [NB]: instantiate decay_mask to avoid heavy recomputation in the kernel with exp
template <int CHUNK_SIZE>
at::Tensor chunk_local_cumsum(const at::Tensor& g, const at::Tensor& cu_seqlens, const at::Tensor& chunk_indices) {
int64_t B = g.size(0);
// int64_t T = g.size(1);
int64_t Hv = g.size(2);
int64_t NT = chunk_indices.size(0);
at::Tensor g_ = at::empty({B, NT, Hv, CHUNK_SIZE}, g.options());
AT_DISPATCH_FLOATING_TYPES(g.scalar_type(), "chunk_local_cumsum", [&] {
chunk_local_cumsum_kernel_impl<scalar_t, CHUNK_SIZE>(
g_.data_ptr<scalar_t>(),
g.data_ptr<scalar_t>(),
cu_seqlens.data_ptr<int32_t>(),
chunk_indices.data_ptr<int32_t>(),
Hv,
NT);
});
return g_;
}
#define LAUNCH_CHUNK_GATED_DELTA_RULE_FWD_INTRA_KERNEL(HD) \
chunk_gated_delta_rule_fwd_intra_kernel_impl<scalar_t, HD, CHUNK_SIZE>( \
w.data_ptr<scalar_t>(), \
u.data_ptr<scalar_t>(), \
decay_mask.data_ptr<float>(), \
k.data_ptr<scalar_t>(), \
v.data_ptr<scalar_t>(), \
g.data_ptr<float>(), \
beta.data_ptr<scalar_t>(), \
cu_seqlens.data_ptr<int32_t>(), \
chunk_indices.data_ptr<int32_t>(), \
H, \
Hv, \
NT, \
k.stride(1), \
k.stride(2), \
v.stride(1), \
v.stride(2));
template <int CHUNK_SIZE>
std::tuple<at::Tensor, at::Tensor, at::Tensor> chunk_gated_delta_rule_fwd_intra(
const at::Tensor& k,
const at::Tensor& v,
const at::Tensor& g,
const at::Tensor& beta,
const at::Tensor& cu_seqlens,
const at::Tensor& chunk_indices) {
int64_t B = k.size(0);
int64_t T = k.size(1);
int64_t H = k.size(2);
int64_t D = k.size(3);
int64_t Hv = v.size(2);
int64_t Dv = v.size(3);
int64_t NT = chunk_indices.size(0);
at::Tensor w = at::empty({B, T, Hv, D}, k.options()); // BFloat16
at::Tensor u = at::empty({B, T, Hv, Dv}, k.options()); // BFloat16
at::Tensor decay_mask = at::empty({B, NT, Hv, CHUNK_SIZE, CHUNK_SIZE}, g.options()); // Float
AT_DISPATCH_REDUCED_FLOATING_TYPES(k.scalar_type(), "chunk_gated_delta_rule_fwd_intra", [&] {
DISPATCH_HEAD_DIM(D, LAUNCH_CHUNK_GATED_DELTA_RULE_FWD_INTRA_KERNEL);
});
return std::make_tuple(w, u, decay_mask);
}
#define LAUNCH_CHUNK_GATED_DELTA_RULE_FWD_INTER_KERNEL(HD) \
chunk_gated_delta_rule_fwd_inter_kernel_impl<scalar_t, HD, CHUNK_SIZE>( \
o.data_ptr<scalar_t>(), \
initial_state.data_ptr<float>(), \
initial_state_indices.data_ptr<int32_t>(), \
q.data_ptr<scalar_t>(), \
k.data_ptr<scalar_t>(), \
w.data_ptr<scalar_t>(), \
u.data_ptr<scalar_t>(), \
g.data_ptr<float>(), \
decay_mask.data_ptr<float>(), \
cu_seqlens.data_ptr<int32_t>(), \
chunk_offsets.data_ptr<int32_t>(), \
H, \
Hv, \
num_seqs, \
q.stride(1), \
q.stride(2), \
k.stride(1), \
k.stride(2));
template <int CHUNK_SIZE>
std::tuple<at::Tensor, at::Tensor> chunk_gated_delta_rule_fwd_inter(
const at::Tensor& q,
const at::Tensor& k,
const at::Tensor& w,
const at::Tensor& u,
const at::Tensor& g,
const at::Tensor& decay_mask,
const at::Tensor& initial_state,
bool output_final_state,
const at::Tensor& cu_seqlens,
const at::Tensor& chunk_offsets,
const at::Tensor& initial_state_indices) {
const int64_t B = q.size(0);
const int64_t T = q.size(1);
const int64_t H = q.size(2);
const int64_t D = q.size(3);
const int64_t Hv = w.size(2);
const int64_t Dv = u.size(3);
const int64_t num_seqs = initial_state_indices.size(0);
at::Tensor o = at::empty({B, T, Hv, Dv}, q.options());
AT_DISPATCH_REDUCED_FLOATING_TYPES(q.scalar_type(), "chunk_gated_delta_rule_fwd_inter", [&] {
DISPATCH_HEAD_DIM(D, LAUNCH_CHUNK_GATED_DELTA_RULE_FWD_INTER_KERNEL);
});
return std::make_tuple(o, initial_state);
}
// [NB]: Support only varlen inputs
// B: packed batch dim of q/k/v (== 1)
// num_seqs: number of variable-length sequences
//
// query: [B, T, H, D]
// key: [B, T, H, D]
// value: [B, T, Hv, Dv]
// g: [B, T, Hv] FP32
// beta: [B, T, Hv]
// initial_state: [num_seqs, Hv, Dv, D] FP32
// cu_seqlens: [num_seqs + 1] INT32
//
std::tuple<at::Tensor, at::Tensor> chunk_gated_delta_rule_cpu(
const at::Tensor& query,
const at::Tensor& key,
const at::Tensor& value,
const at::Tensor& g,
const at::Tensor& beta,
const at::Tensor& initial_state,
bool output_final_state,
const at::Tensor& cu_seqlens,
bool head_first,
bool use_qk_l2norm_in_kernel,
const at::Tensor& initial_state_indices,
double eps = 1e-6) {
TORCH_CHECK(!head_first, "chunk_gated_delta_rule_cpu: does not support head first");
int64_t B = query.size(0);
int64_t T = query.size(1);
int64_t H = query.size(2);
int64_t D = query.size(3);
int64_t Hv = value.size(2);
int64_t Dv = value.size(3);
int64_t num_seqs = initial_state_indices.size(0);
TORCH_CHECK(B == 1, __func__, ": expect batch size to be 1");
TORCH_CHECK(Hv % H == 0, __func__, ": expect num_heads_kv multiple of num_heads.");
TORCH_CHECK(D % 32 == 0, __func__, ": expect head_dim to be multiples of 32.");
TORCH_CHECK(Dv % 32 == 0, __func__, ": expect head_dim_v to be multiples of 32.");
TORCH_CHECK(D == Dv, __func__, ": expect head_dim to be equal to head_dim_v.");
CHECK_INPUT_SHAPE_DTYPE<true>(query, {B, T, H, D}, at::kBFloat16);
CHECK_INPUT_SHAPE_DTYPE<true>(key, {B, T, H, D}, at::kBFloat16);
CHECK_INPUT_SHAPE_DTYPE<true>(value, {B, T, Hv, Dv}, at::kBFloat16);
CHECK_INPUT_SHAPE_DTYPE<false>(g, {B, T, Hv}, at::kFloat);
CHECK_INPUT_SHAPE_DTYPE<false>(beta, {B, T, Hv}, at::kBFloat16);
CHECK_INPUT_SHAPE_DTYPE<false>(cu_seqlens, {num_seqs + 1}, at::kInt);
CHECK_INPUT_SHAPE_DTYPE<false>(initial_state, {initial_state.size(0), Hv, Dv, D}, at::kFloat);
CHECK_INPUT_SHAPE_DTYPE<false>(initial_state_indices, {num_seqs}, at::kInt);
constexpr int CHUNK_SIZE = 64;
// prepare chunk indices
auto [chunk_indices, chunk_offsets] = prepare_chunk_indices<CHUNK_SIZE>(cu_seqlens);
float scale = 1.0 / std::sqrt(D);
auto [query_, key_] = use_qk_l2norm_in_kernel ? l2norm_fwd(query, key, eps) : std::make_tuple(query.mul(scale), key);
auto g_ = chunk_local_cumsum<CHUNK_SIZE>(g, cu_seqlens, chunk_indices);
// fused kkt + solve_tril + recompute_w_u
auto [w, u, decay_mask] =
chunk_gated_delta_rule_fwd_intra<CHUNK_SIZE>(key_, value, g_, beta, cu_seqlens, chunk_indices);
// fused `chunk_gated_delta_rule_fwd_h` + `chunk_fwd_o`
auto [output, final_state] = chunk_gated_delta_rule_fwd_inter<CHUNK_SIZE>(
query_,
key_,
w,
u,
g_,
decay_mask,
initial_state,
output_final_state,
cu_seqlens,
chunk_offsets,
initial_state_indices);
return std::make_tuple(output, final_state);
}
// A_log: [v_num_heads]
// dt_bias: [v_num_heads]
// query: [seq_len, batch_size, num_heads, head_dim]
// key: [seq_len, batch_size, num_heads, head_dim]
// value: [seq_len, batch_size, v_num_heads, v_head_dim]
// a: [batch_size, v_num_heads]
// b: [batch_size, v_num_heads]
// initial_state_source:[num_tokens, v_num_heads, head_dim, v_head_dim]
// initial_state_indices: [batch_size]
// cu_seqlens: [batch_size + 1]
at::Tensor fused_sigmoid_gating_delta_rule_update_cpu(
const at::Tensor& A_log,
const at::Tensor& dt_bias,
const at::Tensor& q,
const at::Tensor& k,
const at::Tensor& v,
const at::Tensor& a,
const at::Tensor& b,
at::Tensor& initial_state_source,
const at::Tensor& initial_state_indices,
const at::Tensor& cu_seqlens,
bool use_qk_l2norm_in_kernel,
double softplus_beta = 1.0,
double softplus_threshold = 20.0) {
CHECK_DIM(4, q);
CHECK_DIM(4, v);
CHECK_LAST_DIM_CONTIGUOUS_INPUT(q);
int64_t seq_len = q.size(0);
int64_t batch_size = q.size(1);
int64_t num_heads = q.size(2);
int64_t head_dim = q.size(3);
int64_t v_num_heads = v.size(2);
int64_t v_head_dim = v.size(3);
CHECK_INPUT_SHAPE_DTYPE<true>(k, {seq_len, batch_size, num_heads, head_dim}, q.scalar_type());
CHECK_INPUT_SHAPE_DTYPE<true>(v, {seq_len, batch_size, v_num_heads, v_head_dim}, q.scalar_type());
CHECK_INPUT_SHAPE_DTYPE<true>(a, {batch_size, v_num_heads}, q.scalar_type());
CHECK_INPUT_SHAPE_DTYPE<true>(dt_bias, {v_num_heads}, q.scalar_type());
CHECK_INPUT_SHAPE_DTYPE<true>(b, {batch_size, v_num_heads}, q.scalar_type());
CHECK_INPUT_SHAPE_DTYPE<true>(initial_state_indices, {batch_size}, at::kInt);
CHECK_INPUT_SHAPE_DTYPE<true>(cu_seqlens, {batch_size + 1}, at::kInt);
CHECK_INPUT_SHAPE_DTYPE<true>(
initial_state_source, {initial_state_source.size(0), v_num_heads, head_dim, v_head_dim}, at::kFloat);
CHECK(initial_state_source.size(0) >= batch_size);
CHECK_EQ(v_num_heads % num_heads, 0);
TORCH_CHECK(
A_log.sizes() == at::IntArrayRef({v_num_heads}),
"Input tensor shape mismatch: expected ",
at::IntArrayRef({v_num_heads}),
", got ",
A_log.sizes());
int64_t q_strideB = q.stride(1);
int64_t q_strideS = q.stride(0);
int64_t q_strideH = q.stride(2);
int64_t k_strideB = k.stride(1);
int64_t k_strideS = k.stride(0);
int64_t k_strideH = k.stride(2);
int64_t v_strideB = v.stride(1);
int64_t v_strideS = v.stride(0);
int64_t v_strideH = v.stride(2);
at::Tensor core_attn_out = at::empty({batch_size, seq_len, v_num_heads, v_head_dim}, q.options());
at::Tensor qk_scale_buf = at::empty({2 * batch_size, seq_len, num_heads}, at::kFloat);
CPU_DISPATCH_REDUCED_FLOATING_TYPES_EXT(
q.scalar_type(), A_log.scalar_type(), "fused_sigmoid_gating_delta_rule_update_kernel_impl", [&] {
fused_sigmoid_gating_delta_rule_update_kernel_impl<scalar_t, param_t>(
q.data_ptr<scalar_t>(),
k.data_ptr<scalar_t>(),
v.data_ptr<scalar_t>(),
A_log.data_ptr<param_t>(),
a.data_ptr<scalar_t>(),
dt_bias.data_ptr<scalar_t>(),
b.data_ptr<scalar_t>(),
initial_state_indices.data_ptr<int32_t>(),
initial_state_source.data_ptr<float>(),
core_attn_out.data_ptr<scalar_t>(),
qk_scale_buf.data_ptr<float>(),
seq_len,
batch_size,
num_heads,
head_dim,
v_num_heads,
v_head_dim,
q_strideB,
q_strideS,
q_strideH,
k_strideB,
k_strideS,
k_strideH,
v_strideB,
v_strideS,
v_strideH,
use_qk_l2norm_in_kernel,
softplus_threshold);
});
return core_attn_out;
}
// A_log: [num_v_heads]
// a: [batch, num_v_heads]
// b: [batch, num_v_heads]
// dt_bias: [num_v_heads]
// -A_log.float().exp() * F.softplus(a.float() + dt_bias)
std::tuple<at::Tensor, at::Tensor>
fused_gdn_gating_cpu(const at::Tensor& A_log, const at::Tensor& a, const at::Tensor& b, const at::Tensor& dt_bias) {
CHECK_DIM(1, A_log);
CHECK_DIM(2, a);
CHECK_DIM(2, b);
CHECK_DIM(1, dt_bias);
CHECK_CONTIGUOUS(a);
CHECK_EQ(A_log.size(0), a.size(1));
CHECK_EQ(A_log.size(0), dt_bias.size(0));
int batch = a.size(0);
int num_heads = a.size(1);
CHECK_EQ(b.size(0), batch);
CHECK_EQ(b.size(1), num_heads);
at::Tensor out = at::empty({1, batch, num_heads}, a.options().dtype(at::kFloat));
at::Tensor beta = at::empty({1, batch, num_heads}, b.options());
CPU_DISPATCH_REDUCED_FLOATING_TYPES_EXT(a.scalar_type(), A_log.scalar_type(), "fused_gdn_gating_kernel", [&] {
fused_gdn_gating_kernel_impl<scalar_t>(
A_log.data_ptr<param_t>(),
a.data_ptr<scalar_t>(),
b.data_ptr<scalar_t>(),
dt_bias.data_ptr<scalar_t>(),
out.data_ptr<float>(),
beta.data_ptr<scalar_t>(),
batch,
num_heads);
});
return std::make_tuple(out, beta);
}