资讯动态

ik_llama.cpp 中 IQ1_S_R4 量化的专属校验机制:PR 191 的实现解析

发布时间:2026/10/13 10:05:50 来源:尧图企业网站定制
人工智能大模型推理引擎本地部署模型量化【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp点击查看免费下载本文围绕 ik_llama.cpp 仓库中 PR #191Add additional checks for iq1_s_r4 quantization的技术记录展开该 PR 针对用IQ1_S_R4量化 DeepSeek-R1 时出现问题的现象在量化与加载两个阶段增加了针对该量化类型的额外检查。结合仓库源码你可以理解IQ1_S_R4为何需要一个区别于其他 k-quants 的校验路径、这些检查具体校验什么f16 row scale 的有限性、以及如何通过llama-quantize与-vq/--validate-quants命令在实际流程中触发这些检查。一、PR #191 要解决的问题仓库中的 PR 记录 191 - Add additional checks for iq1_s_r4 quantization.md 内容非常简短其 Description 原文为Something goes wrong when quantizing DeepSeek-R1 withIQ1_S_R4(see #185), so adding additional checks in the quantization.即作者用IQ1_S_R4量化 DeepSeek-R1 时发现“某处出了问题”该记录未展开具体错误细节因此在量化流程中加入额外检查。该 PR 由ikawrakow于 2025-02-07 提交并关闭是 PR #185 的后续修复工作。PR #185 的完整记录保存在 185 - IQ1_S_R4_ better 1.5 bpw quants.md理解它是理解 #191 的前置背景。该记录说明了IQ1_S_R4的设计要点IQ1_S_R4是IQ1_S的 4 行交错4-row interleaved版本使用 1.5 bpw低于IQ1_S的 1.5625 bpw它移除了IQ1_S中的f16super-block scale改为每行一个f16scale块大小block size选用 32原因是测试模型 DeepSeek-Lite 有大量 row 尺寸不能被 256 整除的张量用IQ1_S时相当一部分张量会退化到IQ4_NL该量化当时仅支持 CPUCaveat: it is CPU only for now。#185 记录中还给出了一个真实的量化日志片段DeepSeek-R1build 3549其中可以看到 imatrix 加载与逐张量转换过程load_imatrix: imatrix datasetimatrix-training-full-3 load_imatrix: loaded 720 importance matrix entries from /mnt/sda/mradermacher_DeepSeek-R1-GGUF/imatrix.dat computed on 315 chunks main: quantizing /mnt/sda/.../opensourcerelease_DeepSeek-R1-Bf16-256x21B-F16.gguf to .../...-IQ1_S_R4.gguf as IQ1_S_R4 using 48 threads这类超大 MoE 模型正是 #191 所针对的场景量化失败或产出异常张量时如果没有任何校验手段问题会被静默地写进 GGUF 文件推理时才暴露为乱码或 NaN。#191 的意义就在于把“数据合法性检查”固化到量化器与加载器中。二、IQ1_S_R4 的存储布局为什么需要专属检查从源码结构看IQ1_S_R4的块定义在 ggml/src/ggml-common.htypedef struct { uint8_t qs[16]; uint16_t qh[4]; } block_iq1_s_r4; static_assert(sizeof(block_iq1_s_r4) 24, wrong iq1_s_r4 block size/padding);在 ggml/src/ggml.c 中该类型注册为iq1_s_r4blck_size 32。结合量化入口的实现 ggml/src/iqk/iqk_quantize.cpp可以看出其数据组织方式for (int row 0; row nrows; row 4) { ggml_half * dptr (ggml_half *)cy; auto y (block_iq1_s_r4 *)(dptr 4); ... cy 4*row_size;即每 4 行共享一个头部4 个f16数值每行一个 row scale其后是交错的量化块数据。反量化侧ggml/src/iqk/iqk_quantize.cpp 的dequantize_row_iq1_s_r4同样从头部 4 个f16恢复 4 个 row scale。这一布局决定了它的风险点整个 4 行组的数值缩放完全依赖头部这 4 个 f16 scale。如果量化时输入张量含异常值NaN/Inf或中间计算溢出max[k]会传播出非有限值写入 row scale 后该组所有行的反量化结果都会变成垃圾数据。相比之下IQ1_S依赖 256 元素块上的 f16 块 scale仓库对这类类型走的是另一套块级检查路径而IQ1_S_R4没有 super-block scale只有 row scale因此 #191 增加了按 row scale 的专属校验。三、量化阶段的检查quantize 时的断言与防护#191 中“adding additional checks in the quantization”直接体现在quantize_iq1_s_r4函数内位于 ggml/src/iqk/iqk_quantize.cpp形状断言入口即要求行数是 4 的倍数、行宽是块大小 32 的倍数不满足时立即触发断言而不是产出错误数据constexpr int kBlockSize 32; GGML_ASSERT(nrows%4 0); GGML_ASSERT(n_per_row%kBlockSize 0);全零块的特判当某个 32 元素块的平方和小于1e-14f时直接写入全零 grid 条目ind 1029并把 row scale 置 0避免对空块做无意义的码本查找if (sumx2 1e-14f) { // all zero int ind 1029; // this is the grid entry with all zeros scales[4*iblk] 0; ... }imatrix 失配防护若提供了 importance matrix 但sumwx 1e-14f权重全零、或 imatrix 非零而权重为零打印Found block with mismatching importance/model weights并回退到不使用 imatrix 的加权方式而不是让异常权重污染量化结果。中间结果的数值断言码本索引与 scale 在写入前都被约束——scale 必须非负grid index 必须落在[0, 2048)范围内GGML_ASSERT(scales[4*iblk] 0); ... GGML_ASSERT(index[i] 0 index[i] 2048);row scale 的定点化写入每行的 f16 scale 由该行所有块 scale 的最大值max[k]推导1.0625f*max[k]/15若max[k]为非有限值写入的 f16 即为 NaN/Inf——这正是加载阶段检查要捕获的目标for (int k 0; k 4; k) { dptr[k] GGML_FP32_TO_FP16(1.0625f*max[k]/15);; invd[k] max[k] ? 15/max[k] : 0.f; }四、加载阶段的检查iqk_validate_tensor 与 F16ScaleRX4量化之后仓库在模型加载路径提供了第二道防线。核心是 ggml/src/iqk/iqk_quantize.cpp 中的iqk_validate_tensor它按张量类型分发到不同检查实现。针对IQ1_S_R4与IQ1_M_R4的分支为case GGML_TYPE_IQ1_S_R4: case GGML_TYPE_IQ1_M_R4: return check_tensor_row_scalesF16ScaleRX4(tensor);其中检查用的模板定义在同文件 L11888-L11932struct F16Scale { static inline int check_row(const char * data) { float d GGML_FP16_TO_FP32(*(const ggml_half *)data); return isfinite(d) ? 0 : 1; } }; template int nr struct F16ScaleRX { static inline int check_row(const char * data) { auto d (const ggml_half *)data; int nbad 0; for (int i 0; i nr; i) { if (!isfinite(GGML_FP16_TO_FP32(d[i]))) nbad; } return nbad; } }; template typename RS bool check_tensor_row_scales(const ggml_tensor * tensor) { auto row_size ggml_row_size(tensor-type, tensor-ne[0]); int num_rows ggml_nrows(tensor); auto data (const char *)tensor-data; int nbad 0; for (int row 0; row num_rows; row) { nbad RS::check_row(data); data row_size; } if (nbad 0) { fprintf(stderr, %s: found %d NaN row scales out of %d rows in tensor %s\n, __func__, nbad, num_rows, tensor-name); return false; } return true; }F16ScaleRX4精确对应IQ1_S_R4的布局读取每 4 行组头部连续存放的 4 个 f16 row scale逐个做isfinite检查并统计坏行数量发现 NaN row scale 时按张量名输出诊断信息。作为对照单行 f16 scale 类型IQ1_BN、IQ2_KS、IQ2_KL、IQ3_KS使用F16Scale而带 256 元素 f16 块 scale 的类型IQ2_K、IQ3_K及各 R4 重打包变体走check_tensor_for_blocks_256_fp16(_repacked)路径——不同存储布局对应不同检查器这正是“为 iq1_s_r4 增加 additional checks”的落点。加载侧的调用链在 src/llama.cpp当validate_quants打开时遍历位于 host buffer 上的全部张量逐个调用iqk_validate_tensor发现任何坏张量即抛出Bad tensors in model并中止加载if (validate_quants) { int nbad 0; for (auto it : model.tensors_by_name) { if (ggml_backend_buffer_is_host(it.second-buffer)) { if (!iqk_validate_tensor(it.second)) nbad; } } if (nbad 0) { LLAMA_LOG_ERROR(Found %d bad tensors in model\n, nbad); throw std::runtime_error(Bad tensors in model); } }从源码结构看该检查只作用于 host buffer 上的张量ggml_backend_buffer_is_host判定已卸载到 GPU 的张量不参与本次校验。五、实际操作如何触发这些检查5.1 量化阶段量化本身由llama-quantize见 examples/quantize/quantize.cpp完成。#185 记录中的 DeepSeek-R1 量化日志显示其支持 imatrix 输入并逐张量打印转换目标类型如converting to iq1_s_r4 .. size 7168.00 MiB - 673.00 MiB。量化过程中第三节的断言会在输入形状非法或中间值越界时立即中断而不是生成一个“看起来正常”的 GGUF 文件。#185 记录还指出一个对 DeepSeek 系模型的重要经验默认情况下 token embedding 会使用Q2_K--token-embedding-type可覆盖记录中作者手动指定了q8_0并且attn_v_b.weight的 bpw 分配需要按模型张量命名做适配。这些属于量化 mix 配置问题与 #191 的检查机制互补检查保证“写出的数据合法”mix 配置决定“写出的数据质量”。5.2 加载阶段加载侧的开关是-vq/--validate-quants参数定义于 common/common.cppif (arg -vq || arg --validate-quants) { params.validate_quants true; return true; }其默认值与帮助文本在同文件 L3359 给出-vq, --validate-quants validate quantized data while loading the model (default: 0)因此对任意使用 iq-quants 的 GGUF 模型可以在推理前做一次全量数据体检llama-cli -m DeepSeek-R1-IQ1_S_R4.gguf -vq -p test -n 1 # 或 llama-perplexity -m DeepSeek-R1-IQ1_S_R4.gguf -vq若某个IQ1_S_R4张量的 row scale 含 NaN会看到形如check_tensor_row_scales: found N NaN row scales out of M rows in tensor blk.X.ffn_gate_exps.weight的 stderr 输出并以Bad tensors in model异常终止。此外还有一个相近但不同的参数--check-tensorscommon/common.cpp对应params.check_tensors用于校验模型张量数据二者都默认关闭按需启用即可。六、检查的适用范围与限制结合仓库证据使用这套检查时需要注意以下边界只覆盖 host buffer 张量src/llama.cpp 中对ggml_backend_buffer_is_host的过滤意味着完全 offload 到 GPU 的张量不会被-vq检查到检查的是 scale 合法性不是输出质量F16ScaleRX4只判断 row scale 是否为有限值通过检查不代表模型输出正确低 bpw 量化本身的质量问题需要 perplexity 等手段评估IQ1_S_R4当前为 CPU 量化路径#185 记录明确标注 CPU onlyCUDA 侧仅提供了 dequantize 支持见 ggml/src/ggml-cuda/convert.cu 的dequantize_block_iq1_s_r4内核PR 状态为 Closed#191 的记录状态为关闭说明该修复已并入作者分支后续版本本文所述的检查代码即当前仓库中可查到的实现形态。七、小结PR #191 虽然只是一条简短的修复记录但它指向的问题具有普遍性IQ1_S_R4这类 sub-2 bpw、4 行交错、以 f16 row scale 为唯一缩放依据的量化格式一旦 scale 出现 NaN 就会让整组行的反量化结果失效且这种损坏会随 GGUF 文件静默传播。仓库给出的解法是双层防御量化阶段在 quantize_iq1_s_r4 中用断言与特判拦截形状、码本与 scale 异常加载阶段通过 iqk_validate_tensor 的F16ScaleRX4分支对每个 host 张量做 row scale 有限性检查并由-vq/--validate-quants参数统一开启。对于在大参数 MoE 模型上使用IQ1_S_R4/IQ1_M_R4量化的开发者这套检查应当成为量化流水线的标准环节。赞分享人工智能大模型推理引擎本地部署模型量化【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp点击查看免费下载相关推荐ik_llama.cpp 的 IQ1_S_R4 量化在 NEON 上的 GEMM/GEVM 提速PR 186 源码级解析与实测数据ik_llama.cpp 的 IQ1_S_R4 量化在 NEON 上的 GEMM/GEVM 提速PR 186 源码级解析与实测数据 导读 本文以 ik_lla人工智能大模型推理引擎本地部署模型量化ik_llama.cpp CUDA 量化加速实战IQ1_S_R4 从 MMQ 内核到 DeepSeek 模型部署ik_llama.cpp CUDA 量化加速实战IQ1_S_R4 从 MMQ 内核到 DeepSeek 模型部署 本文聚焦 ik_llama.cpp 为 IQ人工智能大模型推理引擎本地部署模型量化ik_llama.cpp 量化精度修复实录用 Q8_K_128 替代 Q8_1 根治 IQ1_S_R4 / IQ1_M_R4 的 fp16 溢出ik_llama.cpp 量化精度修复实录用 Q8_K_128 替代 Q8_1 根治 IQ1_S_R4 / IQ1_M_R4 的 fp16 溢出 本文以 ik人工智能大模型推理引擎本地部署模型量化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑