Instructions to use HelloSun/flint_cpu_opt 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 HelloSun/flint_cpu_opt 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 HelloSun/flint_cpu_opt:UD-Q4_K_XL # Run inference directly in the terminal: llama cli -hf HelloSun/flint_cpu_opt:UD-Q4_K_XL
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf HelloSun/flint_cpu_opt:UD-Q4_K_XL # Run inference directly in the terminal: llama cli -hf HelloSun/flint_cpu_opt:UD-Q4_K_XL
Build from source code
git clone https://github.com/ggml-org/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-app # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama serve -hf HelloSun/flint_cpu_opt:UD-Q4_K_XL # Run inference directly in the terminal: ./build/bin/llama cli -hf HelloSun/flint_cpu_opt:UD-Q4_K_XL
Use Docker
docker model run hf.co/HelloSun/flint_cpu_opt:UD-Q4_K_XL
- Llama new
- LM Studio
- Jan
- vLLM
How to use HelloSun/flint_cpu_opt with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "HelloSun/flint_cpu_opt" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "HelloSun/flint_cpu_opt", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/HelloSun/flint_cpu_opt:UD-Q4_K_XL
- Ollama
How to use HelloSun/flint_cpu_opt with Ollama:
ollama run hf.co/HelloSun/flint_cpu_opt:UD-Q4_K_XL
- Unsloth Desktop
- Pi
How to use HelloSun/flint_cpu_opt with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf HelloSun/flint_cpu_opt:UD-Q4_K_XL
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "HelloSun/flint_cpu_opt:UD-Q4_K_XL" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use HelloSun/flint_cpu_opt with Docker Model Runner:
docker model run hf.co/HelloSun/flint_cpu_opt:UD-Q4_K_XL
- Lemonade
How to use HelloSun/flint_cpu_opt with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull HelloSun/flint_cpu_opt:UD-Q4_K_XL
Run and chat with the model
lemonade run user.flint_cpu_opt-UD-Q4_K_XL
List all available models
lemonade list
- Hermes Agent
How to use HelloSun/flint_cpu_opt with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf HelloSun/flint_cpu_opt:UD-Q4_K_XL
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default HelloSun/flint_cpu_opt:UD-Q4_K_XL
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use HelloSun/flint_cpu_opt with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf HelloSun/flint_cpu_opt:UD-Q4_K_XL
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "HelloSun/flint_cpu_opt:UD-Q4_K_XL" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Download patches/flint-cpu-opt.patch from HelloSun/flint_cpu_opt: direct link, hf CLI and curl.
- Browser
- Download file 96.2 kB
-
https://huggingface.co/HelloSun/flint_cpu_opt/resolve/main/patches/flint-cpu-opt.patch
- Command line
-
hf download hf://HelloSun/flint_cpu_opt/patches/flint-cpu-opt.patch
-
curl -L -o flint-cpu-opt.patch https://huggingface.co/HelloSun/flint_cpu_opt/resolve/main/patches/flint-cpu-opt.patch
96.2 kB
| diff --git a/ggml/include/ggml.h b/ggml/include/ggml.h | |
| index d62a63104..0cc5ed9cb 100644 | |
| --- a/ggml/include/ggml.h | |
| +++ b/ggml/include/ggml.h | |
| extern "C" { | |
| struct ggml_tensor * state, | |
| int64_t K); | |
| + // Cache variant of ggml_gated_delta_net (single-snapshot, K == 1): instead of a gathered copy of the | |
| + // initial state, the state of sequence i is read straight from row ids[i] of the state cache, and the | |
| + // final state is written in place to row i of state_dst (a view of consecutive cache rows), so the caller | |
| + // needs no get_rows before and no cpy after the op. | |
| + // state_cache : [S_v*S_v*H_v, n_cells] f32, contiguous rows | |
| + // ids : [n_seqs] i32, source cell of every sequence | |
| + // state_dst : [S_v*S_v*H_v, n_seqs] f32 view into the cache (destination cells) | |
| + // The packed output keeps the layout of ggml_gated_delta_net; its snapshot part is left unwritten. | |
| + GGML_API struct ggml_tensor * ggml_gated_delta_net_cache( | |
| + struct ggml_context * ctx, | |
| + struct ggml_tensor * q, | |
| + struct ggml_tensor * k, | |
| + struct ggml_tensor * v, | |
| + struct ggml_tensor * g, | |
| + struct ggml_tensor * beta, | |
| + struct ggml_tensor * state_cache, | |
| + struct ggml_tensor * ids, | |
| + struct ggml_tensor * state_dst); | |
| + | |
| // DSA lightning indexer | |
| // | |
| // q: [n_embd_idx, n_head_idx, n_batch, ne3 ] | |
| diff --git a/ggml/src/ggml-cpu/amx/amx.cpp b/ggml/src/ggml-cpu/amx/amx.cpp | |
| index e90c2797f..e8b61a2eb 100644 | |
| --- a/ggml/src/ggml-cpu/amx/amx.cpp | |
| +++ b/ggml/src/ggml-cpu/amx/amx.cpp | |
| class extra_buffer_type : ggml::cpu::extra_buffer_type { | |
| if (op->ne[0] % (TILE_N * 2)) { | |
| return false; | |
| } | |
| + // machine-specific knobs: keep selected types out of the AMX buffer (decode then uses the VNNI vec_dot kernels) | |
| + { | |
| + // Q6_K (lm_head): the AVX512-VNNI vec_dot is ~9% faster for decode than the AMX M=1 kernel; opt back in with GGML_AMX_Q6K=1 | |
| + static const char * keep_q6 = getenv("GGML_AMX_Q6K"); | |
| + if (src0->type == GGML_TYPE_Q6_K && !(keep_q6 && keep_q6[0] == '1')) { return false; } | |
| + static const char * skip = getenv("GGML_AMX_SKIP"); | |
| + if (skip) { | |
| + if (src0->type == GGML_TYPE_Q6_K && strstr(skip, "q6_K")) { return false; } | |
| + if (src0->type == GGML_TYPE_Q8_0 && strstr(skip, "q8_0")) { return false; } | |
| + } | |
| + } | |
| int alignment; | |
| switch (src0->type) { | |
| case GGML_TYPE_Q4_0: | |
| diff --git a/ggml/src/ggml-cpu/amx/mmq.cpp b/ggml/src/ggml-cpu/amx/mmq.cpp | |
| index 9f3a744b5..d23e4d971 100644 | |
| --- a/ggml/src/ggml-cpu/amx/mmq.cpp | |
| +++ b/ggml/src/ggml-cpu/amx/mmq.cpp | |
| struct tinygemm_kernel_vnni<block_q8_K, block_iq4_xs, float, BLOCK_M, BLOCK_N, B | |
| (const char *)src0->data + src0_offset + PACKED_INDEX(nb * kTilesN, 0, KB, TILE_SIZE), \ | |
| (float *) dst->data + dst_offset + nb_start, ldc) | |
| +// Q8_0 x Q8_0 AMX gemm with register accumulation: one 16x16 output tile at a time, its 16 float row | |
| +// accumulators live in zmm registers across all K blocks (the generic kernel read-modify-writes C through memory | |
| +// every 32-wide K block and converts the A scale per row). The AMX product of block i+1 is issued before the AVX | |
| +// epilogue of block i so the two overlap. Per-block math (cvt(int32) * (dW*dA) accumulated with an fma, blocks in | |
| +// order) is identical to acc_C, so results are bit-identical. Returns false when KB is too large for the scale cache. | |
| +// tile registers are pasted into the asm string by the intrinsics, so they must be literals: one function per slot | |
| +#define Q8_REGACC_ISSUE_FN(NAME, TB_, TA_, TC_, SLOT) \ | |
| +static ALWAYS_INLINE void NAME(const block_q8_0 * RESTRICT A, const char * RESTRICT B, int KB, int u, int t, \ | |
| + int mrows, int i, int8_t (*TileA)[TILE_M * TILE_K], int32_t (*TileC)[TILE_M * TILE_N]) { \ | |
| + const int TILE_SIZE = get_tile_size<block_q8_0>(); /* 16 x 34 B + 16 x int32 compensation */ \ | |
| + const char * Bb = B + PACKED_INDEX(t, i, KB, TILE_SIZE); \ | |
| + _tile_loadd(TB_, Bb, TILE_N * VNNI_BLK); \ | |
| + const block_q8_0 * Ab = A + (size_t) (u * TILE_M) * KB + i; \ | |
| + if (mrows == TILE_M) { \ | |
| + _tile_loadd(TA_, Ab->qs, KB * sizeof(block_q8_0)); \ | |
| + } else { \ | |
| + unpack_A(TileA[SLOT], Ab, KB, mrows); \ | |
| + _tile_loadd(TA_, TileA[SLOT], TILE_K); \ | |
| + } \ | |
| + _tile_zero(TC_); \ | |
| + _tile_dpbssd(TC_, TA_, TB_); \ | |
| + _tile_stored(TC_, TileC[SLOT], TILE_N * sizeof(int32_t)); \ | |
| +} | |
| +Q8_REGACC_ISSUE_FN(q8_regacc_issue0, 0, 2, 4, 0) | |
| +Q8_REGACC_ISSUE_FN(q8_regacc_issue1, 1, 3, 5, 1) | |
| +#undef Q8_REGACC_ISSUE_FN | |
| + | |
| +static bool tinygemm_kernel_amx_q8_0_regacc(int M, int KB, const block_q8_0 * RESTRICT A, const char * RESTRICT B, | |
| + float * RESTRICT C, int ldc) { | |
| + constexpr int MAX_KB = 256; | |
| + if (KB > MAX_KB || KB < 1) { | |
| + return false; | |
| + } | |
| + const int TILE_SIZE = get_tile_size<block_q8_0>(); /* 16 x 34 B + 16 x int32 compensation */ | |
| + constexpr int SCALE_OFF = TILE_N * TILE_K; | |
| + | |
| + alignas(64) static thread_local int8_t TileA[2][TILE_M * TILE_K]; | |
| + alignas(64) static thread_local int32_t TileC[2][TILE_M * TILE_N]; | |
| + alignas(64) static thread_local float sA[2 * TILE_M][MAX_KB]; | |
| + | |
| + const int rows = std::min(M, 2 * TILE_M); | |
| + for (int r = 0; r < 2 * TILE_M; ++r) { | |
| + for (int i = 0; i < KB; ++i) { | |
| + sA[r][i] = r < rows ? GGML_CPU_FP16_TO_FP32(A[(size_t) r * KB + i].d) : 0.0f; | |
| + } | |
| + } | |
| + | |
| + const int nu = rows > TILE_M ? 2 : 1; | |
| + for (int u = 0; u < nu; ++u) { | |
| + const int mrows = std::min(TILE_M, rows - u * TILE_M); | |
| + for (int t = 0; t < 2; ++t) { | |
| + __m512 acc[TILE_M]; | |
| + #pragma GCC unroll 16 | |
| + for (int m = 0; m < TILE_M; ++m) { | |
| + acc[m] = _mm512_setzero_ps(); | |
| + } | |
| + | |
| + q8_regacc_issue0(A, B, KB, u, t, mrows, 0, TileA, TileC); | |
| + for (int i = 0; i < KB; ++i) { | |
| + const int slot = i & 1; | |
| + if (i + 1 < KB) { | |
| + if (slot == 0) { | |
| + q8_regacc_issue1(A, B, KB, u, t, mrows, i + 1, TileA, TileC); | |
| + } else { | |
| + q8_regacc_issue0(A, B, KB, u, t, mrows, i + 1, TileA, TileC); | |
| + } | |
| + } | |
| + const __m512 vd0 = _mm512_cvtph_ps(_mm256_loadu_si256((const __m256i *) (B + PACKED_INDEX(t, i, KB, TILE_SIZE) + SCALE_OFF))); | |
| + const int32_t * tile = TileC[slot]; | |
| + const float * sa = &sA[u * TILE_M][0]; | |
| + #pragma GCC unroll 16 | |
| + for (int m = 0; m < TILE_M; ++m) { | |
| + const __m512 vtile = _mm512_cvtepi32_ps(_mm512_loadu_si512(tile + m * TILE_N)); | |
| + const __m512 vd1 = _mm512_set1_ps(sa[(size_t) m * MAX_KB + i]); | |
| + acc[m] = _mm512_fmadd_ps(vtile, _mm512_mul_ps(vd0, vd1), acc[m]); | |
| + } | |
| + } | |
| + for (int m = 0; m < mrows; ++m) { | |
| + _mm512_storeu_ps(C + (size_t) (u * TILE_M + m) * ldc + t * TILE_N, acc[m]); | |
| + } | |
| + } | |
| + } | |
| + return true; | |
| +} | |
| + | |
| template <typename TA, typename TB, typename TC, int BLOCK_K, | |
| typename std::enable_if<!is_type_qkk<TB>::value, int>::type = 0> | |
| void tinygemm_kernel_amx(int M, int N, int KB, const void * RESTRICT _A, const void * RESTRICT _B, TC * RESTRICT C, int ldc) { | |
| void tinygemm_kernel_amx(int M, int N, int KB, const void * RESTRICT _A, const v | |
| const bool need_unpack = do_unpack<TB>::value; | |
| GGML_ASSERT(M <= 2 * TILE_M && N == 2 * TILE_N); | |
| + if constexpr (std::is_same<TB, block_q8_0>::value && std::is_same<TA, block_q8_0>::value) { | |
| + static const bool regacc = [] { const char * e = getenv("GGML_AMX_Q8_REGACC"); return !(e && e[0] == '0'); }(); | |
| + if (regacc && tinygemm_kernel_amx_q8_0_regacc(M, KB, static_cast<const block_q8_0 *>(_A), static_cast<const char *>(_B), C, ldc)) { | |
| + return; | |
| + } | |
| + } | |
| const TA * RESTRICT A = static_cast<const TA *>(_A); | |
| const char * RESTRICT B = static_cast<const char *>(_B); | |
| void ggml_backend_amx_mul_mat(const ggml_compute_params * params, struct ggml_te | |
| if (M == 1) { | |
| // MB = 1 and handle 8 tiles in each block | |
| - constexpr int kTilesN = 4; | |
| - constexpr int BLOCK_N = TILE_N * kTilesN; | |
| + // small N (e.g. 512) would give fewer tasks than threads with 64-wide blocks; use 32-wide blocks | |
| + const int BLOCK_N = (N <= 1024) ? 32 : 64; | |
| + const int kTilesN = BLOCK_N / TILE_N; | |
| const int NB = div_up(N, BLOCK_N); | |
| parallel_for_ggml(params, n_batch * NB, [&](int begin, int end) { | |
| diff --git a/ggml/src/ggml-cpu/arch/x86/quants.c b/ggml/src/ggml-cpu/arch/x86/quants.c | |
| index 21cd6312a..37efb7fae 100644 | |
| --- a/ggml/src/ggml-cpu/arch/x86/quants.c | |
| +++ b/ggml/src/ggml-cpu/arch/x86/quants.c | |
| void ggml_vec_dot_q4_1_q8_1(int n, float * GGML_RESTRICT s, size_t bs, const voi | |
| __m256 acc = _mm256_setzero_ps(); | |
| float summs = 0; | |
| + float vtot = 0.0f; | |
| + | |
| +#if defined(__AVX512F__) && defined(__AVX512BW__) && defined(__AVX512VL__) && defined(__AVX512DQ__) && defined(__AVX512VNNI__) && defined(__F16C__) | |
| + // AVX512-VNNI path: two 32-wide blocks per iteration. x codes are 0..15 (unsigned), y codes are int8, so | |
| + // vpdpbusd gives the exact int32 sums; per-block d*dy is applied per 8-lane half, m*sy is accumulated separately. | |
| + { | |
| + static int q41pf = -1; | |
| + if (q41pf < 0) { const char * e = getenv("GGML_Q41_PF"); q41pf = e ? atoi(e) : 192; } | |
| + const __m256i m4 = _mm256_set1_epi8(0x0F); | |
| + __m256 acc0 = _mm256_setzero_ps(); | |
| + __m256 acc1 = _mm256_setzero_ps(); | |
| + __m128 sm = _mm_setzero_ps(); | |
| + const __m256i sidx = _mm256_setr_epi32(0,0,0,0, 2,2,2,2); | |
| + for (; ib + 3 < nb; ib += 4) { | |
| + _mm_prefetch((const char *)(x + ib + q41pf), _MM_HINT_T1); | |
| + _mm_prefetch((const char *)(x + ib + q41pf) + 64, _MM_HINT_T1); | |
| +#define Q41_PAIR(ACC, I) { \ | |
| + const block_q4_1 * xa = x + (I); const block_q4_1 * xb = x + (I) + 1; \ | |
| + const block_q8_1 * ya = y + (I); const block_q8_1 * yb = y + (I) + 1; \ | |
| + /* [block a | block b] in one ymm, built with memory-source vinserti128 (no shuffle-port work) */ \ | |
| + const __m256i v = _mm256_inserti128_si256(_mm256_castsi128_si256(_mm_loadu_si128((const __m128i *) xa->qs)), \ | |
| + _mm_loadu_si128((const __m128i *) xb->qs), 1); \ | |
| + const __m256i lo = _mm256_and_si256(v, m4); \ | |
| + const __m256i hi = _mm256_and_si256(_mm256_srli_epi16(v, 4), m4); \ | |
| + const __m256i ylo = _mm256_inserti128_si256(_mm256_castsi128_si256(_mm_loadu_si128((const __m128i *) ya->qs)), \ | |
| + _mm_loadu_si128((const __m128i *) yb->qs), 1); \ | |
| + const __m256i yhi = _mm256_inserti128_si256(_mm256_castsi128_si256(_mm_loadu_si128((const __m128i *) (ya->qs + 16))), \ | |
| + _mm_loadu_si128((const __m128i *) (yb->qs + 16)), 1); \ | |
| + const __m256i dot = _mm256_dpbusd_epi32(_mm256_dpbusd_epi32(_mm256_setzero_si256(), lo, ylo), hi, yhi); \ | |
| + /* headers: x = (d, m), y = (d, s) -> pp = (d0*dy0, m0*s0, d1*dy1, m1*s1) */ \ | |
| + const __m128 hx = _mm_cvtph_ps(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *) xa), _mm_cvtsi32_si128(*(const int *) xb))); \ | |
| + const __m128 hy = _mm_cvtph_ps(_mm_unpacklo_epi32(_mm_cvtsi32_si128(*(const int *) ya), _mm_cvtsi32_si128(*(const int *) yb))); \ | |
| + const __m128 pp = _mm_mul_ps(hx, hy); \ | |
| + sm = _mm_add_ps(sm, pp); /* lanes 1 and 3 carry m*s */ \ | |
| + const __m256 sc = _mm256_permutevar8x32_ps(_mm256_castps128_ps256(pp), sidx); \ | |
| + ACC = _mm256_fmadd_ps(sc, _mm256_cvtepi32_ps(dot), ACC); } | |
| + Q41_PAIR(acc0, ib) | |
| + Q41_PAIR(acc1, ib + 2) | |
| +#undef Q41_PAIR | |
| + } | |
| + vtot = hsum_float_8(_mm256_add_ps(acc0, acc1)) + _mm_cvtss_f32(_mm_shuffle_ps(sm, sm, 0x01)) + _mm_cvtss_f32(_mm_shuffle_ps(sm, sm, 0x03)); | |
| + } | |
| +#endif | |
| // Main loop | |
| for (; ib < nb; ++ib) { | |
| void ggml_vec_dot_q4_1_q8_1(int n, float * GGML_RESTRICT s, size_t bs, const voi | |
| #endif | |
| } | |
| - *s = hsum_float_8(acc) + summs; | |
| + *s = hsum_float_8(acc) + summs + vtot; | |
| #else | |
| UNUSED(nb); | |
| UNUSED(x); | |
| void ggml_vec_dot_nvfp4_q8_0(int n, float * GGML_RESTRICT s, size_t bs, const vo | |
| int ib = 0; | |
| float sumf = 0; | |
| -#if defined(__AVX2__) | |
| +#if defined(__AVX512F__) && defined(__AVX512VL__) && defined(__AVX512VNNI__) && defined(__AVX2__) | |
| + // AVX512-VNNI path: one 64-element block per iteration. |w| (0..12) is the unsigned vpdpbusd operand and the | |
| + // sign of w moves onto the int8 activations. Int32 lane k sums sub-block k/2 (lo + hi nibbles of 4 bytes). | |
| + { | |
| + const __m256i lut = _mm256_broadcastsi128_si256(_mm_loadu_si128((const __m128i *) kvalues_fp4)); | |
| + const __m256i m4b = _mm256_set1_epi8(0x0f); | |
| + const __m512i perm_lo = _mm512_setr_epi64(0, 2, 4, 6, 0, 0, 0, 0); | |
| + const __m512i perm_hi = _mm512_setr_epi64(1, 3, 5, 7, 0, 0, 0, 0); | |
| + __m256 acc = _mm256_setzero_ps(); | |
| + for (; ib < nb; ++ib) { | |
| + const __m256i qbits = _mm256_loadu_si256((const __m256i *) x[ib].qs); | |
| + const __m256i wlo = _mm256_shuffle_epi8(lut, _mm256_and_si256(qbits, m4b)); | |
| + const __m256i whi = _mm256_shuffle_epi8(lut, _mm256_and_si256(_mm256_srli_epi16(qbits, 4), m4b)); | |
| + | |
| + // activation bytes in weight byte order: lo -> elems 16s+j, hi -> elems 16s+8+j | |
| + const __m512i z = _mm512_inserti64x4(_mm512_castsi256_si512(_mm256_loadu_si256((const __m256i *) y[2*ib + 0].qs)), | |
| + _mm256_loadu_si256((const __m256i *) y[2*ib + 1].qs), 1); | |
| + const __m256i alo = _mm512_castsi512_si256(_mm512_permutexvar_epi64(perm_lo, z)); | |
| + const __m256i ahi = _mm512_castsi512_si256(_mm512_permutexvar_epi64(perm_hi, z)); | |
| + | |
| + const __m256i dot = _mm256_dpbusd_epi32(_mm256_dpbusd_epi32(_mm256_setzero_si256(), | |
| + _mm256_abs_epi8(wlo), _mm256_sign_epi8(alo, wlo)), | |
| + _mm256_abs_epi8(whi), _mm256_sign_epi8(ahi, whi)); | |
| + | |
| + const float dy0 = GGML_CPU_FP16_TO_FP32(y[2*ib + 0].d); | |
| + const float dy1 = GGML_CPU_FP16_TO_FP32(y[2*ib + 1].d); | |
| + const float s0 = GGML_CPU_UE4M3_TO_FP32(x[ib].d[0]) * dy0; | |
| + const float s1 = GGML_CPU_UE4M3_TO_FP32(x[ib].d[1]) * dy0; | |
| + const float s2 = GGML_CPU_UE4M3_TO_FP32(x[ib].d[2]) * dy1; | |
| + const float s3 = GGML_CPU_UE4M3_TO_FP32(x[ib].d[3]) * dy1; | |
| + const __m256 scales = _mm256_set_ps(s3, s3, s2, s2, s1, s1, s0, s0); | |
| + | |
| + acc = _mm256_fmadd_ps(_mm256_cvtepi32_ps(dot), scales, acc); | |
| + } | |
| + sumf = hsum_float_8(acc); | |
| + } | |
| +#elif defined(__AVX2__) | |
| const __m128i values128 = _mm_loadu_si128((const __m128i*)kvalues_fp4); | |
| const __m128i m4b = _mm_set1_epi8(0x0f); | |
| void ggml_vec_dot_q4_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi | |
| uint32_t utmp[4]; | |
| -#if defined __AVX2__ | |
| +#if defined(__AVX512F__) && defined(__AVX512BW__) && defined(__AVX512VL__) && defined(__AVX512VNNI__) | |
| + // AVX512-VNNI path (Q4_K x Q8_K): 64 weights per vector, two 32-wide sub-blocks per vector. | |
| + const __m512i m4 = _mm512_set1_epi8(0xF); | |
| + #define K4_SCALE_IDX(a, b) _mm512_inserti64x4( \ | |
| + _mm512_castsi256_si512(_mm256_set_m128i(_mm_set1_epi16((short)((2*(a)) | ((2*(a)+1) << 8))), _mm_set1_epi16((short)((2*(a)) | ((2*(a)+1) << 8))))), \ | |
| + _mm256_set_m128i(_mm_set1_epi16((short)((2*(b)) | ((2*(b)+1) << 8))), _mm_set1_epi16((short)((2*(b)) | ((2*(b)+1) << 8)))), 1) | |
| + const __m512i sidx0 = K4_SCALE_IDX(0, 2); | |
| + const __m512i sidx1 = K4_SCALE_IDX(1, 3); | |
| + const __m512i sidx2 = K4_SCALE_IDX(4, 6); | |
| + const __m512i sidx3 = K4_SCALE_IDX(5, 7); | |
| + #undef K4_SCALE_IDX | |
| + | |
| + __m512 acc = _mm512_setzero_ps(); | |
| + __m128 acc_m = _mm_setzero_ps(); | |
| + | |
| + for (int i = 0; i < nb; ++i) { | |
| +#ifndef K4_PF_DIST | |
| +#define K4_PF_DIST 40 // ~9 KB ahead, tuned for Xeon 8559C (prefetch into L2) | |
| +#endif | |
| + static int k4pf = -1; | |
| + if (k4pf < 0) { const char * e = getenv("GGML_K4_PF"); k4pf = e ? atoi(e) : K4_PF_DIST; } | |
| + { | |
| + const char * pf = (const char *)(x + i + k4pf); | |
| + _mm_prefetch(pf, _MM_HINT_T1); | |
| + _mm_prefetch(pf + 64, _MM_HINT_T1); | |
| + _mm_prefetch(pf + 128, _MM_HINT_T1); | |
| + } | |
| + | |
| + const float d = y[i].d * GGML_CPU_FP16_TO_FP32(x[i].d); | |
| + const float dmin = -y[i].d * GGML_CPU_FP16_TO_FP32(x[i].dmin); | |
| + | |
| + memcpy(utmp, x[i].scales, 12); | |
| + utmp[3] = ((utmp[2] >> 4) & kmask2) | (((utmp[1] >> 6) & kmask3) << 4); | |
| + const uint32_t uaux = utmp[1] & kmask1; | |
| + utmp[1] = (utmp[2] & kmask2) | (((utmp[0] >> 6) & kmask3) << 4); | |
| + utmp[2] = uaux; | |
| + utmp[0] &= kmask1; | |
| + | |
| + const uint8_t * GGML_RESTRICT q4 = x[i].qs; | |
| + const int8_t * GGML_RESTRICT q8 = y[i].qs; | |
| + | |
| + const __m256i mins_and_scales = _mm256_cvtepu8_epi16(_mm_set_epi32(utmp[3], utmp[2], utmp[1], utmp[0])); | |
| + | |
| + const __m256i q8sums = _mm256_loadu_si256((const __m256i*)y[i].bsums); | |
| + const __m128i q8s = _mm_hadd_epi16(_mm256_extracti128_si256(q8sums, 0), _mm256_extracti128_si256(q8sums, 1)); | |
| + const __m128i prod = _mm_madd_epi16(_mm256_extracti128_si256(mins_and_scales, 1), q8s); | |
| + acc_m = _mm_fmadd_ps(_mm_set1_ps(dmin), _mm_cvtepi32_ps(prod), acc_m); | |
| + | |
| + const __m512i scales = _mm512_broadcast_i32x4(_mm256_extracti128_si256(mins_and_scales, 0)); | |
| + | |
| + const __m512i v0 = _mm512_loadu_si512((const void *)(q4)); | |
| + const __m512i v1 = _mm512_loadu_si512((const void *)(q4 + 64)); | |
| + | |
| + const __m512i Q0 = _mm512_loadu_si512((const void *)(q8)); | |
| + const __m512i Q1 = _mm512_loadu_si512((const void *)(q8 + 64)); | |
| + const __m512i Q2 = _mm512_loadu_si512((const void *)(q8 + 128)); | |
| + const __m512i Q3 = _mm512_loadu_si512((const void *)(q8 + 192)); | |
| + | |
| + const __m512i a0 = _mm512_and_si512(v0, m4); | |
| + const __m512i a1 = _mm512_and_si512(_mm512_srli_epi16(v0, 4), m4); | |
| + const __m512i a2 = _mm512_and_si512(v1, m4); | |
| + const __m512i a3 = _mm512_and_si512(_mm512_srli_epi16(v1, 4), m4); | |
| + | |
| + const __m512i b0 = _mm512_shuffle_i64x2(Q0, Q1, 0x44); | |
| + const __m512i b1 = _mm512_shuffle_i64x2(Q0, Q1, 0xEE); | |
| + const __m512i b2 = _mm512_shuffle_i64x2(Q2, Q3, 0x44); | |
| + const __m512i b3 = _mm512_shuffle_i64x2(Q2, Q3, 0xEE); | |
| + | |
| + __m512i sumi = _mm512_setzero_si512(); | |
| + sumi = _mm512_dpwssd_epi32(sumi, _mm512_maddubs_epi16(a0, b0), _mm512_shuffle_epi8(scales, sidx0)); | |
| + sumi = _mm512_dpwssd_epi32(sumi, _mm512_maddubs_epi16(a1, b1), _mm512_shuffle_epi8(scales, sidx1)); | |
| + sumi = _mm512_dpwssd_epi32(sumi, _mm512_maddubs_epi16(a2, b2), _mm512_shuffle_epi8(scales, sidx2)); | |
| + sumi = _mm512_dpwssd_epi32(sumi, _mm512_maddubs_epi16(a3, b3), _mm512_shuffle_epi8(scales, sidx3)); | |
| + | |
| + acc = _mm512_fmadd_ps(_mm512_set1_ps(d), _mm512_cvtepi32_ps(sumi), acc); | |
| + } | |
| + | |
| + acc_m = _mm_add_ps(acc_m, _mm_movehl_ps(acc_m, acc_m)); | |
| + acc_m = _mm_add_ss(acc_m, _mm_movehdup_ps(acc_m)); | |
| + | |
| + *s = _mm512_reduce_add_ps(acc) + _mm_cvtss_f32(acc_m); | |
| + | |
| +#elif defined __AVX2__ | |
| const __m256i m4 = _mm256_set1_epi8(0xF); | |
| void ggml_vec_dot_q5_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi | |
| uint32_t utmp[4]; | |
| -#if defined __AVX2__ | |
| +#if defined(__AVX512F__) && defined(__AVX512BW__) && defined(__AVX512VL__) && defined(__AVX512VNNI__) | |
| + // AVX512-VNNI path (Q5_K x Q8_K), same structure as the Q4_K kernel: 64 weights per vector, two 32-wide | |
| + // sub-blocks per vector. The 5th bit of sub-block s is bit s of qh[l]; it is merged in as +16 before maddubs. | |
| + const __m512i m4 = _mm512_set1_epi8(0xF); | |
| + const __m512i m10 = _mm512_set1_epi8(0x10); | |
| + #define K5_SCALE_IDX(a, b) _mm512_inserti64x4( \ | |
| + _mm512_castsi256_si512(_mm256_set_m128i(_mm_set1_epi16((short)((2*(a)) | ((2*(a)+1) << 8))), _mm_set1_epi16((short)((2*(a)) | ((2*(a)+1) << 8))))), \ | |
| + _mm256_set_m128i(_mm_set1_epi16((short)((2*(b)) | ((2*(b)+1) << 8))), _mm_set1_epi16((short)((2*(b)) | ((2*(b)+1) << 8)))), 1) | |
| + const __m512i sidx0 = K5_SCALE_IDX(0, 2); | |
| + const __m512i sidx1 = K5_SCALE_IDX(1, 3); | |
| + const __m512i sidx2 = K5_SCALE_IDX(4, 6); | |
| + const __m512i sidx3 = K5_SCALE_IDX(5, 7); | |
| + #undef K5_SCALE_IDX | |
| + // per-vector bit selectors: low 32 bytes test bit s, high 32 bytes test bit s+2 (sub-block pairs (0,2),(1,3),(4,6),(5,7)) | |
| + #define K5_BM(a, b) _mm512_inserti64x4(_mm512_castsi256_si512(_mm256_set1_epi8((char)(1 << (a)))), _mm256_set1_epi8((char)(1 << (b))), 1) | |
| + const __m512i bm0 = K5_BM(0, 2); | |
| + const __m512i bm1 = K5_BM(1, 3); | |
| + const __m512i bm2 = K5_BM(4, 6); | |
| + const __m512i bm3 = K5_BM(5, 7); | |
| + #undef K5_BM | |
| + | |
| + static int k5pf = -1; | |
| + if (k5pf < 0) { const char * e = getenv("GGML_K5_PF"); k5pf = e ? atoi(e) : 48; } | |
| + | |
| + __m512 acc = _mm512_setzero_ps(); | |
| + __m128 acc_m = _mm_setzero_ps(); | |
| + | |
| + for (int i = 0; i < nb; ++i) { | |
| + { | |
| + const char * pf = (const char *)(x + i + k5pf); | |
| + _mm_prefetch(pf, _MM_HINT_T1); | |
| + _mm_prefetch(pf + 64, _MM_HINT_T1); | |
| + _mm_prefetch(pf + 128, _MM_HINT_T1); | |
| + } | |
| + | |
| + const float d = y[i].d * GGML_CPU_FP16_TO_FP32(x[i].d); | |
| + const float dmin = -y[i].d * GGML_CPU_FP16_TO_FP32(x[i].dmin); | |
| + | |
| + memcpy(utmp, x[i].scales, 12); | |
| + utmp[3] = ((utmp[2] >> 4) & kmask2) | (((utmp[1] >> 6) & kmask3) << 4); | |
| + const uint32_t uaux = utmp[1] & kmask1; | |
| + utmp[1] = (utmp[2] & kmask2) | (((utmp[0] >> 6) & kmask3) << 4); | |
| + utmp[2] = uaux; | |
| + utmp[0] &= kmask1; | |
| + | |
| + const uint8_t * GGML_RESTRICT q5 = x[i].qs; | |
| + const int8_t * GGML_RESTRICT q8 = y[i].qs; | |
| + | |
| + const __m256i mins_and_scales = _mm256_cvtepu8_epi16(_mm_set_epi32(utmp[3], utmp[2], utmp[1], utmp[0])); | |
| + | |
| + const __m256i q8sums = _mm256_loadu_si256((const __m256i*)y[i].bsums); | |
| + const __m128i q8s = _mm_hadd_epi16(_mm256_extracti128_si256(q8sums, 0), _mm256_extracti128_si256(q8sums, 1)); | |
| + const __m128i prod = _mm_madd_epi16(_mm256_extracti128_si256(mins_and_scales, 1), q8s); | |
| + acc_m = _mm_fmadd_ps(_mm_set1_ps(dmin), _mm_cvtepi32_ps(prod), acc_m); | |
| + | |
| + const __m512i scales = _mm512_broadcast_i32x4(_mm256_extracti128_si256(mins_and_scales, 0)); | |
| + | |
| + const __m512i qhv = _mm512_broadcast_i64x4(_mm256_loadu_si256((const __m256i *)x[i].qh)); | |
| + | |
| + const __m512i v0 = _mm512_loadu_si512((const void *)(q5)); | |
| + const __m512i v1 = _mm512_loadu_si512((const void *)(q5 + 64)); | |
| + | |
| + const __m512i Q0 = _mm512_loadu_si512((const void *)(q8)); | |
| + const __m512i Q1 = _mm512_loadu_si512((const void *)(q8 + 64)); | |
| + const __m512i Q2 = _mm512_loadu_si512((const void *)(q8 + 128)); | |
| + const __m512i Q3 = _mm512_loadu_si512((const void *)(q8 + 192)); | |
| + | |
| + // a_k = nibble + 16 * bit(qh[l], sub-block). qhv holds qh (32 B) duplicated in both 256-bit halves; | |
| + // lanes 0-31 of a vector are sub-block s (low half) and 32-63 are sub-block s+2 (high half) -> per-half bit masks. | |
| + const __m512i n0 = _mm512_and_si512(v0, m4); | |
| + const __m512i a0 = _mm512_mask_add_epi8(n0, _mm512_test_epi8_mask(qhv, bm0), n0, m10); | |
| + const __m512i n1 = _mm512_and_si512(_mm512_srli_epi16(v0, 4), m4); | |
| + const __m512i a1 = _mm512_mask_add_epi8(n1, _mm512_test_epi8_mask(qhv, bm1), n1, m10); | |
| + const __m512i n2 = _mm512_and_si512(v1, m4); | |
| + const __m512i a2 = _mm512_mask_add_epi8(n2, _mm512_test_epi8_mask(qhv, bm2), n2, m10); | |
| + const __m512i n3 = _mm512_and_si512(_mm512_srli_epi16(v1, 4), m4); | |
| + const __m512i a3 = _mm512_mask_add_epi8(n3, _mm512_test_epi8_mask(qhv, bm3), n3, m10); | |
| + | |
| + const __m512i b0 = _mm512_shuffle_i64x2(Q0, Q1, 0x44); | |
| + const __m512i b1 = _mm512_shuffle_i64x2(Q0, Q1, 0xEE); | |
| + const __m512i b2 = _mm512_shuffle_i64x2(Q2, Q3, 0x44); | |
| + const __m512i b3 = _mm512_shuffle_i64x2(Q2, Q3, 0xEE); | |
| + | |
| + __m512i sumi = _mm512_setzero_si512(); | |
| + sumi = _mm512_dpwssd_epi32(sumi, _mm512_maddubs_epi16(a0, b0), _mm512_shuffle_epi8(scales, sidx0)); | |
| + sumi = _mm512_dpwssd_epi32(sumi, _mm512_maddubs_epi16(a1, b1), _mm512_shuffle_epi8(scales, sidx1)); | |
| + sumi = _mm512_dpwssd_epi32(sumi, _mm512_maddubs_epi16(a2, b2), _mm512_shuffle_epi8(scales, sidx2)); | |
| + sumi = _mm512_dpwssd_epi32(sumi, _mm512_maddubs_epi16(a3, b3), _mm512_shuffle_epi8(scales, sidx3)); | |
| + | |
| + acc = _mm512_fmadd_ps(_mm512_set1_ps(d), _mm512_cvtepi32_ps(sumi), acc); | |
| + } | |
| + | |
| + acc_m = _mm_add_ps(acc_m, _mm_movehl_ps(acc_m, acc_m)); | |
| + acc_m = _mm_add_ss(acc_m, _mm_movehdup_ps(acc_m)); | |
| + | |
| + *s = _mm512_reduce_add_ps(acc) + _mm_cvtss_f32(acc_m); | |
| + | |
| +#elif defined __AVX2__ | |
| const __m256i m4 = _mm256_set1_epi8(0xF); | |
| const __m128i mzero = _mm_setzero_si128(); | |
| void ggml_vec_dot_q6_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi | |
| const int nb = n / QK_K; | |
| -#if defined __AVX2__ | |
| +#if defined(__AVX512F__) && defined(__AVX512BW__) && defined(__AVX512VL__) && defined(__AVX512VNNI__) | |
| + // AVX512-VNNI path (Q6_K x Q8_K): each 64-byte vector holds two 32-wide chunks. | |
| + const __m512i m15 = _mm512_set1_epi8(0xF); | |
| + const __m512i m30 = _mm512_set1_epi8(0x30); | |
| + const __m512i shl_cnt = _mm512_inserti64x4(_mm512_castsi256_si512(_mm256_set1_epi16(4)), _mm256_set1_epi16(2), 1); | |
| + const __m512i shr_cnt = _mm512_inserti64x4(_mm512_castsi256_si512(_mm256_set1_epi16(0)), _mm256_set1_epi16(2), 1); | |
| + #define Q6_SIDX(c1, c2) _mm512_inserti64x4( \ | |
| + _mm512_castsi256_si512(_mm256_set_m128i(_mm_set1_epi16((short)(2*(c1)+1)), _mm_set1_epi16((short)(2*(c1))))), \ | |
| + _mm256_set_m128i(_mm_set1_epi16((short)(2*(c2)+1)), _mm_set1_epi16((short)(2*(c2)))), 1) | |
| + const __m512i sidx0 = Q6_SIDX(0, 1); | |
| + const __m512i sidx1 = Q6_SIDX(2, 3); | |
| + const __m512i sidx2 = Q6_SIDX(4, 5); | |
| + const __m512i sidx3 = Q6_SIDX(6, 7); | |
| + #undef Q6_SIDX | |
| + | |
| + __m512 acc = _mm512_setzero_ps(); | |
| + | |
| + for (int i = 0; i < nb; ++i) { | |
| +#ifndef Q6_PF_DIST | |
| +#define Q6_PF_DIST 44 // ~9 KB ahead, tuned for Xeon 8559C (prefetch into L2) | |
| +#endif | |
| + static int q6pf = -1; | |
| + if (q6pf < 0) { const char * e = getenv("GGML_Q6_PF"); q6pf = e ? atoi(e) : Q6_PF_DIST; } | |
| + { | |
| + const char * pf = (const char *)(x + i + q6pf); | |
| + _mm_prefetch(pf, _MM_HINT_T1); | |
| + _mm_prefetch(pf + 64, _MM_HINT_T1); | |
| + _mm_prefetch(pf + 128, _MM_HINT_T1); | |
| + _mm_prefetch(pf + 192, _MM_HINT_T1); | |
| + } | |
| + | |
| + const float d = y[i].d * GGML_CPU_FP16_TO_FP32(x[i].d); | |
| + | |
| + const uint8_t * GGML_RESTRICT ql = x[i].ql; | |
| + const uint8_t * GGML_RESTRICT qh = x[i].qh; | |
| + const int8_t * GGML_RESTRICT q8 = y[i].qs; | |
| + | |
| + // -32 offset handled with bsums | |
| + const __m256i q8sums = _mm256_loadu_si256((const __m256i*)y[i].bsums); | |
| + const __m256i scales_16 = _mm256_cvtepi8_epi16(_mm_loadu_si128((const __m128i*)x[i].scales)); | |
| + const __m256i q8sclsub = _mm256_slli_epi32(_mm256_madd_epi16(q8sums, scales_16), 5); | |
| + const __m512i scales512 = _mm512_zextsi256_si512(scales_16); | |
| + | |
| + const __m512i QL0 = _mm512_loadu_si512((const void *)(ql)); | |
| + const __m512i QL1 = _mm512_loadu_si512((const void *)(ql + 64)); | |
| + const __m512i H0 = _mm512_broadcast_i64x4(_mm256_loadu_si256((const __m256i*)(qh))); | |
| + const __m512i H1 = _mm512_broadcast_i64x4(_mm256_loadu_si256((const __m256i*)(qh + 32))); | |
| + | |
| + const __m512i X0 = _mm512_loadu_si512((const void *)(q8)); | |
| + const __m512i X1 = _mm512_loadu_si512((const void *)(q8 + 64)); | |
| + const __m512i X2 = _mm512_loadu_si512((const void *)(q8 + 128)); | |
| + const __m512i X3 = _mm512_loadu_si512((const void *)(q8 + 192)); | |
| + | |
| + // 6-bit values (unsigned 0..63): low nibble / high nibble of ql combined with 2 bits of qh | |
| + const __m512i q6_0 = _mm512_or_si512(_mm512_and_si512(QL0, m15), | |
| + _mm512_and_si512(_mm512_sllv_epi16(H0, shl_cnt), m30)); | |
| + const __m512i q6_1 = _mm512_or_si512(_mm512_and_si512(_mm512_srli_epi16(QL0, 4), m15), | |
| + _mm512_and_si512(_mm512_srlv_epi16(H0, shr_cnt), m30)); | |
| + const __m512i q6_2 = _mm512_or_si512(_mm512_and_si512(QL1, m15), | |
| + _mm512_and_si512(_mm512_sllv_epi16(H1, shl_cnt), m30)); | |
| + const __m512i q6_3 = _mm512_or_si512(_mm512_and_si512(_mm512_srli_epi16(QL1, 4), m15), | |
| + _mm512_and_si512(_mm512_srlv_epi16(H1, shr_cnt), m30)); | |
| + | |
| + __m512i sumi = _mm512_zextsi256_si512(_mm256_sub_epi32(_mm256_setzero_si256(), q8sclsub)); | |
| + sumi = _mm512_dpwssd_epi32(sumi, _mm512_maddubs_epi16(q6_0, X0), _mm512_permutexvar_epi16(sidx0, scales512)); | |
| + sumi = _mm512_dpwssd_epi32(sumi, _mm512_maddubs_epi16(q6_1, X1), _mm512_permutexvar_epi16(sidx1, scales512)); | |
| + sumi = _mm512_dpwssd_epi32(sumi, _mm512_maddubs_epi16(q6_2, X2), _mm512_permutexvar_epi16(sidx2, scales512)); | |
| + sumi = _mm512_dpwssd_epi32(sumi, _mm512_maddubs_epi16(q6_3, X3), _mm512_permutexvar_epi16(sidx3, scales512)); | |
| + | |
| + acc = _mm512_fmadd_ps(_mm512_set1_ps(d), _mm512_cvtepi32_ps(sumi), acc); | |
| + } | |
| + | |
| + *s = _mm512_reduce_add_ps(acc); | |
| + | |
| +#elif defined __AVX2__ | |
| const __m256i m3 = _mm256_set1_epi8(3); | |
| const __m256i m15 = _mm256_set1_epi8(15); | |
| void ggml_vec_dot_iq4_xs_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const v | |
| const int nb = n / QK_K; | |
| -#if defined __AVX2__ | |
| +#if defined(__AVX512F__) && defined(__AVX512BW__) && defined(__AVX512VL__) && defined(__AVX512VNNI__) | |
| + // AVX512-VNNI path (IQ4_XS x Q8_K). 64 qs bytes = 4 sub-blocks (16 B each, low nibble -> values 0..15, high | |
| + // nibble -> values 16..31 of the same sub-block). The kvalues LUT is stored as unsigned (v + 128) so that | |
| + // vpdpbusd can be used with 32-bit accumulation; the +128 bias is removed once per super-block through bsums. | |
| + const __m512i lut = _mm512_broadcast_i32x4(_mm_xor_si128(_mm_loadu_si128((const __m128i *)kvalues_iq4nl), _mm_set1_epi8((char)0x80))); | |
| + const __m512i m4 = _mm512_set1_epi8(0xF); | |
| + const __m512i idxA = _mm512_setr_epi32(0,0,0,0, 1,1,1,1, 2,2,2,2, 3,3,3,3); | |
| + const __m512i idxB = _mm512_setr_epi32(4,4,4,4, 5,5,5,5, 6,6,6,6, 7,7,7,7); | |
| + const __m128i hsh = _mm_setr_epi16(0, 2, 4, 6, 8, 10, 12, 14); | |
| + | |
| + static int ipf = -1; | |
| + if (ipf < 0) { const char * e = getenv("GGML_IQ4XS_PF"); ipf = e ? atoi(e) : 40; } | |
| + | |
| + __m512 acc = _mm512_setzero_ps(); | |
| + | |
| + for (int ibl = 0; ibl < nb; ++ibl) { | |
| + { | |
| + const char * pf = (const char *)(x + ibl + ipf); | |
| + _mm_prefetch(pf, _MM_HINT_T1); | |
| + _mm_prefetch(pf + 64, _MM_HINT_T1); | |
| + _mm_prefetch(pf + 128, _MM_HINT_T1); | |
| + } | |
| + | |
| + // 6-bit sub-block scales (-32), 8 per super-block | |
| + uint32_t sl; memcpy(&sl, x[ibl].scales_l, 4); | |
| + const __m128i xl = _mm_cvtsi32_si128((int)sl); | |
| + const __m128i l8 = _mm_unpacklo_epi8(_mm_and_si128(xl, _mm_set1_epi8(0x0f)), | |
| + _mm_and_si128(_mm_srli_epi16(xl, 4), _mm_set1_epi8(0x0f))); | |
| + const __m128i h16 = _mm_slli_epi16(_mm_and_si128(_mm_srlv_epi16(_mm_set1_epi16((short)x[ibl].scales_h), hsh), _mm_set1_epi16(3)), 4); | |
| + const __m128i ls16 = _mm_sub_epi16(_mm_or_si128(_mm_cvtepu8_epi16(l8), h16), _mm_set1_epi16(32)); | |
| + const __m512i ls512 = _mm512_castsi256_si512(_mm256_cvtepi16_epi32(ls16)); | |
| + const __m512i sc0 = _mm512_permutexvar_epi32(idxA, ls512); | |
| + const __m512i sc1 = _mm512_permutexvar_epi32(idxB, ls512); | |
| + | |
| + const uint8_t * qs = x[ibl].qs; | |
| + const int8_t * q8 = y[ibl].qs; | |
| + | |
| + const __m512i q0 = _mm512_loadu_si512((const void *)(qs)); | |
| + const __m512i q1 = _mm512_loadu_si512((const void *)(qs + 64)); | |
| + const __m512i Y0 = _mm512_loadu_si512((const void *)(q8)); | |
| + const __m512i Y1 = _mm512_loadu_si512((const void *)(q8 + 64)); | |
| + const __m512i Y2 = _mm512_loadu_si512((const void *)(q8 + 128)); | |
| + const __m512i Y3 = _mm512_loadu_si512((const void *)(q8 + 192)); | |
| + | |
| + const __m512i xl0 = _mm512_shuffle_epi8(lut, _mm512_and_si512(q0, m4)); | |
| + const __m512i xh0 = _mm512_shuffle_epi8(lut, _mm512_and_si512(_mm512_srli_epi16(q0, 4), m4)); | |
| + const __m512i xl1 = _mm512_shuffle_epi8(lut, _mm512_and_si512(q1, m4)); | |
| + const __m512i xh1 = _mm512_shuffle_epi8(lut, _mm512_and_si512(_mm512_srli_epi16(q1, 4), m4)); | |
| + | |
| + __m512i p0 = _mm512_dpbusd_epi32(_mm512_setzero_si512(), xl0, _mm512_shuffle_i64x2(Y0, Y1, 0x88)); | |
| + p0 = _mm512_dpbusd_epi32(p0, xh0, _mm512_shuffle_i64x2(Y0, Y1, 0xDD)); | |
| + __m512i p1 = _mm512_dpbusd_epi32(_mm512_setzero_si512(), xl1, _mm512_shuffle_i64x2(Y2, Y3, 0x88)); | |
| + p1 = _mm512_dpbusd_epi32(p1, xh1, _mm512_shuffle_i64x2(Y2, Y3, 0xDD)); | |
| + | |
| + __m512i sumi = _mm512_add_epi32(_mm512_mullo_epi32(p0, sc0), _mm512_mullo_epi32(p1, sc1)); | |
| + | |
| + // remove the +128 bias: 128 * sum_k ls_k * (bsums[2k] + bsums[2k+1]) | |
| + const __m256i bs = _mm256_loadu_si256((const __m256i *)y[ibl].bsums); | |
| + const __m256i lsd = _mm256_set_m128i(_mm_unpackhi_epi16(ls16, ls16), _mm_unpacklo_epi16(ls16, ls16)); | |
| + const __m512i corr = _mm512_slli_epi32(_mm512_zextsi256_si512(_mm256_madd_epi16(bs, lsd)), 7); | |
| + sumi = _mm512_sub_epi32(sumi, corr); | |
| + | |
| + const float d = GGML_CPU_FP16_TO_FP32(x[ibl].d) * y[ibl].d; | |
| + acc = _mm512_fmadd_ps(_mm512_set1_ps(d), _mm512_cvtepi32_ps(sumi), acc); | |
| + } | |
| + | |
| + *s = _mm512_reduce_add_ps(acc); | |
| + | |
| +#elif defined __AVX2__ | |
| const __m128i values128 = _mm_loadu_si128((const __m128i*)kvalues_iq4nl); | |
| const __m128i m4b = _mm_set1_epi8(0x0f); | |
| diff --git a/ggml/src/ggml-cpu/ops.cpp b/ggml/src/ggml-cpu/ops.cpp | |
| index 85e17018c..119d3c997 100644 | |
| --- a/ggml/src/ggml-cpu/ops.cpp | |
| +++ b/ggml/src/ggml-cpu/ops.cpp | |
| static void ggml_compute_forward_get_rows_f32( | |
| const int ith = params->ith; | |
| const int nth = params->nth; | |
| + // few rows: parallelize across the row instead of across rows so that a single | |
| + // large gather (e.g. the recurrent state of a single sequence) uses all threads | |
| + if (nr < nth && nc >= 4096) { | |
| + const int64_t per = (nc + nth - 1)/nth; | |
| + const int64_t c0 = per*ith; | |
| + const int64_t c1 = MIN(c0 + per, nc); | |
| + if (c0 < c1) { | |
| + for (int64_t i = 0; i < nr; ++i) { | |
| + const int64_t i12 = i/(ne11*ne10); | |
| + const int64_t i11 = (i - i12*ne11*ne10)/ne10; | |
| + const int64_t i10 = (i - i12*ne11*ne10 - i11*ne10); | |
| + const int64_t i01 = *(int32_t *) ((char *) src1->data + i10*nb10 + i11*nb11 + i12*nb12); | |
| + | |
| + GGML_ASSERT(i01 >= 0 && i01 < ne01); | |
| + | |
| + memcpy((char *) dst->data + i10*nb1 + i11*nb2 + i12*nb3 + c0*sizeof(float), | |
| + (char *) src0->data + i01*nb01 + i11*nb02 + i12*nb03 + c0*sizeof(float), | |
| + (c1 - c0)*sizeof(float)); | |
| + } | |
| + } | |
| + return; | |
| + } | |
| + | |
| // rows per thread | |
| const int dr = (nr + nth - 1)/nth; | |
| static void ggml_compute_forward_gated_delta_net_one_chunk( | |
| ggml_tensor * src_g = dst->src[3]; | |
| ggml_tensor * src_beta = dst->src[4]; | |
| ggml_tensor * src_state = dst->src[5]; | |
| + // cache mode (see ggml_gated_delta_net_cache): src[6] = source cell ids, src[7] = in-place destination view | |
| + const ggml_tensor * src_ids = dst->src[6]; | |
| + const ggml_tensor * src_sdst = dst->src[7]; | |
| const int64_t S_v = src_v->ne[0]; | |
| const int64_t H = src_v->ne[1]; | |
| static void ggml_compute_forward_gated_delta_net_one_chunk( | |
| GGML_ASSERT(ggml_is_contiguous_rows(src_v)); | |
| GGML_ASSERT(ggml_is_contiguous(src_g)); | |
| GGML_ASSERT(ggml_is_contiguous(src_beta)); | |
| - GGML_ASSERT(ggml_is_contiguous(src_state)); | |
| + GGML_ASSERT(src_ids ? src_state->nb[1] == (size_t) (S_v * S_v * H) * sizeof(float) : ggml_is_contiguous(src_state)); | |
| GGML_ASSERT(src_g->ne[0] == 1 || src_g->ne[0] == S_v); | |
| GGML_ASSERT(src_beta->ne[0] == 1); | |
| static void ggml_compute_forward_gated_delta_net_one_chunk( | |
| // K (snapshot slot count) is an op param; state holds s0 only [S_v, S_v, H, n_seqs]. | |
| const int64_t K = ggml_get_op_params_i32(dst, 0); | |
| GGML_ASSERT(K >= 1); | |
| + GGML_ASSERT(!src_ids || K == 1); | |
| // per-seq stride in floats (seq s starts at state + s * seq_stride) | |
| const int64_t state_seq_stride = src_state->nb[3] / sizeof(float); | |
| static void ggml_compute_forward_gated_delta_net_one_chunk( | |
| // For K>1, work in scratch and copy out per-token when the slot is in range. | |
| float * s_out = (K > 1) | |
| ? state_work | |
| - : state_out_base + (iv3 * H + iv1) * S_v * S_v; | |
| + : (src_sdst | |
| + ? (float *) src_sdst->data + iv3 * (src_sdst->nb[1] / sizeof(float)) + iv1 * S_v * S_v | |
| + : state_out_base + (iv3 * H + iv1) * S_v * S_v); | |
| // copy input state into the working buffer and operate in-place | |
| // state layout [S_v, S_v, H, n_seqs]: seq iv3 starts at iv3 * state_seq_stride. | |
| - const float * s_in = state_in_base + iv3 * state_seq_stride + iv1 * S_v * S_v; | |
| - memcpy(s_out, s_in, S_v * S_v * sizeof(float)); | |
| - | |
| + const float * s_in = src_ids | |
| + ? state_in_base + (int64_t) ((const int32_t *) src_ids->data)[iv3] * (src_state->nb[1] / sizeof(float)) + iv1 * S_v * S_v | |
| + : state_in_base + iv3 * state_seq_stride + iv1 * S_v * S_v; | |
| + if (s_out != s_in) { | |
| + memcpy(s_out, s_in, S_v * S_v * sizeof(float)); | |
| + } | |
| // attn output pointer for first token of this (head, seq) | |
| float * attn_data = attn_out_base + (iv3 * n_tokens * H + iv1) * S_v; | |
| static void ggml_compute_forward_gated_delta_net_one_chunk( | |
| // state is stored transposed: s_out[j*S_v + i] = S[i][j] | |
| // so row j of s_out = column j of S (contiguous access) | |
| + bool gdn_done = false; | |
| +#if defined(__AVX512F__) | |
| + if (!kda && S_v == 128) { | |
| + // Fused 2-pass update for the scalar-gate case (non-KDA), S_v == 128: | |
| + // delta[j] = (v[j] - g * dot(S_j, k)) * beta | |
| + // S_j <- g * S_j + delta[j] * k ; out[j] = dot(S_j, q) * scale | |
| + // Same element-wise fma as the reference path; only the dot rounding order differs. | |
| + const float g = expf(g_d[0]); | |
| + __m512 kv[8], qv[8]; | |
| + for (int i = 0; i < 8; ++i) { | |
| + kv[i] = _mm512_loadu_ps(k_d + 16 * i); | |
| + qv[i] = _mm512_loadu_ps(q_d + 16 * i); | |
| + } | |
| + for (int64_t j = 0; j < 128; j += 4) { | |
| + float sums[4]; | |
| + for (int r = 0; r < 4; ++r) { | |
| + const float * row = s_out + (j + r) * 128; | |
| + __m512 a0 = _mm512_mul_ps(_mm512_loadu_ps(row), kv[0]); | |
| + __m512 a1 = _mm512_mul_ps(_mm512_loadu_ps(row + 16), kv[1]); | |
| + a0 = _mm512_fmadd_ps(_mm512_loadu_ps(row + 32), kv[2], a0); | |
| + a1 = _mm512_fmadd_ps(_mm512_loadu_ps(row + 48), kv[3], a1); | |
| + a0 = _mm512_fmadd_ps(_mm512_loadu_ps(row + 64), kv[4], a0); | |
| + a1 = _mm512_fmadd_ps(_mm512_loadu_ps(row + 80), kv[5], a1); | |
| + a0 = _mm512_fmadd_ps(_mm512_loadu_ps(row + 96), kv[6], a0); | |
| + a1 = _mm512_fmadd_ps(_mm512_loadu_ps(row + 112), kv[7], a1); | |
| + sums[r] = _mm512_reduce_add_ps(_mm512_add_ps(a0, a1)); | |
| + } | |
| + for (int r = 0; r < 4; ++r) { | |
| + delta[j + r] = (v_d[j + r] - g * sums[r]) * beta_val; | |
| + } | |
| + } | |
| + const __m512 gv = _mm512_set1_ps(g); | |
| + for (int64_t j = 0; j < 128; ++j) { | |
| + float * row = s_out + j * 128; | |
| + const __m512 dv = _mm512_set1_ps(delta[j]); | |
| + __m512 aq0 = _mm512_setzero_ps(), aq1 = _mm512_setzero_ps(); | |
| + for (int i = 0; i < 8; i += 2) { | |
| + const __m512 s0 = _mm512_fmadd_ps(dv, kv[i], _mm512_mul_ps(gv, _mm512_loadu_ps(row + 16 * i))); | |
| + const __m512 s1 = _mm512_fmadd_ps(dv, kv[i + 1], _mm512_mul_ps(gv, _mm512_loadu_ps(row + 16 * (i + 1)))); | |
| + _mm512_storeu_ps(row + 16 * i, s0); | |
| + _mm512_storeu_ps(row + 16 * (i + 1), s1); | |
| + aq0 = _mm512_fmadd_ps(s0, qv[i], aq0); | |
| + aq1 = _mm512_fmadd_ps(s1, qv[i + 1], aq1); | |
| + } | |
| + attn_data[j] = _mm512_reduce_add_ps(_mm512_add_ps(aq0, aq1)) * scale; | |
| + } | |
| + gdn_done = true; | |
| + } | |
| +#endif | |
| + if (!gdn_done) { | |
| if (kda) { | |
| // precompute exp(g) into delta scratch (reused below) | |
| for (int64_t i = 0; i < S_v; ++i) { | |
| static void ggml_compute_forward_gated_delta_net_one_chunk( | |
| attn_data[j] = sum * scale; | |
| } | |
| + } | |
| + | |
| attn_data += S_v * H; // advance to next token | |
| if (K > 1) { | |
| diff --git a/ggml/src/ggml-cpu/repack.cpp b/ggml/src/ggml-cpu/repack.cpp | |
| index 9282ca299..0431b7ada 100644 | |
| --- a/ggml/src/ggml-cpu/repack.cpp | |
| +++ b/ggml/src/ggml-cpu/repack.cpp | |
| static const ggml::cpu::tensor_traits * ggml_repack_get_optimal_repack_type(cons | |
| #endif | |
| } | |
| } else if (cur->type == GGML_TYPE_Q4_K) { | |
| - if (ggml_cpu_has_avx2()) { | |
| + const char * rp_q4k = getenv("GGML_REPACK_Q4K"); | |
| + // Xeon 8559C: the AVX512-VNNI Q4_K vec_dot beats the AVX2 8x8 repack on decode+prefill; opt in with GGML_REPACK_Q4K=1 | |
| + const bool q4k_repack = (rp_q4k && rp_q4k[0] == '1'); | |
| + if (q4k_repack && ggml_cpu_has_avx2()) { | |
| if (cur->ne[1] % 8 == 0) { | |
| return &q4_K_8x8_q8_K; | |
| } | |
| diff --git a/ggml/src/ggml-cpu/tiled/tiled-kernel.cpp b/ggml/src/ggml-cpu/tiled/tiled-kernel.cpp | |
| index 5819ac07c..b28255b79 100644 | |
| --- a/ggml/src/ggml-cpu/tiled/tiled-kernel.cpp | |
| +++ b/ggml/src/ggml-cpu/tiled/tiled-kernel.cpp | |
| static void tiled_run_microtile_vnni(const tiled_tile_src0 & src0, const tiled_t | |
| tiled_run_micro_vnni_8x16<SUBBLK, HAS_MIN, BIAS, NK>(src0, src1, i0 + 8, j0, num_k, slab, buf, buf_stride); | |
| } | |
| +// q4_1: every 32-wide block has its own f32 scale and min on both sides (weights: d,m; activations: d). | |
| +// weight = d_w*q + m_w (q in 0..15), act = d_a*code (code int8), so one 32-block contributes | |
| +// d_a * (d_w * dot(q, code) + m_w * sum(code)). | |
| +// The integer dot and the integer sum are exact; the four per-block f32 scale factors are applied in f32, | |
| +// which only changes the accumulation order (same as the reference vec_dot, which also accumulates in f32). | |
| +template <int NK> | |
| +static void tiled_run_micro_q41_vnni_8x16(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1, | |
| + int i0, int j0, int num_k, int slab, float * buf, int buf_stride) { | |
| + constexpr int SUBBLK = 32; | |
| + constexpr int NB = TILED_TILE_K / SUBBLK; // 8 | |
| + constexpr int NS = SUBBLK / 16; // 2 | |
| + constexpr int NG = SUBBLK / 4; // 8 | |
| + constexpr int NUM_ROWS = 8; | |
| + | |
| + const int nkr = (NK > 0) ? NK : num_k; | |
| + const int seff = (NK > 0) ? 0 : slab; | |
| + const int qk_stride = nkr * TILED_TILE_K; | |
| + const int qk_off = seff * TILED_TILE_K; | |
| + const int nb_stride = NB * nkr; | |
| + const int nb_off = seff * NB; | |
| + const int bs_stride = (nkr == 1) ? TILED_TILE_ROWS : TILED_MICRO; | |
| + | |
| + __m512 facc[NUM_ROWS]; | |
| + for (int t = 0; t < NUM_ROWS; t++) { facc[t] = _mm512_setzero_ps(); } | |
| + | |
| + const int32_t * bsums = src1.bsums; | |
| + for (int s = 0; s < NB; s++) { | |
| + __m512i bsums32 = _mm512_load_si512((const __m512i *) &bsums[(seff * TILED_MICRO + s * NS) * bs_stride + j0]); | |
| + for (int u = 1; u < NS; u++) { | |
| + bsums32 = _mm512_add_epi32(bsums32, _mm512_load_si512((const __m512i *) &bsums[(seff * TILED_MICRO + s * NS + u) * bs_stride + j0])); | |
| + } | |
| + const __m512 bterm = _mm512_cvtepi32_ps(bsums32); | |
| + | |
| + __m512i acc16[NUM_ROWS]; | |
| + for (int t = 0; t < NUM_ROWS; t++) { acc16[t] = _mm512_setzero_si512(); } | |
| + | |
| +#ifdef __GNUC__ | |
| + #pragma GCC unroll 8 | |
| +#endif | |
| + for (int g = 0; g < NG; g++) { | |
| + const int kg = s * NG + g; | |
| + const __m512i codes = _mm512_load_si512((const __m512i *) &src1.q[(j0 / TILED_MICRO) * (TILED_MICRO * qk_stride) | |
| + + qk_off + (kg % TILED_MICRO) * qk_stride + (kg / TILED_MICRO) * (TILED_MICRO * 4)]); | |
| + for (int t = 0; t < NUM_ROWS; t++) { | |
| + const uint32_t u4 = *(const uint32_t *) &src0.q[(i0 + t) * qk_stride + qk_off + kg * 4]; | |
| + const __m512i u4b = _mm512_set1_epi32((int) u4); | |
| + acc16[t] = _mm512_dpbusd_epi32(acc16[t], u4b, codes); | |
| + } | |
| + } | |
| + | |
| + const __m512 dav = _mm512_loadu_ps(&src1.sd[(seff * NB + s) * bs_stride + j0]); | |
| + for (int t = 0; t < NUM_ROWS; t++) { | |
| + const int ar = i0 + t; | |
| + const float dw = src0.sd[ar * nb_stride + nb_off + s]; | |
| + const float mw = src0.sm[ar * nb_stride + nb_off + s]; | |
| + const __m512 raw = _mm512_cvtepi32_ps(acc16[t]); | |
| + const __m512 tv = _mm512_fmadd_ps(_mm512_set1_ps(dw), raw, _mm512_mul_ps(_mm512_set1_ps(mw), bterm)); | |
| + facc[t] = _mm512_fmadd_ps(tv, dav, facc[t]); | |
| + } | |
| + } | |
| + | |
| + for (int t = 0; t < NUM_ROWS; t++) { | |
| + float * p = &buf[(i0 + t) * buf_stride + j0]; | |
| + _mm512_store_ps(p, _mm512_add_ps(_mm512_load_ps(p), facc[t])); | |
| + } | |
| +} | |
| + | |
| +template <int NK> | |
| +static void tiled_run_microtile_q41_vnni(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1, | |
| + int i0, int j0, int num_k, int slab, float * buf, int buf_stride) { | |
| + tiled_run_micro_q41_vnni_8x16<NK>(src0, src1, i0, j0, num_k, slab, buf, buf_stride); | |
| + tiled_run_micro_q41_vnni_8x16<NK>(src0, src1, i0 + 8, j0, num_k, slab, buf, buf_stride); | |
| +} | |
| + | |
| +// nvfp4: 16 subblocks of 16 per 256-slab, each with its own f32 weight scale (sd16); q8_0 activations carry one | |
| +// f32 scale per 32-block (sd). Codes are LUT + 12 (unsigned), so the bias is removed per subblock with the | |
| +// int32 code sums (bsums): sum(w*a) = sum((w+12)*a) - 12*sum(a). Integer sums are exact. | |
| +template <int NK> | |
| +static void tiled_run_micro_nv4_vnni_8x16(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1, | |
| + int i0, int j0, int num_k, int slab, float * buf, int buf_stride) { | |
| + constexpr int NB32 = TILED_TILE_K / 32; // 8 activation blocks | |
| + constexpr int NB16 = TILED_TILE_K / 16; // 16 weight subblocks | |
| + constexpr int NG = 4; // 4-element groups per 16-subblock | |
| + constexpr int NUM_ROWS = 8; | |
| + | |
| + const int nkr = (NK > 0) ? NK : num_k; | |
| + const int seff = (NK > 0) ? 0 : slab; | |
| + const int qk_stride = nkr * TILED_TILE_K; | |
| + const int qk_off = seff * TILED_TILE_K; | |
| + const int nb_stride = NB16 * nkr; | |
| + const int nb_off = seff * NB16; | |
| + const int bs_stride = (nkr == 1) ? TILED_TILE_ROWS : TILED_MICRO; | |
| + | |
| + __m512 facc[NUM_ROWS]; | |
| + for (int t = 0; t < NUM_ROWS; t++) { facc[t] = _mm512_setzero_ps(); } | |
| + | |
| + const int32_t * bsums = src1.bsums; | |
| + const __m512i twelve = _mm512_set1_epi32(12); | |
| + for (int sb = 0; sb < NB16; sb++) { | |
| + const __m512i bsum12 = _mm512_mullo_epi32(twelve, | |
| + _mm512_load_si512((const __m512i *) &bsums[(seff * TILED_MICRO + sb) * bs_stride + j0])); | |
| + | |
| + __m512i acc16[NUM_ROWS]; | |
| + for (int t = 0; t < NUM_ROWS; t++) { acc16[t] = _mm512_setzero_si512(); } | |
| + | |
| +#ifdef __GNUC__ | |
| + #pragma GCC unroll 4 | |
| +#endif | |
| + for (int g = 0; g < NG; g++) { | |
| + const int kg = sb * NG + g; | |
| + const __m512i codes = _mm512_load_si512((const __m512i *) &src1.q[(j0 / TILED_MICRO) * (TILED_MICRO * qk_stride) | |
| + + qk_off + (kg % TILED_MICRO) * qk_stride + (kg / TILED_MICRO) * (TILED_MICRO * 4)]); | |
| + for (int t = 0; t < NUM_ROWS; t++) { | |
| + const uint32_t u4 = *(const uint32_t *) &src0.q[(i0 + t) * qk_stride + qk_off + kg * 4]; | |
| + const __m512i u4b = _mm512_set1_epi32((int) u4); | |
| + acc16[t] = _mm512_dpbusd_epi32(acc16[t], u4b, codes); | |
| + } | |
| + } | |
| + | |
| + const __m512 dav = _mm512_loadu_ps(&src1.sd[(seff * NB32 + sb / 2) * bs_stride + j0]); | |
| + for (int t = 0; t < NUM_ROWS; t++) { | |
| + const int ar = i0 + t; | |
| + const float dw = src0.sd16[ar * nb_stride + nb_off + sb]; | |
| + const __m512 raw = _mm512_cvtepi32_ps(_mm512_sub_epi32(acc16[t], bsum12)); | |
| + facc[t] = _mm512_fmadd_ps(raw, _mm512_mul_ps(_mm512_set1_ps(dw), dav), facc[t]); | |
| + } | |
| + } | |
| + | |
| + for (int t = 0; t < NUM_ROWS; t++) { | |
| + float * p = &buf[(i0 + t) * buf_stride + j0]; | |
| + _mm512_store_ps(p, _mm512_add_ps(_mm512_load_ps(p), facc[t])); | |
| + } | |
| +} | |
| + | |
| +template <int NK> | |
| +static void tiled_run_microtile_nv4_vnni(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1, | |
| + int i0, int j0, int num_k, int slab, float * buf, int buf_stride) { | |
| + tiled_run_micro_nv4_vnni_8x16<NK>(src0, src1, i0, j0, num_k, slab, buf, buf_stride); | |
| + tiled_run_micro_nv4_vnni_8x16<NK>(src0, src1, i0 + 8, j0, num_k, slab, buf, buf_stride); | |
| +} | |
| + | |
| #endif // __AVX512VNNI__ && __AVX512VL__ | |
| #if defined(__AVX2__) | |
| static void tiled_run_microtile_avx(const tiled_tile_src0 & src0, const tiled_ti | |
| #endif // __AVX__ && !__AVX2__ | |
| +// q4_1 reference/scalar path (non-VNNI builds): same math as the VNNI kernel, per 32-block f32 scales. | |
| +template <int NK> | |
| +static void tiled_run_microtile_q41_scalar(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1, | |
| + int i0, int j0, int num_k, int slab, float * buf, int buf_stride) { | |
| + constexpr int SUBBLK = 32; | |
| + constexpr int NB = TILED_TILE_K / SUBBLK; | |
| + constexpr int NS = SUBBLK / 16; | |
| + | |
| + const int nkr = (NK > 0) ? NK : num_k; | |
| + const int seff = (NK > 0) ? 0 : slab; | |
| + const int qk_stride = nkr * TILED_TILE_K; | |
| + const int qk_off = seff * TILED_TILE_K; | |
| + const int nb_stride = NB * nkr; | |
| + const int nb_off = seff * NB; | |
| + const int bs_stride = (nkr == 1) ? TILED_TILE_ROWS : TILED_MICRO; | |
| + | |
| + for (int t = 0; t < TILED_MICRO; t++) { | |
| + const int ar = i0 + t; | |
| + const uint8_t * q0 = &src0.q[ar * qk_stride + qk_off]; | |
| + for (int j = 0; j < TILED_MICRO; j++) { | |
| + const int br = j0 + j; | |
| + const int8_t * q1 = &src1.q[br * qk_stride + qk_off]; | |
| + float acc = 0.0f; | |
| + for (int s = 0; s < NB; s++) { | |
| + int32_t bsum = 0; | |
| + for (int u = 0; u < NS; u++) { bsum += src1.bsums[(seff * TILED_MICRO + s * NS + u) * bs_stride + br]; } | |
| + int32_t raw = 0; | |
| + for (int e = 0; e < SUBBLK; e++) { raw += (int32_t) q0[s * SUBBLK + e] * (int32_t) q1[s * SUBBLK + e]; } | |
| + const float da = src1.sd[(seff * NB + s) * bs_stride + br]; | |
| + const float dw = src0.sd[ar * nb_stride + nb_off + s]; | |
| + const float mw = src0.sm[ar * nb_stride + nb_off + s]; | |
| + acc += da * (dw * (float) raw + mw * (float) bsum); | |
| + } | |
| + buf[ar * buf_stride + j0 + j] += acc; | |
| + } | |
| + } | |
| +} | |
| + | |
| // main microtile entry point. num_k = K-blocks per row (the narrow path reads num_k slabs at row | |
| // stride num_k*256 so each weight row is one long stream); slab = this call's slab. num_k=1, | |
| // slab=0 is the standard single-slab path. | |
| // num_k==1 dispatches to the NK=1 instantiation, whose strides/offsets are compile-time | |
| // constants (shifts/scaled-leas). num_k>1 (the narrow path) uses the NK=0 runtime-strided | |
| // version; it is memory-bound, so one version serves all of it. | |
| -template <int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS> | |
| +// Q41: q4_1 weights + q8_1 activations (per-32-block f32 scales) use the dedicated kernels above. | |
| +template <int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, bool Q41> | |
| void tiled_run_microtile(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1, | |
| int i0, int j0, int num_k, int slab, float * buf, int buf_stride) { | |
| const bool standard = (num_k == 1); | |
| #if defined(__AVX512VNNI__) && defined(__AVX512VL__) && defined(__AVX512DQ__) | |
| + if constexpr (Q41) { | |
| + if constexpr (SUBBLK == 16) { | |
| + // nvfp4 (per-16 weight scales), see tiled_run_micro_nv4_vnni_8x16 | |
| + if (standard) { | |
| + tiled_run_microtile_nv4_vnni<1>(src0, src1, i0, j0, num_k, slab, buf, buf_stride); | |
| + } else { | |
| + tiled_run_microtile_nv4_vnni<0>(src0, src1, i0, j0, num_k, slab, buf, buf_stride); | |
| + } | |
| + return; | |
| + } | |
| + if (standard) { | |
| + tiled_run_microtile_q41_vnni<1>(src0, src1, i0, j0, num_k, slab, buf, buf_stride); | |
| + } else { | |
| + tiled_run_microtile_q41_vnni<0>(src0, src1, i0, j0, num_k, slab, buf, buf_stride); | |
| + } | |
| + return; | |
| + } | |
| if (standard) { | |
| tiled_run_microtile_vnni<SUBBLK, HAS_MIN, BIAS, 1>(src0, src1, i0, j0, num_k, slab, buf, buf_stride); | |
| } else { | |
| tiled_run_microtile_vnni<SUBBLK, HAS_MIN, BIAS, 0>(src0, src1, i0, j0, num_k, slab, buf, buf_stride); | |
| } | |
| -#elif defined(__AVX2__) | |
| +#else | |
| + if constexpr (Q41) { | |
| + if (standard) { | |
| + tiled_run_microtile_q41_scalar<1>(src0, src1, i0, j0, num_k, slab, buf, buf_stride); | |
| + } else { | |
| + tiled_run_microtile_q41_scalar<0>(src0, src1, i0, j0, num_k, slab, buf, buf_stride); | |
| + } | |
| + return; | |
| + } | |
| +#if defined(__AVX2__) | |
| if (standard) { | |
| tiled_run_microtile_avx2<SUBBLK, HAS_MIN, BIAS, 1, ACTBIAS>(src0, src1, i0, j0, num_k, slab, buf, buf_stride); | |
| } else { | |
| void tiled_run_microtile(const tiled_tile_src0 & src0, const tiled_tile_src1 & s | |
| tiled_run_microtile_scalar<SUBBLK, HAS_MIN, BIAS, 0>(src0, src1, i0, j0, num_k, slab, buf, buf_stride); | |
| } | |
| #endif | |
| +#endif | |
| } | |
| // explicit instantiations for the in-use formats (q4_K and q5_K share the constants) | |
| template void tiled_run_microtile<16, false, 4, true>(const tiled_tile_src0 & sr | |
| // q2_K: BIAS = 0 | |
| template void tiled_run_microtile<16, true, 0, false>(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1, | |
| int i0, int j0, int num_k, int slab, float * buf, int buf_stride); | |
| +// q4_1: per-32-block f32 scales, q8_1 activations | |
| +template void tiled_run_microtile<32, false, 0, false, true>(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1, | |
| + int i0, int j0, int num_k, int slab, float * buf, int buf_stride); | |
| +// nvfp4: per-16-subblock f32 scales, q8_0 activations | |
| +template void tiled_run_microtile<16, false, 0, false, true>(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1, | |
| + int i0, int j0, int num_k, int slab, float * buf, int buf_stride); | |
| #define MIN(a, b) ((a) < (b) ? (a) : (b)) | |
| diff --git a/ggml/src/ggml-cpu/tiled/tiled-kernel.h b/ggml/src/ggml-cpu/tiled/tiled-kernel.h | |
| index 1a97f406a..c5d081420 100644 | |
| --- a/ggml/src/ggml-cpu/tiled/tiled-kernel.h | |
| +++ b/ggml/src/ggml-cpu/tiled/tiled-kernel.h | |
| // SUBBLK=32 formats index at stride 8 and leave the slack unused. | |
| struct tiled_tile_src0 { | |
| static constexpr int NB_MAX = TILED_TILE_K / 16; // max subblocks per 256-elem block | |
| + static constexpr int NB32 = TILED_TILE_K / 32; // 32-wide subblocks (q4_1) | |
| alignas(64) uint8_t q[TILED_TILE_ROWS * TILED_TILE_K]; // unsigned quants, widened to uint8 | |
| float d[TILED_TILE_ROWS]; // One d from each input block, widened to f32 | |
| float dmin[TILED_TILE_ROWS]; // dmin from each input block (if applicable), widened to F32 | |
| int32_t scales[TILED_TILE_ROWS * NB_MAX]; // per-subblock scale, stored as int32_t | |
| int32_t mins[TILED_TILE_ROWS * NB_MAX]; // per-subblock min, used when HAS_MIN | |
| + // q4_1: no super-block scale, each 32-wide block carries its own f32 scale/min | |
| + float sd[TILED_TILE_ROWS * NB32]; // per-32-block scale | |
| + float sm[TILED_TILE_ROWS * NB32]; // per-32-block min | |
| + // nvfp4: one f32 scale per 16-wide subblock (UE4M3 decoded), codes are LUT + 12 (0..24) | |
| + float sd16[TILED_TILE_ROWS * NB_MAX]; | |
| }; | |
| // src1 tile: built from q8_K (wdata) | |
| struct tiled_tile_src1 { | |
| alignas(64) int32_t bsums[(TILED_TILE_K / 16) * TILED_TILE_ROWS]; | |
| // f32 (not f16): q8_k stores fp16, the unpack converts once | |
| float d[TILED_TILE_ROWS]; | |
| + // q4_1: q8_1 activations carry a scale per 32-wide block (no super-block d) | |
| + float sd[(TILED_TILE_K / 32) * TILED_TILE_ROWS]; | |
| }; | |
| // per-thread workspace: all tiled state lives here, allocated in wdata (one slot per thread) | |
| inline void tiled_unpk_nib4(const uint8_t * src, uint8_t * lo, uint8_t * hi) { | |
| _mm256_storeu_si256((__m256i *) lo, _mm256_and_si256(v, _mm256_set1_epi8(0x0F))); | |
| _mm256_storeu_si256((__m256i *) hi, _mm256_srli_epi32(_mm256_and_si256(v, _mm256_set1_epi8((int8_t) 0xF0)), 4)); | |
| } | |
| +// nvfp4: 32 packed bytes (one 64-element block) -> 64 codes, LUT(kvalues_fp4) + 12 in 0..24. | |
| +// byte B = 8s+j: lo -> elem 16s+j, hi -> elem 16s+8+j | |
| +inline void tiled_unpk_fp4_64(const uint8_t * src, uint8_t * dst) { | |
| + const __m256i lut = _mm256_setr_epi8(12, 13, 14, 15, 16, 18, 20, 24, 12, 11, 10, 9, 8, 6, 4, 0, | |
| + 12, 13, 14, 15, 16, 18, 20, 24, 12, 11, 10, 9, 8, 6, 4, 0); | |
| + const __m256i v = _mm256_loadu_si256((const __m256i *) src); | |
| + const __m256i lo = _mm256_shuffle_epi8(lut, _mm256_and_si256(v, _mm256_set1_epi8(0x0F))); | |
| + const __m256i hi = _mm256_shuffle_epi8(lut, _mm256_and_si256(_mm256_srli_epi16(v, 4), _mm256_set1_epi8(0x0F))); | |
| + // x = [s0 | s2], y = [s1 | s3] (128-bit halves) | |
| + const __m256i x = _mm256_unpacklo_epi64(lo, hi); | |
| + const __m256i y = _mm256_unpackhi_epi64(lo, hi); | |
| + _mm256_storeu_si256((__m256i *) dst, _mm256_permute2x128_si256(x, y, 0x20)); | |
| + _mm256_storeu_si256((__m256i *) (dst + 32), _mm256_permute2x128_si256(x, y, 0x31)); | |
| +} | |
| +// 16-byte source (one q4_1 block: lo -> elems 0..15, hi -> elems 16..31) | |
| +inline void tiled_unpk_nib4_16(const uint8_t * src, uint8_t * lo, uint8_t * hi) { | |
| + const __m128i v = _mm_loadu_si128((const __m128i *) src); | |
| + _mm_storeu_si128((__m128i *) lo, _mm_and_si128(v, _mm_set1_epi8(0x0F))); | |
| + _mm_storeu_si128((__m128i *) hi, _mm_srli_epi32(_mm_and_si128(v, _mm_set1_epi8((int8_t) 0xF0)), 4)); | |
| +} | |
| // 2-bit values at bit offset S | |
| template <int S> inline void tiled_unpk_2bit(const uint8_t * src, uint8_t * dst) { | |
| _mm256_storeu_si256((__m256i *) dst, _mm256_and_si256( | |
| inline void tiled_unpk_tern8(const uint8_t * src, int8_t delta, uint8_t * dst) { | |
| inline void tiled_unpk_nib4(const uint8_t * src, uint8_t * lo, uint8_t * hi) { | |
| for (int l = 0; l < 32; l++) { lo[l] = (uint8_t) (src[l] & 0xF); hi[l] = (uint8_t) (src[l] >> 4); } | |
| } | |
| +inline void tiled_unpk_fp4_64(const uint8_t * src, uint8_t * dst) { | |
| + static const uint8_t lut[16] = { 12, 13, 14, 15, 16, 18, 20, 24, 12, 11, 10, 9, 8, 6, 4, 0 }; | |
| + for (int B = 0; B < 32; B++) { | |
| + const int s = B / 8, j = B % 8; | |
| + dst[16 * s + j] = lut[src[B] & 0xF]; | |
| + dst[16 * s + 8 + j] = lut[src[B] >> 4]; | |
| + } | |
| +} | |
| +inline void tiled_unpk_nib4_16(const uint8_t * src, uint8_t * lo, uint8_t * hi) { | |
| + for (int l = 0; l < 16; l++) { lo[l] = (uint8_t) (src[l] & 0xF); hi[l] = (uint8_t) (src[l] >> 4); } | |
| +} | |
| template <int S> | |
| inline void tiled_unpk_2bit(const uint8_t * src, uint8_t * dst) { | |
| for (int l = 0; l < 32; l++) { dst[l] = (uint8_t) ((src[l] >> S) & 3); } | |
| inline void tiled_unpk_tern8(const uint8_t * src, int8_t delta, uint8_t * dst) { | |
| // ACTBIAS (AVX2 only): the activation is pre-biased +128 by tiled_repack_src1, | |
| // num_k = K-blocks per row: the tile holds num_k slabs at row stride num_k*256 | |
| // (Default case is num_k=1, 256x256 tiles, we go to longer num_k to improve memory bandwidth when num_rows is small) | |
| -template <int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS> | |
| +// Q41: src0 is q4_1 (per-32-block f32 scale/min in sd/sm) and src1 is q8_1 (per-32-block f32 scale in sd); | |
| +// the other format constants (SUBBLK=32, HAS_MIN=false, BIAS=0, ACTBIAS=false) are unused in that mode. | |
| +template <int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, bool Q41 = false> | |
| void tiled_run_microtile(const tiled_tile_src0 & src0, const tiled_tile_src1 & src1, | |
| int i0, int j0, int num_k, int slab, float * buf, int buf_stride); | |
| diff --git a/ggml/src/ggml-cpu/tiled/tiled.cpp b/ggml/src/ggml-cpu/tiled/tiled.cpp | |
| index 77e1a9945..96640b50d 100644 | |
| --- a/ggml/src/ggml-cpu/tiled/tiled.cpp | |
| +++ b/ggml/src/ggml-cpu/tiled/tiled.cpp | |
| static void tiled_unpack_src0(const block_iq1_m * rows, int64_t row_stride, int | |
| } | |
| } | |
| +// q4_1: 8 x 32-wide blocks per 256-slab, each with its own f32 d (scale) and m (min). | |
| +static void tiled_unpack_src0(const block_q4_1 * rows, int64_t row_stride, int n_rows, tiled_tile_src0 * tile, int num_k) { | |
| + GGML_ASSERT(n_rows <= TILED_TILE_ROWS); | |
| + constexpr int NB = TILED_TILE_K / 32; // 8 x 32-wide blocks | |
| + | |
| + const int qk_stride = num_k * TILED_TILE_K; | |
| + const int nb_stride = NB * num_k; | |
| + for (int slab = 0; slab < num_k; slab++) { | |
| + for (int r = 0; r < n_rows; r++) { | |
| + const int q_off = r * qk_stride + slab * TILED_TILE_K; | |
| + const int s_off = r * nb_stride + slab * NB; | |
| + const block_q4_1 * x = rows + r * row_stride + slab * NB; | |
| + for (int s = 0; s < NB; s++) { | |
| + tile->sd[s_off + s] = ggml_fp16_to_fp32(x[s].data.data.d); | |
| + tile->sm[s_off + s] = ggml_fp16_to_fp32(x[s].data.data.m); | |
| + // 16 bytes -> elems 0..15 (low nibble) and 16..31 (high nibble) | |
| + tiled_unpk_nib4_16(x[s].qs, &tile->q[q_off + s * 32], &tile->q[q_off + s * 32 + 16]); | |
| + } | |
| + } | |
| + } | |
| +} | |
| + | |
| +// nvfp4: 4 x 64-element blocks per 256-slab, each block 4 UE4M3 subblock scales (16 elems each) + 32 nibble bytes. | |
| +// Scales go to sd16 (one per 16-wide subblock); codes are LUT + 12 (see tiled_unpk_fp4_64). | |
| +static void tiled_unpack_src0(const block_nvfp4 * rows, int64_t row_stride, int n_rows, tiled_tile_src0 * tile, int num_k) { | |
| + GGML_ASSERT(n_rows <= TILED_TILE_ROWS); | |
| + constexpr int NBLK = TILED_TILE_K / 64; // 4 nvfp4 blocks per 256-slab | |
| + constexpr int NB16 = TILED_TILE_K / 16; // 16 subblocks per 256-slab | |
| + | |
| + const int qk_stride = num_k * TILED_TILE_K; | |
| + const int nb_stride = NB16 * num_k; | |
| + for (int slab = 0; slab < num_k; slab++) { | |
| + for (int r = 0; r < n_rows; r++) { | |
| + const int q_off = r * qk_stride + slab * TILED_TILE_K; | |
| + const int s_off = r * nb_stride + slab * NB16; | |
| + const block_nvfp4 * x = rows + r * row_stride + slab * NBLK; | |
| + for (int b = 0; b < NBLK; b++) { | |
| + for (int s = 0; s < 4; s++) { | |
| + tile->sd16[s_off + 4 * b + s] = ggml_ue4m3_to_fp32(x[b].d[s]); | |
| + } | |
| + tiled_unpk_fp4_64(x[b].qs, &tile->q[q_off + b * 64]); | |
| + } | |
| + } | |
| + } | |
| +} | |
| + | |
| // unpack src1 tile from q8_K rows. num_k = K-blocks per row: each row decodes num_k consecutive | |
| // source blocks (rows[r][kblk + slab]) into the tile at row stride num_k*256, so each activation | |
| // row is one long stream. num_k=1 is the standard single-slab path (byte-identical to before). | |
| -static void tiled_unpack_src1_q8_K(const block_q8_K * const * rows, int n_rows, tiled_tile_src1 * tile, | |
| - int kblk, int num_k) { | |
| +static void tiled_unpack_src1(const block_q8_K * const * rows, int n_rows, tiled_tile_src1 * tile, | |
| + int kblk, int num_k) { | |
| GGML_ASSERT(n_rows <= TILED_TILE_ROWS); | |
| const int n_padded = (n_rows + TILED_MICRO - 1) & ~(TILED_MICRO - 1); | |
| const int bs_stride = (num_k == 1) ? TILED_TILE_ROWS : TILED_MICRO; | |
| static void tiled_unpack_src1_q8_K(const block_q8_K * const * rows, int n_rows, | |
| } | |
| } | |
| +// q4_1 src1: q8_1 activation rows. 8 blocks of 32 per 256-slab, each block has its own f32 scale; | |
| +// the integer per-16 sums are recomputed from the codes (q8_1 only stores d and d*sum). | |
| +static void tiled_unpack_src1(const block_q8_1 * const * rows, int n_rows, tiled_tile_src1 * tile, | |
| + int kblk, int num_k) { | |
| + GGML_ASSERT(n_rows <= TILED_TILE_ROWS); | |
| + const int n_padded = (n_rows + TILED_MICRO - 1) & ~(TILED_MICRO - 1); | |
| + const int bs_stride = (num_k == 1) ? TILED_TILE_ROWS : TILED_MICRO; | |
| + constexpr int NBLK = TILED_TILE_K / 32; // 8 q8_1 blocks per 256-slab | |
| + constexpr int n16 = TILED_TILE_K / TILED_MICRO; | |
| + for (int slab = 0; slab < num_k; slab++) { | |
| + const int q_off = slab * TILED_TILE_K; | |
| + for (int r = 0; r < n_padded; r++) { | |
| + if (r < n_rows) { | |
| + const block_q8_1 * xb = rows[r] + (kblk + slab) * NBLK; | |
| + for (int b = 0; b < NBLK; b++) { | |
| + memcpy(&tile->q[r * (num_k * TILED_TILE_K) + q_off + b * 32], xb[b].qs, 32); | |
| + int32_t s0 = 0, s1 = 0; | |
| + for (int e = 0; e < 16; e++) { s0 += xb[b].qs[e]; s1 += xb[b].qs[16 + e]; } | |
| + tile->bsums[(slab * n16 + 2 * b + 0) * bs_stride + r] = s0; | |
| + tile->bsums[(slab * n16 + 2 * b + 1) * bs_stride + r] = s1; | |
| + tile->sd[(slab * NBLK + b) * bs_stride + r] = ggml_fp16_to_fp32(xb[b].data.data.d); | |
| + } | |
| + tile->d[slab * TILED_MICRO + r] = 1.0f; // per-32 scales are applied in the kernel | |
| + } else { | |
| + memset(&tile->q[r * (num_k * TILED_TILE_K) + q_off], 0, TILED_TILE_K); | |
| + for (int i = 0; i < n16; i++) { tile->bsums[(slab * n16 + i) * bs_stride + r] = 0; } | |
| + for (int b = 0; b < NBLK; b++) { tile->sd[(slab * NBLK + b) * bs_stride + r] = 0.0f; } | |
| + tile->d[slab * TILED_MICRO + r] = 1.0f; | |
| + } | |
| + } | |
| + } | |
| +} | |
| + | |
| +// nvfp4 src1: q8_0 activation rows (one f32 scale per 32-wide block, per-16 sums recomputed from the codes). | |
| +static void tiled_unpack_src1(const block_q8_0 * const * rows, int n_rows, tiled_tile_src1 * tile, | |
| + int kblk, int num_k) { | |
| + GGML_ASSERT(n_rows <= TILED_TILE_ROWS); | |
| + const int n_padded = (n_rows + TILED_MICRO - 1) & ~(TILED_MICRO - 1); | |
| + const int bs_stride = (num_k == 1) ? TILED_TILE_ROWS : TILED_MICRO; | |
| + constexpr int NBLK = TILED_TILE_K / 32; // 8 q8_0 blocks per 256-slab | |
| + constexpr int n16 = TILED_TILE_K / TILED_MICRO; | |
| + for (int slab = 0; slab < num_k; slab++) { | |
| + const int q_off = slab * TILED_TILE_K; | |
| + for (int r = 0; r < n_padded; r++) { | |
| + if (r < n_rows) { | |
| + const block_q8_0 * xb = rows[r] + (kblk + slab) * NBLK; | |
| + for (int b = 0; b < NBLK; b++) { | |
| + memcpy(&tile->q[r * (num_k * TILED_TILE_K) + q_off + b * 32], xb[b].qs, 32); | |
| + int32_t s0 = 0, s1 = 0; | |
| + for (int e = 0; e < 16; e++) { s0 += xb[b].qs[e]; s1 += xb[b].qs[16 + e]; } | |
| + tile->bsums[(slab * n16 + 2 * b + 0) * bs_stride + r] = s0; | |
| + tile->bsums[(slab * n16 + 2 * b + 1) * bs_stride + r] = s1; | |
| + tile->sd[(slab * NBLK + b) * bs_stride + r] = ggml_fp16_to_fp32(xb[b].d); | |
| + } | |
| + tile->d[slab * TILED_MICRO + r] = 1.0f; // per-32 scales are applied in the kernel | |
| + } else { | |
| + memset(&tile->q[r * (num_k * TILED_TILE_K) + q_off], 0, TILED_TILE_K); | |
| + for (int i = 0; i < n16; i++) { tile->bsums[(slab * n16 + i) * bs_stride + r] = 0; } | |
| + for (int b = 0; b < NBLK; b++) { tile->sd[(slab * NBLK + b) * bs_stride + r] = 0.0f; } | |
| + tile->d[slab * TILED_MICRO + r] = 1.0f; | |
| + } | |
| + } | |
| + } | |
| +} | |
| + | |
| // GGML_CPU_TILED_MM: master switch, on by default. If off, we fast return false and normal vec_dot mul_mat resumes | |
| static bool ggml_tiled_matmul_enabled(void) { | |
| static bool enabled = true; | |
| static bool ggml_tiled_supported(const struct ggml_tensor * src0, | |
| case GGML_TYPE_Q6_K: | |
| case GGML_TYPE_Q5_K: | |
| case GGML_TYPE_Q4_K: | |
| + case GGML_TYPE_Q4_1: | |
| + case GGML_TYPE_NVFP4: | |
| case GGML_TYPE_Q3_K: | |
| case GGML_TYPE_Q2_K: | |
| case GGML_TYPE_IQ4_XS: | |
| static void tiled_store_window(const float * buf, int n_src0, int n_src1, int bu | |
| // unpack and microtile are reused unchanged from mul_mat. | |
| // src0 rows per g group; finer than the TILED_TILE_K kernel tile so all threads stay busy at small ne01 | |
| -#define TILED_MMID_GROUP 64 | |
| +#ifndef TILED_MMID_GROUP | |
| +#define TILED_MMID_GROUP 32 | |
| +#endif | |
| // like tiled_store_window, but the dst columns are not contiguous: col_ptrs[j] points at the | |
| // start of dst column j, whose rows are contiguous (dim 0, nb0 == 4 bytes). | |
| static void tiled_store_window_scatter(const float * buf, int n_src0, int n_src1 | |
| // one (g, k) macrotile: zero the acc window, sweep K in 256 element slabs, scatter the result rows | |
| // rows points at this k window's routed src1 rows, row r at its base block | |
| -template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS> | |
| +template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, typename BA = block_q8_K, bool Q41 = false> | |
| static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_tensor * src0, | |
| const char * src0_cur, int64_t r, int64_t k, int64_t nrows, | |
| const int32_t * expert_rows, | |
| - const block_q8_K * const * rows, | |
| + const BA * const * rows, | |
| tiled_ws * ws) { | |
| const int64_t ne00 = src0->ne[0]; | |
| const int64_t ne01 = src0->ne[1]; | |
| static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_t | |
| const size_t src0_bs = ggml_type_size(src0->type); | |
| const int64_t src0_stride = src0->nb[1] / src0_bs; | |
| + // source blocks per 256-element slab (1 for K-quants, 8 for q4_1's 32-wide blocks) | |
| + const int kblk_scale = (int) (TILED_TILE_K / ggml_blck_size(src0->type)); | |
| // the window is at most TILED_MMID_GROUP x TILED_TILE_K, so zero only that region of acc | |
| for (int64_t i = 0; i < n_src0; i++) { | |
| static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_t | |
| const int kstart = (int) (k0 / TILED_TILE_K); | |
| // activation: one 16-row band (nrows <= 16). Unpack the whole k_extent chunk (all | |
| // slabs) so each activation row is a long stream, then repack each slab in place | |
| - tiled_unpack_src1_q8_K(rows, nrows, &ws->src1, kstart, num_k); | |
| + tiled_unpack_src1(rows, nrows, &ws->src1, kstart, num_k); | |
| // repack the whole k_extent chunk in place: num_k slabs x 4 tiles, 16 rows, row stride num_k*256 | |
| tiled_repack_src1(&ws->src1, 0, num_k, ACTBIAS); | |
| // weight groups (16 at a time): unpack the k_extent chunk of the group (the long per-row | |
| // read), then one standard MAC per slab accumulating into the same acc rows | |
| for (int64_t ir0 = r; ir0 < r_end; ir0 += TILED_MICRO) { | |
| const int n0 = (int) MIN(TILED_MICRO, r_end - ir0); | |
| - const B * wbase = (const B *) (src0_cur + ir0 * src0->nb[1] + kstart * src0_bs); | |
| + const B * wbase = (const B *) (src0_cur + ir0 * src0->nb[1] + kstart * kblk_scale * src0_bs); | |
| tiled_unpack_src0(wbase, src0_stride, n0, &ws->src0, num_k); | |
| tiled_repack_src0<SUBBLK>(&ws->src0, n0, num_k, BIAS, ACTBIAS); | |
| float * buf = ws->acc + (ir0 - r) * TILED_TILE_ROWS; | |
| for (int slab = 0; slab < num_k; slab++) { | |
| - tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS>(ws->src0, ws->src1, 0, 0, num_k, slab, buf, TILED_TILE_ROWS); | |
| + tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS, Q41>(ws->src0, ws->src1, 0, 0, num_k, slab, buf, TILED_TILE_ROWS); | |
| } | |
| } | |
| } | |
| static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_t | |
| // standard per-slab path: K is stepped in 256-element slabs | |
| for (int64_t ib = 0; ib < ne00; ib += TILED_TILE_K) { | |
| const int kblk = (int) (ib / TILED_TILE_K); | |
| - tiled_unpack_src0((const B *) (src0_cur + r * src0->nb[1] + kblk * src0_bs), src0_stride, n_src0, &ws->src0, 1); | |
| + tiled_unpack_src0((const B *) (src0_cur + r * src0->nb[1] + kblk * kblk_scale * src0_bs), src0_stride, n_src0, &ws->src0, 1); | |
| tiled_repack_src0<SUBBLK>(&ws->src0, n_src0, 1, BIAS, ACTBIAS); | |
| - tiled_unpack_src1_q8_K(rows, nrows, &ws->src1, kblk, 1); | |
| + tiled_unpack_src1(rows, nrows, &ws->src1, kblk, 1); | |
| // 16x16 microtiles sweeping the window; repack each src1 band just-in-time | |
| // (j0-outer) so only the bands actually used are repacked and each is L1-hot | |
| for (int64_t ir1 = 0; ir1 < nrows; ir1 += TILED_MICRO) { | |
| tiled_repack_src1(&ws->src1, (int) ir1, 1, ACTBIAS); | |
| for (int64_t ir0 = r; ir0 < r_end; ir0 += TILED_MICRO) { | |
| - tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS>(ws->src0, ws->src1, | |
| + tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS, Q41>(ws->src0, ws->src1, | |
| (int) (ir0 - r), (int) ir1, 1, 0, | |
| ws->acc, TILED_TILE_ROWS); | |
| } | |
| static void tiled_mmid_gemm_window(struct ggml_tensor * dst, const struct ggml_t | |
| // one expert of MUL_MAT_ID: each k window's dispatched rows are pointed at by a per-window | |
| // row pointer list and swept over the thread's row windows, the dst rows are scattered back | |
| // per window | |
| -template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS> | |
| +template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, typename BA = block_q8_K, bool Q41 = false> | |
| static void ggml_compute_forward_mul_mat_id_tiled_one_expert( | |
| const struct ggml_compute_params * params, | |
| struct ggml_tensor * dst, | |
| static void ggml_compute_forward_mul_mat_id_tiled_one_expert( | |
| for (int64_t k = 0; k < cne1; k += TILED_TILE_K) { | |
| const int64_t nrows = MIN(TILED_TILE_K, cne1 - k); | |
| - const block_q8_K * rows[TILED_TILE_ROWS]; | |
| + const BA * rows[TILED_TILE_ROWS]; | |
| for (int64_t i = 0; i < nrows; i++) { | |
| const int64_t i11 = expert_rows[2 * (k + i) + 0] % ne11; | |
| const int64_t i12 = expert_rows[2 * (k + i) + 1]; | |
| - rows[i] = (const block_q8_K *) ((const char *) wdata + (i11 + i12 * ne11) * nbw1); | |
| + rows[i] = (const BA *) ((const char *) wdata + (i11 + i12 * ne11) * nbw1); | |
| } | |
| for (int64_t g = g0; g < g1; g++) { | |
| const int64_t r = g * TILED_MMID_GROUP; | |
| - tiled_mmid_gemm_window<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS>(dst, src0, src0_cur, r, k, nrows, expert_rows, rows, ws); | |
| + tiled_mmid_gemm_window<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS, BA, Q41>(dst, src0, src0_cur, r, k, nrows, expert_rows, rows, ws); | |
| } | |
| } | |
| } | |
| -template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS> | |
| +template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, typename BA = block_q8_K, bool Q41 = false> | |
| static void ggml_compute_forward_mul_mat_tiled_one_chunk( | |
| const struct ggml_compute_params * params, | |
| struct ggml_tensor * dst, | |
| static void ggml_compute_forward_mul_mat_tiled_one_chunk( | |
| const int64_t src0_stride = nb01 / src0_bs; // blocks between src0 rows | |
| const int64_t src1_stride = (src1->type == vec_dot_type ? src1->nb[1] : row_size) / src1_bs; | |
| + // source blocks per 256-element slab (1 for K-quants, 8 for q4_1's 32-wide blocks) | |
| + const int kblk_scale = (int) (TILED_TILE_K / ggml_blck_size(src0->type)); | |
| const int64_t TILE = 256; | |
| const int64_t MICRO = 16; | |
| static void ggml_compute_forward_mul_mat_tiled_one_chunk( | |
| char * dst_col = (char *) dst->data + i12 * nb2 + i13 * nb3; | |
| // rows[r] is the window's row r base block; the k-slab offset is applied in the unpack | |
| - const block_q8_K * rows[TILED_TILE_ROWS]; | |
| + const BA * rows[TILED_TILE_ROWS]; | |
| for (int r = 0; r < n_src1; r++) { | |
| - rows[r] = (const block_q8_K *) ((const char *) wdata + (iir1 + r) * src1_stride * src1_bs); | |
| + rows[r] = (const BA *) ((const char *) wdata + (iir1 + r) * src1_stride * src1_bs); | |
| } | |
| for (int64_t iir0 = ir0_start; iir0 < ir0_end; iir0 += TILE) { | |
| static void ggml_compute_forward_mul_mat_tiled_one_chunk( | |
| const int kstart = (int) (k0 / TILED_TILE_K); | |
| // activation: one 16-row band (n_src1 <= 16). Unpack the whole k_extent chunk | |
| // (all slabs) so each activation row is a long stream, then repack each slab in place | |
| - tiled_unpack_src1_q8_K(rows, n_src1, &ws->src1, kstart, num_k); | |
| + tiled_unpack_src1(rows, n_src1, &ws->src1, kstart, num_k); | |
| // repack the whole k_extent chunk in place, if kernel wants to: num_k slabs x 4 tiles, 16 rows, row stride num_k*256 | |
| tiled_repack_src1(&ws->src1, 0, num_k, ACTBIAS); | |
| // weight groups: unpack the k_extent chunk of each 16-row group (the long per-row | |
| // read), then one standard MAC per slab accumulating into the same acc rows | |
| for (int64_t ir0 = iir0; ir0 < iir0_end; ir0 += MICRO) { | |
| const int n0 = (int) MIN(MICRO, iir0_end - ir0); | |
| - const B * wbase = (const B *) (src0_row + ir0 * nb01 + kstart * src0_bs); | |
| + const B * wbase = (const B *) (src0_row + ir0 * nb01 + kstart * kblk_scale * src0_bs); | |
| tiled_unpack_src0(wbase, src0_stride, n0, &ws->src0, num_k); | |
| tiled_repack_src0<SUBBLK>(&ws->src0, n0, num_k, BIAS, ACTBIAS); | |
| float * buf = ws->acc + (ir0 - iir0) * TILED_TILE_ROWS; | |
| for (int slab = 0; slab < num_k; slab++) { | |
| - tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS>(ws->src0, ws->src1, 0, 0, num_k, slab, buf, TILED_TILE_ROWS); | |
| + tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS, Q41>(ws->src0, ws->src1, 0, 0, num_k, slab, buf, TILED_TILE_ROWS); | |
| } | |
| } | |
| } | |
| static void ggml_compute_forward_mul_mat_tiled_one_chunk( | |
| // standard per-slab path: K is stepped in 256-element chunks | |
| for (int64_t ib = 0; ib < ne00; ib += TILE) { | |
| const int kblk = (int) (ib / TILE); | |
| - tiled_unpack_src0((const B *) (src0_row + iir0 * nb01 + kblk * src0_bs), src0_stride, n_src0, &ws->src0, 1); | |
| + tiled_unpack_src0((const B *) (src0_row + iir0 * nb01 + kblk * kblk_scale * src0_bs), src0_stride, n_src0, &ws->src0, 1); | |
| tiled_repack_src0<SUBBLK>(&ws->src0, n_src0, 1, BIAS, ACTBIAS); | |
| - tiled_unpack_src1_q8_K(rows, n_src1, &ws->src1, kblk, 1); | |
| + tiled_unpack_src1(rows, n_src1, &ws->src1, kblk, 1); | |
| // 16x16 microtiles sweeping the window; repack each src1 band before first use | |
| for (int64_t ir1 = iir1; ir1 < iir1_end; ir1 += MICRO) { | |
| tiled_repack_src1(&ws->src1, (int) (ir1 - iir1), 1, ACTBIAS); | |
| for (int64_t ir0 = iir0; ir0 < iir0_end; ir0 += MICRO) { | |
| - tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS>(ws->src0, ws->src1, | |
| + tiled_run_microtile<SUBBLK, HAS_MIN, BIAS, ACTBIAS, Q41>(ws->src0, ws->src1, | |
| (int) (ir0 - iir0), (int) (ir1 - iir1), 1, 0, | |
| ws->acc, TILED_TILE_ROWS); | |
| } | |
| static void ggml_compute_forward_mul_mat_tiled_one_chunk( | |
| } | |
| } | |
| -template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS> | |
| +template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, typename BA = block_q8_K, bool Q41 = false> | |
| static void ggml_compute_forward_mul_mat_tiled_driver( | |
| const struct ggml_compute_params * params, | |
| struct ggml_tensor * dst) { | |
| static void ggml_compute_forward_mul_mat_tiled_driver( | |
| const int64_t ir1_start = dr1 * ith1; | |
| const int64_t ir1_end = MIN(ir1_start + dr1, nr1); | |
| - ggml_compute_forward_mul_mat_tiled_one_chunk<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS>(params, dst, ir0_start, ir0_end, ir1_start, ir1_end, ws); | |
| + ggml_compute_forward_mul_mat_tiled_one_chunk<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS, BA, Q41>(params, dst, ir0_start, ir0_end, ir1_start, ir1_end, ws); | |
| if (nth >= nchunk0 * nchunk1) { | |
| break; | |
| static void ggml_compute_forward_mul_mat_tiled_driver( | |
| // src0 type dispatch, shared by the MUL_MAT and MUL_MAT_ID entries: one expert for | |
| // MUL_MAT_ID (expert_rows != NULL), the full op for MUL_MAT | |
| -template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS> | |
| +template <typename B, int SUBBLK, bool HAS_MIN, int BIAS, bool ACTBIAS, typename BA = block_q8_K, bool Q41 = false> | |
| static bool tiled_matmul_dispatch(const struct ggml_compute_params * params, | |
| struct ggml_tensor * dst, | |
| const int32_t * expert_rows, | |
| static bool tiled_matmul_dispatch(const struct ggml_compute_params * params, | |
| int64_t cne1, | |
| char * scratch) { | |
| if (expert_rows == NULL) { | |
| - ggml_compute_forward_mul_mat_tiled_driver<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS>(params, dst); | |
| + ggml_compute_forward_mul_mat_tiled_driver<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS, BA, Q41>(params, dst); | |
| } else { | |
| - ggml_compute_forward_mul_mat_id_tiled_one_expert<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS>(params, dst, cur_a, cne1, expert_rows, scratch); | |
| + ggml_compute_forward_mul_mat_id_tiled_one_expert<B, SUBBLK, HAS_MIN, BIAS, ACTBIAS, BA, Q41>(params, dst, cur_a, cne1, expert_rows, scratch); | |
| } | |
| return true; | |
| } | |
| static bool ggml_tiled_matmul_type_dispatch(const struct ggml_compute_params * p | |
| return tiled_matmul_dispatch<block_q5_K, 32, true, 0, false>(params, dst, expert_rows, cur_a, cne1, scratch); | |
| case GGML_TYPE_Q4_K: | |
| return tiled_matmul_dispatch<block_q4_K, 32, true, 0, false>(params, dst, expert_rows, cur_a, cne1, scratch); | |
| + case GGML_TYPE_Q4_1: | |
| + return tiled_matmul_dispatch<block_q4_1, 32, false, 0, false, block_q8_1, true>(params, dst, expert_rows, cur_a, cne1, scratch); | |
| +#if defined(__AVX512VNNI__) && defined(__AVX512VL__) && defined(__AVX512DQ__) | |
| + case GGML_TYPE_NVFP4: | |
| + return tiled_matmul_dispatch<block_nvfp4, 16, false, 0, false, block_q8_0, true>(params, dst, expert_rows, cur_a, cne1, scratch); | |
| +#endif | |
| case GGML_TYPE_Q3_K: | |
| return tiled_matmul_dispatch<block_q3_K, 16, false, 4, true>(params, dst, expert_rows, cur_a, cne1, scratch); | |
| case GGML_TYPE_Q2_K: | |
| diff --git a/ggml/src/ggml.c b/ggml/src/ggml.c | |
| index 5d8449a88..203f15e43 100644 | |
| --- a/ggml/src/ggml.c | |
| +++ b/ggml/src/ggml.c | |
| struct ggml_tensor * ggml_solve_tri( | |
| // ggml_gated_delta_net | |
| +struct ggml_tensor * ggml_gated_delta_net_cache( | |
| + struct ggml_context * ctx, | |
| + struct ggml_tensor * q, | |
| + struct ggml_tensor * k, | |
| + struct ggml_tensor * v, | |
| + struct ggml_tensor * g, | |
| + struct ggml_tensor * beta, | |
| + struct ggml_tensor * state_cache, | |
| + struct ggml_tensor * ids, | |
| + struct ggml_tensor * state_dst) { | |
| + GGML_ASSERT(ggml_is_contiguous_rows(q)); | |
| + GGML_ASSERT(ggml_is_contiguous_rows(k)); | |
| + GGML_ASSERT(ggml_is_contiguous_rows(v)); | |
| + GGML_ASSERT(ggml_is_contiguous(g)); | |
| + GGML_ASSERT(ggml_is_contiguous(beta)); | |
| + GGML_ASSERT(q->type == GGML_TYPE_F32 && k->type == GGML_TYPE_F32 && v->type == GGML_TYPE_F32); | |
| + GGML_ASSERT(g->type == GGML_TYPE_F32 && beta->type == GGML_TYPE_F32); | |
| + | |
| + const int64_t S_v = v->ne[0]; | |
| + const int64_t H = v->ne[1]; | |
| + const int64_t n_tokens = v->ne[2]; | |
| + const int64_t n_seqs = v->ne[3]; | |
| + | |
| + GGML_ASSERT(g->ne[0] == 1 || g->ne[0] == S_v); | |
| + GGML_ASSERT(beta->ne[0] == 1); | |
| + | |
| + GGML_ASSERT(state_cache->type == GGML_TYPE_F32); | |
| + GGML_ASSERT(state_cache->ne[0] == S_v * S_v * H); | |
| + GGML_ASSERT(state_cache->nb[1] == (size_t) (S_v * S_v * H) * sizeof(float)); | |
| + GGML_ASSERT(ids->type == GGML_TYPE_I32); | |
| + GGML_ASSERT(ids->ne[0] == n_seqs); | |
| + GGML_ASSERT(state_dst->type == GGML_TYPE_F32); | |
| + GGML_ASSERT(state_dst->ne[0] == S_v * S_v * H && state_dst->ne[1] == n_seqs); | |
| + GGML_ASSERT(state_dst->nb[1] == state_cache->nb[1]); | |
| + | |
| + const int64_t ne[4] = { S_v * H, n_tokens * n_seqs + S_v * n_seqs, 1, 1 }; | |
| + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); | |
| + | |
| + ggml_set_op_params_i32(result, 0, 1); // K == 1 | |
| + | |
| + result->op = GGML_OP_GATED_DELTA_NET; | |
| + result->src[0] = q; | |
| + result->src[1] = k; | |
| + result->src[2] = v; | |
| + result->src[3] = g; | |
| + result->src[4] = beta; | |
| + result->src[5] = state_cache; | |
| + result->src[6] = ids; | |
| + result->src[7] = state_dst; | |
| + | |
| + return result; | |
| +} | |
| + | |
| struct ggml_tensor * ggml_gated_delta_net( | |
| struct ggml_context * ctx, | |
| struct ggml_tensor * q, | |
| diff --git a/src/llama-graph.cpp b/src/llama-graph.cpp | |
| index 7309705dc..de5403dbe 100644 | |
| --- a/src/llama-graph.cpp | |
| +++ b/src/llama-graph.cpp | |
| ggml_tensor * llm_graph_context::build_rs( | |
| // {state_size, rs_size} -> {state_size, n_rs - i0} | |
| const int64_t i0 = n_rs - state_copy->ne[0]; | |
| + // custom getter + no extra states: nothing to gather or copy | |
| + if (get_state_rows && n_rs == (uint32_t) n_seqs) { | |
| + ggml_tensor * output_states = get_state_rows(ctx0, states, state_copy_main); | |
| + ggml_build_forward_expand(gf, output_states); | |
| + return output_states; | |
| + } | |
| + | |
| ggml_tensor * states_all = ggml_get_rows(ctx0, states, state_copy); | |
| ggml_tensor * output_states = get_state_rows ? | |
| diff --git a/src/models/delta-net-base.cpp b/src/models/delta-net-base.cpp | |
| index 2e15777ee..efc8c0e8e 100644 | |
| --- a/src/models/delta-net-base.cpp | |
| +++ b/src/models/delta-net-base.cpp | |
| ggml_tensor * llm_build_delta_net_base::build_conv_state( | |
| return conv_input; | |
| } | |
| +ggml_tensor * llm_build_delta_net_base::build_recurrent_attn_cache( | |
| + llm_graph_input_rs * inp, | |
| + ggml_tensor * ssm_states_all, | |
| + ggml_tensor * state_cache, | |
| + ggml_tensor * q, | |
| + ggml_tensor * k, | |
| + ggml_tensor * v, | |
| + ggml_tensor * g, | |
| + ggml_tensor * b, | |
| + int il) { | |
| + const auto * mctx_cur = inp->mctx; | |
| + const auto kv_head = mctx_cur->get_head(); | |
| + | |
| + const int64_t S_v = v->ne[0]; | |
| + const int64_t H_v = v->ne[1]; | |
| + const int64_t n_seq_tokens = q->ne[2]; | |
| + const int64_t n_seqs = q->ne[3]; | |
| + | |
| + GGML_ASSERT(n_seqs == 1 && cparams.n_rs_seq == 0); | |
| + | |
| + ggml_tensor * state_dst = ggml_view_2d(ctx0, ssm_states_all, hparams.n_embd_s(), n_seqs, ssm_states_all->nb[1], | |
| + kv_head * hparams.n_embd_s() * ggml_element_size(ssm_states_all)); | |
| + | |
| + ggml_tensor * result = ggml_gated_delta_net_cache(ctx0, q, k, v, g, b, state_cache, inp->s_copy_main, state_dst); | |
| + if (n_seq_tokens == 1) { | |
| + res->add_fused_node({LLM_FUSED_OP_GDN_AR, result, il}); | |
| + } else { | |
| + res->add_fused_node({LLM_FUSED_OP_GDN_CH, result, il}); | |
| + } | |
| + | |
| + ggml_tensor * output = ggml_view_4d(ctx0, result, | |
| + S_v, H_v, n_seq_tokens, n_seqs, | |
| + ggml_row_size(result->type, S_v), | |
| + ggml_row_size(result->type, S_v * H_v), | |
| + ggml_row_size(result->type, S_v * H_v * n_seq_tokens), 0); | |
| + cb(output, "attn_output", il); | |
| + | |
| + return output; | |
| +} | |
| + | |
| ggml_tensor * llm_build_delta_net_base::build_recurrent_attn( | |
| llm_graph_input_rs * inp, | |
| ggml_tensor * ssm_states_all, | |
| diff --git a/src/models/models.h b/src/models/models.h | |
| index 5d9b6077a..d0ba683fc 100644 | |
| --- a/src/models/models.h | |
| +++ b/src/models/models.h | |
| struct llm_build_delta_net_base : public llm_graph_context { | |
| // run delta-net attention and write the new recurrent state(s) back to ssm_states_all | |
| // s: (head_v_dim, head_v_dim, num_v_heads, n_seqs); returns output: (head_v_dim, num_v_heads, n_seq_tokens, n_seqs) | |
| + // single-sequence fast path: reads the state straight from the cache and updates it in place | |
| + // (no get_rows before, no cpy after); only valid for n_rs_seq == 0, n_seqs == 1 and the fused GDN ops | |
| + ggml_tensor * build_recurrent_attn_cache( | |
| + llm_graph_input_rs * inp, | |
| + ggml_tensor * ssm_states_all, | |
| + ggml_tensor * state_cache, | |
| + ggml_tensor * q, | |
| + ggml_tensor * k, | |
| + ggml_tensor * v, | |
| + ggml_tensor * g, | |
| + ggml_tensor * b, | |
| + int il); | |
| + | |
| ggml_tensor * build_recurrent_attn( | |
| llm_graph_input_rs * inp, | |
| ggml_tensor * ssm_states_all, | |
| diff --git a/src/models/qwen35moe.cpp b/src/models/qwen35moe.cpp | |
| index 939f10f7f..ffb396c1c 100644 | |
| --- a/src/models/qwen35moe.cpp | |
| +++ b/src/models/qwen35moe.cpp | |
| +#include <cstdlib> | |
| #include "models.h" | |
| #include "llama-memory-recurrent.h" | |
| ggml_tensor * llama_model_qwen35moe::graph::build_layer_attn_linear( | |
| ggml_tensor * conv_input = build_conv_state(inp, conv_states_all, qkv_mixed, conv_kernel_size, conv_channels, il); | |
| - ggml_tensor * state = build_rs(inp, ssm_states_all, hparams.n_embd_s(), n_seqs); | |
| - state = ggml_reshape_4d(ctx0, state, head_v_dim, head_v_dim, num_v_heads, n_seqs); | |
| - cb(state, "state_predelta", il); | |
| + // Single-sequence decode/prefill fast path (CPU GDN): read the recurrent state straight from the cache and | |
| + // update it in place, instead of get_rows + gdn + cpy. Disable with LLAMA_GDN_NO_INPLACE=1. | |
| + static const bool gdn_inplace_allowed = getenv("LLAMA_GDN_NO_INPLACE") == nullptr; | |
| + const bool gdn_inplace = gdn_inplace_allowed && n_seqs == 1 && cparams.n_rs_seq == 0 && | |
| + (n_seq_tokens == 1 ? cparams.fused_gdn_ar : cparams.fused_gdn_ch) && | |
| + mctx_cur->get_n_rs() == 1; | |
| + | |
| + ggml_tensor * state = nullptr; | |
| + ggml_tensor * state_cache = nullptr; | |
| + if (gdn_inplace) { | |
| + // the custom getter hands back the cache itself; build_rs still orders the optional state clearing | |
| + state_cache = build_rs(inp, ssm_states_all, hparams.n_embd_s(), n_seqs, | |
| + [](ggml_context *, ggml_tensor * states, ggml_tensor *) { return states; }); | |
| + } else { | |
| + state = build_rs(inp, ssm_states_all, hparams.n_embd_s(), n_seqs); | |
| + state = ggml_reshape_4d(ctx0, state, head_v_dim, head_v_dim, num_v_heads, n_seqs); | |
| + cb(state, "state_predelta", il); | |
| + } | |
| ggml_tensor * conv_output_proper = ggml_ssm_conv(ctx0, conv_input, conv_kernel); | |
| cb(conv_output_proper, "conv_output_raw", il); | |
| ggml_tensor * llama_model_qwen35moe::graph::build_layer_attn_linear( | |
| cb(k_conv, "k_conv_predelta", il); | |
| cb(v_conv, "v_conv_predelta", il); | |
| - ggml_tensor * output = build_recurrent_attn(inp, ssm_states_all, q_conv, k_conv, v_conv, gate, beta, state, il); | |
| + ggml_tensor * output = gdn_inplace | |
| + ? build_recurrent_attn_cache(inp, ssm_states_all, state_cache, q_conv, k_conv, v_conv, gate, beta, il) | |
| + : build_recurrent_attn(inp, ssm_states_all, q_conv, k_conv, v_conv, gate, beta, state, il); | |
| // z: [head_dim, n_heads, n_tokens, n_seqs] -> [n_heads * n_tokens * n_seqs, head_dim] | |
| ggml_tensor * z_2d = ggml_reshape_4d(ctx0, z, head_v_dim, num_v_heads, n_seq_tokens, n_seqs); | |