Files
Strata/tests/core/dequant_q5_1_test.cpp
Anonandksanislo 74945954fe ple: read Q8_0 and Q5_1 n-gram tables (#651)
Ordinary GGUFs ship per_layer_token_embd.weight in other formats than IQ4_NL: Unsloth's UD-Q6_K_XL and
Swift-1.5 Q4_K_L as Q8_0 (170-byte rows, ~54 GB), a Q5_K_M finetune as Q5_1 (120-byte rows). The engine refused
both at start.

- dequantize_q5_1 in strata/artifact/dequant.hpp (ggml's dequantize_row_q5_1: unsigned 5-bit codes times d plus the
  block minimum m); Q8_0 rows use the existing dequantize_q8_0
- two entries in the PLE format list (the table-driven commit before this one): Q5_1 and Q8_0, on both readers;
  PLE_ROW_BYTES_MAX grows to 170
- tests: dequant_q5_1_test (synthetic blocks against ggml, no model, no GPU), dequant_bf16_test knows Q5_1,
  ple_q5_parity takes Q5_0, Q5_1 or Q8_0 tables and checks both the mapped and the direct reader

Ported onto the format table by the maintainer from anon761's PR #651 (the --ple-io ram fault-in is the next
commit). Quality of the Q8_0 table: ksanislo measured 0.53% error against the BF16 table, FP8 2.66%, IQ4_NL 7.59%.

Co-authored-by: ksanislo <[email protected]>
2026-10-05 12:18:47 +02:00

41 lines
1.9 KiB
C++

// dequantize_q5_1 (strata/artifact/dequant.hpp: the PLE table of a Q5_1 GGUF) against ggml's dequantize_row_q5_1,
// on synthetic blocks: random codes, high bits and scales, plus the edge codes 0 and 31 and a negative minimum.
// No model, no GPU.
#include "strata/artifact/dequant.hpp"
#include "ggml.h"
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <random>
#include <vector>
int main() {
constexpr int kBlocks = 4096;
constexpr int kBlockBytes = 24;
std::mt19937 rng(20261002);
std::uniform_int_distribution<int> byte(0, 255);
std::uniform_real_distribution<float> scale(-0.05f, 0.05f);
std::vector<uint8_t> blocks((size_t) kBlocks * kBlockBytes);
for (int b = 0; b < kBlocks; ++b) {
uint8_t* blk = blocks.data() + (size_t) b * kBlockBytes;
const ggml_fp16_t d = ggml_fp32_to_fp16(scale(rng));
const ggml_fp16_t m = ggml_fp32_to_fp16(b == 1 ? -1.5f : scale(rng) * 20.0f);
std::memcpy(blk, &d, 2);
std::memcpy(blk + 2, &m, 2);
for (int i = 4; i < kBlockBytes; ++i) blk[i] = (uint8_t) byte(rng);
if (b == 0) std::memset(blk + 4, 0x00, 20); // every code 0: the value is m
if (b == 1) std::memset(blk + 4, 0xFF, 20); // every code 31 (all high bits set)
}
const auto* traits = ggml_get_type_traits(GGML_TYPE_Q5_1);
std::vector<float> want((size_t) kBlocks * 32), got((size_t) kBlocks * 32);
traits->to_float(blocks.data(), want.data(), (int64_t) want.size());
for (int b = 0; b < kBlocks; ++b) strata::dequantize_q5_1(blocks.data() + (size_t) b * kBlockBytes, got.data() + b * 32);
double max_abs = 0.0;
for (size_t i = 0; i < want.size(); ++i) max_abs = std::fmax(max_abs, std::fabs((double) got[i] - want[i]));
const bool ok = max_abs <= 1e-6;
std::printf("Q5_1 dequant: %d blocks, max_abs %.3e %s\n", kBlocks, max_abs, ok ? "PASS" : "FAIL");
return ok ? 0 : 1;
}