博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

ggml的量化处理

Posted on 2023-11-07 20:15  Antel  阅读(615)  评论(3编辑  收藏  举报

量化

ggml中的张量为ggml_tensor,

ggml_tensor有几个值得注意的属性:

  • enum ggml_op op 表示这个tensor是从哪个操作得到。

  • enum ggml_type type,为tensor的格式,

ggml_type的定义如下:

enum ggml_type {
    GGML_TYPE_F32  = 0,
    GGML_TYPE_F16  = 1,
    GGML_TYPE_Q4_0 = 2,
    GGML_TYPE_Q4_1 = 3,
    // GGML_TYPE_Q4_2 = 4, support has been removed
    // GGML_TYPE_Q4_3 (5) support has been removed
    GGML_TYPE_Q5_0 = 6,
    GGML_TYPE_Q5_1 = 7,
    GGML_TYPE_Q8_0 = 8,
    GGML_TYPE_Q8_1 = 9,
    // k-quantizations
    GGML_TYPE_Q2_K = 10,
    GGML_TYPE_Q3_K = 11,
    GGML_TYPE_Q4_K = 12,
    GGML_TYPE_Q5_K = 13,
    GGML_TYPE_Q6_K = 14,
    GGML_TYPE_Q8_K = 15,
    GGML_TYPE_I8,
    GGML_TYPE_I16,
    GGML_TYPE_I32,
    GGML_TYPE_COUNT,
};

其中,包含Q的表示经过量化得到的数据类型。

ggml在处理神经网络正向传播时,首先构建计算图,记录了每个节点的operator,然后进行统一处理。

在进行计算时,针对是否是量化tensor,分情况进行处理。

比如说,针对add操作,对应的ggml_op为GGML_OP_ADD,就包括了ggml_compute_forward_add_f32ggml_compute_forward_add_q_f32两个处理操作,分别处理非量化和量化的数据。

查看量化情况下的add操作

static void ggml_compute_forward_add_q_f32(
        const struct ggml_compute_params * params,
        const struct ggml_tensor * src0,
        const struct ggml_tensor * src1,
        struct ggml_tensor * dst)
{
    ...
        
    ggml_to_float_t const dequantize_row_q = type_traits[type].to_float;
    ggml_from_float_t const quantize_row_q = type_traits[dtype].from_float;
    ...
    // unquantize row from src0 to temp buffer
    dequantize_row_q(src0_row, wdata, ne00);
    // add src1
    ggml_vec_acc_f32(ne00, wdata, src1_row);
    // quantize row to dst
    if (quantize_row_q != NULL) {
        quantize_row_q(wdata, dst_row, ne00);
    } else {
        memcpy(dst_row, wdata, ne0*nb0);
    }
    ...
}

省略了部分代码 ,可以看到有两个神奇函数dequantize_row_qquantize_row_q,在进行实际add操作是需要将量化数据转化成float进行add,再转换回量化

这两个函数来自

ggml_to_float_t const dequantize_row_q = type_traits[type].to_float;
ggml_from_float_t const quantize_row_q = type_traits[dtype].from_float;

从type_traits截取部分,

[GGML_TYPE_Q4_0] = {
    .type_name                = "q4_0",
    .blck_size                = QK4_0,
    .type_size                = sizeof(block_q4_0),
    .is_quantized             = true,
    .to_float                 = (ggml_to_float_t) dequantize_row_q4_0,
    .from_float               = quantize_row_q4_0,
    .from_float_reference     = (ggml_from_float_t) quantize_row_q4_0_reference,
    .vec_dot                  = ggml_vec_dot_q4_0_q8_0,
    .vec_dot_type             = GGML_TYPE_Q8_0,
},

type_traits中,以ggml_type为GGML_TYPE_Q4_0为例,实际上每个量化type都有对应的quantize和unquantize,为和float之间的转换。

于是关注下to_float也就是dequantize_row_q4_0

static void dequantize_row_q4_0(const block_q4_0 * restrict x, float * restrict y, int k) {
    // x是需要dequantize的tensor, 以block_q4_0格式存储信息
    // y是得到的tensor
    // k是元素个数
    static const int qk = QK4_0; // 32
    

    assert(k % qk == 0);  // 总元素个数需能被32整除

    const int nb = k / qk; // k/32, dim0

    for (int i = 0; i < nb; i++) {
        const float d = GGML_FP16_TO_FP32(x[i].d); // 提取出这一组的scale

        for (int j = 0; j < qk/2; ++j) {
            const int x0 = (x[i].qs[j] & 0x0F) - 8; // 后4位处理
            const int x1 = (x[i].qs[j] >>   4) - 8; // 前4位处理

            y[i*qk + j + 0   ] = x0*d; // scale,将tensor拆开
            y[i*qk + j + qk/2] = x1*d;
        }
    }
}

其中,QK4_0是32,注意到

 y[i*qk + j + 0   ] = x0*d;
 y[i*qk + j + qk/2] = x1*d;

这两行都乘了个d,为作者对delta的缩写,也就是scale,来自于x[i].d

x[i]的类型为block_q4_0:

typedef struct {
    ggml_fp16_t d;          // delta
    uint8_t qs[QK4_0 / 2];  // nibbles / quants
} block_q4_0;

block_q4_0的d就是一个fp16。

实参为void * src0_row = (void *) ((char *) src0->data + (i01*nb01 + i02*nb02 + i03*nb03));

也就是进行计算的的tensor,进行了类型转化。

至此,实现了ggml中的tensor的unquantize流程,quantize同理。

附上quantize代码

static void quantize_row_q4_0_reference(const float * restrict x, block_q4_0 * restrict y, int k) {
    // x是需要quantize的tensor
    // y是得到的tensor, 用block_q4_0格式存储信息
    // k是元素个数
    static const int qk = QK4_0; // 32

    assert(k % qk == 0);  // 总元素个数需能被32整除

    const int nb = k / qk; // k/32, dim0, 划分的组数

    for (int i = 0; i < nb; i++) {
        float amax = 0.0f; // absolute max
        float max  = 0.0f;

        for (int j = 0; j < qk; j++) {
            const float v = x[i*qk + j];
            if (amax < fabsf(v)) {
                amax = fabsf(v);
                max  = v;
            }
        }

        const float d  = max / -8;  // scale
        const float id = d ? 1.0f/d : 0.0f; // 原本需要的除scale转化成乘

        y[i].d = GGML_FP32_TO_FP16(d);  // 保存这一组的scale

        for (int j = 0; j < qk/2; ++j) {
            const float x0 = x[i*qk + 0    + j]*id; // 量化
            const float x1 = x[i*qk + qk/2 + j]*id;

            const uint8_t xi0 = MIN(15, (int8_t)(x0 + 8.5f));
            const uint8_t xi1 = MIN(15, (int8_t)(x1 + 8.5f));

            y[i].qs[j]  = xi0;
            y[i].qs[j] |= xi1 << 4;  // 两个int4组装成一个int8放在一起
        }
    }
}