test-quantize-fns.cpp 7.27 KB
Newer Older
xuxzh1's avatar
init  
xuxzh1 committed
1
2
3
// Unit tests for quantization specific functions - quantize, dequantize and dot product

#include "ggml.h"
xuxzh1's avatar
update  
xuxzh1 committed
4
#include "ggml-cpu.h"
xuxzh1's avatar
init  
xuxzh1 committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18

#undef NDEBUG
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <string>
#include <vector>

#if defined(_MSC_VER)
#pragma warning(disable: 4244 4267) // possible loss of data
#endif

constexpr float MAX_QUANTIZATION_REFERENCE_ERROR = 0.0001f;
constexpr float MAX_QUANTIZATION_TOTAL_ERROR = 0.002f;
xuxzh1's avatar
update  
xuxzh1 committed
19
constexpr float MAX_QUANTIZATION_TOTAL_ERROR_TERNARY = 0.01f;
xuxzh1's avatar
init  
xuxzh1 committed
20
21
22
23
24
constexpr float MAX_QUANTIZATION_TOTAL_ERROR_2BITS = 0.0075f;
constexpr float MAX_QUANTIZATION_TOTAL_ERROR_3BITS = 0.0040f;
constexpr float MAX_QUANTIZATION_TOTAL_ERROR_3BITS_XXS = 0.0050f;
constexpr float MAX_DOT_PRODUCT_ERROR = 0.02f;
constexpr float MAX_DOT_PRODUCT_ERROR_LOWBIT = 0.04f;
xuxzh1's avatar
update  
xuxzh1 committed
25
constexpr float MAX_DOT_PRODUCT_ERROR_TERNARY = 0.15f;
xuxzh1's avatar
init  
xuxzh1 committed
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

static const char* RESULT_STR[] = {"ok", "FAILED"};


// Generate synthetic data
static void generate_data(float offset, size_t n, float * dst) {
    for (size_t i = 0; i < n; i++) {
        dst[i] = 0.1 + 2*cosf(i + offset);
    }
}

// Calculate RMSE between two float arrays
static float array_rmse(const float * a1, const float * a2, size_t n) {
    double sum = 0;
    for (size_t i = 0; i < n; i++) {
        double diff = a1[i] - a2[i];
        sum += diff * diff;
    }
    return sqrtf(sum) / n;
}

// Total quantization error on test data
xuxzh1's avatar
update  
xuxzh1 committed
48
static float total_quantization_error(const ggml_type_traits * qfns, const ggml_type_traits_cpu * qfns_cpu, size_t test_size, const float * test_data) {
xuxzh1's avatar
init  
xuxzh1 committed
49
50
51
    std::vector<uint8_t> tmp_q(2*test_size);
    std::vector<float> tmp_out(test_size);

xuxzh1's avatar
update  
xuxzh1 committed
52
53
    qfns_cpu->from_float(test_data, tmp_q.data(), test_size);
    qfns->to_float(tmp_q.data(), tmp_out.data(), test_size);
xuxzh1's avatar
init  
xuxzh1 committed
54
55
56
57
    return array_rmse(test_data, tmp_out.data(), test_size);
}

// Total quantization error on test data
xuxzh1's avatar
update  
xuxzh1 committed
58
static float reference_quantization_error(const ggml_type_traits * qfns, const ggml_type_traits_cpu * qfns_cpu, size_t test_size, const float * test_data) {
xuxzh1's avatar
init  
xuxzh1 committed
59
60
61
62
    std::vector<uint8_t> tmp_q(2*test_size);
    std::vector<float> tmp_out(test_size);
    std::vector<float> tmp_out_ref(test_size);

xuxzh1's avatar
update  
xuxzh1 committed
63
64
65
    // FIXME: why is done twice?
    qfns_cpu->from_float(test_data, tmp_q.data(), test_size);
    qfns->to_float(tmp_q.data(), tmp_out.data(), test_size);
xuxzh1's avatar
init  
xuxzh1 committed
66

xuxzh1's avatar
update  
xuxzh1 committed
67
68
    qfns->from_float_ref(test_data, tmp_q.data(), test_size);
    qfns->to_float(tmp_q.data(), tmp_out_ref.data(), test_size);
xuxzh1's avatar
init  
xuxzh1 committed
69
70
71
72
73
74
75
76
77
78
79
80
81
82

    return array_rmse(tmp_out.data(), tmp_out_ref.data(), test_size);
}

static float dot_product(const float * a1, const float * a2, size_t test_size) {
    double sum = 0;
    for (size_t i = 0; i < test_size; i++) {
        sum += a1[i] * a2[i];
    }
    return sum;
}

// Total dot product error
static float dot_product_error(
xuxzh1's avatar
update  
xuxzh1 committed
83
    const ggml_type_traits * qfns, const ggml_type_traits_cpu * qfns_cpu, size_t test_size, const float * test_data1, const float *test_data2
xuxzh1's avatar
init  
xuxzh1 committed
84
85
86
87
) {
    std::vector<uint8_t> tmp_q1(2*test_size);
    std::vector<uint8_t> tmp_q2(2*test_size);

xuxzh1's avatar
update  
xuxzh1 committed
88
    const auto * vdot = ggml_get_type_traits_cpu(qfns_cpu->vec_dot_type);
xuxzh1's avatar
init  
xuxzh1 committed
89

xuxzh1's avatar
update  
xuxzh1 committed
90
91
    qfns_cpu->from_float(test_data1, tmp_q1.data(), test_size);
    vdot->from_float(test_data2, tmp_q2.data(), test_size);
xuxzh1's avatar
init  
xuxzh1 committed
92
93

    float result = INFINITY;
xuxzh1's avatar
update  
xuxzh1 committed
94
    qfns_cpu->vec_dot(test_size, &result, 0, tmp_q1.data(), 0, tmp_q2.data(), 0, 1);
xuxzh1's avatar
init  
xuxzh1 committed
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

    const float dot_ref = dot_product(test_data1, test_data2, test_size);

    return fabsf(result - dot_ref) / test_size;
}

int main(int argc, char * argv[]) {
    bool verbose = false;
    const size_t test_size = 32 * 128;

    std::string arg;
    for (int i = 1; i < argc; i++) {
        arg = argv[i];

        if (arg == "-v") {
            verbose = true;
        } else {
            fprintf(stderr, "error: unknown argument: %s\n", arg.c_str());
            return 1;
        }
    }

    std::vector<float> test_data(test_size);
    std::vector<float> test_data2(test_size);

    generate_data(0.0, test_data.size(), test_data.data());
    generate_data(1.0, test_data2.size(), test_data2.data());

    // Initialize GGML, ensures float conversion tables are initialized
    struct ggml_init_params ggml_params = {
        /* .mem_size   = */ 1*1024,
        /* .mem_buffer = */ NULL,
        /* .no_alloc   = */ true,
    };
    struct ggml_context * ctx = ggml_init(ggml_params);

    int num_failed = 0;
    bool failed = false;

    for (int i = 0; i < GGML_TYPE_COUNT; i++) {
        ggml_type type = (ggml_type) i;
xuxzh1's avatar
update  
xuxzh1 committed
136
137
        const auto * qfns = ggml_get_type_traits(type);
        const auto * qfns_cpu = ggml_get_type_traits_cpu(type);
xuxzh1's avatar
init  
xuxzh1 committed
138
139

        // deprecated - skip
xuxzh1's avatar
update  
xuxzh1 committed
140
        if (qfns->blck_size == 0) {
xuxzh1's avatar
init  
xuxzh1 committed
141
142
143
144
145
146
147
148
            continue;
        }

        const ggml_type ei = (ggml_type)i;

        printf("Testing %s\n", ggml_type_name((ggml_type) i));
        ggml_quantize_init(ei);

xuxzh1's avatar
update  
xuxzh1 committed
149
150
        if (qfns_cpu->from_float && qfns->to_float) {
            const float total_error = total_quantization_error(qfns, qfns_cpu, test_size, test_data.data());
xuxzh1's avatar
init  
xuxzh1 committed
151
            const float max_quantization_error =
xuxzh1's avatar
update  
xuxzh1 committed
152
153
                type == GGML_TYPE_TQ1_0   ? MAX_QUANTIZATION_TOTAL_ERROR_TERNARY :
                type == GGML_TYPE_TQ2_0   ? MAX_QUANTIZATION_TOTAL_ERROR_TERNARY :
xuxzh1's avatar
init  
xuxzh1 committed
154
155
156
157
158
159
160
161
162
163
164
                type == GGML_TYPE_Q2_K    ? MAX_QUANTIZATION_TOTAL_ERROR_2BITS :
                type == GGML_TYPE_IQ2_S   ? MAX_QUANTIZATION_TOTAL_ERROR_2BITS :
                type == GGML_TYPE_Q3_K    ? MAX_QUANTIZATION_TOTAL_ERROR_3BITS :
                type == GGML_TYPE_IQ3_S   ? MAX_QUANTIZATION_TOTAL_ERROR_3BITS :
                type == GGML_TYPE_IQ3_XXS ? MAX_QUANTIZATION_TOTAL_ERROR_3BITS_XXS : MAX_QUANTIZATION_TOTAL_ERROR;
            failed = !(total_error < max_quantization_error);
            num_failed += failed;
            if (failed || verbose) {
                printf("%5s absolute quantization error:    %s (%f)\n", ggml_type_name(type), RESULT_STR[failed], total_error);
            }

xuxzh1's avatar
update  
xuxzh1 committed
165
            const float reference_error = reference_quantization_error(qfns, qfns_cpu, test_size, test_data.data());
xuxzh1's avatar
init  
xuxzh1 committed
166
167
168
169
170
171
            failed = !(reference_error < MAX_QUANTIZATION_REFERENCE_ERROR);
            num_failed += failed;
            if (failed || verbose) {
                printf("%5s reference implementation error: %s (%f)\n", ggml_type_name(type), RESULT_STR[failed], reference_error);
            }

xuxzh1's avatar
update  
xuxzh1 committed
172
            const float vec_dot_error = dot_product_error(qfns, qfns_cpu, test_size, test_data.data(), test_data2.data());
xuxzh1's avatar
init  
xuxzh1 committed
173
174
175
            const float max_allowed_error = type == GGML_TYPE_Q2_K || type == GGML_TYPE_IQ2_XS || type == GGML_TYPE_IQ2_XXS ||
                                            type == GGML_TYPE_IQ3_XXS || type == GGML_TYPE_IQ3_S || type == GGML_TYPE_IQ2_S
                                          ? MAX_DOT_PRODUCT_ERROR_LOWBIT
xuxzh1's avatar
update  
xuxzh1 committed
176
177
                                          : type == GGML_TYPE_TQ1_0 || type == GGML_TYPE_TQ2_0
                                          ? MAX_DOT_PRODUCT_ERROR_TERNARY
xuxzh1's avatar
init  
xuxzh1 committed
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
                                          : MAX_DOT_PRODUCT_ERROR;
            failed = !(vec_dot_error < max_allowed_error);
            num_failed += failed;
            if (failed || verbose) {
                printf("%5s dot product error:              %s (%f)\n", ggml_type_name(type), RESULT_STR[failed], vec_dot_error);
            }
        }
    }

    if (num_failed || verbose) {
        printf("%d tests failed\n", num_failed);
    }

    ggml_free(ctx);

    return num_failed > 0;
}