Instructions to use tchbcb/MiniCPM5-2B-cpu-mixed with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use tchbcb/MiniCPM5-2B-cpu-mixed with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf tchbcb/MiniCPM5-2B-cpu-mixed # Run inference directly in the terminal: llama cli -hf tchbcb/MiniCPM5-2B-cpu-mixed
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf tchbcb/MiniCPM5-2B-cpu-mixed # Run inference directly in the terminal: llama cli -hf tchbcb/MiniCPM5-2B-cpu-mixed
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf tchbcb/MiniCPM5-2B-cpu-mixed # Run inference directly in the terminal: ./llama-cli -hf tchbcb/MiniCPM5-2B-cpu-mixed
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf tchbcb/MiniCPM5-2B-cpu-mixed # Run inference directly in the terminal: ./build/bin/llama-cli -hf tchbcb/MiniCPM5-2B-cpu-mixed
Use Docker
docker model run hf.co/tchbcb/MiniCPM5-2B-cpu-mixed
- LM Studio
- Jan
- Ollama
How to use tchbcb/MiniCPM5-2B-cpu-mixed with Ollama:
ollama run hf.co/tchbcb/MiniCPM5-2B-cpu-mixed
- Unsloth Desktop
- Docker Model Runner
How to use tchbcb/MiniCPM5-2B-cpu-mixed with Docker Model Runner:
docker model run hf.co/tchbcb/MiniCPM5-2B-cpu-mixed
- Lemonade
How to use tchbcb/MiniCPM5-2B-cpu-mixed with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull tchbcb/MiniCPM5-2B-cpu-mixed
Run and chat with the model
lemonade run user.MiniCPM5-2B-cpu-mixed-{{QUANT_TAG}}List all available models
lemonade list
- Atomic Chat
| /* minicpm_server.c - MiniCPM5-2B (Llama arch) mixed-precision CPU inference for LAL | |
| * | |
| * Architecture: 42 layers, 2048 hidden, 16Q/2KV heads (GQA 8:1), 128 head_dim, | |
| * 6144 MLP (SwiGLU), 130560 vocab, RoPE theta 5e6, RMSNorm eps 1e-6. | |
| * | |
| * Mixed precision (from GPQ8 file, per-tensor qtype): | |
| * qtype=0 F32 : norms | |
| * qtype=1 Q8 row : embed_tokens + lm_head (int8 + per-row fp32 scale) | |
| * qtype=3 Q8_0 : layer 0 and 41 (8-bit, sensitive edge layers), 34B/32elem | |
| * qtype=5 Q4_K : main body q/k/v/o/down + gate/up outside ternary range, 144B/256elem | |
| * qtype=6 TERNARY : gate/up of layers 8..31, 3-value {-s,0,+s}, 18B/64elem | |
| * (2B fp16 absmean scale + 8B bit0 stream + 8B bit1 stream; | |
| * kernel: t = b0 - b1, y = x_scale * sum_b s_b * (S0-S1)) | |
| * | |
| * Windows (MinGW-w64) port: CreateFileMapping mmap, QueryPerformanceCounter, | |
| * no dlopen/weak-symbol LAL bridge, no pthread (OpenMP only). | |
| * | |
| * Build: gcc -O3 -march=native -fopenmp -I. -o prebuilt/minicpm_server.exe | |
| * tools/server/minicpm_server.c -lm | |
| * Run: prebuilt/minicpm_server.exe --weights C:/models/minicpm_mixed.bin | |
| * --tokenizer C:/models/MiniCPM5-2B-cpu --prompt "..." --n 64 --threads 4 | |
| */ | |
| /* === Architecture constants (MiniCPM5-2B, LlamaForCausalLM) === */ | |
| /* === SIMD wrappers (AVX2) === */ | |
| typedef __m256 v8f; | |
| static inline float v8f_hsum(v8f v) { | |
| __m128 hi = _mm256_extractf128_ps(v, 1); | |
| __m128 lo = _mm256_castps256_ps128(v); | |
| __m128 s = _mm_add_ps(lo, hi); | |
| s = _mm_hadd_ps(s, s); s = _mm_hadd_ps(s, s); | |
| return _mm_cvtss_f32(s); | |
| } | |
| /* === Reusable LAL SDK headers === */ | |
| /* === Aligned alloc (portable) === */ | |
| static void *xalloc(size_t size, size_t align) { | |
| return _aligned_malloc(size ? size : 1, align); | |
| void *p = NULL; | |
| if (posix_memalign(&p, align, size ? size : 1)) return NULL; | |
| return p; | |
| } | |
| /* === Timer === */ | |
| static double now_sec(void) { | |
| static LARGE_INTEGER freq = {0}; | |
| LARGE_INTEGER c; | |
| if (!freq.QuadPart) QueryPerformanceFrequency(&freq); | |
| QueryPerformanceCounter(&c); | |
| return (double)c.QuadPart / (double)freq.QuadPart; | |
| struct timespec ts; | |
| clock_gettime(CLOCK_MONOTONIC, &ts); | |
| return ts.tv_sec + ts.tv_nsec * 1e-9; | |
| } | |
| /* === Windows read-only mmap === */ | |
| static void *g_mmap_base; | |
| static size_t g_mmap_size; | |
| static void *win_mmap_ro(const char *path, size_t *out_size) { | |
| HANDLE f = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, | |
| OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); | |
| if (f == INVALID_HANDLE_VALUE) { fprintf(stderr, "[!] cannot open %s\n", path); exit(1); } | |
| LARGE_INTEGER sz; | |
| GetFileSizeEx(f, &sz); | |
| HANDLE m = CreateFileMappingA(f, NULL, PAGE_READONLY, 0, 0, NULL); | |
| if (!m) { fprintf(stderr, "[!] CreateFileMapping failed\n"); exit(1); } | |
| void *p = MapViewOfFile(m, FILE_MAP_READ, 0, 0, 0); | |
| CloseHandle(m); CloseHandle(f); | |
| if (!p) { fprintf(stderr, "[!] MapViewOfFile failed\n"); exit(1); } | |
| *out_size = (size_t)sz.QuadPart; | |
| return p; | |
| } | |
| static void *g_mmap_base; | |
| static size_t g_mmap_size; | |
| static void *win_mmap_ro(const char *path, size_t *out_size) { | |
| int fd = open(path, O_RDONLY); | |
| if (fd < 0) { fprintf(stderr, "[!] cannot open %s\n", path); exit(1); } | |
| struct stat st; | |
| fstat(fd, &st); | |
| void *p = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); | |
| close(fd); | |
| if (p == MAP_FAILED) { fprintf(stderr, "[!] mmap failed\n"); exit(1); } | |
| madvise(p, st.st_size, MADV_SEQUENTIAL); | |
| *out_size = st.st_size; | |
| return p; | |
| } | |
| /* === Global state === */ | |
| static int g_n_threads = 1; | |
| static float g_temperature = 0.8f; | |
| static int g_top_k = 40; | |
| static float g_rep_penalty = 1.1f; | |
| static int g_recent[256], g_n_recent = 0; | |
| /* === TERNARY AVX2 kernel (qtype=6) === | |
| * Block: 18 bytes / 64 elems = [2B fp16 scale][8B bit0][8B bit1] | |
| * t_i = bit0_i - bit1_i in {-1,0,+1}; w_i ~= scale * t_i | |
| * dot = x_scale * sum_b scale_b * (S0_b - S1_b), S = sum(bit * xq) (int32) | |
| */ | |
| static uint8_t g_bit_lut[256][8]; | |
| static void bit_lut_init(void) { | |
| for (int b = 0; b < 256; b++) | |
| for (int j = 0; j < 8; j++) | |
| g_bit_lut[b][j] = (uint8_t)((b >> j) & 1); | |
| } | |
| /* binary dot over 32 elems: bits = 4 bytes (little-endian bitstream), xq = 32 int8 */ | |
| static inline int32_t bdot32(const uint8_t *bits, const int8_t *xq) { | |
| uint8_t exp32[32] __attribute__((aligned(32))); | |
| memcpy(exp32 + 0, g_bit_lut[bits[0]], 8); | |
| memcpy(exp32 + 8, g_bit_lut[bits[1]], 8); | |
| memcpy(exp32 + 16, g_bit_lut[bits[2]], 8); | |
| memcpy(exp32 + 24, g_bit_lut[bits[3]], 8); | |
| __m256i a = _mm256_loadu_si256((const __m256i *)exp32); /* unsigned 0/1 */ | |
| __m256i w = _mm256_loadu_si256((const __m256i *)xq); /* signed int8 */ | |
| __m256i r16 = _mm256_maddubs_epi16(a, w); /* 16 x int16 */ | |
| __m256i r32 = _mm256_madd_epi16(_mm256_set1_epi16(1), r16); /* 8 x int32 */ | |
| __m128i lo = _mm256_castsi256_si128(r32); | |
| __m128i hi = _mm256_extractf128_si256(r32, 1); | |
| __m128i s = _mm_add_epi32(lo, hi); | |
| s = _mm_add_epi32(s, _mm_shuffle_epi32(s, _MM_SHUFFLE(1, 0, 3, 2))); | |
| s = _mm_add_epi32(s, _mm_shuffle_epi32(s, _MM_SHUFFLE(2, 3, 0, 1))); | |
| return _mm_cvtsi128_si32(s); | |
| } | |
| static inline float tern_dot_row(const uint8_t *row, const int8_t *xq, | |
| float x_scale, int in_dim) { | |
| int nb = in_dim / 64; | |
| float acc = 0; | |
| for (int b = 0; b < nb; b++) { | |
| const uint8_t *blk = row + (size_t)b * 18; | |
| uint16_t s16; | |
| memcpy(&s16, blk, 2); | |
| __m128i sh = _mm_set1_epi16((short)s16); | |
| float s = _mm_cvtss_f32(_mm_cvtph_ps(sh)); | |
| const int8_t *xb = xq + (size_t)b * 64; | |
| int32_t i0 = bdot32(blk + 2, xb) + bdot32(blk + 6, xb + 32); | |
| int32_t i1 = bdot32(blk + 10, xb) + bdot32(blk + 14, xb + 32); | |
| acc += s * (float)(i0 - i1); | |
| } | |
| return acc * x_scale; | |
| } | |
| static void parallel_matmul_ternary(float *y, const uint8_t *W, | |
| const int8_t *xq, float x_scale, | |
| int in_dim, int out_dim) { | |
| int nb = in_dim / 64; | |
| int row_stride = nb * 18; | |
| if (g_n_threads <= 1 || out_dim < 1024) { | |
| for (int r = 0; r < out_dim; r++) | |
| y[r] = tern_dot_row(W + (size_t)r * row_stride, xq, x_scale, in_dim); | |
| return; | |
| } | |
| { | |
| int tid = omp_get_thread_num(); | |
| int n = omp_get_num_threads(); | |
| int chunk = (out_dim + n - 1) / n; | |
| int start = tid * chunk; | |
| int end = start + chunk; | |
| if (end > out_dim) end = out_dim; | |
| for (int r = start; r < end; r++) | |
| y[r] = tern_dot_row(W + (size_t)r * row_stride, xq, x_scale, in_dim); | |
| } | |
| } | |
| /* === Parallel Q8_0 matmul (packed 34B blocks, kernel quantizes x internally) === */ | |
| static void parallel_matmul_q8_0_w(float *y, const uint8_t *q8_0_W, | |
| const float *x, int in_dim, int out_dim) { | |
| if (g_n_threads <= 1 || out_dim < 1024) { | |
| lal_matmul_q8_0(y, q8_0_W, x, NULL, in_dim, out_dim); | |
| return; | |
| } | |
| int row_stride = (in_dim / 32) * 34; | |
| { | |
| int tid = omp_get_thread_num(); | |
| int n = omp_get_num_threads(); | |
| int chunk = (out_dim + n - 1) / n; | |
| int start = tid * chunk; | |
| int end = start + chunk; | |
| if (end > out_dim) end = out_dim; | |
| if (start < out_dim) | |
| lal_matmul_q8_0(y + start, q8_0_W + (size_t)start * row_stride, | |
| x, NULL, in_dim, end - start); | |
| } | |
| } | |
| /* === Parallel Q4_K matmul (prepared) === */ | |
| static void parallel_matmul_q4_k_p(float *y, const uint8_t *q4k_W, const float *x, | |
| int in_dim, int out_dim, | |
| const int8_t *xq, const int16_t *bsums, | |
| const int8_t *xq_arr, float x_scale) { | |
| if (g_n_threads <= 1 || out_dim < 1024) { | |
| lal_matmul_q4_k_prepared(y, q4k_W, x, NULL, in_dim, out_dim, | |
| xq, bsums, xq_arr, x_scale); | |
| return; | |
| } | |
| int row_stride = (in_dim / 256) * 144; | |
| { | |
| int tid = omp_get_thread_num(); | |
| int n = omp_get_num_threads(); | |
| int chunk = (out_dim + n - 1) / n; | |
| int start = tid * chunk; | |
| int end = start + chunk; | |
| if (end > out_dim) end = out_dim; | |
| if (start < out_dim) | |
| lal_matmul_q4_k_prepared(y + start, q4k_W + (size_t)start * row_stride, | |
| x, NULL, in_dim, end - start, | |
| xq, bsums, xq_arr, x_scale); | |
| } | |
| } | |
| static int8_t *g_wte_q; static float *g_wte_s; /* Q8row embedding */ | |
| static float *g_norm_f_w; | |
| static int8_t *g_lm_head_q; /* Q8row lm_head */ | |
| static float *g_lm_head_s; | |
| static float *g_x, *g_ln, *g_q, *g_k, *g_v, *g_attn_out, *g_proj, *g_mlp_out; | |
| static float *g_logits; | |
| static int8_t *g_xq_cache; | |
| static float **kv_k, **kv_v; | |
| static int g_rope_need = 0; | |
| /* === RMSNorm / RoPE / GQA wrappers === */ | |
| static void qwen_rms_norm(float *out, const float *x, const float *w, int n) { | |
| lal_rms_norm_simd(out, x, w, n, RMS_EPS); | |
| } | |
| static float g_rope_cos[N_CTX][HEAD_DIM / 2]; | |
| static float g_rope_sin[N_CTX][HEAD_DIM / 2]; | |
| static void rope_init(void) { | |
| for (int p = 0; p < N_CTX; p++) | |
| for (int d = 0; d < HEAD_DIM / 2; d++) { | |
| float theta = (float)p / powf(ROPE_THETA, (float)(2 * d) / HEAD_DIM); | |
| g_rope_cos[p][d] = cosf(theta); | |
| g_rope_sin[p][d] = sinf(theta); | |
| } | |
| } | |
| static void rope_apply(float *q, float *k, int pos) { | |
| lal_rope_apply_simd(q, k, N_HEAD, N_KV_HEAD, HEAD_DIM, pos, | |
| g_rope_cos[pos], g_rope_sin[pos]); | |
| } | |
| static void gqa_attn(float *out, const float *Q, const float *Kn, const float *Vn, | |
| int layer, int pos) { | |
| lal_gqa_attn_simd(out, Q, Kn, Vn, kv_k[layer], kv_v[layer], pos, | |
| N_HEAD, N_KV_HEAD, HEAD_DIM, N_Q_PER_KV, KV_DIM, N_CTX); | |
| } | |
| /* === GPQ8 tensor file === */ | |
| typedef struct { | |
| char key[128]; | |
| int ndim, shape[4]; | |
| int qtype; /* 0=F32, 1=Q8row, 3=Q8_0, 5=Q4_K, 6=TERNARY */ | |
| uint64_t data_len; | |
| void *data; | |
| int n_scale; | |
| float *scale; | |
| } GPQ8Tensor; | |
| static GPQ8Tensor *g_gp_tensors; | |
| static int g_gp_n; | |
| static GPQ8Tensor *gp_find(const char *key) { | |
| for (int i = 0; i < g_gp_n; i++) | |
| if (strcmp(g_gp_tensors[i].key, key) == 0) return &g_gp_tensors[i]; | |
| fprintf(stderr, "[!] tensor not found: %s\n", key); | |
| return NULL; | |
| } | |
| static void load_gpq8(const char *path) { | |
| printf("[*] mmap-loading %s ...\n", path); fflush(stdout); | |
| g_mmap_base = win_mmap_ro(path, &g_mmap_size); | |
| /* warm pages: touch 1 byte per 64KB (fast, avoids inference stalls) */ | |
| { | |
| volatile char sink = 0; | |
| const char *base = (const char *)g_mmap_base; | |
| for (size_t off = 0; off < g_mmap_size; off += 65536) sink += base[off]; | |
| printf("[*] mmap warmed (%.1f MB)\n", g_mmap_size / 1048576.0); fflush(stdout); | |
| } | |
| const unsigned char *p = (const unsigned char *)g_mmap_base; | |
| if (memcmp(p, "GPQ8", 4) != 0) { fprintf(stderr, "[!] bad magic\n"); exit(1); } | |
| p += 4; | |
| g_gp_n = *(const int *)p; p += 4; | |
| printf("[*] %d tensors (%.2f GB mmap'd)\n", g_gp_n, g_mmap_size / 1073741824.0); | |
| fflush(stdout); | |
| g_gp_tensors = calloc(g_gp_n, sizeof(GPQ8Tensor)); | |
| for (int i = 0; i < g_gp_n; i++) { | |
| GPQ8Tensor *t = &g_gp_tensors[i]; | |
| int klen = *(const int *)p; p += 4; | |
| memcpy(t->key, p, klen); t->key[klen] = 0; p += klen; | |
| t->ndim = *(const int *)p; p += 4; | |
| for (int d = 0; d < t->ndim; d++) { t->shape[d] = *(const int *)p; p += 4; } | |
| t->qtype = *p; p += 1; | |
| t->data_len = *(const uint64_t *)p; p += 8; | |
| t->data = (void *)p; p += t->data_len; | |
| t->n_scale = *(const int *)p; p += 4; | |
| t->scale = (t->n_scale > 0) ? (float *)p : NULL; | |
| if (t->n_scale > 0) p += (size_t)t->n_scale * 4; | |
| } | |
| printf("[*] all tensors mapped\n"); fflush(stdout); | |
| } | |
| static float *get_f32(const char *key) { | |
| GPQ8Tensor *t = gp_find(key); | |
| if (!t || t->qtype != 0) { fprintf(stderr, "[!] %s not F32\n", key); exit(1); } | |
| return (float *)t->data; | |
| } | |
| static const uint8_t *get_packed(const char *key, int want_qtype) { | |
| GPQ8Tensor *t = gp_find(key); | |
| if (!t || t->qtype != want_qtype) { | |
| fprintf(stderr, "[!] %s qtype=%d expected %d\n", key, t ? t->qtype : -1, want_qtype); | |
| exit(1); | |
| } | |
| return (const uint8_t *)t->data; | |
| } | |
| /* === Layer struct: per-matrix qtype + packed pointer === */ | |
| typedef struct { | |
| float *norm1_w, *norm2_w; | |
| int tq, tk, tv, to, tgate, tup, tdown; /* per-matrix qtype (3/5/6) */ | |
| const uint8_t *wq, *wk, *wv, *wo, *wgate, *wup, *wdown; | |
| } Layer; | |
| static Layer g_layers[N_LAYER]; | |
| /* === Mixed-precision matmul dispatch === */ | |
| static void matmul_single(float *y, int qtype, const uint8_t *W, | |
| const float *x, int in_dim, int out_dim) { | |
| if (qtype == 3) { | |
| parallel_matmul_q8_0_w(y, W, x, in_dim, out_dim); | |
| return; | |
| } | |
| /* prepare x once (shared by Q4_K and TERNARY) */ | |
| static int8_t xq[XQ_MAX] __attribute__((aligned(32))); | |
| static int16_t bsums[XQ_MAX / 32] __attribute__((aligned(32))); | |
| static int8_t xq_arr[XQ_MAX] __attribute__((aligned(32))); | |
| float xs = lal_q4k_prepare_x(x, in_dim, xq, bsums, xq_arr); | |
| if (qtype == 5) { | |
| parallel_matmul_q4_k_p(y, W, x, in_dim, out_dim, xq, bsums, xq_arr, xs); | |
| } else if (qtype == 6) { | |
| parallel_matmul_ternary(y, W, xq, xs, in_dim, out_dim); | |
| } else { | |
| fprintf(stderr, "[!] bad qtype %d\n", qtype); exit(1); | |
| } | |
| } | |
| /* QKV: share one prepare across q/k/v when any is Q4_K/TERNARY */ | |
| static void matmul_qkv(Layer *L, const float *x) { | |
| static int8_t xq[XQ_MAX] __attribute__((aligned(32))); | |
| static int16_t bsums[XQ_MAX / 32] __attribute__((aligned(32))); | |
| static int8_t xq_arr[XQ_MAX] __attribute__((aligned(32))); | |
| float xs = 0; | |
| if (L->tq == 5 || L->tq == 6 || L->tk == 5 || L->tv == 5) | |
| xs = lal_q4k_prepare_x(x, N_EMBD, xq, bsums, xq_arr); | |
| if (L->tq == 5) | |
| parallel_matmul_q4_k_p(g_q, L->wq, x, N_EMBD, Q_DIM, xq, bsums, xq_arr, xs); | |
| else if (L->tq == 6) | |
| parallel_matmul_ternary(g_q, L->wq, xq, xs, N_EMBD, Q_DIM); | |
| else | |
| parallel_matmul_q8_0_w(g_q, L->wq, x, N_EMBD, Q_DIM); | |
| if (L->tk == 5) | |
| parallel_matmul_q4_k_p(g_k, L->wk, x, N_EMBD, KV_DIM, xq, bsums, xq_arr, xs); | |
| else if (L->tk == 6) | |
| parallel_matmul_ternary(g_k, L->wk, xq, xs, N_EMBD, KV_DIM); | |
| else | |
| parallel_matmul_q8_0_w(g_k, L->wk, x, N_EMBD, KV_DIM); | |
| if (L->tv == 5) | |
| parallel_matmul_q4_k_p(g_v, L->wv, x, N_EMBD, KV_DIM, xq, bsums, xq_arr, xs); | |
| else if (L->tv == 6) | |
| parallel_matmul_ternary(g_v, L->wv, xq, xs, N_EMBD, KV_DIM); | |
| else | |
| parallel_matmul_q8_0_w(g_v, L->wv, x, N_EMBD, KV_DIM); | |
| } | |
| /* MLP: gate/up (mixed) + SiLU + down (prepared from act) */ | |
| static void mlp_forward(Layer *L, const float *x, float *out) { | |
| static float gate_buf[MLP_DIM], up_buf[MLP_DIM], act_buf[MLP_DIM]; | |
| static int8_t xq[XQ_MAX] __attribute__((aligned(32))); | |
| static int16_t bsums[XQ_MAX / 32] __attribute__((aligned(32))); | |
| static int8_t xq_arr[XQ_MAX] __attribute__((aligned(32))); | |
| float xs = lal_q4k_prepare_x(x, N_EMBD, xq, bsums, xq_arr); | |
| if (L->tgate == 5) | |
| parallel_matmul_q4_k_p(gate_buf, L->wgate, x, N_EMBD, MLP_DIM, xq, bsums, xq_arr, xs); | |
| else if (L->tgate == 6) | |
| parallel_matmul_ternary(gate_buf, L->wgate, xq, xs, N_EMBD, MLP_DIM); | |
| else | |
| parallel_matmul_q8_0_w(gate_buf, L->wgate, x, N_EMBD, MLP_DIM); | |
| if (L->tup == 5) | |
| parallel_matmul_q4_k_p(up_buf, L->wup, x, N_EMBD, MLP_DIM, xq, bsums, xq_arr, xs); | |
| else if (L->tup == 6) | |
| parallel_matmul_ternary(up_buf, L->wup, xq, xs, N_EMBD, MLP_DIM); | |
| else | |
| parallel_matmul_q8_0_w(up_buf, L->wup, x, N_EMBD, MLP_DIM); | |
| if (L->tdown == 5) { | |
| /* fused SiLU(gate)*up + quantize act + bsums + rearrange for Q4_K down */ | |
| static int8_t d_xq[XQ_MAX] __attribute__((aligned(32))); | |
| static int16_t d_bsums[XQ_MAX / 32] __attribute__((aligned(32))); | |
| static int8_t d_xq_arr[XQ_MAX] __attribute__((aligned(32))); | |
| float ds = lal_silu_mul_prepare_simd(act_buf, gate_buf, up_buf, MLP_DIM, | |
| d_xq, d_bsums, d_xq_arr); | |
| parallel_matmul_q4_k_p(out, L->wdown, act_buf, MLP_DIM, N_EMBD, | |
| d_xq, d_bsums, d_xq_arr, ds); | |
| } else if (L->tdown == 6) { | |
| lal_silu_mul_simd(act_buf, gate_buf, up_buf, MLP_DIM); | |
| static int8_t d_xq[XQ_MAX] __attribute__((aligned(32))); | |
| static int16_t d_bsums[XQ_MAX / 32] __attribute__((aligned(32))); | |
| static int8_t d_xq_arr[XQ_MAX] __attribute__((aligned(32))); | |
| float ds = lal_q4k_prepare_x(act_buf, MLP_DIM, d_xq, d_bsums, d_xq_arr); | |
| parallel_matmul_ternary(out, L->wdown, d_xq, ds, MLP_DIM, N_EMBD); | |
| } else { | |
| lal_silu_mul_simd(act_buf, gate_buf, up_buf, MLP_DIM); | |
| parallel_matmul_q8_0_w(out, L->wdown, act_buf, MLP_DIM, N_EMBD); | |
| } | |
| } | |
| static int forward(int tok, int pos) { | |
| int dbg = getenv("LAL_DEBUG") ? atoi(getenv("LAL_DEBUG")) : 0; | |
| if (tok < 0 || tok >= VOCAB_SIZE) tok = 0; | |
| /* embedding lookup (Q8row -> f32) */ | |
| lal_dequant_row_f32(g_wte_q + (size_t)tok * N_EMBD, g_x, g_wte_s[tok], N_EMBD); | |
| if (dbg) { | |
| float s = 0; for (int i = 0; i < N_EMBD; i++) s += g_x[i] * g_x[i]; | |
| fprintf(stderr, "[dbg] emb |x|=%.4f x0=%f\n", sqrtf(s), g_x[0]); | |
| } | |
| for (int l = 0; l < N_LAYER; l++) { | |
| Layer *L = &g_layers[l]; | |
| qwen_rms_norm(g_ln, g_x, L->norm1_w, N_EMBD); | |
| matmul_qkv(L, g_ln); | |
| if (dbg && l < 2) { | |
| float s = 0; for (int i = 0; i < Q_DIM; i++) s += g_q[i] * g_q[i]; | |
| fprintf(stderr, "[dbg] L%d q|.|=%.4f q0=%f\n", l, sqrtf(s), g_q[0]); | |
| } | |
| rope_apply(g_q, g_k, pos); | |
| gqa_attn(g_attn_out, g_q, g_k, g_v, l, pos); | |
| matmul_single(g_proj, L->to, L->wo, g_attn_out, Q_DIM, N_EMBD); | |
| lal_residual_add_simd(g_x, g_proj, N_EMBD); | |
| if (dbg && l < 2) { | |
| float s = 0; for (int i = 0; i < N_EMBD; i++) s += g_x[i] * g_x[i]; | |
| fprintf(stderr, "[dbg] L%d post-attn |x|=%.4f\n", l, sqrtf(s)); | |
| } | |
| qwen_rms_norm(g_ln, g_x, L->norm2_w, N_EMBD); | |
| mlp_forward(L, g_ln, g_mlp_out); | |
| if (dbg && l < 2) { | |
| float s = 0; for (int i = 0; i < N_EMBD; i++) s += g_mlp_out[i] * g_mlp_out[i]; | |
| fprintf(stderr, "[dbg] L%d mlp|.|=%.4f m0=%f\n", l, sqrtf(s), g_mlp_out[0]); | |
| } | |
| lal_residual_add_simd(g_x, g_mlp_out, N_EMBD); | |
| } | |
| qwen_rms_norm(g_ln, g_x, g_norm_f_w, N_EMBD); | |
| if (dbg) { | |
| float s = 0; for (int i = 0; i < N_EMBD; i++) s += g_ln[i] * g_ln[i]; | |
| fprintf(stderr, "[dbg] final |ln|=%.4f ln0=%f\n", sqrtf(s), g_ln[0]); | |
| } | |
| /* int8 LM head with abs-xq trick, parallel over vocab */ | |
| float scale_x = lal_quantize_x_int8(g_ln, g_xq_cache, N_EMBD); | |
| static uint8_t abs_xq[N_EMBD] __attribute__((aligned(32))); | |
| lal_compute_abs_xq(g_xq_cache, abs_xq, N_EMBD); | |
| { | |
| int tid = omp_get_thread_num(); | |
| int n = omp_get_num_threads(); | |
| int v_per = (VOCAB_SIZE + n - 1) / n; | |
| int v_start = tid * v_per; | |
| int v_end = v_start + v_per; | |
| if (v_end > VOCAB_SIZE) v_end = VOCAB_SIZE; | |
| if (v_start < VOCAB_SIZE) | |
| lal_lm_head_int8_range_abs(g_logits, g_xq_cache, abs_xq, scale_x, | |
| g_lm_head_q, g_lm_head_s, | |
| v_start, v_end, N_EMBD); | |
| } | |
| /* sample */ | |
| if (dbg) { | |
| int nan = 0; | |
| for (int v = 0; v < VOCAB_SIZE; v++) { | |
| float x = g_logits[v]; | |
| if (!(x == x) || x > 1e30f || x < -1e30f) nan++; | |
| } | |
| fprintf(stderr, "[dbg] logits nan/inf=%d L[0..7]=%.3f %.3f %.3f %.3f %.3f %.3f %.3f %.3f\n", | |
| nan, g_logits[0], g_logits[1], g_logits[2], g_logits[3], | |
| g_logits[4], g_logits[5], g_logits[6], g_logits[7]); | |
| } | |
| int next = lal_sample_token(g_logits, VOCAB_SIZE, g_temperature, g_top_k, | |
| g_rep_penalty, g_recent, g_n_recent); | |
| if (g_n_recent < 256) g_recent[g_n_recent++] = next; | |
| else { memmove(g_recent, g_recent + 1, 255 * sizeof(int)); g_recent[255] = next; } | |
| return next; | |
| } | |
| /* === Tokenizer: vocab hash + greedy longest-match over byte-level BPE space === */ | |
| typedef struct { char key[512]; int id; } TEntry; | |
| static TEntry g_htab[TOK_HASH_SIZE]; | |
| static char **g_vocab_str; | |
| static int g_vocab_str_n; | |
| static unsigned tok_hash(const char *s, int len) { | |
| unsigned h = 2166136261u; | |
| for (int i = 0; i < len; i++) { h ^= (unsigned char)s[i]; h *= 16777619u; } | |
| return h & (TOK_HASH_SIZE - 1); | |
| } | |
| static void tins(const char *key, int id) { | |
| unsigned h = tok_hash(key, (int)strlen(key)); | |
| while (g_htab[h].key[0]) h = (h + 1) & (TOK_HASH_SIZE - 1); | |
| strncpy(g_htab[h].key, key, 511); | |
| g_htab[h].key[511] = 0; | |
| g_htab[h].id = id; | |
| } | |
| static int tok_find(const char *key) { | |
| unsigned h = tok_hash(key, (int)strlen(key)); | |
| while (g_htab[h].key[0]) { | |
| if (strcmp(g_htab[h].key, key) == 0) return g_htab[h].id; | |
| h = (h + 1) & (TOK_HASH_SIZE - 1); | |
| } | |
| return -1; | |
| } | |
| static void load_tokenizer(const char *dir) { | |
| /* vocab.tsv: "id<TAB>token" lines, generated from tokenizer.json | |
| * (model.vocab + added_tokens merged; token escaped: \\ \t \n) */ | |
| char path[1024]; | |
| snprintf(path, sizeof(path), "%s/vocab.tsv", dir); | |
| FILE *f = fopen(path, "rb"); | |
| if (!f) { fprintf(stderr, "[!] cannot open %s\n", path); exit(1); } | |
| int mx = VOCAB_SIZE + 200; | |
| g_vocab_str = calloc(mx, sizeof(char *)); | |
| char line[1024]; | |
| int cnt = 0; | |
| while (fgets(line, sizeof line, f)) { | |
| char *tab = strchr(line, '\t'); | |
| if (!tab) continue; | |
| *tab = 0; | |
| int id = atoi(line); | |
| char *tok = tab + 1; | |
| int len = (int)strlen(tok); | |
| while (len && (tok[len - 1] == '\n' || tok[len - 1] == '\r')) tok[--len] = 0; | |
| char real[512]; | |
| int r = 0; | |
| for (int i = 0; i < len && r < 511; i++) { | |
| if (tok[i] == '\\' && i + 1 < len) { | |
| i++; | |
| if (tok[i] == 't') real[r++] = '\t'; | |
| else if (tok[i] == 'n') real[r++] = '\n'; | |
| else if (tok[i] == 'r') real[r++] = '\r'; | |
| else real[r++] = tok[i]; | |
| } else real[r++] = tok[i]; | |
| } | |
| real[r] = 0; | |
| if (id >= 0 && id < mx) { | |
| g_vocab_str[id] = strdup(real); | |
| tins(real, id); | |
| if (id + 1 > g_vocab_str_n) g_vocab_str_n = id + 1; | |
| cnt++; | |
| } | |
| } | |
| fclose(f); | |
| printf("[*] tokenizer: %d tokens (max id %d)\n", cnt, g_vocab_str_n - 1); | |
| } | |
| /* convert raw utf-8 text to byte-level BPE "magic" string */ | |
| static int text_to_magic(const char *text, char *out, int cap) { | |
| int n = 0; | |
| for (const char *p = text; *p; p++) { | |
| unsigned cp = lal_bpe_cp_for_byte((unsigned char)*p); | |
| char tmp[5]; | |
| int m = lal_utf8_encode(cp, tmp); | |
| for (int i = 0; i < m && n < cap - 1; i++) out[n++] = tmp[i]; | |
| } | |
| out[n] = 0; | |
| return n; | |
| } | |
| /* greedy longest-match encoding over the magic string */ | |
| static int *encode_text(const char *text, int *n_out) { | |
| int cap = (int)strlen(text) * 4 + 8; | |
| char *magic = malloc(cap); | |
| int mlen = text_to_magic(text, magic, cap); | |
| int *ids = malloc((mlen + 16) * sizeof(int)); | |
| int n = 0, pos = 0; | |
| while (pos < mlen) { | |
| int max_len = mlen - pos; | |
| if (max_len > 48) max_len = 48; | |
| int found = -1; | |
| char tmp[64]; | |
| for (int len = max_len; len >= 1; len--) { | |
| memcpy(tmp, magic + pos, len); | |
| tmp[len] = 0; | |
| found = tok_find(tmp); | |
| if (found >= 0) { pos += len; break; } | |
| } | |
| if (found < 0) { /* single magic char should always match */ | |
| fprintf(stderr, "[!] encode stuck at %d\n", pos); | |
| pos++; | |
| continue; | |
| } | |
| ids[n++] = found; | |
| } | |
| free(magic); | |
| *n_out = n; | |
| return ids; | |
| } | |
| static void decode_token(int id, char *out, int maxlen) { | |
| if (id < 0 || id >= g_vocab_str_n || !g_vocab_str[id]) { out[0] = 0; return; } | |
| lal_decode_bpe_token(g_vocab_str[id], out, maxlen); | |
| } | |
| /* === Main === */ | |
| int main(int argc, char **argv) { | |
| srand((unsigned)time(NULL)); | |
| const char *weights = "C:/models/minicpm_mixed.bin"; | |
| const char *tokdir = "C:/models/MiniCPM5-2B-cpu"; | |
| const char *prompt = NULL; | |
| const char *prompt_file = NULL; | |
| int n_gen = 48; | |
| for (int i = 1; i < argc; i++) { | |
| if (!strcmp(argv[i], "--weights") && i + 1 < argc) weights = argv[++i]; | |
| else if (!strcmp(argv[i], "--tokenizer") && i + 1 < argc) tokdir = argv[++i]; | |
| else if (!strcmp(argv[i], "--prompt") && i + 1 < argc) prompt = argv[++i]; | |
| else if (!strcmp(argv[i], "--prompt-file") && i + 1 < argc) prompt_file = argv[++i]; | |
| else if (!strcmp(argv[i], "--n") && i + 1 < argc) n_gen = atoi(argv[++i]); | |
| else if (!strcmp(argv[i], "--threads") && i + 1 < argc) g_n_threads = atoi(argv[++i]); | |
| else if (!strcmp(argv[i], "--temp") && i + 1 < argc) g_temperature = (float)atof(argv[++i]); | |
| else if (!strcmp(argv[i], "--top-k") && i + 1 < argc) g_top_k = atoi(argv[++i]); | |
| else if (!strcmp(argv[i], "--rep-penalty") && i + 1 < argc) g_rep_penalty = (float)atof(argv[++i]); | |
| else { fprintf(stderr, "[?] unknown arg %s\n", argv[i]); } | |
| } | |
| static char prompt_buf[4096]; | |
| if (prompt_file) { | |
| FILE *pf = fopen(prompt_file, "rb"); | |
| if (!pf) { fprintf(stderr, "[!] cannot open %s\n", prompt_file); return 1; } | |
| size_t pn = fread(prompt_buf, 1, sizeof(prompt_buf) - 1, pf); | |
| while (pn && (prompt_buf[pn - 1] == '\n' || prompt_buf[pn - 1] == '\r')) pn--; | |
| prompt_buf[pn] = 0; | |
| fclose(pf); | |
| prompt = prompt_buf; | |
| } | |
| if (!prompt) prompt = "Hello"; | |
| printf("=== MiniCPM5-2B (LAL, mixed-precision: Q4_K body + Q8_0 edges + TERNARY gate/up) ===\n"); | |
| printf("[*] %d layers, %d hidden, %dQ/%dKV heads, %d head_dim, %d MLP, %d vocab\n", | |
| N_LAYER, N_EMBD, N_HEAD, N_KV_HEAD, HEAD_DIM, MLP_DIM, VOCAB_SIZE); | |
| printf("[*] threads=%d temp=%.2f top_k=%d\n", g_n_threads, g_temperature, g_top_k); | |
| bit_lut_init(); | |
| load_gpq8(weights); | |
| /* wire global tensors */ | |
| g_norm_f_w = get_f32("model.norm.weight"); | |
| { | |
| GPQ8Tensor *t = gp_find("model.embed_tokens.weight"); | |
| if (!t || t->qtype != 1 || t->n_scale != VOCAB_SIZE) { | |
| fprintf(stderr, "[!] embed_tokens Q8row mismatch\n"); exit(1); | |
| } | |
| g_wte_q = (int8_t *)t->data; | |
| g_wte_s = t->scale; | |
| } | |
| { | |
| GPQ8Tensor *t = gp_find("lm_head.weight"); | |
| if (!t || t->qtype != 1 || t->n_scale != VOCAB_SIZE) { | |
| fprintf(stderr, "[!] lm_head Q8row mismatch\n"); exit(1); | |
| } | |
| g_lm_head_q = (int8_t *)t->data; | |
| g_lm_head_s = t->scale; | |
| } | |
| /* wire layers (per-matrix mixed precision) */ | |
| char key[256]; | |
| int cnt_q8_0 = 0, cnt_q4k = 0, cnt_tern = 0; | |
| for (int l = 0; l < N_LAYER; l++) { | |
| Layer *L = &g_layers[l]; | |
| snprintf(key, sizeof(key), "model.layers.%d.input_layernorm.weight", l); | |
| L->norm1_w = get_f32(key); | |
| snprintf(key, sizeof(key), "model.layers.%d.post_attention_layernorm.weight", l); | |
| L->norm2_w = get_f32(key); | |
| struct { const char *name; int *t; const uint8_t **w; } mats[7] = { | |
| {"self_attn.q_proj.weight", &L->tq, &L->wq}, | |
| {"self_attn.k_proj.weight", &L->tk, &L->wk}, | |
| {"self_attn.v_proj.weight", &L->tv, &L->wv}, | |
| {"self_attn.o_proj.weight", &L->to, &L->wo}, | |
| {"mlp.gate_proj.weight", &L->tgate, &L->wgate}, | |
| {"mlp.up_proj.weight", &L->tup, &L->wup}, | |
| {"mlp.down_proj.weight", &L->tdown, &L->wdown}, | |
| }; | |
| for (int m = 0; m < 7; m++) { | |
| snprintf(key, sizeof(key), "model.layers.%d.%s", l, mats[m].name); | |
| GPQ8Tensor *t = gp_find(key); | |
| if (!t) exit(1); | |
| *mats[m].t = t->qtype; | |
| *mats[m].w = (const uint8_t *)t->data; | |
| if (t->qtype == 3) cnt_q8_0++; | |
| else if (t->qtype == 5) cnt_q4k++; | |
| else if (t->qtype == 6) cnt_tern++; | |
| } | |
| } | |
| printf("[*] layer matrices: %d x Q8_0 (8-bit edges), %d x Q4_K (4-bit), %d x TERNARY (3-value)\n", | |
| cnt_q8_0, cnt_q4k, cnt_tern); | |
| /* working buffers */ | |
| g_x = memalign(32, N_EMBD * sizeof(float)); | |
| g_ln = memalign(32, N_EMBD * sizeof(float)); | |
| g_q = memalign(32, Q_DIM * sizeof(float)); | |
| g_k = memalign(32, KV_DIM * sizeof(float)); | |
| g_v = memalign(32, KV_DIM * sizeof(float)); | |
| g_attn_out = memalign(32, Q_DIM * sizeof(float)); | |
| g_proj = memalign(32, N_EMBD * sizeof(float)); | |
| g_mlp_out = memalign(32, N_EMBD * sizeof(float)); | |
| g_logits = memalign(32, VOCAB_SIZE * sizeof(float)); | |
| g_xq_cache = memalign(32, N_EMBD); | |
| /* KV cache: 42 x 2048 x 256 x 4B x 2 = 176 MB */ | |
| kv_k = malloc(N_LAYER * sizeof(float *)); | |
| kv_v = malloc(N_LAYER * sizeof(float *)); | |
| for (int l = 0; l < N_LAYER; l++) { | |
| kv_k[l] = memalign(32, (size_t)N_CTX * KV_DIM * sizeof(float)); | |
| kv_v[l] = memalign(32, (size_t)N_CTX * KV_DIM * sizeof(float)); | |
| } | |
| printf("[*] KV cache: %.0f MB\n", (double)N_LAYER * N_CTX * KV_DIM * 4 * 2 / 1048576); | |
| rope_init(); | |
| load_tokenizer(tokdir); | |
| /* special token ids (resolved from vocab) */ | |
| int tok_bos = tok_find("<s>"); | |
| int tok_im_start = tok_find("<|im_start|>"); | |
| int tok_im_end = tok_find("<|im_end|>"); | |
| printf("[*] special tokens: <s>=%d <|im_start|>=%d <|im_end|>=%d\n", | |
| tok_bos, tok_im_start, tok_im_end); | |
| /* build chat prompt (MiniCPM5 template, thinking disabled): | |
| * <s><|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n */ | |
| int *pids = malloc((strlen(prompt) * 4 + 128) * sizeof(int)); | |
| int n_prompt = 0; | |
| pids[n_prompt++] = tok_bos; | |
| int nt; | |
| int *t; | |
| t = encode_text("<|im_start|>user\n", &nt); | |
| for (int i = 0; i < nt; i++) pids[n_prompt++] = t[i]; | |
| free(t); | |
| t = encode_text(prompt, &nt); | |
| for (int i = 0; i < nt; i++) pids[n_prompt++] = t[i]; | |
| free(t); | |
| /* debug: print prompt token ids + text */ | |
| { | |
| int echo = getenv("ECHO_TOKENS") ? atoi(getenv("ECHO_TOKENS")) : 0; | |
| if (echo) { | |
| int base = 1; /* skip bos for display mapping */ | |
| fprintf(stderr, "[echo] prompt pieces:\n"); | |
| char buf[256]; | |
| for (int i = 1; i < n_prompt; i++) { | |
| decode_token(pids[i], buf, (int)sizeof(buf)); | |
| fprintf(stderr, " %d -> id=%d text=[%s]\n", i, pids[i], buf); | |
| } | |
| (void)base; | |
| } | |
| } | |
| t = encode_text("<|im_end|>\n", &nt); | |
| for (int i = 0; i < nt; i++) pids[n_prompt++] = t[i]; | |
| free(t); | |
| t = encode_text("<|im_start|>assistant\n<think>\n\n</think>\n\n", &nt); | |
| for (int i = 0; i < nt; i++) pids[n_prompt++] = t[i]; | |
| free(t); | |
| printf("[*] prompt: %d tokens (incl. chat template)\n", n_prompt); | |
| printf("[*] generating %d tokens...\n\n", n_gen); | |
| double t0 = now_sec(); | |
| int pos = 0, next = -1; | |
| for (int i = 0; i < n_prompt; i++) { | |
| next = forward(pids[i], pos); | |
| pos++; | |
| if (pos >= N_CTX) break; | |
| } | |
| double t_prefill = now_sec() - t0; | |
| int gen_count = 0; | |
| char out_buf[65536] = {0}; | |
| int opos = 0; | |
| double t_gen0 = now_sec(); | |
| for (int g = 0; g < n_gen && pos < N_CTX; g++) { | |
| char ts[256]; | |
| if (next >= 0 && next < g_vocab_str_n && g_vocab_str[next]) | |
| lal_decode_bpe_token(g_vocab_str[next], ts, (int)sizeof(ts)); | |
| else ts[0] = 0; | |
| int slen = (int)strlen(ts); | |
| if (opos + slen < (int)sizeof(out_buf) - 1) { | |
| memcpy(out_buf + opos, ts, slen); | |
| opos += slen; | |
| } | |
| if (next == tok_im_end || next == 1) break; /* EOS */ | |
| printf("%s", ts); | |
| fflush(stdout); | |
| next = forward(next, pos); | |
| pos++; | |
| gen_count++; | |
| } | |
| out_buf[opos] = 0; | |
| double t_gen = now_sec() - t_gen0; | |
| printf("\n\n[*] prefill: %d tokens in %.2fs (%.0f tok/s)\n", | |
| n_prompt, t_prefill, n_prompt / (t_prefill + 1e-9)); | |
| printf("[*] decode: %d tokens in %.2fs (%.1f tok/s, %d threads)\n", | |
| gen_count, t_gen, gen_count / (t_gen + 1e-9), g_n_threads); | |
| printf("[*] output: %s\n", out_buf); | |
| free(pids); | |
| return 0; | |
| } | |