mirror of
https://github.com/JustVugg/colibri.git
synced 2026-10-02 02:54:37 +08:00
Merge pull request #1716 from jtinbergen/sse41-tier-upstream-next-v2
perf(qwen36): int8-during-load dense weights, SIMD f16/bf16 conversion, wire slot_ensure_int8 onto #1271's unpack_int4_to_int8
This commit is contained in:
+30
-2
@@ -508,7 +508,7 @@ TEST_RULES := $(shell sed -n 's|^tests/\(test_[a-z0-9_]*\)\$$(EXE):.*|\1|p' $(f
|
||||
TEST_EXCLUDE = test_uring test_deepseek_v4 test_v4_ownership test_v4_serve_framing \
|
||||
test_segment_adapters_registration test_segment_adapters_real \
|
||||
test_edge_adapters_registration test_edge_adapters_real \
|
||||
test_gsgemv_sse41 test_qgemv_sse41 test_olmoe_dot_i8_16_sse41 test_qwen38_tier_engine \
|
||||
test_gsgemv_sse41 test_qgemv_sse41 test_olmoe_dot_i8_16_sse41 test_st_f16_bf16_simd_sse41 test_qwen38_tier_engine \
|
||||
test_e8x4g64_loader \
|
||||
test_i4_grouped_sse41 test_i4_grouped_sse41_o1 test_i4_grouped_sse41_no_contract
|
||||
TEST_BINS = $(addprefix tests/,$(addsuffix $(EXE),$(filter-out $(TEST_EXCLUDE),$(TEST_RULES))))
|
||||
@@ -518,7 +518,8 @@ TEST_BINS += tests/test_exact_dot$(EXE)
|
||||
endif
|
||||
ifneq (,$(X86_64))
|
||||
TEST_BINS += tests/test_gsgemv_sse41$(EXE) tests/test_qgemv_sse41$(EXE) \
|
||||
tests/test_olmoe_dot_i8_16_sse41$(EXE) tests/test_i4_grouped_sse41$(EXE) \
|
||||
tests/test_olmoe_dot_i8_16_sse41$(EXE) tests/test_st_f16_bf16_simd_sse41$(EXE) \
|
||||
tests/test_i4_grouped_sse41$(EXE) \
|
||||
tests/test_i4_grouped_sse41_o1$(EXE) tests/test_i4_grouped_sse41_no_contract$(EXE)
|
||||
endif
|
||||
ifeq ($(COLI_V4_SUPPORTED),1)
|
||||
@@ -1240,6 +1241,11 @@ tests/test_qwen36_tok_truncated$(EXE): tests/test_qwen36_tok_truncated.c gsgemv.
|
||||
tests/test_qwen36_encode_oom$(EXE): tests/test_qwen36_encode_oom.c gsgemv.h qgemv.h sse41_kernels.h qwen36.c expert_ffn.h qwen36_tier.h st.h json.h compat.h $(QWEN36_TIER_SRC) $(CUDA_OBJ)
|
||||
$(CC) $(QWEN36_CFLAGS) $< $(QWEN36_TIER_SRC) $(CUDA_OBJ) -o $@ $(QWEN36_LDFLAGS)
|
||||
|
||||
# slot_ensure_int8's wiring onto #1271's unpack_int4_to_int8: regression check
|
||||
# that its output matches the old separate scalar nibble-unpack loop it replaced.
|
||||
tests/test_qwen36_slot_int8$(EXE): tests/test_qwen36_slot_int8.c qwen36.c qwen36_tier.h st.h json.h compat.h $(QWEN36_TIER_SRC) $(CUDA_OBJ)
|
||||
$(CC) $(QWEN36_CFLAGS) $< $(QWEN36_TIER_SRC) $(CUDA_OBJ) -o $@ $(QWEN36_LDFLAGS)
|
||||
|
||||
# the dense trunk's integer path: quantizer contract, int4 planar packing, dispatch.
|
||||
tests/test_qwen36_dense_idot$(EXE): tests/test_qwen36_dense_idot.c gsgemv.h qgemv.h sse41_kernels.h qwen36.c expert_ffn.h qwen36_tier.h st.h json.h compat.h $(QWEN36_TIER_SRC) $(CUDA_OBJ)
|
||||
$(CC) $(QWEN36_CFLAGS) $< $(QWEN36_TIER_SRC) $(CUDA_OBJ) -o $@ $(QWEN36_LDFLAGS)
|
||||
@@ -1381,6 +1387,28 @@ tests/test_dup_name_refusal$(EXE): tests/test_dup_name_refusal.c st.h json.h com
|
||||
tests/test_st_shape$(EXE): tests/test_st_shape.c st.h json.h compat.h
|
||||
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
|
||||
|
||||
# Exhaustive bit-exact gate for st.h's bf16_to_f32_bulk/f16_to_f32_bulk AVX2 tier
|
||||
# (all 65536 patterns per format -- see the file for why no tolerance applies).
|
||||
tests/test_st_f16_bf16_simd$(EXE): tests/test_st_f16_bf16_simd.c st.h json.h compat.h
|
||||
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
|
||||
|
||||
# Same gate, forced onto the SSE4.1 tier: overrides the host's default -march
|
||||
# so the SSE4.1 body in st.h is actually exercised even on a devbox that
|
||||
# would otherwise always pick the AVX2 tier.
|
||||
tests/test_st_f16_bf16_simd_sse41$(EXE): tests/test_st_f16_bf16_simd.c st.h json.h compat.h
|
||||
$(CC) $(CFLAGS) -msse4.1 -mno-avx2 -mno-fma $< -o $@ $(LDFLAGS)
|
||||
|
||||
# bf16_to_f32_bulk/f16_to_f32_bulk vs the scalar per-element reference, NOT a
|
||||
# test gate. Build on demand: make tests/bench_st_f16_bf16_simd ARCH=native
|
||||
tests/bench_st_f16_bf16_simd$(EXE): tests/bench_st_f16_bf16_simd.c st.h json.h compat.h
|
||||
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
|
||||
|
||||
# Synthetic peak-RSS proxy for qwen36.c's dense-int8-during-load change
|
||||
# (load_tq vs the old post-hoc qdw_register pass), NOT a test gate. Build on
|
||||
# demand: make tests/bench_load_tq_peak_rss
|
||||
tests/bench_load_tq_peak_rss$(EXE): tests/bench_load_tq_peak_rss.c
|
||||
$(CC) -O3 -march=native $< -o $@ -lm
|
||||
|
||||
tests/test_st$(EXE): tests/test_st.c st.h json.h compat.h
|
||||
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
|
||||
|
||||
|
||||
+202
-188
@@ -691,16 +691,35 @@ typedef struct {
|
||||
int expert_down_bits, expert_down_gs;
|
||||
} Cfg;
|
||||
|
||||
/* ---- Dense int8: a dense matrix that is quantized to int8 during load
|
||||
* (load_tq, below matmul_d) instead of loaded as f32 and quantized in a
|
||||
* separate pass afterward -- so the f32 staging buffer for THIS matrix alone
|
||||
* is what's briefly resident, not every dense matrix in the model at once.
|
||||
* `w` is the f32 copy: kept (and `q`/`sc` left NULL) when COLI_DENSE_I8=0,
|
||||
* the reference/parity path; freed once `q`/`sc` are populated otherwise
|
||||
* (COLI_KEEP_F32=1 keeps it alongside them, for debugging). matmul_d
|
||||
* dispatches on q!=NULL directly -- no pointer-keyed scan. */
|
||||
/* q/sc: int8 rows with one scale per row (the classic copy, what the VRAM
|
||||
* tier uploads). q4/sg: the same matrix as int4 planar blocks of 64 with one
|
||||
* scale per group (COLI_DENSE_BITS=4), the layout the K1b grouped kernel
|
||||
* reads; ng = I/64 groups per row. */
|
||||
typedef struct { const float *w; int8_t *q; float *sc; int I, O; uint8_t *q4; float *sg; int ng; } QW;
|
||||
static void qw_free(QW *w) {
|
||||
free((void*)w->w); free(w->q); free(w->sc); free(w->q4); free(w->sg);
|
||||
w->w = NULL; w->q = NULL; w->sc = NULL; w->q4 = NULL; w->sg = NULL; w->ng = 0;
|
||||
}
|
||||
|
||||
/* ---------- per-layer dense weights ---------- */
|
||||
typedef struct {
|
||||
float *in_ln, *post_ln, *q, *k, *v, *o, *qn, *kn, *gate, *gate_bias;
|
||||
float *sh_g, *sh_u, *sh_d, *sh_gate; /* shared expert (dense f32) + shared_expert_gate */
|
||||
float *in_ln, *post_ln, *qn, *kn, *gate_bias;
|
||||
QW q, k, v, o, gate;
|
||||
QW sh_g, sh_u, sh_d; float *sh_gate; /* shared expert (dense, int8-during-load) + shared_expert_gate */
|
||||
/* Gated DeltaNet (linear_attention) dense weights (f16->f32 via st_read_f32). */
|
||||
float *dn_qkv, *dn_z, *dn_b, *dn_a; /* in_proj_qkv/z/b/a */
|
||||
QW dn_qkv, dn_z; float *dn_b, *dn_a; /* in_proj_qkv/z (int8-during-load), b/a (not dense-matmul'd) */
|
||||
float *dn_conv; /* conv1d.weight [conv_dim, convk] (groups=conv_dim) */
|
||||
float *dn_dtbias, *dn_alog; /* dt_bias[vh], A_log[vh] */
|
||||
float *dn_norm; /* RMSNormGated weight [vdim] */
|
||||
float *dn_out; /* out_proj [hidden, value_dim] */
|
||||
QW dn_out; /* out_proj [hidden, value_dim] */
|
||||
/* VRAM copies the tier placed (qt_dense handle + 1, 0 = stays on the CPU):
|
||||
* the DeltaNet out_proj, the attention q/k/v/o and the shared expert's
|
||||
* three matrices. Offered per layer as "dnout", "attnproj", "shexp";
|
||||
@@ -731,7 +750,8 @@ typedef struct {
|
||||
Cfg c;
|
||||
shards S;
|
||||
int quant_bits;
|
||||
float *embed, *lm_head, *final_norm;
|
||||
float *embed, *final_norm;
|
||||
QW lm_head;
|
||||
Layer *L;
|
||||
LCache *cache; /* [n_layers] */
|
||||
int *active_of; /* [n_layers] original->active idx (Phase 2: identity for all layers) */
|
||||
@@ -1079,15 +1099,9 @@ static void matmul_qd(float *y, const float *x, const int8_t *q, const float *sc
|
||||
}
|
||||
|
||||
/* ---- Dense int8: per-row quantized copies of the large f32 matrices.
|
||||
* matmul_d dispatches via pointer lookup to matmul_q; COLI_DENSE_I8=0 falls
|
||||
* back to f32 (reference path for parity tests). ~4x less memory traffic. */
|
||||
#define QDW_MAX 1024
|
||||
/* q/sc: int8 rows with one scale per row (the classic copy, what the VRAM
|
||||
* tier uploads). q4/sg: the same matrix as int4 planar blocks of 64 with one
|
||||
* scale per group (COLI_DENSE_BITS=4), the layout the K1b grouped kernel
|
||||
* reads; ng = I/64 groups per row. */
|
||||
static struct { const float *w; int8_t *q; float *sc; int I, O; uint8_t *q4; float *sg; int ng; } g_qdw[QDW_MAX];
|
||||
static int g_qdw_n = 0;
|
||||
* matmul_d dispatches directly off QW.q (no pointer-keyed scan -- see QW,
|
||||
* above Layer); COLI_DENSE_I8=0 falls back to f32 (QW.w, reference path for
|
||||
* parity tests). ~4x less memory traffic. */
|
||||
/* ---- Dense trunk, integer dot products ------------------------------------
|
||||
*
|
||||
* Every dense GEMV of a token (DeltaNet projections and out_proj, attention
|
||||
@@ -1187,10 +1201,16 @@ static int dense_int4_wanted(const char *tag){
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static void qdw_register_as(const float *W, int I, int O, const char *tag){
|
||||
if (!W || !dense_i8_on() || g_qdw_n >= QDW_MAX) return;
|
||||
/* Per-row max-abs / round-clamp int8 quantization of an in-memory f32 matrix
|
||||
* W [O][I] row-major -- same math load_tq runs during streamed load, factored
|
||||
* out so it can also run on a caller-owned buffer directly (tests that
|
||||
* synthesize weights in memory, without a shard file to load from). Does not
|
||||
* touch out->w -- the caller sets that (or leaves it, e.g. load_tq frees it
|
||||
* right after). `tag` selects the int4 planar copy per dense_int4_wanted
|
||||
* (NULL/COLI_DENSE_BITS!=4: skipped, out->q4 stays NULL). */
|
||||
static void qw_quantize(const float *W, int I, int O, const char *tag, QW *out) {
|
||||
int8_t *q = malloc((size_t)O*I); float *sc = malloc((size_t)O*sizeof(float));
|
||||
if (!q || !sc) { free(q); free(sc); return; }
|
||||
if (!q || !sc) { fprintf(stderr, "OOM qw_quantize\n"); exit(1); }
|
||||
#pragma omp parallel for schedule(static)
|
||||
for (int o = 0; o < O; o++) {
|
||||
const float *r = W + (int64_t)o*I; float am = 0.f;
|
||||
@@ -1199,47 +1219,45 @@ static void qdw_register_as(const float *W, int I, int O, const char *tag){
|
||||
int8_t *d = q + (int64_t)o*I;
|
||||
for (int i = 0; i < I; i++) { int v = (int)lrintf(r[i]*inv); if (v>127) v=127; if (v<-127) v=-127; d[i] = (int8_t)v; }
|
||||
}
|
||||
g_qdw[g_qdw_n].w=W; g_qdw[g_qdw_n].q=q; g_qdw[g_qdw_n].sc=sc; g_qdw[g_qdw_n].I=I; g_qdw[g_qdw_n].O=O;
|
||||
g_qdw[g_qdw_n].q4=NULL; g_qdw[g_qdw_n].sg=NULL; g_qdw[g_qdw_n].ng=0;
|
||||
if (dense_int4_wanted(tag) && I%64==0) {
|
||||
uint8_t *q4=malloc((size_t)O*(I/2)); float *sg=malloc((size_t)O*(I/64)*sizeof(float));
|
||||
out->q = q; out->sc = sc; out->I = I; out->O = O;
|
||||
out->q4 = NULL; out->sg = NULL; out->ng = 0;
|
||||
if (dense_int4_wanted(tag) && I % 64 == 0) {
|
||||
uint8_t *q4 = malloc((size_t)O*(I/2)); float *sg = malloc((size_t)O*(I/64)*sizeof(float));
|
||||
if (q4 && sg) {
|
||||
pack_int4_g64_planar(W, q4, sg, O, I);
|
||||
g_qdw[g_qdw_n].q4=q4; g_qdw[g_qdw_n].sg=sg; g_qdw[g_qdw_n].ng=I/64;
|
||||
out->q4 = q4; out->sg = sg; out->ng = I/64;
|
||||
} else { free(q4); free(sg); }
|
||||
}
|
||||
g_qdw_n++;
|
||||
}
|
||||
static void qdw_register(const float *W, int I, int O){ qdw_register_as(W, I, O, NULL); }
|
||||
static void matmul_d(float *y, const float *x, const float *W, int S, int I, int O){
|
||||
static void matmul_d(float *y, const float *x, const QW *w, int S, int I, int O){
|
||||
#ifdef COLI_QWEN_BATCH_TEST
|
||||
g_qwen_matmul_d_calls++;
|
||||
#endif
|
||||
for (int i = 0; i < g_qdw_n; i++) if (g_qdw[i].w == W && g_qdw[i].I == I) {
|
||||
if (g_qdw[i].q4 || dense_idot_on()) {
|
||||
if (w->q) {
|
||||
if (w->q4 || dense_idot_on()) {
|
||||
/* integer dot: the activation rows to int8 once, then the K1b
|
||||
* grouped kernel (int4 planar) or the per-row int8 kernel */
|
||||
int ng = I / 64;
|
||||
int8_t *xq = malloc((size_t)S * I);
|
||||
float *sx = malloc((size_t)S * sizeof(float));
|
||||
int32_t *xsg = g_qdw[i].q4 ? malloc((size_t)S * ng * sizeof(int32_t)) : NULL;
|
||||
if (xq && sx && (!g_qdw[i].q4 || xsg)) {
|
||||
int32_t *xsg = w->q4 ? malloc((size_t)S * ng * sizeof(int32_t)) : NULL;
|
||||
if (xq && sx && (!w->q4 || xsg)) {
|
||||
for (int s = 0; s < S; s++)
|
||||
sx[s] = dense_act_i8(x + (int64_t)s * I, I, xq + (int64_t)s * I, xsg ? xsg + (int64_t)s * ng : NULL);
|
||||
if (g_qdw[i].q4) matmul_i4p_grouped_idot(y, xq, sx, xsg, g_qdw[i].q4, g_qdw[i].sg, S, I, O, 64);
|
||||
else matmul_q_idot(y, xq, sx, g_qdw[i].q, g_qdw[i].sc, S, I, O);
|
||||
if (w->q4) matmul_i4p_grouped_idot(y, xq, sx, xsg, w->q4, w->sg, S, I, O, 64);
|
||||
else matmul_q_idot(y, xq, sx, w->q, w->sc, S, I, O);
|
||||
free(xq); free(sx); free(xsg);
|
||||
return;
|
||||
}
|
||||
free(xq); free(sx); free(xsg); /* out of memory: the f32 path below */
|
||||
}
|
||||
if (S > 1 && dense_batch_on())
|
||||
matmul_q_batch(y, x, g_qdw[i].q, g_qdw[i].sc, S, I, O);
|
||||
matmul_q_batch(y, x, w->q, w->sc, S, I, O);
|
||||
else
|
||||
for (int s = 0; s < S; s++) matmul_q(y+(int64_t)s*O, x+(int64_t)s*I, g_qdw[i].q, g_qdw[i].sc, I, O);
|
||||
for (int s = 0; s < S; s++) matmul_q(y+(int64_t)s*O, x+(int64_t)s*I, w->q, w->sc, I, O);
|
||||
return;
|
||||
}
|
||||
matmul(y, x, W, S, I, O);
|
||||
matmul(y, x, w->w, S, I, O);
|
||||
}
|
||||
/* A dense matrix the tier placed in VRAM (handle+1 kept in the Layer, 0 = CPU):
|
||||
* a device matmul, or 0 and the caller runs matmul_d as before. The
|
||||
@@ -1250,22 +1268,16 @@ static inline int qtd_batch(int hp1, float *y, const float *x, int S, int I, int
|
||||
static inline int qtd(int hp1, float *y, const float *x, int I, int O){
|
||||
return hp1 > 0 && qt_dense_matmul(hp1 - 1, y, x, I, O);
|
||||
}
|
||||
/* Bytes of W's dense-i8 copy (int8 rows + per-row scales), 0 when there is
|
||||
/* Bytes of w's dense-i8 copy (int8 rows + per-row scales), 0 when there is
|
||||
* none (COLI_DENSE_I8=0): nothing to offer, the CPU path stands. */
|
||||
static size_t qdw_bytes(const float *W){
|
||||
for (int i = 0; i < g_qdw_n; i++)
|
||||
if (g_qdw[i].w == W) return (size_t)g_qdw[i].I * g_qdw[i].O + (size_t)g_qdw[i].O * sizeof(float);
|
||||
return 0;
|
||||
static size_t qdw_bytes(const QW *w){
|
||||
return w->q ? (size_t)w->I * w->O + (size_t)w->O * sizeof(float) : 0;
|
||||
}
|
||||
/* Upload W's dense-i8 copy to `dev`; handle+1, or 0 when it stays on the CPU. */
|
||||
static int qdw_place(const float *W, int dev){
|
||||
if (dev == QT_PLACE_CPU || !W) return 0;
|
||||
for (int i = 0; i < g_qdw_n; i++)
|
||||
if (g_qdw[i].w == W) {
|
||||
int h = qt_dense_init(g_qdw[i].q, g_qdw[i].sc, g_qdw[i].I, g_qdw[i].O, dev);
|
||||
return h >= 0 ? h + 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
/* Upload w's dense-i8 copy to `dev`; handle+1, or 0 when it stays on the CPU. */
|
||||
static int qdw_place(const QW *w, int dev){
|
||||
if (dev == QT_PLACE_CPU || !w->q) return 0;
|
||||
int h = qt_dense_init(w->q, w->sc, w->I, w->O, dev);
|
||||
return h >= 0 ? h + 1 : 0;
|
||||
}
|
||||
|
||||
/* rmsnorm over a row of length D (in-place capable: out may == x).
|
||||
@@ -1484,6 +1496,27 @@ static float *load_t_n(Model *m, const char *name, int64_t want) {
|
||||
return p;
|
||||
}
|
||||
|
||||
/* Dense matrix load, quantized to int8 (+ int4 planar per `tag`, see
|
||||
* dense_int4_wanted) DURING loading rather than in a separate pass over the
|
||||
* whole model afterward (see QW, above Layer): reads `name` (I*O elements,
|
||||
* same size discipline as load_t_n), and when `quantize` && COLI_DENSE_I8 is
|
||||
* on, quantizes it via qw_quantize and frees the f32 staging buffer right
|
||||
* away -- so at most one dense matrix's f32 copy is ever resident at a time,
|
||||
* not the whole model's. `quantize` is false for loaders that never ran
|
||||
* through the old post-hoc qdw_register pass either (the Segment/Edge
|
||||
* adapters build partial or auxiliary models straight off
|
||||
* model_init_range/load_t_n, never main()'s dense-i8 block) -- passing it
|
||||
* through keeps their f32-only behavior exactly as it was; `tag` is unused
|
||||
* on that path. */
|
||||
static void load_tq(Model *m, const char *name, int I, int O, int quantize, const char *tag, QW *out) {
|
||||
float *p = load_t_n(m, name, (int64_t)I * O);
|
||||
out->w = p; out->q = NULL; out->sc = NULL; out->I = I; out->O = O;
|
||||
out->q4 = NULL; out->sg = NULL; out->ng = 0;
|
||||
if (!quantize || !dense_i8_on()) return;
|
||||
qw_quantize(p, I, O, tag, out);
|
||||
if (getenv("COLI_KEEP_F32")) out->w = p; else { free(p); out->w = NULL; }
|
||||
}
|
||||
|
||||
static void model_init_range(Model *m, const char *snap, int cap, int bits,
|
||||
int layer_begin, int layer_end,
|
||||
int load_boundaries, int allocate_state) {
|
||||
@@ -1509,9 +1542,17 @@ static void model_init_range(Model *m, const char *snap, int cap, int bits,
|
||||
exit(1);
|
||||
}
|
||||
double t0 = now_s();
|
||||
/* Quantize during load only for the full-model path (main()'s static Model,
|
||||
* load_boundaries=1): the Segment/Edge adapters build partial or auxiliary
|
||||
* models straight off this same loop and never ran the old post-hoc
|
||||
* qdw_register pass either, so gating on load_boundaries keeps their
|
||||
* f32-only numerics exactly as they were. */
|
||||
int quantize_dense = load_boundaries && dense_i8_on();
|
||||
int qcount = 0; double qfreed = 0;
|
||||
if (load_boundaries) {
|
||||
m->embed = load_t_n(m, "model.embed_tokens.weight", (int64_t)c->vocab * c->hidden);
|
||||
m->lm_head = load_t_n(m, "lm_head.weight", (int64_t)c->vocab * c->hidden);
|
||||
m->embed = load_t_n(m, "model.embed_tokens.weight", (int64_t)c->vocab * c->hidden);
|
||||
load_tq(m, "lm_head.weight", c->hidden, c->vocab, quantize_dense, "lmhead", &m->lm_head);
|
||||
if (m->lm_head.q) { qcount++; qfreed += (double)c->hidden * c->vocab * sizeof(float); }
|
||||
m->final_norm = load_t_n(m, "model.norm.weight", c->hidden);
|
||||
}
|
||||
m->L = calloc((size_t)c->n_layers, sizeof(Layer));
|
||||
@@ -1521,15 +1562,19 @@ static void model_init_range(Model *m, const char *snap, int cap, int bits,
|
||||
m->active_of = malloc((size_t)c->n_layers * sizeof(int));
|
||||
for (int i = 0; i < c->n_layers; i++) m->active_of[i] = i;
|
||||
char nm[256];
|
||||
int q_out = c->q_heads * c->q_head_dim, kv_out = c->kv_heads * c->k_head_dim;
|
||||
#define QCOUNT(field) do { if ((field).q) { qcount++; qfreed += (double)(field).I * (field).O * sizeof(float); } } while (0)
|
||||
for (int i = layer_begin; i < layer_end; i++) {
|
||||
int ai = m->active_of[i]; /* == i for Phase 2 */
|
||||
Layer *l = &m->L[i];
|
||||
/* input/post layernorms + MoE exist for every layer */
|
||||
/* input/post layernorms exist for every layer */
|
||||
#define LD(field, suffix, want) snprintf(nm,sizeof(nm),"model.layers.%d." suffix,ai); l->field = load_t_n(m,nm,(want))
|
||||
LD(in_ln, "input_layernorm.weight", c->hidden);
|
||||
LD(post_ln,"post_attention_layernorm.weight", c->hidden);
|
||||
LD(gate, "mlp.gate.weight", (int64_t)c->n_experts * c->hidden);
|
||||
#undef LD
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.mlp.gate.weight", ai);
|
||||
load_tq(m, nm, c->hidden, c->n_experts, quantize_dense, "router", &l->gate);
|
||||
QCOUNT(l->gate);
|
||||
/* q/k norms are per-head [head_dim]; only on attention layers, load if present */
|
||||
if (c->has_qk_norm) {
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.self_attn.q_norm.weight", ai);
|
||||
@@ -1541,42 +1586,51 @@ static void model_init_range(Model *m, const char *snap, int cap, int bits,
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.mlp.gate.e_score_correction_bias", ai);
|
||||
if (st_has(&m->S, nm)) { l->gate_bias = falloc(c->n_experts); st_read_f32(&m->S, nm, l->gate_bias, 0); }
|
||||
else l->gate_bias = NULL;
|
||||
/* shared expert (dense f32) */
|
||||
#define LD2(field, suffix, want) snprintf(nm,sizeof(nm),"model.layers.%d.mlp.shared_expert." suffix,ai); l->field = load_t_n(m,nm,(want))
|
||||
LD2(sh_g, "gate_proj.weight", (int64_t)c->shared_inter * c->hidden);
|
||||
LD2(sh_u, "up_proj.weight", (int64_t)c->shared_inter * c->hidden);
|
||||
LD2(sh_d, "down_proj.weight", (int64_t)c->hidden * c->shared_inter);
|
||||
#undef LD2
|
||||
/* shared expert (dense, int8-during-load) */
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.mlp.shared_expert.gate_proj.weight", ai);
|
||||
load_tq(m, nm, c->hidden, c->shared_inter, quantize_dense, "shexp", &l->sh_g); QCOUNT(l->sh_g);
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.mlp.shared_expert.up_proj.weight", ai);
|
||||
load_tq(m, nm, c->hidden, c->shared_inter, quantize_dense, "shexp", &l->sh_u); QCOUNT(l->sh_u);
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.mlp.shared_expert.down_proj.weight", ai);
|
||||
load_tq(m, nm, c->shared_inter, c->hidden, quantize_dense, "shexp", &l->sh_d); QCOUNT(l->sh_d);
|
||||
/* shared_expert_gate: Linear(hidden -> 1), sigmoid-gated shared expert */
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.mlp.shared_expert_gate.weight", ai);
|
||||
l->sh_gate = st_has(&m->S, nm) ? load_t_n(m, nm, c->hidden) : NULL;
|
||||
if (c->is_attn[i]) {
|
||||
/* Gated Attention (full_attention) layer */
|
||||
#define LD3(field, suffix, want) snprintf(nm,sizeof(nm),"model.layers.%d.self_attn." suffix,ai); l->field = load_t_n(m,nm,(want))
|
||||
LD3(q, "q_proj.weight", (int64_t)c->q_heads * c->q_head_dim * c->hidden);
|
||||
LD3(k, "k_proj.weight", (int64_t)c->kv_heads * c->k_head_dim * c->hidden);
|
||||
LD3(v, "v_proj.weight", (int64_t)c->kv_heads * c->v_head_dim * c->hidden);
|
||||
LD3(o, "o_proj.weight", (int64_t)c->hidden * c->o_in);
|
||||
#undef LD3
|
||||
l->dn_qkv=l->dn_z=l->dn_b=l->dn_a=l->dn_conv=NULL;
|
||||
l->dn_dtbias=l->dn_alog=l->dn_norm=l->dn_out=NULL;
|
||||
/* Gated Attention (full_attention) layer, dense projections int8-during-load */
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.self_attn.q_proj.weight", ai);
|
||||
load_tq(m, nm, c->hidden, q_out, quantize_dense, "attn", &l->q); QCOUNT(l->q);
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.self_attn.k_proj.weight", ai);
|
||||
load_tq(m, nm, c->hidden, kv_out, quantize_dense, "attn", &l->k); QCOUNT(l->k);
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.self_attn.v_proj.weight", ai);
|
||||
load_tq(m, nm, c->hidden, kv_out, quantize_dense, "attn", &l->v); QCOUNT(l->v);
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.self_attn.o_proj.weight", ai);
|
||||
load_tq(m, nm, c->o_in, c->hidden, quantize_dense, "attn", &l->o); QCOUNT(l->o);
|
||||
l->dn_qkv=l->dn_z=(QW){0}; l->dn_b=l->dn_a=l->dn_conv=NULL;
|
||||
l->dn_dtbias=l->dn_alog=l->dn_norm=NULL; l->dn_out=(QW){0};
|
||||
} else {
|
||||
/* Gated DeltaNet (linear_attention) layer */
|
||||
l->q=l->k=l->v=l->o=NULL;
|
||||
l->q=l->k=l->v=l->o=(QW){0};
|
||||
#define LD4(field, suffix, want) snprintf(nm,sizeof(nm),"model.layers.%d.linear_attn." suffix,ai); l->field = load_t_n(m,nm,(want))
|
||||
int64_t vdim_tot = (int64_t)c->dn_vheads * c->dn_vdim;
|
||||
LD4(dn_qkv, "in_proj_qkv.weight", (int64_t)c->dn_conv_dim * c->hidden);
|
||||
LD4(dn_z, "in_proj_z.weight", vdim_tot * c->hidden);
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.linear_attn.in_proj_qkv.weight", ai);
|
||||
load_tq(m, nm, c->hidden, c->dn_conv_dim, quantize_dense, "dnproj", &l->dn_qkv); QCOUNT(l->dn_qkv);
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.linear_attn.in_proj_z.weight", ai);
|
||||
load_tq(m, nm, c->hidden, (int)vdim_tot, quantize_dense, "dnproj", &l->dn_z); QCOUNT(l->dn_z);
|
||||
LD4(dn_b, "in_proj_b.weight", (int64_t)c->dn_vheads * c->hidden);
|
||||
LD4(dn_a, "in_proj_a.weight", (int64_t)c->dn_vheads * c->hidden);
|
||||
LD4(dn_conv,"conv1d.weight", (int64_t)c->dn_conv_dim * c->dn_convk);
|
||||
LD4(dn_dtbias, "dt_bias", c->dn_vheads);
|
||||
LD4(dn_alog,"A_log", c->dn_vheads);
|
||||
LD4(dn_norm, "norm.weight", c->dn_vdim);
|
||||
LD4(dn_out, "out_proj.weight", (int64_t)c->hidden * vdim_tot);
|
||||
#undef LD4
|
||||
snprintf(nm,sizeof(nm),"model.layers.%d.linear_attn.out_proj.weight", ai);
|
||||
load_tq(m, nm, (int)vdim_tot, c->hidden, quantize_dense, "dnout", &l->dn_out); QCOUNT(l->dn_out);
|
||||
}
|
||||
}
|
||||
#undef QCOUNT
|
||||
if (quantize_dense)
|
||||
fprintf(stderr, "[dense-i8] %d matrices quantized during load, %.1f GB f32 freed\n", qcount, qfreed/1073741824.0);
|
||||
m->cache = calloc((size_t)c->n_layers, sizeof(LCache));
|
||||
for (int i = layer_begin; i < layer_end; i++) {
|
||||
m->cache[i].cap = cap;
|
||||
@@ -1822,19 +1876,15 @@ static void slot_ensure_int8(Model *m, Slot *s) {
|
||||
int64_t ng = (int64_t)c->inter * c->hidden, nd = (int64_t)c->hidden * c->inter;
|
||||
int8_t *w = malloc((size_t)(ng + ng + nd));
|
||||
if (!w) { fprintf(stderr, "OOM slot_ensure_int8\n"); exit(1); }
|
||||
const uint8_t *src4[3] = { s->g4, s->u4, s->d4 };
|
||||
int64_t lens[3] = { ng, ng, nd };
|
||||
int8_t *dst = w;
|
||||
for (int t = 0; t < 3; t++) {
|
||||
const uint8_t *p = src4[t];
|
||||
for (int64_t i = 0; i < lens[t]; i += 2) {
|
||||
uint8_t b = p[i >> 1];
|
||||
int8_t lo = (int8_t)(b & 0xF); if (lo & 8) lo -= 16;
|
||||
int8_t hi = (int8_t)((b >> 4) & 0xF); if (hi & 8) hi -= 16;
|
||||
dst[i] = lo; dst[i + 1] = hi;
|
||||
}
|
||||
dst += lens[t];
|
||||
}
|
||||
/* #1271's unpack_int4_to_int8 (branchless, AVX2/NEON/scalar -- see its
|
||||
* definition above load_expert_merged, which already uses it for the
|
||||
* container's int4 read) instead of this function's own separate scalar
|
||||
* copy of the same nibble-unpack math. g4/u4/d4 are three independently
|
||||
* malloc'd packed buffers here (unlike load_expert_merged's single
|
||||
* contiguous `raw`), so one call per segment. */
|
||||
unpack_int4_to_int8(w, s->g4, ng);
|
||||
unpack_int4_to_int8(w + ng, s->u4, ng);
|
||||
unpack_int4_to_int8(w + ng + ng, s->d4, nd);
|
||||
s->g = w; s->u = w + ng; s->d = w + ng + ng;
|
||||
}
|
||||
|
||||
@@ -2047,9 +2097,9 @@ static void attention(Model *m, Layer *l, int layer, float *x, int S, int pos_ba
|
||||
float *vv= falloc((int64_t)S*kv_out);
|
||||
/* The projections the tier placed answer from VRAM for the whole batch,
|
||||
* with one backend call per matrix; unavailable handles use CPU matmul. */
|
||||
if (!qtd_batch(l->qth_q, q, x, S, D, q_out)) matmul_d(q, x, l->q, S, D, q_out);
|
||||
if (!qtd_batch(l->qth_k, k, x, S, D, kv_out)) matmul_d(k, x, l->k, S, D, kv_out);
|
||||
if (!qtd_batch(l->qth_v, vv, x, S, D, kv_out)) matmul_d(vv, x, l->v, S, D, kv_out);
|
||||
if (!qtd_batch(l->qth_q, q, x, S, D, q_out)) matmul_d(q, x, &l->q, S, D, q_out);
|
||||
if (!qtd_batch(l->qth_k, k, x, S, D, kv_out)) matmul_d(k, x, &l->k, S, D, kv_out);
|
||||
if (!qtd_batch(l->qth_v, vv, x, S, D, kv_out)) matmul_d(vv, x, &l->v, S, D, kv_out);
|
||||
/* split q into query (first hd) and gate (next gate_dim), both per head */
|
||||
float *query = falloc((int64_t)S*H*hd);
|
||||
float *gate = falloc((int64_t)S*H*gate_dim);
|
||||
@@ -2111,7 +2161,7 @@ static void attention(Model *m, Layer *l, int layer, float *x, int S, int pos_ba
|
||||
float g = gate_dim ? gate[o] : 0.f;
|
||||
ag[o] = ctx[o] * (1.f / (1.f + expf(-g)));
|
||||
}
|
||||
if (!qtd_batch(l->qth_o, out, ag, S, H*hd, D)) matmul_d(out, ag, l->o, S, H*hd, D);
|
||||
if (!qtd_batch(l->qth_o, out, ag, S, H*hd, D)) matmul_d(out, ag, &l->o, S, H*hd, D);
|
||||
free(q); free(k); free(vv); free(query); free(gate); free(ctx); free(ag);
|
||||
}
|
||||
|
||||
@@ -2144,10 +2194,10 @@ static void qwen_shared_experts_cpu(Model *m, Layer *l, const float *x, int S,
|
||||
if (B == 1) {
|
||||
for (int s=0;s<S;s++) {
|
||||
const float *xs=x+(int64_t)s*D;
|
||||
if(!qtd(l->qth_shg,g,xs,D,I)) matmul_d(g,xs,l->sh_g,1,D,I);
|
||||
if(!qtd(l->qth_shu,u,xs,D,I)) matmul_d(u,xs,l->sh_u,1,D,I);
|
||||
if(!qtd(l->qth_shg,g,xs,D,I)) matmul_d(g,xs,&l->sh_g,1,D,I);
|
||||
if(!qtd(l->qth_shu,u,xs,D,I)) matmul_d(u,xs,&l->sh_u,1,D,I);
|
||||
for(int i=0;i<I;i++){float sv=g[i];g[i]=(sv/(1.f+expf(-sv)))*u[i];}
|
||||
if(!qtd(l->qth_shd,hh,g,I,D)) matmul_d(hh,g,l->sh_d,1,I,D);
|
||||
if(!qtd(l->qth_shd,hh,g,I,D)) matmul_d(hh,g,&l->sh_d,1,I,D);
|
||||
float sgate=1.f;
|
||||
if(l->sh_gate){float sg=0.f;for(int i=0;i<D;i++)sg+=xs[i]*l->sh_gate[i];sgate=1.f/(1.f+expf(-sg));}
|
||||
float *os=out+(int64_t)s*D;
|
||||
@@ -2158,10 +2208,10 @@ static void qwen_shared_experts_cpu(Model *m, Layer *l, const float *x, int S,
|
||||
float *bh=falloc((int64_t)B*D);
|
||||
for(int base=0;base<S;base+=B){
|
||||
int rows=S-base<B?S-base:B;
|
||||
matmul_d(bg,x+(int64_t)base*D,l->sh_g,rows,D,I);
|
||||
matmul_d(bu,x+(int64_t)base*D,l->sh_u,rows,D,I);
|
||||
matmul_d(bg,x+(int64_t)base*D,&l->sh_g,rows,D,I);
|
||||
matmul_d(bu,x+(int64_t)base*D,&l->sh_u,rows,D,I);
|
||||
for(int64_t q=0;q<(int64_t)rows*I;q++){float sv=bg[q];bg[q]=(sv/(1.f+expf(-sv)))*bu[q];}
|
||||
matmul_d(bh,bg,l->sh_d,rows,I,D);
|
||||
matmul_d(bh,bg,&l->sh_d,rows,I,D);
|
||||
for(int s=0;s<rows;s++){
|
||||
const float *xs=x+(int64_t)(base+s)*D;
|
||||
float sgate=1.f;
|
||||
@@ -2324,7 +2374,7 @@ static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out) {
|
||||
Cfg *c = &m->c; int D = c->hidden, E = c->n_experts, K = c->topk, I = c->inter;
|
||||
float *logits = falloc((int64_t)S*E);
|
||||
double _tr = tm_now();
|
||||
matmul_d(logits, x, l->gate, S, D, E);
|
||||
matmul_d(logits, x, &l->gate, S, D, E);
|
||||
tm_add(S, 4, tm_now()-_tr);
|
||||
if (c->has_bias && l->gate_bias) {
|
||||
for (int s = 0; s < S; s++) { float *pr = logits + (int64_t)s*E; for (int e = 0; e < E; e++) pr[e] += l->gate_bias[e]; }
|
||||
@@ -2429,10 +2479,10 @@ static void moe(Model *m, Layer *l, int layer, float *x, int S, float *out) {
|
||||
{
|
||||
double _ts2 = tm_now();
|
||||
int Ish = c->shared_inter;
|
||||
if (!qtd(l->qth_shg, sh, xs, D, Ish)) matmul_d(sh, xs, l->sh_g, 1, D, Ish);
|
||||
if (!qtd(l->qth_shu, shu, xs, D, Ish)) matmul_d(shu, xs, l->sh_u, 1, D, Ish);
|
||||
if (!qtd(l->qth_shg, sh, xs, D, Ish)) matmul_d(sh, xs, &l->sh_g, 1, D, Ish);
|
||||
if (!qtd(l->qth_shu, shu, xs, D, Ish)) matmul_d(shu, xs, &l->sh_u, 1, D, Ish);
|
||||
for (int i = 0; i < Ish; i++) { float sv = sh[i]; sh[i] = (sv / (1.f + expf(-sv))) * shu[i]; }
|
||||
if (!qtd(l->qth_shd, shd, sh, Ish, D)) matmul_d(shd, sh, l->sh_d, 1, Ish, D);
|
||||
if (!qtd(l->qth_shd, shd, sh, Ish, D)) matmul_d(shd, sh, &l->sh_d, 1, Ish, D);
|
||||
float sgate = 1.f;
|
||||
if (l->sh_gate) {
|
||||
float sg = 0.f; const float *wg = l->sh_gate;
|
||||
@@ -2537,8 +2587,8 @@ static void deltanet(Model *m, Layer *l, int layer, float *x, int S, int pos_bas
|
||||
float *qkv = qkvz + (int64_t)(s % B) * proj_dim;
|
||||
float *z = qkv + conv_dim;
|
||||
if (!gpu_block) {
|
||||
matmul_d(qkv, xs, l->dn_qkv, 1, H, conv_dim);
|
||||
matmul_d(z, xs, l->dn_z, 1, H, value_dim);
|
||||
matmul_d(qkv, xs, &l->dn_qkv, 1, H, conv_dim);
|
||||
matmul_d(z, xs, &l->dn_z, 1, H, value_dim);
|
||||
}
|
||||
matmul(b, xs, l->dn_b, 1, H, vh);
|
||||
matmul(a, xs, l->dn_a, 1, H, vh);
|
||||
@@ -2637,7 +2687,7 @@ static void deltanet(Model *m, Layer *l, int layer, float *x, int S, int pos_bas
|
||||
}
|
||||
}
|
||||
if (!qtd(l->qth_dnout, out + (int64_t)s * H, outr, value_dim, H))
|
||||
matmul_d(out + (int64_t)s * H, outr, l->dn_out, 1, value_dim, H);
|
||||
matmul_d(out + (int64_t)s * H, outr, &l->dn_out, 1, value_dim, H);
|
||||
if (tm_on() && S==1){ g_dn_sub[3]+=tm_now()-_d0; }
|
||||
if (layer == 0 && s == 0 && getenv("DN_DBG")) {
|
||||
FILE *dbg = fopen(getenv("DN_DBG"), "wb");
|
||||
@@ -2677,18 +2727,18 @@ static void trunk_offer_dense(Model *m){
|
||||
Cfg *c = &m->c;
|
||||
for (int i = 0; i < c->n_layers; i++) {
|
||||
if (c->is_attn[i]) continue;
|
||||
size_t b = qdw_bytes(m->L[i].dn_out);
|
||||
size_t b = qdw_bytes(&m->L[i].dn_out);
|
||||
if (b) qt_trunk_offer("dnout", i, b);
|
||||
}
|
||||
for (int i = 0; i < c->n_layers; i++) {
|
||||
if (!c->is_attn[i]) continue;
|
||||
Layer *l = &m->L[i];
|
||||
size_t bq = qdw_bytes(l->q), bk = qdw_bytes(l->k), bv = qdw_bytes(l->v), bo = qdw_bytes(l->o);
|
||||
size_t bq = qdw_bytes(&l->q), bk = qdw_bytes(&l->k), bv = qdw_bytes(&l->v), bo = qdw_bytes(&l->o);
|
||||
if (bq && bk && bv && bo) qt_trunk_offer("attnproj", i, bq + bk + bv + bo);
|
||||
}
|
||||
for (int i = 0; i < c->n_layers; i++) {
|
||||
Layer *l = &m->L[i];
|
||||
size_t bg = qdw_bytes(l->sh_g), bu = qdw_bytes(l->sh_u), bd = qdw_bytes(l->sh_d);
|
||||
size_t bg = qdw_bytes(&l->sh_g), bu = qdw_bytes(&l->sh_u), bd = qdw_bytes(&l->sh_d);
|
||||
if (bg && bu && bd) qt_trunk_offer("shexp", i, bg + bu + bd);
|
||||
}
|
||||
}
|
||||
@@ -2701,22 +2751,22 @@ static int trunk_place_dense(Model *m, double *vram_bytes){
|
||||
for (int i = 0; i < c->n_layers; i++) {
|
||||
Layer *l = &m->L[i];
|
||||
if (!c->is_attn[i]) {
|
||||
l->qth_dnout = qdw_place(l->dn_out, qt_place_of("dnout", i));
|
||||
if (l->qth_dnout) { placed++; vram += (double)qdw_bytes(l->dn_out); }
|
||||
l->qth_dnout = qdw_place(&l->dn_out, qt_place_of("dnout", i));
|
||||
if (l->qth_dnout) { placed++; vram += (double)qdw_bytes(&l->dn_out); }
|
||||
} else {
|
||||
int dev = qt_place_of("attnproj", i);
|
||||
l->qth_q = qdw_place(l->q, dev); l->qth_k = qdw_place(l->k, dev);
|
||||
l->qth_v = qdw_place(l->v, dev); l->qth_o = qdw_place(l->o, dev);
|
||||
if (l->qth_q) { placed++; vram += (double)qdw_bytes(l->q); }
|
||||
if (l->qth_k) { placed++; vram += (double)qdw_bytes(l->k); }
|
||||
if (l->qth_v) { placed++; vram += (double)qdw_bytes(l->v); }
|
||||
if (l->qth_o) { placed++; vram += (double)qdw_bytes(l->o); }
|
||||
l->qth_q = qdw_place(&l->q, dev); l->qth_k = qdw_place(&l->k, dev);
|
||||
l->qth_v = qdw_place(&l->v, dev); l->qth_o = qdw_place(&l->o, dev);
|
||||
if (l->qth_q) { placed++; vram += (double)qdw_bytes(&l->q); }
|
||||
if (l->qth_k) { placed++; vram += (double)qdw_bytes(&l->k); }
|
||||
if (l->qth_v) { placed++; vram += (double)qdw_bytes(&l->v); }
|
||||
if (l->qth_o) { placed++; vram += (double)qdw_bytes(&l->o); }
|
||||
}
|
||||
int dev = qt_place_of("shexp", i);
|
||||
l->qth_shg = qdw_place(l->sh_g, dev); l->qth_shu = qdw_place(l->sh_u, dev); l->qth_shd = qdw_place(l->sh_d, dev);
|
||||
if (l->qth_shg) { placed++; vram += (double)qdw_bytes(l->sh_g); }
|
||||
if (l->qth_shu) { placed++; vram += (double)qdw_bytes(l->sh_u); }
|
||||
if (l->qth_shd) { placed++; vram += (double)qdw_bytes(l->sh_d); }
|
||||
l->qth_shg = qdw_place(&l->sh_g, dev); l->qth_shu = qdw_place(&l->sh_u, dev); l->qth_shd = qdw_place(&l->sh_d, dev);
|
||||
if (l->qth_shg) { placed++; vram += (double)qdw_bytes(&l->sh_g); }
|
||||
if (l->qth_shu) { placed++; vram += (double)qdw_bytes(&l->sh_u); }
|
||||
if (l->qth_shd) { placed++; vram += (double)qdw_bytes(&l->sh_d); }
|
||||
}
|
||||
if (vram_bytes) *vram_bytes = vram;
|
||||
return placed;
|
||||
@@ -2738,17 +2788,16 @@ static int trunk_probe_gpu_wins(Model *m){
|
||||
if (e && *e == '0') return 1;
|
||||
if (!qt_place_is_auto()) return 1;
|
||||
Cfg *c = &m->c;
|
||||
int qi = -1, dev = QT_PLACE_CPU;
|
||||
for (int i = 0; i < c->n_layers && qi < 0; i++) {
|
||||
QW *w = NULL; int dev = QT_PLACE_CPU;
|
||||
for (int i = 0; i < c->n_layers && !w; i++) {
|
||||
if (c->is_attn[i]) continue;
|
||||
for (int j = 0; j < g_qdw_n; j++)
|
||||
if (g_qdw[j].w == m->L[i].dn_qkv) { qi = j; dev = qt_place_of("dnproj", i); break; }
|
||||
if (m->L[i].dn_qkv.q) { w = &m->L[i].dn_qkv; dev = qt_place_of("dnproj", i); }
|
||||
}
|
||||
if (qi < 0) return 1; /* dense-i8 off: nothing will be placed */
|
||||
if (!w) return 1; /* dense-i8 off: nothing will be placed */
|
||||
if (dev == QT_PLACE_CPU) dev = qt_place_of("lmhead", 0);
|
||||
if (dev == QT_PLACE_CPU) return 1; /* nothing placed: nothing to measure */
|
||||
int I = g_qdw[qi].I, O = g_qdw[qi].O;
|
||||
int h = qt_dense_init(g_qdw[qi].q, g_qdw[qi].sc, I, O, dev);
|
||||
int I = w->I, O = w->O;
|
||||
int h = qt_dense_init(w->q, w->sc, I, O, dev);
|
||||
if (h < 0) return 1; /* cannot measure: the placer's word stands */
|
||||
float *x = malloc((size_t)I * sizeof(float)), *y = malloc((size_t)O * sizeof(float));
|
||||
if (!x || !y) { free(x); free(y); return 1; }
|
||||
@@ -2759,9 +2808,9 @@ static int trunk_probe_gpu_wins(Model *m){
|
||||
double t0 = now_s();
|
||||
for (int k = 0; k < 10; k++) if (!qt_dense_matmul(h, y, x, I, O)) { free(x); free(y); return 1; }
|
||||
double tg = (now_s() - t0) / 10;
|
||||
for (int k = 0; k < 3; k++) matmul_q(y, x, g_qdw[qi].q, g_qdw[qi].sc, I, O);
|
||||
for (int k = 0; k < 3; k++) matmul_q(y, x, w->q, w->sc, I, O);
|
||||
t0 = now_s();
|
||||
for (int k = 0; k < 10; k++) matmul_q(y, x, g_qdw[qi].q, g_qdw[qi].sc, I, O);
|
||||
for (int k = 0; k < 10; k++) matmul_q(y, x, w->q, w->sc, I, O);
|
||||
double tc = (now_s() - t0) / 10;
|
||||
if (tg < gpu) gpu = tg;
|
||||
if (tc < cpu) cpu = tc;
|
||||
@@ -2898,7 +2947,7 @@ static float *step(Model *m, const int *ids, int S, int pos_base) {
|
||||
for (int p = 0; p + 1 < S; p++) {
|
||||
rmsnorm_row(erow, x + (int64_t)p*D, m->final_norm, D, c->eps);
|
||||
if (!qt_lmhead_matmul(elog, erow, D, c->vocab))
|
||||
matmul_d(elog, erow, m->lm_head, 1, D, c->vocab);
|
||||
matmul_d(elog, erow, &m->lm_head, 1, D, c->vocab);
|
||||
serve_echo(g_echo_id, pos_base + p + 1, ids[p+1], elog, c->vocab, g_echo_k);
|
||||
}
|
||||
free(erow); free(elog);
|
||||
@@ -2909,7 +2958,7 @@ static float *step(Model *m, const int *ids, int S, int pos_base) {
|
||||
float *logit = falloc(c->vocab);
|
||||
double _th = tm_now();
|
||||
if (!qt_lmhead_matmul(logit, last, D, c->vocab))
|
||||
matmul_d(logit, last, m->lm_head, 1, D, c->vocab);
|
||||
matmul_d(logit, last, &m->lm_head, 1, D, c->vocab);
|
||||
if (tm_on()) { tm_add(S, 5, tm_now()-_th); if (S==1) g_tm_dec_tokens++; else g_tm_pre_tokens += S; }
|
||||
free(x); free(last);
|
||||
if (lf) fclose(lf);
|
||||
@@ -2970,7 +3019,7 @@ static void pilot_prefetch(Model *m, int lnext, const float *x, int S) {
|
||||
Layer *l = &m->L[lnext];
|
||||
float *nrm_x = falloc((int64_t)S * D);
|
||||
for (int s = 0; s < S; s++) rmsnorm_row(nrm_x + (int64_t)s*D, x + (int64_t)s*D, l->post_ln, D, c->eps);
|
||||
matmul_d(logits, nrm_x, l->gate, S, D, E); /* int8 copy (f32 may be freed) */
|
||||
matmul_d(logits, nrm_x, &l->gate, S, D, E); /* int8 copy (f32 may be freed) */
|
||||
free(nrm_x);
|
||||
for (int s = 0; s < S; s++) {
|
||||
float *pr = logits + (int64_t)s*E;
|
||||
@@ -3832,36 +3881,10 @@ int main(int argc, char **argv) {
|
||||
g_expert_gs = m.c.expert_gs;
|
||||
if (g_expert_gs) fprintf(stderr, "[qwen36] group-scaled experts: gs=%d\n", g_expert_gs);
|
||||
fprintf(stderr, "resident weights loaded in %.1fs | RSS after load: %.2f GB\n", m.dense_load_s, rss_gb());
|
||||
/* quantize the large dense matrices to int8 (COLI_DENSE_I8=0 disables) */
|
||||
if (dense_i8_on()) {
|
||||
double tq = now_s();
|
||||
Cfg *qc = &m.c; int D2 = qc->hidden;
|
||||
int q_out = qc->q_heads * qc->q_head_dim, kv_out = qc->kv_heads * qc->k_head_dim;
|
||||
for (int i = 0; i < qc->n_layers; i++) {
|
||||
Layer *l = &m.L[i];
|
||||
qdw_register_as(l->q, D2, q_out, "attn"); qdw_register_as(l->k, D2, kv_out, "attn");
|
||||
qdw_register_as(l->v, D2, kv_out, "attn"); qdw_register_as(l->o, qc->o_in, D2, "attn");
|
||||
qdw_register_as(l->gate, D2, qc->n_experts, "router");
|
||||
qdw_register_as(l->sh_g, D2, qc->shared_inter, "shexp"); qdw_register_as(l->sh_u, D2, qc->shared_inter, "shexp");
|
||||
qdw_register_as(l->sh_d, qc->shared_inter, D2, "shexp");
|
||||
qdw_register_as(l->dn_qkv, D2, qc->dn_conv_dim, "dnproj");
|
||||
qdw_register_as(l->dn_z, D2, qc->dn_vheads * qc->dn_vdim, "dnproj");
|
||||
qdw_register_as(l->dn_out, qc->dn_vheads * qc->dn_vdim, D2, "dnout");
|
||||
}
|
||||
qdw_register_as(m.lm_head, D2, qc->vocab, "lmhead");
|
||||
/* Free the f32 originals -- the pointers only serve as lookup keys in
|
||||
* matmul_d from here on (never dereferenced again).
|
||||
* COLI_KEEP_F32=1 keeps them (debug). */
|
||||
double freed = 0;
|
||||
if (!getenv("COLI_KEEP_F32")) {
|
||||
for (int i = 0; i < g_qdw_n; i++) {
|
||||
freed += (double)g_qdw[i].I * g_qdw[i].O * sizeof(float);
|
||||
free((void*)g_qdw[i].w);
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "[dense-i8] %d matrices quantized in %.1f s, %.1f GB f32 freed\n",
|
||||
g_qdw_n, now_s()-tq, freed/1073741824.0);
|
||||
}
|
||||
/* dense matrices are quantized to int8 (+ int4 planar per COLI_DENSE_BITS/
|
||||
* COLI_DENSE_INT4, see dense_int4_wanted) during model_init above
|
||||
* (COLI_DENSE_I8=0 disables it) -- see load_tq/QW; model_init_range
|
||||
* already logged the count and freed bytes. */
|
||||
|
||||
/* Optional CUDA VRAM expert tier (COLI_CUDA=1): hot experts live in
|
||||
* DEVICE_LOCAL memory across the configured GPUs, misses fall back to the
|
||||
@@ -3897,15 +3920,11 @@ int main(int argc, char **argv) {
|
||||
* No entry (dense-i8 off) means nothing to offer, and the CPU path stands. */
|
||||
{
|
||||
int O_qkv = m.c.dn_conv_dim, O_z = m.c.dn_vheads * m.c.dn_vdim;
|
||||
for (int i = 0; i < g_qdw_n; i++)
|
||||
if (g_qdw[i].w == m.lm_head)
|
||||
qt_trunk_offer("lmhead", 0, (size_t)g_qdw[i].I * g_qdw[i].O + (size_t)g_qdw[i].O * sizeof(float));
|
||||
if (m.lm_head.q)
|
||||
qt_trunk_offer("lmhead", 0, (size_t)m.lm_head.I * m.lm_head.O + (size_t)m.lm_head.O * sizeof(float));
|
||||
for (int i = 0; i < m.c.n_layers; i++) {
|
||||
if (m.c.is_attn[i]) continue;
|
||||
int have = 0;
|
||||
for (int j = 0; j < g_qdw_n; j++)
|
||||
if (g_qdw[j].w == m.L[i].dn_qkv || g_qdw[j].w == m.L[i].dn_z) have++;
|
||||
if (have == 2)
|
||||
if (m.L[i].dn_qkv.q && m.L[i].dn_z.q)
|
||||
qt_trunk_offer("dnproj", i, (size_t)(O_qkv + O_z) * m.c.hidden + (size_t)(O_qkv + O_z) * sizeof(float));
|
||||
}
|
||||
trunk_offer_dense(&m); /* dnout, attnproj, shexp: the rest of the per-token dense work */
|
||||
@@ -3920,13 +3939,10 @@ int main(int argc, char **argv) {
|
||||
/* The placer predicted; measure before uploading a byte of trunk. */
|
||||
if (!trunk_probe_gpu_wins(&m)) qt_trunk_withdraw("measured slower than the CPU");
|
||||
/* R4 role split: park the dense-i8 lm_head on COLI_LMHEAD_GPU. The
|
||||
* qdw entry keyed by m.lm_head holds the int8 rows + per-row scales
|
||||
* the CPU path uses; the GPU applies the identical semantics. */
|
||||
for (int i = 0; i < g_qdw_n; i++)
|
||||
if (g_qdw[i].w == m.lm_head) {
|
||||
qt_lmhead_init(g_qdw[i].q, g_qdw[i].sc, g_qdw[i].I, g_qdw[i].O);
|
||||
break;
|
||||
}
|
||||
* QW struct on m.lm_head holds the int8 rows + per-row scales the
|
||||
* CPU path uses; the GPU applies the identical semantics. */
|
||||
if (m.lm_head.q)
|
||||
qt_lmhead_init(m.lm_head.q, m.lm_head.sc, m.lm_head.I, m.lm_head.O);
|
||||
/* R4 step 2: DeltaNet input projections, per layer, wherever
|
||||
* COLI_PLACE puts them. qkv and z are both [O_x, hidden] int8 with
|
||||
* per-row scales, so fusing them is a concatenation along O -- two
|
||||
@@ -3940,11 +3956,8 @@ int main(int argc, char **argv) {
|
||||
if (m.c.is_attn[i]) continue;
|
||||
int dev = qt_place_of("dnproj", i);
|
||||
if (dev == QT_PLACE_CPU) continue;
|
||||
const int8_t *q1 = NULL, *q2 = NULL; const float *s1 = NULL, *s2 = NULL;
|
||||
for (int j = 0; j < g_qdw_n; j++) {
|
||||
if (g_qdw[j].w == m.L[i].dn_qkv) { q1 = g_qdw[j].q; s1 = g_qdw[j].sc; }
|
||||
if (g_qdw[j].w == m.L[i].dn_z) { q2 = g_qdw[j].q; s2 = g_qdw[j].sc; }
|
||||
}
|
||||
const int8_t *q1 = m.L[i].dn_qkv.q, *q2 = m.L[i].dn_z.q;
|
||||
const float *s1 = m.L[i].dn_qkv.sc, *s2 = m.L[i].dn_z.sc;
|
||||
if (!q1 || !q2) continue; /* dense-i8 off: CPU path stands */
|
||||
int8_t *qf = malloc((size_t)Of * Hd);
|
||||
float *sf = malloc((size_t)Of * sizeof(float));
|
||||
@@ -4090,12 +4103,12 @@ typedef struct {
|
||||
|
||||
static void qwen36_segment_layer_free(Layer *layer) {
|
||||
free(layer->in_ln); free(layer->post_ln);
|
||||
free(layer->q); free(layer->k); free(layer->v); free(layer->o);
|
||||
free(layer->qn); free(layer->kn); free(layer->gate); free(layer->gate_bias);
|
||||
free(layer->sh_g); free(layer->sh_u); free(layer->sh_d); free(layer->sh_gate);
|
||||
free(layer->dn_qkv); free(layer->dn_z); free(layer->dn_b); free(layer->dn_a);
|
||||
qw_free(&layer->q); qw_free(&layer->k); qw_free(&layer->v); qw_free(&layer->o);
|
||||
free(layer->qn); free(layer->kn); qw_free(&layer->gate); free(layer->gate_bias);
|
||||
qw_free(&layer->sh_g); qw_free(&layer->sh_u); qw_free(&layer->sh_d); free(layer->sh_gate);
|
||||
qw_free(&layer->dn_qkv); qw_free(&layer->dn_z); free(layer->dn_b); free(layer->dn_a);
|
||||
free(layer->dn_conv); free(layer->dn_dtbias); free(layer->dn_alog);
|
||||
free(layer->dn_norm); free(layer->dn_out);
|
||||
free(layer->dn_norm); qw_free(&layer->dn_out);
|
||||
}
|
||||
|
||||
static void qwen36_segment_model_destroy(Qwen36SegmentEngine *engine) {
|
||||
@@ -4483,7 +4496,7 @@ static void qwen36_edge_engine_destroy(void *engine_impl) {
|
||||
Qwen36EdgeEngine *engine = (Qwen36EdgeEngine *)engine_impl;
|
||||
if (!engine) return;
|
||||
free(engine->model.embed);
|
||||
free(engine->model.lm_head);
|
||||
qw_free(&engine->model.lm_head);
|
||||
free(engine->model.final_norm);
|
||||
free(engine->model.c.is_attn);
|
||||
st_destroy(&engine->model.S);
|
||||
@@ -4522,9 +4535,10 @@ static int qwen36_edge_engine_open(
|
||||
engine->model.embed = load_t_n(
|
||||
&engine->model, "model.embed_tokens.weight",
|
||||
(int64_t)config->vocab * config->hidden);
|
||||
engine->model.lm_head = load_t_n(
|
||||
&engine->model, "lm_head.weight",
|
||||
(int64_t)config->vocab * config->hidden);
|
||||
/* quantize=0: this engine never ran the old post-hoc qdw_register pass
|
||||
* either (only main()'s static Model did), so lm_head stays f32-only here,
|
||||
* exactly as before. */
|
||||
load_tq(&engine->model, "lm_head.weight", config->hidden, config->vocab, 0, "lmhead", &engine->model.lm_head);
|
||||
engine->model.final_norm = load_t_n(
|
||||
&engine->model, "model.norm.weight", config->hidden);
|
||||
engine->model.quant_bits = container_layer_is_int4(&engine->model, 0) ? 4 : 8;
|
||||
@@ -4672,7 +4686,7 @@ static int qwen36_edge_select(void *engine_impl,
|
||||
}
|
||||
rmsnorm_row(normalized, input + (size_t)row * config->hidden,
|
||||
engine->model.final_norm, config->hidden, config->eps);
|
||||
matmul_d(logits, normalized, engine->model.lm_head,
|
||||
matmul_d(logits, normalized, &engine->model.lm_head,
|
||||
1, config->hidden, config->vocab);
|
||||
if (coli_edge_argmax(logits, (uint32_t)config->vocab,
|
||||
&request->token_ids[row],
|
||||
@@ -4703,7 +4717,7 @@ static int qwen36_edge_logits(void *engine_impl,
|
||||
rmsnorm_row(normalized, input + (size_t)row * config->hidden,
|
||||
engine->model.final_norm, config->hidden, config->eps);
|
||||
matmul_d(request->logits + (size_t)row * config->vocab,
|
||||
normalized, engine->model.lm_head,
|
||||
normalized, &engine->model.lm_head,
|
||||
1, config->hidden, config->vocab);
|
||||
}
|
||||
free(normalized);
|
||||
|
||||
@@ -138,6 +138,121 @@ static inline float f16_to_f32(uint16_t h) {
|
||||
float f; memcpy(&f, &u, 4); return f;
|
||||
}
|
||||
|
||||
/* ---- bulk BF16/F16 -> F32, AVX2/SSE4.1/scalar tiers --------------------
|
||||
* st_read_f32/st_read_slice_f32 convert whole tensors (up to the embed/
|
||||
* lm_head matrix, vocab*hidden elements) through bf16_to_f32/f16_to_f32 one
|
||||
* halfword at a time; that loop is pure overhead once the read syscall is
|
||||
* off the critical path. The tiers below batch it, same idea and layering
|
||||
* as gsgemv.h's AVX2/SSE4.1 kernels (immintrin.h only under the ISA guard,
|
||||
* sse41_kernels.h not needed here -- no FMA, no shared load helper to reuse).
|
||||
*
|
||||
* BF16 -> F32 is an exact zero-pad widening for every bit pattern (BF16
|
||||
* shares F32's 8-bit exponent field, so there is no special-casing --
|
||||
* zero, normal, subnormal, inf, NaN all take the same `<<16`), so the
|
||||
* vectorized tiers cannot disagree with the scalar reference.
|
||||
*
|
||||
* F16 -> F32's zero/normal/inf-NaN classes are each the same closed-form
|
||||
* bit algebra as the scalar reference above, just run on several lanes at
|
||||
* once -- no reassociation, nothing to round, so still bit-exact. True
|
||||
* subnormals (exp==0, man!=0) need the scalar reference's shift-to-normalize
|
||||
* loop, which does not vectorize; those (rare in real model weights) fall
|
||||
* back to f16_to_f32 per element. Exhaustive verification (all 65536
|
||||
* patterns per format) lives in tests/test_st_f16_bf16_simd.c. */
|
||||
#if defined(__AVX2__) || defined(__SSE4_1__)
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
#if defined(__AVX2__)
|
||||
static void bf16_to_f32_bulk(const uint16_t *src, float *dst, int64_t n) {
|
||||
int64_t i = 0;
|
||||
for (; i + 8 <= n; i += 8) {
|
||||
__m128i h = _mm_loadu_si128((const __m128i*)(src + i));
|
||||
__m256i w = _mm256_slli_epi32(_mm256_cvtepu16_epi32(h), 16);
|
||||
_mm256_storeu_ps(dst + i, _mm256_castsi256_ps(w));
|
||||
}
|
||||
for (; i < n; i++) dst[i] = bf16_to_f32(src[i]);
|
||||
}
|
||||
static void f16_to_f32_bulk(const uint16_t *src, float *dst, int64_t n) {
|
||||
int64_t i = 0;
|
||||
const __m256i vsign_mask = _mm256_set1_epi32(0x8000);
|
||||
const __m256i vexp_mask = _mm256_set1_epi32(0x1F);
|
||||
const __m256i vman_mask = _mm256_set1_epi32(0x3FF);
|
||||
const __m256i v112 = _mm256_set1_epi32(112);
|
||||
const __m256i vinfnan_e = _mm256_set1_epi32(0x7F800000);
|
||||
const __m256i vzero = _mm256_setzero_si256();
|
||||
const __m256i v31 = _mm256_set1_epi32(31);
|
||||
for (; i + 8 <= n; i += 8) {
|
||||
__m128i h16 = _mm_loadu_si128((const __m128i*)(src + i));
|
||||
__m256i h = _mm256_cvtepu16_epi32(h16);
|
||||
__m256i sign = _mm256_slli_epi32(_mm256_and_si256(h, vsign_mask), 16);
|
||||
__m256i exp = _mm256_and_si256(_mm256_srli_epi32(h, 10), vexp_mask);
|
||||
__m256i man = _mm256_and_si256(h, vman_mask);
|
||||
__m256i normal_u = _mm256_or_si256(sign, _mm256_or_si256(
|
||||
_mm256_slli_epi32(_mm256_add_epi32(exp, v112), 23), _mm256_slli_epi32(man, 13)));
|
||||
__m256i infnan_u = _mm256_or_si256(sign, _mm256_or_si256(vinfnan_e, _mm256_slli_epi32(man, 13)));
|
||||
__m256i exp_is_zero = _mm256_cmpeq_epi32(exp, vzero);
|
||||
__m256i man_is_zero = _mm256_cmpeq_epi32(man, vzero);
|
||||
__m256i is_zero = _mm256_and_si256(exp_is_zero, man_is_zero);
|
||||
__m256i is_subnorm = _mm256_andnot_si256(man_is_zero, exp_is_zero);
|
||||
__m256i is_infnan = _mm256_cmpeq_epi32(exp, v31);
|
||||
__m256i result = _mm256_blendv_epi8(normal_u, sign, is_zero);
|
||||
result = _mm256_blendv_epi8(result, infnan_u, is_infnan);
|
||||
_mm256_storeu_ps(dst + i, _mm256_castsi256_ps(result));
|
||||
int m = _mm256_movemask_ps(_mm256_castsi256_ps(is_subnorm));
|
||||
if (m) for (int k = 0; k < 8; k++) if ((m >> k) & 1) dst[i+k] = f16_to_f32(src[i+k]);
|
||||
}
|
||||
for (; i < n; i++) dst[i] = f16_to_f32(src[i]);
|
||||
}
|
||||
#elif defined(__SSE4_1__)
|
||||
static void bf16_to_f32_bulk(const uint16_t *src, float *dst, int64_t n) {
|
||||
int64_t i = 0;
|
||||
for (; i + 4 <= n; i += 4) {
|
||||
__m128i h = _mm_loadl_epi64((const __m128i*)(src + i));
|
||||
__m128i w = _mm_slli_epi32(_mm_cvtepu16_epi32(h), 16);
|
||||
_mm_storeu_ps(dst + i, _mm_castsi128_ps(w));
|
||||
}
|
||||
for (; i < n; i++) dst[i] = bf16_to_f32(src[i]);
|
||||
}
|
||||
static void f16_to_f32_bulk(const uint16_t *src, float *dst, int64_t n) {
|
||||
int64_t i = 0;
|
||||
const __m128i vsign_mask = _mm_set1_epi32(0x8000);
|
||||
const __m128i vexp_mask = _mm_set1_epi32(0x1F);
|
||||
const __m128i vman_mask = _mm_set1_epi32(0x3FF);
|
||||
const __m128i v112 = _mm_set1_epi32(112);
|
||||
const __m128i vinfnan_e = _mm_set1_epi32(0x7F800000);
|
||||
const __m128i vzero = _mm_setzero_si128();
|
||||
const __m128i v31 = _mm_set1_epi32(31);
|
||||
for (; i + 4 <= n; i += 4) {
|
||||
__m128i h16 = _mm_loadl_epi64((const __m128i*)(src + i));
|
||||
__m128i h = _mm_cvtepu16_epi32(h16);
|
||||
__m128i sign = _mm_slli_epi32(_mm_and_si128(h, vsign_mask), 16);
|
||||
__m128i exp = _mm_and_si128(_mm_srli_epi32(h, 10), vexp_mask);
|
||||
__m128i man = _mm_and_si128(h, vman_mask);
|
||||
__m128i normal_u = _mm_or_si128(sign, _mm_or_si128(
|
||||
_mm_slli_epi32(_mm_add_epi32(exp, v112), 23), _mm_slli_epi32(man, 13)));
|
||||
__m128i infnan_u = _mm_or_si128(sign, _mm_or_si128(vinfnan_e, _mm_slli_epi32(man, 13)));
|
||||
__m128i exp_is_zero = _mm_cmpeq_epi32(exp, vzero);
|
||||
__m128i man_is_zero = _mm_cmpeq_epi32(man, vzero);
|
||||
__m128i is_zero = _mm_and_si128(exp_is_zero, man_is_zero);
|
||||
__m128i is_subnorm = _mm_andnot_si128(man_is_zero, exp_is_zero);
|
||||
__m128i is_infnan = _mm_cmpeq_epi32(exp, v31);
|
||||
__m128i result = _mm_blendv_epi8(normal_u, sign, is_zero);
|
||||
result = _mm_blendv_epi8(result, infnan_u, is_infnan);
|
||||
_mm_storeu_ps(dst + i, _mm_castsi128_ps(result));
|
||||
int m = _mm_movemask_ps(_mm_castsi128_ps(is_subnorm));
|
||||
if (m) for (int k = 0; k < 4; k++) if ((m >> k) & 1) dst[i+k] = f16_to_f32(src[i+k]);
|
||||
}
|
||||
for (; i < n; i++) dst[i] = f16_to_f32(src[i]);
|
||||
}
|
||||
#else
|
||||
static void bf16_to_f32_bulk(const uint16_t *src, float *dst, int64_t n) {
|
||||
for (int64_t i = 0; i < n; i++) dst[i] = bf16_to_f32(src[i]);
|
||||
}
|
||||
static void f16_to_f32_bulk(const uint16_t *src, float *dst, int64_t n) {
|
||||
for (int64_t i = 0; i < n; i++) dst[i] = f16_to_f32(src[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int st_open_fd(shards *S, const char *path) {
|
||||
for (int i = 0; i < S->nfd; i++) if (!strcmp(S->paths[i], path)) return S->fds[i];
|
||||
int fd = open(path, COMPAT_O_RDONLY);
|
||||
@@ -956,9 +1071,9 @@ static int64_t st_read_f32(shards *S, const char *name, float *out, int drop) {
|
||||
if (t->dtype == 2) {
|
||||
memcpy(out, raw, t->nbytes);
|
||||
} else if (t->dtype == 0) {
|
||||
uint16_t *p = (uint16_t *)raw; for (int64_t i = 0; i < t->numel; i++) out[i] = bf16_to_f32(p[i]);
|
||||
bf16_to_f32_bulk((uint16_t *)raw, out, t->numel);
|
||||
} else {
|
||||
uint16_t *p = (uint16_t *)raw; for (int64_t i = 0; i < t->numel; i++) out[i] = f16_to_f32(p[i]);
|
||||
f16_to_f32_bulk((uint16_t *)raw, out, t->numel);
|
||||
}
|
||||
free(raw);
|
||||
if (drop) posix_fadvise(t->fd, t->off, t->nbytes, POSIX_FADV_DONTNEED);
|
||||
@@ -1344,8 +1459,8 @@ static void st_read_slice_f32(shards *S, const char *name, int64_t elem_off, int
|
||||
if (nb) st_pread_full(t->fd, raw, nb, boff, "pread slice"); /* dev #331: chunked + EINTR + honest short-read */
|
||||
if (nb) {
|
||||
if (t->dtype == 2) memcpy(out, raw, (size_t)nb);
|
||||
else if (t->dtype == 0) { uint16_t *p = raw; for (int64_t i = 0; i < n_elems; i++) out[i] = bf16_to_f32(p[i]); }
|
||||
else { uint16_t *p = raw; for (int64_t i = 0; i < n_elems; i++) out[i] = f16_to_f32(p[i]); }
|
||||
else if (t->dtype == 0) bf16_to_f32_bulk((uint16_t *)raw, out, n_elems);
|
||||
else f16_to_f32_bulk((uint16_t *)raw, out, n_elems);
|
||||
}
|
||||
free(raw);
|
||||
if (drop && nb) posix_fadvise(t->fd, boff, nb, POSIX_FADV_DONTNEED);
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/* Synthetic peak-RSS proxy for the dense-int8-during-load change (QW/load_tq
|
||||
* in qwen36.c). NOT a test gate, NOT an end-to-end model-load benchmark: no
|
||||
* full-size Qwen3.6 checkpoint (~70 GB) is available in this environment, so
|
||||
* this isolates the actual malloc/free pattern the refactor changes, at
|
||||
* matrix sizes representative of Qwen3.6-35B-A3B's dense projections
|
||||
* (hidden=4096-ish square matrices), repeated for a layer count comparable to
|
||||
* a real model (48 dense matrices -- roughly q/k/v/o/gate/shared x 8 layers).
|
||||
*
|
||||
* OLD pattern (qdw_register, pre-#1271-adjacent refactor): allocate+read every
|
||||
* matrix's f32 copy first, THEN make a second pass quantizing all of them --
|
||||
* so all N f32 copies and all N int8 copies are resident at the same time at
|
||||
* the crossover point.
|
||||
* NEW pattern (load_tq): allocate+read one matrix's f32 copy, quantize it,
|
||||
* free the f32 copy, move to the next -- at most one f32 copy is ever
|
||||
* resident alongside the accumulating int8 copies.
|
||||
*
|
||||
* Reports peak RSS (Linux /proc/self/status VmHWM) for both patterns in
|
||||
* separate process runs (VmHWM is monotonic non-decreasing for a process's
|
||||
* lifetime, so OLD and NEW must run as separate processes, not sequentially
|
||||
* in one -- an in-process "old then new" run would report the OLD pattern's
|
||||
* peak forever after). Build on demand:
|
||||
*
|
||||
* make tests/bench_load_tq_peak_rss ARCH=native
|
||||
* ./tests/bench_load_tq_peak_rss old
|
||||
* ./tests/bench_load_tq_peak_rss new
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
|
||||
enum { HIDDEN = 4096, OUT = 4096, N_MATRICES = 48 };
|
||||
|
||||
static long vmhwm_kb(void) {
|
||||
FILE *f = fopen("/proc/self/status", "r");
|
||||
if (!f) return -1;
|
||||
char line[256]; long kb = -1;
|
||||
while (fgets(line, sizeof line, f)) {
|
||||
if (!strncmp(line, "VmHWM:", 6)) { sscanf(line + 6, "%ld", &kb); break; }
|
||||
}
|
||||
fclose(f);
|
||||
return kb;
|
||||
}
|
||||
|
||||
static void fill(float *w, int64_t n, int salt) {
|
||||
for (int64_t i = 0; i < n; i++) w[i] = (float)(((i * 2654435761u + salt) % 2003) - 1000) * 0.001f;
|
||||
}
|
||||
|
||||
static void quantize_row_major(const float *w, int I, int O, int8_t *q, float *sc) {
|
||||
for (int o = 0; o < O; o++) {
|
||||
const float *r = w + (int64_t)o * I; float am = 0.f;
|
||||
for (int i = 0; i < I; i++) { float a = fabsf(r[i]); if (a > am) am = a; }
|
||||
float s = am > 1e-12f ? am / 127.f : 1.f; sc[o] = s; float inv = 1.f / s;
|
||||
int8_t *d = q + (int64_t)o * I;
|
||||
for (int i = 0; i < I; i++) { int v = (int)lrintf(r[i] * inv); if (v > 127) v = 127; if (v < -127) v = -127; d[i] = (int8_t)v; }
|
||||
}
|
||||
}
|
||||
|
||||
static void run_old(void) {
|
||||
int64_t n = (int64_t)HIDDEN * OUT;
|
||||
float **f32s = malloc(N_MATRICES * sizeof(float*));
|
||||
/* pass 1: "load" every matrix's f32 copy (as model_init_range used to,
|
||||
* for every dense matrix in the whole model, before any quantization) */
|
||||
for (int m = 0; m < N_MATRICES; m++) {
|
||||
f32s[m] = malloc((size_t)n * sizeof(float));
|
||||
fill(f32s[m], n, m);
|
||||
}
|
||||
printf("after loading all %d f32 matrices: VmHWM=%ld MiB\n", N_MATRICES, vmhwm_kb() / 1024);
|
||||
/* pass 2: quantize every matrix (as the old post-hoc qdw_register loop
|
||||
* did), freeing each f32 copy only after ALL quantization is done */
|
||||
int8_t **qs = malloc(N_MATRICES * sizeof(int8_t*));
|
||||
float **scs = malloc(N_MATRICES * sizeof(float*));
|
||||
for (int m = 0; m < N_MATRICES; m++) {
|
||||
qs[m] = malloc((size_t)n);
|
||||
scs[m] = malloc((size_t)OUT * sizeof(float));
|
||||
quantize_row_major(f32s[m], HIDDEN, OUT, qs[m], scs[m]);
|
||||
}
|
||||
printf("after quantizing all (pre-free): VmHWM=%ld MiB\n", vmhwm_kb() / 1024);
|
||||
for (int m = 0; m < N_MATRICES; m++) free(f32s[m]);
|
||||
printf("[old pattern] peak VmHWM=%ld MiB\n", vmhwm_kb() / 1024);
|
||||
}
|
||||
|
||||
static void run_new(void) {
|
||||
int64_t n = (int64_t)HIDDEN * OUT;
|
||||
int8_t **qs = malloc(N_MATRICES * sizeof(int8_t*));
|
||||
float **scs = malloc(N_MATRICES * sizeof(float*));
|
||||
/* load_tq: read one matrix's f32 copy, quantize it, free it, next matrix */
|
||||
for (int m = 0; m < N_MATRICES; m++) {
|
||||
float *w = malloc((size_t)n * sizeof(float));
|
||||
fill(w, n, m);
|
||||
qs[m] = malloc((size_t)n);
|
||||
scs[m] = malloc((size_t)OUT * sizeof(float));
|
||||
quantize_row_major(w, HIDDEN, OUT, qs[m], scs[m]);
|
||||
free(w);
|
||||
}
|
||||
printf("[new pattern] peak VmHWM=%ld MiB\n", vmhwm_kb() / 1024);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 2 || (strcmp(argv[1], "old") && strcmp(argv[1], "new"))) {
|
||||
fprintf(stderr, "usage: %s old|new\n", argv[0]); return 2;
|
||||
}
|
||||
printf("N_MATRICES=%d each %dx%d f32 (%.1f MiB) -- %s pattern\n",
|
||||
N_MATRICES, HIDDEN, OUT, (double)HIDDEN * OUT * 4 / 1048576.0, argv[1]);
|
||||
if (!strcmp(argv[1], "old")) run_old(); else run_new();
|
||||
return 0;
|
||||
}
|
||||
@@ -39,13 +39,13 @@ static double shared_run(Model *m,Layer *l,const float *x,const float *seed,
|
||||
static int shared_benchmark(void) {
|
||||
Model m;memset(&m,0,sizeof(m));m.c.hidden=I;m.c.shared_inter=O;
|
||||
Layer l;memset(&l,0,sizeof(l));
|
||||
l.sh_g=falloc((int64_t)O*I);l.sh_u=falloc((int64_t)O*I);
|
||||
l.sh_d=falloc((int64_t)I*O);l.sh_gate=falloc(I);
|
||||
for(int64_t i=0;i<(int64_t)O*I;i++){l.sh_g[i]=value(i,2);l.sh_u[i]=value(i,3);}
|
||||
for(int64_t i=0;i<(int64_t)I*O;i++)l.sh_d[i]=value(i,4);
|
||||
l.sh_g.w=falloc((int64_t)O*I);l.sh_u.w=falloc((int64_t)O*I);
|
||||
l.sh_d.w=falloc((int64_t)I*O);l.sh_gate=falloc(I);
|
||||
for(int64_t i=0;i<(int64_t)O*I;i++){((float*)l.sh_g.w)[i]=value(i,2);((float*)l.sh_u.w)[i]=value(i,3);}
|
||||
for(int64_t i=0;i<(int64_t)I*O;i++)((float*)l.sh_d.w)[i]=value(i,4);
|
||||
for(int i=0;i<I;i++)l.sh_gate[i]=value(i,5);
|
||||
qdw_register(l.sh_g,I,O);qdw_register(l.sh_u,I,O);qdw_register(l.sh_d,O,I);
|
||||
if(g_qdw_n!=3){fprintf(stderr,"FAIL: expected three dense-int8 copies, got %d\n",g_qdw_n);return 1;}
|
||||
qw_quantize(l.sh_g.w,I,O,NULL,&l.sh_g);qw_quantize(l.sh_u.w,I,O,NULL,&l.sh_u);qw_quantize(l.sh_d.w,O,I,NULL,&l.sh_d);
|
||||
if(!l.sh_g.q||!l.sh_u.q||!l.sh_d.q){fprintf(stderr,"FAIL: expected three dense-int8 copies\n");return 1;}
|
||||
float *x=falloc((int64_t)S*I),*seed=falloc((int64_t)S*I);
|
||||
float *a=falloc((int64_t)S*I),*b=falloc((int64_t)S*I);
|
||||
for(int64_t i=0;i<(int64_t)S*I;i++){x[i]=value(i,6);seed[i]=value(i,7);}
|
||||
@@ -60,8 +60,7 @@ static int shared_benchmark(void) {
|
||||
printf("qwen shared int8: S=%d D=%d I=%d weights=%.1f MiB\n",S,I,O,
|
||||
3.0*I*O/1048576.0);
|
||||
printf("scalar %.6f s batch %.6f s speedup %.2fx calls %d -> 3\n",ts,tb,ts/tb,S*3);
|
||||
for(int i=0;i<g_qdw_n;i++){free(g_qdw[i].q);free(g_qdw[i].sc);}g_qdw_n=0;
|
||||
free(l.sh_g);free(l.sh_u);free(l.sh_d);free(l.sh_gate);
|
||||
qw_free(&l.sh_g);qw_free(&l.sh_u);qw_free(&l.sh_d);free(l.sh_gate);
|
||||
free(x);free(seed);free(a);free(b);unsetenv("QWEN_SHARED_BATCH");unsetenv("QWEN_DENSE_BATCH");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/* Local A/B for st.h's bf16_to_f32_bulk/f16_to_f32_bulk vectorized tiers vs the
|
||||
* scalar per-element reference (bf16_to_f32/f16_to_f32) they replace at the
|
||||
* st_read_f32/st_read_slice_f32 call sites. Not a test gate (see bench_idot,
|
||||
* bench_mla_simd, bench_gemv_stream for the same pattern) -- build on demand:
|
||||
*
|
||||
* make tests/bench_st_f16_bf16_simd ARCH=native
|
||||
* ./tests/bench_st_f16_bf16_simd
|
||||
*/
|
||||
#include "../st.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
enum { N = 4 * 1024 * 1024, REPS = 5 };
|
||||
|
||||
static double now_s(void) {
|
||||
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
|
||||
}
|
||||
|
||||
static double scalar_bf16(const uint16_t *src, float *dst, int64_t n) {
|
||||
double t0 = now_s();
|
||||
for (int64_t i = 0; i < n; i++) dst[i] = bf16_to_f32(src[i]);
|
||||
return now_s() - t0;
|
||||
}
|
||||
static double scalar_f16(const uint16_t *src, float *dst, int64_t n) {
|
||||
double t0 = now_s();
|
||||
for (int64_t i = 0; i < n; i++) dst[i] = f16_to_f32(src[i]);
|
||||
return now_s() - t0;
|
||||
}
|
||||
static double bulk_bf16(const uint16_t *src, float *dst, int64_t n) {
|
||||
double t0 = now_s(); bf16_to_f32_bulk(src, dst, n); return now_s() - t0;
|
||||
}
|
||||
static double bulk_f16(const uint16_t *src, float *dst, int64_t n) {
|
||||
double t0 = now_s(); f16_to_f32_bulk(src, dst, n); return now_s() - t0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
uint16_t *src = malloc((size_t)N * sizeof(uint16_t));
|
||||
float *a = malloc((size_t)N * sizeof(float));
|
||||
float *b = malloc((size_t)N * sizeof(float));
|
||||
if (!src || !a || !b) { fprintf(stderr, "OOM\n"); return 2; }
|
||||
/* representative mix: mostly normal-range values (real weight magnitudes),
|
||||
* a scattering of exact zeros, occasional subnormal/inf/nan bit patterns --
|
||||
* not the uniform all-65536-once sweep the exactness test uses. */
|
||||
for (int64_t i = 0; i < N; i++) {
|
||||
int64_t m = i % 97;
|
||||
if (m == 0) src[i] = 0;
|
||||
else if (m == 1) src[i] = 0x7C00; /* +inf */
|
||||
else if (m == 2) src[i] = 0x03FF; /* max subnormal */
|
||||
else src[i] = (uint16_t)((i * 2654435761u) & 0x7BFF);
|
||||
}
|
||||
|
||||
printf("st f16/bf16 simd bench: N=%d elements (%.1f MiB src)\n", N, N * sizeof(uint16_t) / 1048576.0);
|
||||
|
||||
double ts = 0, tb = 0;
|
||||
for (int r = 0; r < REPS; r++) {
|
||||
if (r & 1) { tb += bulk_bf16(src, b, N); ts += scalar_bf16(src, a, N); }
|
||||
else { ts += scalar_bf16(src, a, N); tb += bulk_bf16(src, b, N); }
|
||||
}
|
||||
printf("bf16->f32: scalar %.6f s bulk %.6f s speedup %.2fx\n", ts / REPS, tb / REPS, ts / tb);
|
||||
|
||||
ts = 0; tb = 0;
|
||||
for (int r = 0; r < REPS; r++) {
|
||||
if (r & 1) { tb += bulk_f16(src, b, N); ts += scalar_f16(src, a, N); }
|
||||
else { ts += scalar_f16(src, a, N); tb += bulk_f16(src, b, N); }
|
||||
}
|
||||
printf("f16->f32: scalar %.6f s bulk %.6f s speedup %.2fx\n", ts / REPS, tb / REPS, ts / tb);
|
||||
|
||||
free(src); free(a); free(b);
|
||||
return 0;
|
||||
}
|
||||
@@ -48,21 +48,34 @@ static void one_shape(int S, int I, int O) {
|
||||
free(x);free(q);free(sc);free(ref);free(got);
|
||||
}
|
||||
|
||||
static void clear_qdw(void) {
|
||||
for(int i=0;i<g_qdw_n;i++){free(g_qdw[i].q);free(g_qdw[i].sc);}
|
||||
g_qdw_n=0;
|
||||
static void env_set(const char *name,const char *value) {
|
||||
#ifdef _WIN32
|
||||
_putenv_s(name,value);
|
||||
#else
|
||||
setenv(name,value,1);
|
||||
#endif
|
||||
}
|
||||
static void env_unset(const char *name) {
|
||||
#ifdef _WIN32
|
||||
_putenv_s(name,"");
|
||||
#else
|
||||
unsetenv(name);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void clear_qw(Layer *l) { qw_free(&l->sh_g); qw_free(&l->sh_u); qw_free(&l->sh_d); }
|
||||
|
||||
static void shared_case(const char *format,int quantized) {
|
||||
enum { S=12,D=64,I=32 };
|
||||
Model m;memset(&m,0,sizeof(m));m.c.hidden=D;m.c.shared_inter=I;
|
||||
Layer l;memset(&l,0,sizeof(l));
|
||||
l.sh_g=falloc((int64_t)I*D);l.sh_u=falloc((int64_t)I*D);
|
||||
l.sh_d=falloc((int64_t)D*I);l.sh_gate=falloc(D);
|
||||
for(int64_t i=0;i<(int64_t)I*D;i++){l.sh_g[i]=input_value(i,2);l.sh_u[i]=input_value(i,3);}
|
||||
for(int64_t i=0;i<(int64_t)D*I;i++)l.sh_d[i]=input_value(i,4);
|
||||
l.sh_g.w=falloc((int64_t)I*D);l.sh_u.w=falloc((int64_t)I*D);
|
||||
l.sh_d.w=falloc((int64_t)D*I);l.sh_gate=falloc(D);
|
||||
l.sh_g.I=D;l.sh_g.O=I; l.sh_u.I=D;l.sh_u.O=I; l.sh_d.I=I;l.sh_d.O=D;
|
||||
for(int64_t i=0;i<(int64_t)I*D;i++){((float*)l.sh_g.w)[i]=input_value(i,2);((float*)l.sh_u.w)[i]=input_value(i,3);}
|
||||
for(int64_t i=0;i<(int64_t)D*I;i++)((float*)l.sh_d.w)[i]=input_value(i,4);
|
||||
for(int i=0;i<D;i++)l.sh_gate[i]=input_value(i,5);
|
||||
if(quantized){qdw_register(l.sh_g,D,I);qdw_register(l.sh_u,D,I);qdw_register(l.sh_d,I,D);}
|
||||
if(quantized){qw_quantize(l.sh_g.w,D,I,NULL,&l.sh_g);qw_quantize(l.sh_u.w,D,I,NULL,&l.sh_u);qw_quantize(l.sh_d.w,I,D,NULL,&l.sh_d);}
|
||||
float *x=falloc((int64_t)S*D),*seed=falloc((int64_t)S*D);
|
||||
float *ref=falloc((int64_t)S*D),*got=falloc((int64_t)S*D);
|
||||
float *g=falloc(I),*u=falloc(I),*hh=falloc(D);
|
||||
@@ -88,7 +101,7 @@ static void shared_case(const char *format,int quantized) {
|
||||
(unsigned long long)g_qwen_matmul_d_calls);
|
||||
printf("qwen shared batch exact: format=%s S=%d calls=%d -> 3\n",format,S,S*3);
|
||||
|
||||
clear_qdw();free(l.sh_g);free(l.sh_u);free(l.sh_d);free(l.sh_gate);
|
||||
clear_qw(&l);free(l.sh_gate);
|
||||
free(x);free(seed);free(ref);free(got);free(g);free(u);free(hh);
|
||||
}
|
||||
|
||||
|
||||
@@ -81,17 +81,18 @@ int main(void) {
|
||||
for (size_t i = 0; i < (size_t)O * I; i++) W[i] = rnd();
|
||||
for (size_t i = 0; i < (size_t)S * I; i++) x[i] = rnd() * 2.f;
|
||||
ref_matmul(ref, x, W, S, I, O);
|
||||
/* the classic path (no flags): int8 rows, f32 activations */
|
||||
/* the classic path (no flags): int8 rows, f32 activations.
|
||||
* dense_idot_on caches its answer: this TU reads the env once, so
|
||||
* set it before the first call (matmul_d, via qw_quantize below). */
|
||||
setenv("COLI_DENSE_IDOT", "0", 1); setenv("COLI_DENSE_BITS", "8", 1);
|
||||
qdw_register(W, I, O);
|
||||
matmul_d(y0, x, W, S, I, O);
|
||||
QW w = {0}; qw_quantize(W, I, O, NULL, &w);
|
||||
matmul_d(y0, x, &w, S, I, O);
|
||||
double g0 = rel_gap(y0, ref, S * O);
|
||||
ck(g0 < 2e-2, "classic int8 path within 2% of the f32 reference (per-row int8)");
|
||||
matmul_d(y, x, W, 1, I, O);
|
||||
matmul_d(y, x, &w, 1, I, O);
|
||||
ck(!memcmp(y, y0, (size_t)O * sizeof(float)), "one row and the first of a batch agree on the classic path");
|
||||
/* the integer path on the same int8 rows */
|
||||
g_qdw_n = 0;
|
||||
/* dense_idot_on caches its answer: this TU reads the env once, so set it before the first call */
|
||||
qw_free(&w);
|
||||
free(W); free(x); free(ref); free(y); free(y0);
|
||||
}
|
||||
{
|
||||
/* fresh process-wide state for the flag readers: emulate by direct calls */
|
||||
|
||||
@@ -54,13 +54,19 @@ int main(void) {
|
||||
Model m={0}; Layer l={0};
|
||||
m.c.hidden=H; m.c.dn_kheads=KH; m.c.dn_vheads=VH;
|
||||
m.c.dn_kdim=KD; m.c.dn_vdim=VD; m.c.dn_convk=CK; m.c.dn_conv_dim=C; m.c.eps=1e-6f;
|
||||
l.dn_qkv=weights(C*H); l.dn_z=weights(Z*H); l.dn_out=weights(H*Z);
|
||||
/* dn_qkv/dn_z: quantized straight into the QW the Layer holds, the same
|
||||
* shape load_tq uses. dn_out stays unquantized (.w only, .q == NULL) --
|
||||
* the pre-QW test never registered it either, so it always took the f32
|
||||
* matmul_d fallback; both deltanet() calls below share this same l.dn_out,
|
||||
* so which path it takes doesn't affect the cpu/gpu comparison. */
|
||||
{ float *w = weights(C*H); qw_quantize(w, H, C, NULL, &l.dn_qkv); free(w); }
|
||||
{ float *w = weights(Z*H); qw_quantize(w, H, Z, NULL, &l.dn_z); free(w); }
|
||||
l.dn_out.w=weights(H*Z);
|
||||
l.dn_b=weights(VH*H); l.dn_a=weights(VH*H); l.dn_conv=weights(C*CK);
|
||||
l.dn_alog=weights(VH); l.dn_dtbias=weights(VH); l.dn_norm=weights(VD);
|
||||
qdw_register(l.dn_qkv,H,C); qdw_register(l.dn_z,H,Z);
|
||||
int8_t q[O*H]; float sc[O];
|
||||
memcpy(q,g_qdw[0].q,C*H); memcpy(q+C*H,g_qdw[1].q,Z*H);
|
||||
memcpy(sc,g_qdw[0].sc,C*sizeof(float)); memcpy(sc+C,g_qdw[1].sc,Z*sizeof(float));
|
||||
memcpy(q,l.dn_qkv.q,C*H); memcpy(q+C*H,l.dn_z.q,Z*H);
|
||||
memcpy(sc,l.dn_qkv.sc,C*sizeof(float)); memcpy(sc+C,l.dn_z.sc,Z*sizeof(float));
|
||||
state(&m);
|
||||
enum { S=259 };
|
||||
float *x=weights(S*H), cpu[S*H], gpu[S*H], rec[VH*KD*VD], conv[C*(CK-1)];
|
||||
@@ -126,9 +132,9 @@ int main(void) {
|
||||
coli_cuda_shutdown();
|
||||
#endif
|
||||
free(x); free(m.DN_rec[0]); free(m.DN_conv[0]); free(m.DN_rec); free(m.DN_conv);
|
||||
free(l.dn_qkv); free(l.dn_z); free(l.dn_out); free(l.dn_b); free(l.dn_a); free(l.dn_conv);
|
||||
qw_free(&l.dn_qkv); qw_free(&l.dn_z); free((void*)l.dn_out.w);
|
||||
free(l.dn_b); free(l.dn_a); free(l.dn_conv);
|
||||
free(l.dn_alog); free(l.dn_dtbias); free(l.dn_norm);
|
||||
for(int i=0;i<g_qdw_n;i++){free(g_qdw[i].q);free(g_qdw[i].sc);} g_qdw_n=0;
|
||||
printf("DeltaNet batch: %s\n",failed?"FAIL":"outputs and recurrent state match; 259 calls -> 2");
|
||||
return failed!=0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/* Regression gate for wiring slot_ensure_int8() onto #1271's unpack_int4_to_int8
|
||||
* (the same branchless AVX2/NEON/scalar kernel load_expert_merged already uses
|
||||
* for the container's int4 read) instead of its own separate scalar
|
||||
* nibble-unpack loop. Integer nibble extraction + sign-extend is bit-exact by
|
||||
* construction (no reduction, no rounding), so this compares the NEW
|
||||
* slot_ensure_int8 (calling unpack_int4_to_int8) against a copy of the OLD
|
||||
* scalar loop it replaced -- not just "it compiles", a real regression check
|
||||
* that the two nibble-unpack formulas produce identical bytes. */
|
||||
#define main qwen36_main_unused
|
||||
#include "../qwen36.c"
|
||||
#undef main
|
||||
|
||||
static int failures;
|
||||
#define CHECK(cond, ...) do { if (!(cond)) { \
|
||||
fprintf(stderr,"FAIL %s:%d: ",__FILE__,__LINE__); \
|
||||
fprintf(stderr,__VA_ARGS__); fputc('\n',stderr); failures++; } } while (0)
|
||||
|
||||
/* Verbatim copy of slot_ensure_int8's pre-#1271 scalar loop (the code this
|
||||
* change replaces), used here only as the regression oracle. */
|
||||
static void old_unpack(int8_t *dst, const uint8_t *p, int64_t len) {
|
||||
for (int64_t i = 0; i < len; i += 2) {
|
||||
uint8_t b = p[i >> 1];
|
||||
int8_t lo = (int8_t)(b & 0xF); if (lo & 8) lo -= 16;
|
||||
int8_t hi = (int8_t)((b >> 4) & 0xF); if (hi & 8) hi -= 16;
|
||||
dst[i] = lo; dst[i + 1] = hi;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t *random_packed(int64_t nbytes, uint32_t seed) {
|
||||
uint8_t *p = malloc((size_t)nbytes);
|
||||
uint32_t x = seed ? seed : 1;
|
||||
for (int64_t i = 0; i < nbytes; i++) {
|
||||
x ^= x << 13; x ^= x >> 17; x ^= x << 5; /* xorshift32 */
|
||||
p[i] = (uint8_t)x;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/* inter/hidden chosen so ng=inter*hidden and nd=hidden*inter land on both
|
||||
* sides of the AVX2 16-byte (32-element) vector boundary across the three
|
||||
* segments (g/u sized ng, d sized nd). ng/nd must stay even: nibble-packing
|
||||
* always yields ceil(n/2) bytes, and real containers guarantee even
|
||||
* inter*hidden (SIMD/GS64-block-aligned dims), so old_unpack -- a verbatim
|
||||
* copy of the pre-#1271 scalar loop, kept unmodified on purpose -- was never
|
||||
* written to handle an odd total (its last iteration writes dst[i+1]
|
||||
* unconditionally) and shouldn't be, either: that shape cannot occur. */
|
||||
static void one_case(int inter, int hidden, const char *label) {
|
||||
Model m; memset(&m, 0, sizeof(m));
|
||||
m.c.inter = inter; m.c.hidden = hidden;
|
||||
int64_t ng = (int64_t)inter * hidden, nd = (int64_t)hidden * inter;
|
||||
CHECK(ng % 2 == 0, "%s: ng must be even (nibble-packed, see one_case)", label);
|
||||
if (ng % 2) return;
|
||||
|
||||
Slot s; memset(&s, 0, sizeof(s));
|
||||
s.g4 = random_packed(ng / 2, 1001);
|
||||
s.u4 = random_packed(ng / 2, 2002);
|
||||
s.d4 = random_packed(nd / 2, 3003);
|
||||
|
||||
slot_ensure_int8(&m, &s);
|
||||
CHECK(s.g != NULL, "%s: slot_ensure_int8 did not populate s->g", label);
|
||||
if (!s.g) return;
|
||||
|
||||
int8_t *exp_g = malloc((size_t)ng), *exp_u = malloc((size_t)ng), *exp_d = malloc((size_t)nd);
|
||||
old_unpack(exp_g, s.g4, ng);
|
||||
old_unpack(exp_u, s.u4, ng);
|
||||
old_unpack(exp_d, s.d4, nd);
|
||||
|
||||
CHECK(!memcmp(s.g, exp_g, (size_t)ng), "%s: gate segment differs from old scalar loop", label);
|
||||
CHECK(!memcmp(s.u, exp_u, (size_t)ng), "%s: up segment differs from old scalar loop", label);
|
||||
CHECK(!memcmp(s.d, exp_d, (size_t)nd), "%s: down segment differs from old scalar loop", label);
|
||||
printf("qwen36 slot_ensure_int8 exact: %s inter=%d hidden=%d (ng=%lld nd=%lld)\n",
|
||||
label, inter, hidden, (long long)ng, (long long)nd);
|
||||
|
||||
free(exp_g); free(exp_u); free(exp_d);
|
||||
free(s.g); /* s.u/s.d point into the same block as s.g (slot_ensure_int8's `w`) */
|
||||
free(s.g4); free(s.u4); free(s.d4);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
one_case(4, 8, "tiny, well under one vector"); /* ng=32, nd=32 */
|
||||
one_case(16, 16, "exact AVX2 vector boundary"); /* ng=256, nd=256, /16=16 (mult of 16-byte block) */
|
||||
one_case(17, 16, "vector body + scalar tail"); /* ng=272 -> nb=136, not mult of 16 */
|
||||
one_case(37, 42, "odd, real-expert-shaped"); /* ng=1554, nd=1554 (even: see one_case) */
|
||||
one_case(2048, 512, "large, real Qwen3.6 expert scale"); /* ng=1048576, nd=1048576 */
|
||||
|
||||
/* re-entrancy guard: slot_ensure_int8 must no-op when s->g is already set
|
||||
* (the "rematerialize only if evicted" contract) -- unrelated to which
|
||||
* unpack kernel runs, but worth pinning since this function was touched */
|
||||
{
|
||||
Model m; memset(&m, 0, sizeof(m)); m.c.inter = 4; m.c.hidden = 8;
|
||||
Slot s; memset(&s, 0, sizeof(s));
|
||||
s.g4 = random_packed(16, 42); s.u4 = random_packed(16, 43); s.d4 = random_packed(16, 44);
|
||||
slot_ensure_int8(&m, &s);
|
||||
int8_t *g_before = s.g;
|
||||
slot_ensure_int8(&m, &s); /* should be a no-op: s->g already non-NULL */
|
||||
CHECK(s.g == g_before, "slot_ensure_int8 re-ran when s->g was already set");
|
||||
free(s.g); free(s.g4); free(s.u4); free(s.d4);
|
||||
}
|
||||
|
||||
if (failures) { fprintf(stderr, "qwen36 slot_ensure_int8: %d failure(s)\n", failures); return 1; }
|
||||
puts("qwen36 slot_ensure_int8: ok");
|
||||
return 0;
|
||||
}
|
||||
@@ -50,10 +50,10 @@ static float *rnd_matrix(int rows, int cols) {
|
||||
* matmul_d. The fake backend computes exactly what a real one does with these
|
||||
* bytes (x . int8 row, times the row scale), so the two must agree to float
|
||||
* accumulation order. */
|
||||
static double gemv_gap(int hp1, const float *W, int I, int O, int *served) {
|
||||
static double gemv_gap(int hp1, const QW *w, int I, int O, int *served) {
|
||||
float *x = rnd_matrix(1, I), *ya = calloc((size_t)O, sizeof(float)), *yb = calloc((size_t)O, sizeof(float));
|
||||
*served = qtd(hp1, ya, x, I, O);
|
||||
matmul_d(yb, x, W, 1, I, O);
|
||||
matmul_d(yb, x, w, 1, I, O);
|
||||
double worst = 0, scale = 1e-6;
|
||||
for (int o = 0; o < O; o++) {
|
||||
double d = fabs((double)ya[o] - yb[o]);
|
||||
@@ -64,6 +64,15 @@ static double gemv_gap(int hp1, const float *W, int I, int O, int *served) {
|
||||
return worst / scale;
|
||||
}
|
||||
|
||||
/* Quantize a freshly-built random matrix straight into *out (the QW the
|
||||
* Layer holds), then discard the f32 staging buffer -- the same "quantize
|
||||
* once, free the f32 copy" shape load_tq uses. */
|
||||
static void register_qw(int rows, int cols, QW *out) {
|
||||
float *w = rnd_matrix(rows, cols);
|
||||
qw_quantize(w, cols, rows, NULL, out);
|
||||
free(w);
|
||||
}
|
||||
|
||||
static void build(Model *m) {
|
||||
memset(m, 0, sizeof *m);
|
||||
Cfg *c = &m->c;
|
||||
@@ -73,20 +82,20 @@ static void build(Model *m) {
|
||||
c->is_attn = calloc(NL, 1); c->is_attn[1] = 1;
|
||||
m->L = calloc(NL, sizeof(Layer));
|
||||
Layer *dn = &m->L[0], *at = &m->L[1];
|
||||
dn->dn_out = rnd_matrix(D, VH * VD); qdw_register(dn->dn_out, VH * VD, D);
|
||||
dn->sh_g = rnd_matrix(SH, D); qdw_register(dn->sh_g, D, SH);
|
||||
dn->sh_u = rnd_matrix(SH, D); qdw_register(dn->sh_u, D, SH);
|
||||
dn->sh_d = rnd_matrix(D, SH); qdw_register(dn->sh_d, SH, D);
|
||||
at->q = rnd_matrix(QH * QD, D); qdw_register(at->q, D, QH * QD);
|
||||
at->k = rnd_matrix(KVH * KD, D); qdw_register(at->k, D, KVH * KD);
|
||||
at->v = rnd_matrix(KVH * KD, D); qdw_register(at->v, D, KVH * KD);
|
||||
at->o = rnd_matrix(D, QH * KD); qdw_register(at->o, QH * KD, D);
|
||||
register_qw(D, VH * VD, &dn->dn_out);
|
||||
register_qw(SH, D, &dn->sh_g);
|
||||
register_qw(SH, D, &dn->sh_u);
|
||||
register_qw(D, SH, &dn->sh_d);
|
||||
register_qw(QH * QD, D, &at->q);
|
||||
register_qw(KVH * KD, D, &at->k);
|
||||
register_qw(KVH * KD, D, &at->v);
|
||||
register_qw(D, QH * KD, &at->o);
|
||||
/* the attention layer's shared expert is INCOMPLETE on purpose: gate and
|
||||
* up have a dense-i8 copy, down does not (never registered), so "shexp"
|
||||
* for layer 1 must not be offered and its three handles must stay 0 */
|
||||
at->sh_g = rnd_matrix(SH, D); qdw_register(at->sh_g, D, SH);
|
||||
at->sh_u = rnd_matrix(SH, D); qdw_register(at->sh_u, D, SH);
|
||||
at->sh_d = rnd_matrix(D, SH); /* no qdw_register */
|
||||
register_qw(SH, D, &at->sh_g);
|
||||
register_qw(SH, D, &at->sh_u);
|
||||
/* at->sh_d: left zeroed (q == NULL) -- no dense-i8 copy, never registered */
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
@@ -109,17 +118,17 @@ int main(void) {
|
||||
trunk_offer_dense(&m);
|
||||
/* dnout(layer 0) + attnproj(layer 1) + shexp(layer 0); NOT shexp(layer 1) */
|
||||
ck(G_offer_n - before == 3, "three components offered: dnout, attnproj, shexp of the complete layer only");
|
||||
size_t want_attn = qdw_bytes(at->q) + qdw_bytes(at->k) + qdw_bytes(at->v) + qdw_bytes(at->o);
|
||||
size_t want_attn = qdw_bytes(&at->q) + qdw_bytes(&at->k) + qdw_bytes(&at->v) + qdw_bytes(&at->o);
|
||||
int seen_attn = 0, seen_dnout = 0, seen_shexp1 = 0;
|
||||
for (int o = before; o < G_offer_n; o++) {
|
||||
if (!strcmp(G_offer[o].name, "attnproj") && G_offer[o].layer == 1 && G_offer[o].bytes == want_attn) seen_attn = 1;
|
||||
if (!strcmp(G_offer[o].name, "dnout") && G_offer[o].layer == 0 && G_offer[o].bytes == qdw_bytes(dn->dn_out)) seen_dnout = 1;
|
||||
if (!strcmp(G_offer[o].name, "dnout") && G_offer[o].layer == 0 && G_offer[o].bytes == qdw_bytes(&dn->dn_out)) seen_dnout = 1;
|
||||
if (!strcmp(G_offer[o].name, "shexp") && G_offer[o].layer == 1) seen_shexp1 = 1;
|
||||
}
|
||||
ck(seen_dnout, "dnout offered for the DeltaNet layer with the bytes of its dense-i8 copy");
|
||||
ck(seen_attn, "attnproj offered for the attention layer as the sum of q, k, v, o");
|
||||
ck(!seen_shexp1, "a shared expert missing one dense-i8 copy is not offered");
|
||||
ck(qdw_bytes(at->sh_d) == 0, "a matrix without a dense-i8 copy reports zero bytes");
|
||||
ck(qdw_bytes(&at->sh_d) == 0, "a matrix without a dense-i8 copy reports zero bytes");
|
||||
|
||||
printf("placement\n");
|
||||
ck(qt_init(NL, NE, D, IH, NE, 1, 0, 1), "tier starts (int4 mode, cap == n_experts)");
|
||||
@@ -129,7 +138,7 @@ int main(void) {
|
||||
double vram = 0;
|
||||
int placed = trunk_place_dense(&m, &vram);
|
||||
ck(placed == 8, "eight matrices placed: dnout, q k v o, and the complete shared expert");
|
||||
ck(vram == (double)(qdw_bytes(dn->dn_out) + want_attn + qdw_bytes(dn->sh_g) + qdw_bytes(dn->sh_u) + qdw_bytes(dn->sh_d)),
|
||||
ck(vram == (double)(qdw_bytes(&dn->dn_out) + want_attn + qdw_bytes(&dn->sh_g) + qdw_bytes(&dn->sh_u) + qdw_bytes(&dn->sh_d)),
|
||||
"placed bytes are the sum of the dense-i8 copies uploaded");
|
||||
ck(fake_uploads == 8, "one upload per placed matrix");
|
||||
ck(dn->qth_dnout > 0 && at->qth_q > 0 && at->qth_k > 0 && at->qth_v > 0 && at->qth_o > 0, "handles kept in the Layer");
|
||||
@@ -137,11 +146,11 @@ int main(void) {
|
||||
ck(at->qth_shg == 0 && at->qth_shu == 0 && at->qth_shd == 0, "no handle for the shared expert that was not offered");
|
||||
|
||||
printf("GEMV from VRAM == matmul_d on the CPU\n");
|
||||
struct { const char *name; int hp1; const float *w; int I, O; } mats[] = {
|
||||
{"dn_out", dn->qth_dnout, dn->dn_out, VH * VD, D},
|
||||
{"q", at->qth_q, at->q, D, QH * QD}, {"k", at->qth_k, at->k, D, KVH * KD},
|
||||
{"v", at->qth_v, at->v, D, KVH * KD}, {"o", at->qth_o, at->o, QH * KD, D},
|
||||
{"sh_g", dn->qth_shg, dn->sh_g, D, SH}, {"sh_u", dn->qth_shu, dn->sh_u, D, SH}, {"sh_d", dn->qth_shd, dn->sh_d, SH, D},
|
||||
struct { const char *name; int hp1; const QW *w; int I, O; } mats[] = {
|
||||
{"dn_out", dn->qth_dnout, &dn->dn_out, VH * VD, D},
|
||||
{"q", at->qth_q, &at->q, D, QH * QD}, {"k", at->qth_k, &at->k, D, KVH * KD},
|
||||
{"v", at->qth_v, &at->v, D, KVH * KD}, {"o", at->qth_o, &at->o, QH * KD, D},
|
||||
{"sh_g", dn->qth_shg, &dn->sh_g, D, SH}, {"sh_u", dn->qth_shu, &dn->sh_u, D, SH}, {"sh_d", dn->qth_shd, &dn->sh_d, SH, D},
|
||||
};
|
||||
for (size_t i = 0; i < sizeof mats / sizeof mats[0]; i++) {
|
||||
int served = 0;
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/* Exhaustive bit-exact gate for st.h's bf16_to_f32_bulk / f16_to_f32_bulk
|
||||
* (AVX2/SSE4.1/scalar tiers). BF16->F32 and F16->F32 are lossless IEEE-754
|
||||
* widenings: for every one of the 65536 possible input bit patterns -- every
|
||||
* normal and subnormal exponent, +-0, +-inf, and every NaN payload -- there
|
||||
* is exactly one correct output, so there is no tolerance to allow: the
|
||||
* vectorized tiers must produce bit-identical floats to the scalar reference
|
||||
* (bf16_to_f32/f16_to_f32) for the entire input space, not just a sample.
|
||||
*
|
||||
* Runs the full 65536-value sweep (which lands on an exact multiple of both
|
||||
* the AVX2 8-wide and SSE4.1 4-wide vector width) and a set of odd-length,
|
||||
* odd-offset slices of the same data so the scalar remainder tail in each
|
||||
* tier's loop is also exercised, not just the vectorized body. */
|
||||
#include "../st.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int failures;
|
||||
#define CHECK(cond, ...) do { if (!(cond)) { \
|
||||
fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \
|
||||
fprintf(stderr, __VA_ARGS__); fputc('\n', stderr); failures++; } } while (0)
|
||||
|
||||
static int32_t bits_of(float f) { int32_t u; memcpy(&u, &f, 4); return u; }
|
||||
|
||||
static void check_range(const uint16_t *src, int64_t n, const char *label) {
|
||||
float *bulk_bf = malloc((size_t)n * sizeof(float));
|
||||
float *bulk_f16 = malloc((size_t)n * sizeof(float));
|
||||
if (!bulk_bf || !bulk_f16) { fprintf(stderr, "OOM\n"); exit(2); }
|
||||
bf16_to_f32_bulk(src, bulk_bf, n);
|
||||
f16_to_f32_bulk(src, bulk_f16, n);
|
||||
int64_t bf_diffs = 0, f16_diffs = 0;
|
||||
int64_t bf_first = -1, f16_first = -1;
|
||||
for (int64_t i = 0; i < n; i++) {
|
||||
float ref_bf = bf16_to_f32(src[i]);
|
||||
float ref_f16 = f16_to_f32(src[i]);
|
||||
if (bits_of(ref_bf) != bits_of(bulk_bf[i])) { bf_diffs++; if (bf_first < 0) bf_first = i; }
|
||||
if (bits_of(ref_f16) != bits_of(bulk_f16[i])) { f16_diffs++; if (f16_first < 0) f16_first = i; }
|
||||
}
|
||||
if (bf_diffs) fprintf(stderr, " bf16 first mismatch idx=%lld h=0x%04x scalar=0x%08x bulk=0x%08x\n",
|
||||
(long long)bf_first, src[bf_first], bits_of(bf16_to_f32(src[bf_first])), bits_of(bulk_bf[bf_first]));
|
||||
if (f16_diffs) fprintf(stderr, " f16 first mismatch idx=%lld h=0x%04x scalar=0x%08x bulk=0x%08x\n",
|
||||
(long long)f16_first, src[f16_first], bits_of(f16_to_f32(src[f16_first])), bits_of(bulk_f16[f16_first]));
|
||||
CHECK(bf_diffs == 0, "%s: bf16_to_f32_bulk %lld/%lld mismatches", label, (long long)bf_diffs, (long long)n);
|
||||
CHECK(f16_diffs == 0, "%s: f16_to_f32_bulk %lld/%lld mismatches", label, (long long)f16_diffs, (long long)n);
|
||||
free(bulk_bf); free(bulk_f16);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
/* every possible 16-bit pattern, once each -- covers +-0, every normal
|
||||
* and subnormal exponent, +-inf, and every NaN payload for both formats */
|
||||
uint16_t *all = malloc(65536 * sizeof(uint16_t));
|
||||
if (!all) { fprintf(stderr, "OOM\n"); return 2; }
|
||||
for (int32_t h = 0; h <= 0xFFFF; h++) all[h] = (uint16_t)h;
|
||||
|
||||
check_range(all, 65536, "full sweep");
|
||||
printf("st f16/bf16 simd: exhaustive 65536/65536 patterns exact\n");
|
||||
|
||||
/* odd offsets/lengths so the scalar remainder tail (n%8 for AVX2, n%4 for
|
||||
* SSE4.1) actually runs, on real (not synthetic-zero) data */
|
||||
static const int64_t offs[] = {0, 1, 2, 3, 5, 7, 9, 13, 15, 16, 17, 100, 12345};
|
||||
static const int64_t lens[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 15, 16, 17, 65423, 65535};
|
||||
for (size_t oi = 0; oi < sizeof(offs)/sizeof(offs[0]); oi++) {
|
||||
for (size_t li = 0; li < sizeof(lens)/sizeof(lens[0]); li++) {
|
||||
int64_t off = offs[oi], len = lens[li];
|
||||
if (off + len > 65536) continue;
|
||||
char label[64]; snprintf(label, sizeof label, "off=%lld len=%lld", (long long)off, (long long)len);
|
||||
check_range(all + off, len, label);
|
||||
}
|
||||
}
|
||||
printf("st f16/bf16 simd: tail-remainder slices ok\n");
|
||||
|
||||
free(all);
|
||||
if (failures) { fprintf(stderr, "st f16/bf16 simd: %d failure(s)\n", failures); return 1; }
|
||||
puts("st f16/bf16 simd: ok");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user