mirror of
https://github.com/Niko1221/Strata.git
synced 2026-11-13 16:51:30 -10:00
Fusions (all bit-exact vs the pipelines they replace; bench/micro/fusions_parity.cu checks every one bitwise and times the before/after at decode shapes): - gate/up dual GEMV + SwiGLU in ONE kernel (llama.cpp's mul_mat_gated): s_gemv_pair_silu, one read of the activation for both projections and the SwiGLU in the epilogue - the up intermediate never lands in memory. Rows are accumulated by the shared row functions (s_rowdev.cuh) exactly as the standalone kernels accumulate them. shared_expert uses it whenever both projections are S-family (bitwise checked for all three silu kinds). - silu(gate)*up + the intermediate quantizer fused (was 2 kernels): swilu_quantize_q8_0/_scaled/_q8_K, warp-parallel swilu then the verbatim quantizer body. Wired into the S2 expert entry points and shared_expert. - every activation image of x in one pass (was 2-3 kernels): quantize_act_images produces block_q8_0 + block_q8_K + bf16 from one read of x. Wired into gdn_layer, qsa_layer and moe_shared. - multi-weight bf16 GEMV (llama.cpp's mul_mat_multi): bf16_gemv_pair computes two matrices from one read of x; used for ssm_alpha/ssm_beta and for the indexer's q/k projections (q and k now one launch). The call sites are gated on STRATA_EXPERIMENTAL_SM60 (the Pascal/Volta build), so the ready-made engine runs exactly the pipelines it did before; the fused kernels are the same code either way. Measured (V100, CUDA events, n_embd 2560 / FF 640, 200 reps): expert intermediate (swilu+q8_0) 15.7 -> 11.6 us M=1, 18.8 -> 11.4 M=8 activation images (q8_0+q8_K+bf16) 46.2 -> 30.4 us dual GEMV + swilu (gate,up) 30.4 -> 14.8 us bf16 pair (alpha,beta) 24.2 -> 14.4 us M=1, 23.7 -> 14.1 M=8 Parity: fusions_parity 0 failures (bitwise), plus s_gemv/s_gemv_q8k/s2_gemv_q8/ s2_expert_grouped/shared_expert/quantize_act/bf16_gemv/elementwise/mmvq_multi/ iq_multi/native_expert parity suites all pass unchanged.
114 lines
4.6 KiB
Plaintext
114 lines
4.6 KiB
Plaintext
// include/strata/kernels/quantize_act_dev.cuh - the per-block activation quantizers, ONE copy each.
|
|
//
|
|
// The fusion work (V100) folds `silu(gate) * up` and the activation quantizer that follows it into one
|
|
// kernel. For that to be bit-exact the fused kernel must quantize EXACTLY what `quantize_act.cu`'s
|
|
// kernels quantize - same amax order, same double `rint`, same clamps. A second copy of the math is a
|
|
// second thing to drift, so `quantize_act.cu` and the fused kernels both include this file and call the
|
|
// same functions. The standalone kernels are thin wrappers around these; there is no other difference.
|
|
#pragma once
|
|
|
|
#include "strata/kernels/f16_bits.hpp"
|
|
|
|
#include <cuda_runtime.h>
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
|
|
namespace strata::kernels {
|
|
|
|
constexpr int Q8_0_BLOCK = 32;
|
|
constexpr int Q8_0_BLOCK_BYTES = 34;
|
|
constexpr int Q8_K_BLOCK = 256;
|
|
constexpr int Q8_K_BLOCK_BYTES = 292;
|
|
|
|
/// ggml's `nearest_int` (ggml-quants.c L621). The magic constant is 1.5 * 2^23; it is transcribed rather
|
|
/// than rewritten as `rintf` because the two differ on exact ties - which is the whole point of it.
|
|
__device__ __forceinline__ int nearest_int_dev(float fval) {
|
|
const float val = fval + 12582912.0f;
|
|
int i;
|
|
memcpy(&i, &val, 4);
|
|
return (i & 0x007fffff) - 0x00400000;
|
|
}
|
|
|
|
/// `block_q8_0` for one 32-element block, ggml's bytes: `d32 = amax/127` in fp32, `d` stored fp16, the
|
|
/// QUANTIZED INTEGERS divided by `d32` (not by the stored fp16), and `rint` in DOUBLE. See the three
|
|
/// subtleties in `quantize_act.cu`'s header comment - every one of them is load-bearing.
|
|
__device__ __forceinline__ void quantize_q8_0_block(const float* __restrict__ xb, uint8_t* __restrict__ out) {
|
|
float amax = 0.0f;
|
|
for (int i = 0; i < Q8_0_BLOCK; ++i) amax = fmaxf(amax, fabsf(xb[i]));
|
|
if (amax == 0.0f) {
|
|
const uint16_t zb = f16_from_f32(0.0f);
|
|
out[0] = (uint8_t) (zb & 0xFF);
|
|
out[1] = (uint8_t) (zb >> 8);
|
|
for (int i = 0; i < Q8_0_BLOCK; ++i) out[2 + i] = 0;
|
|
return;
|
|
}
|
|
const float d32 = amax / 127.0f;
|
|
const uint16_t d16bits = f16_from_f32(d32);
|
|
out[0] = (uint8_t) (d16bits & 0xFF);
|
|
out[1] = (uint8_t) (d16bits >> 8);
|
|
for (int i = 0; i < Q8_0_BLOCK; ++i) {
|
|
double q = rint((double) xb[i] / (double) d32);
|
|
if (q > 127.0) q = 127.0;
|
|
if (q < -128.0) q = -128.0;
|
|
out[2 + i] = (uint8_t) (int8_t) q;
|
|
}
|
|
}
|
|
|
|
/// `block_q8_0` bytes PLUS the fp32 scale and the CPU's rounding rule (round half away from zero) - the
|
|
/// `quantize_q8_0_scaled_kernel` body, verbatim, so a hit and a CPU miss agree.
|
|
__device__ __forceinline__ void quantize_q8_0_scaled_block(const float* __restrict__ xb,
|
|
uint8_t* __restrict__ out, float* __restrict__ scale_out) {
|
|
float amax = 0.0f;
|
|
for (int i = 0; i < Q8_0_BLOCK; ++i) amax = fmaxf(amax, fabsf(xb[i]));
|
|
const float s = amax > 0.f ? amax / 127.f : 0.f;
|
|
const float inv = s > 0.f ? 1.f / s : 0.f;
|
|
*scale_out = s;
|
|
const uint16_t d16bits = f16_from_f32(s);
|
|
out[0] = (uint8_t) (d16bits & 0xFF);
|
|
out[1] = (uint8_t) (d16bits >> 8);
|
|
for (int i = 0; i < Q8_0_BLOCK; ++i) {
|
|
const float t = xb[i] * inv;
|
|
const float r = t + (t >= 0.f ? 0.5f : -0.5f);
|
|
int v = (int) r;
|
|
v = v < -127 ? -127 : (v > 127 ? 127 : v);
|
|
out[2 + i] = (uint8_t) (int8_t) v;
|
|
}
|
|
}
|
|
|
|
/// `block_q8_K` for one 256-element block - the `quantize_q8_K_kernel` body, verbatim (including the
|
|
/// zero-block divergence note: bsums are zeroed where ggml leaves them unwritten).
|
|
__device__ __forceinline__ void quantize_q8_K_block(const float* __restrict__ xb, uint8_t* __restrict__ out) {
|
|
float* d = (float*) out;
|
|
int8_t* qs = (int8_t*) (out + 4);
|
|
int16_t* bsums = (int16_t*) (out + 4 + Q8_K_BLOCK);
|
|
float max = 0.0f, amax = 0.0f;
|
|
for (int j = 0; j < Q8_K_BLOCK; ++j) {
|
|
const float ax = fabsf(xb[j]);
|
|
if (ax > amax) {
|
|
amax = ax;
|
|
max = xb[j];
|
|
}
|
|
}
|
|
if (amax == 0.0f) {
|
|
*d = 0.0f;
|
|
for (int j = 0; j < Q8_K_BLOCK; ++j) qs[j] = 0;
|
|
for (int j = 0; j < Q8_K_BLOCK / 16; ++j) bsums[j] = 0;
|
|
return;
|
|
}
|
|
const float iscale = -127.0f / max;
|
|
for (int j = 0; j < Q8_K_BLOCK; ++j) {
|
|
const int v = nearest_int_dev(__fmul_rn(iscale, xb[j]));
|
|
qs[j] = (int8_t) min(127, v);
|
|
}
|
|
for (int j = 0; j < Q8_K_BLOCK / 16; ++j) {
|
|
int sum = 0;
|
|
for (int ii = 0; ii < 16; ++ii) sum += qs[j * 16 + ii];
|
|
bsums[j] = (int16_t) sum;
|
|
}
|
|
*d = 1.0f / iscale;
|
|
}
|
|
|
|
} // namespace strata::kernels
|