char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Pascalggml : add GGML_OP_COL2IM_1D (#24206)26021699b

char/aliceai-t5-moe
6.5 KiB159 linesraw
1// test-col2im-1d.cpp: validate GGML_OP_COL2IM_1D against ggml_conv_transpose_1d.
2//
3// A ConvTranspose1d factorizes as a GEMM followed by an overlap-add:
4//   conv_transpose_1d(w, x)  equals  col2im_1d(mul_mat(w_perm, x_t), s0, OC, p0)
5// with w_perm the [IC, K*OC] permutation of the [K, OC, IC] kernel and x_t the
6// [IC, T_in] transpose of the [T_in, IC] input. The test derives both alternative
7// layouts from one logical weight and one logical input with graph ops only
8// (permute + cont + reshape), runs the two paths on the CPU backend, and compares
9// them in F32. The F16 and BF16 kernels are exercised by casting the column
10// matrix before the scatter. Cropping (p0 > 0) is checked against the shifted
11// slice of the uncropped reference, which conv_transpose_1d cannot express.
12
13#include "ggml.h"
14#include "ggml-cpu.h"
15
16#include <cmath>
17#include <cstdint>
18#include <cstdio>
19#include <cstring>
20#include <vector>
21
22// One geometry: kernel size, output channels, input length, stride, crop
23struct col2im_case {
24    int64_t K;
25    int64_t OC;
26    int64_t T_in;
27    int     s0;
28    int     p0;
29};
30
31// Mirrors the eval grid of test-backend-ops
32static const col2im_case CASES[] = {
33    { 16, 32, 197, 8, 0 },  // kernel = 2*stride, DAC upsampling shape
34    {  4,  3,   7, 2, 0 },
35    {  1,  5,  13, 1, 0 },  // stride 1, no overlap
36    {  6,  4,  11, 3, 1 },  // with cropping
37    {  2,  3,   9, 3, 0 },  // kernel < stride, gap positions are zeroed
38    {  5,  4,  11, 2, 0 },  // kernel not a multiple of stride, alternating overlap
39    {  8,  4,  13, 4, 2 },  // padding = stride/2, DAC causal cropping
40    {  4,  3,   1, 2, 0 },  // single column, pure kernel unfold
41    { 16,  1, 197, 8, 0 },  // OC = 1, mono output stage
42    {  1,  5,  13, 3, 0 },  // K = 1 with stride > 1, sparse scatter
43    {  8,  2,   3, 2, 5 },  // cropping eats most of the signal, T_out = 2
44};
45
46// Input channels of the GEMM, shared by every case
47static const int64_t IC = 7;
48
49// Deterministic LCG mapped to [-1, 1]
50static uint64_t g_rng = 0x12345678ULL;
51static float frand(void) {
52    g_rng = g_rng * 6364136223846793005ULL + 1442695040888963407ULL;
53    return (float)((g_rng >> 33) & 0xffffff) / (float)0x800000 - 1.0f;
54}
55
56// Read a F32/F16/BF16 tensor back as a flat F32 vector
57static std::vector<float> tensor_to_f32(const struct ggml_tensor * t) {
58    const int64_t n = ggml_nelements(t);
59    std::vector<float> out(n);
60    if (t->type == GGML_TYPE_F32) {
61        memcpy(out.data(), t->data, n * sizeof(float));
62    } else if (t->type == GGML_TYPE_F16) {
63        for (int64_t i = 0; i < n; i++) {
64            out[i] = ggml_fp16_to_fp32(((const ggml_fp16_t *) t->data)[i]);
65        }
66    } else {
67        for (int64_t i = 0; i < n; i++) {
68            out[i] = ggml_bf16_to_fp32(((const ggml_bf16_t *) t->data)[i]);
69        }
70    }
71    return out;
72}
73
74// NMSE of the cropped output against the p0 shifted slice of the full reference
75static double nmse_cropped(const float * y, const float * ref, int64_t T_out, int64_t T_ref, int64_t OC, int p0) {
76    double num = 0.0;
77    double den = 0.0;
78    for (int64_t oc = 0; oc < OC; oc++) {
79        for (int64_t t = 0; t < T_out; t++) {
80            const double a = y  [t      + oc * T_out];
81            const double b = ref[t + p0 + oc * T_ref];
82            num += (a - b) * (a - b);
83            den += b * b;
84        }
85    }
86    return num / (den + 1e-30);
87}
88
89int main(void) {
90    int fails = 0;
91
92    for (const col2im_case & c : CASES) {
93        const int64_t T_ref = (c.T_in - 1) * c.s0 + c.K;
94        const int64_t T_out = T_ref - 2 * c.p0;
95
96        struct ggml_init_params params = {
97            /* .mem_size   = */ (size_t) 64 << 20,
98            /* .mem_base   = */ NULL,
99            /* .no_alloc   = */ false,
100        };
101        struct ggml_context * ctx = ggml_init(params);
102
103        // One logical weight and one logical input feed both paths
104        struct ggml_tensor * w = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, c.K, c.OC, IC);
105        struct ggml_tensor * x = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, c.T_in, IC);
106        for (int64_t i = 0; i < ggml_nelements(w); i++) {
107            ((float *) w->data)[i] = frand();
108        }
109        for (int64_t i = 0; i < ggml_nelements(x); i++) {
110            ((float *) x->data)[i] = frand();
111        }
112
113        // Reference path: the native op, uncropped
114        struct ggml_tensor * y_ref = ggml_conv_transpose_1d(ctx, w, x, c.s0, 0, 1);
115
116        // Decomposed path: [K, OC, IC] -> [IC, K, OC] -> [IC, K*OC], k fastest inside each oc block
117        struct ggml_tensor * w_perm = ggml_cont(ctx, ggml_permute(ctx, w, 1, 2, 0, 3));
118        w_perm                      = ggml_reshape_2d(ctx, w_perm, IC, c.K * c.OC);
119        struct ggml_tensor * x_t    = ggml_cont(ctx, ggml_transpose(ctx, x));
120        struct ggml_tensor * col    = ggml_mul_mat(ctx, w_perm, x_t);
121        struct ggml_tensor * y32    = ggml_col2im_1d(ctx, col, c.s0, (int) c.OC, c.p0);
122
123        // Half precision kernels: the same columns cast before the scatter
124        struct ggml_tensor * y16 = ggml_col2im_1d(ctx, ggml_cast(ctx, col, GGML_TYPE_F16),  c.s0, (int) c.OC, c.p0);
125        struct ggml_tensor * ybf = ggml_col2im_1d(ctx, ggml_cast(ctx, col, GGML_TYPE_BF16), c.s0, (int) c.OC, c.p0);
126
127        GGML_ASSERT(y_ref->ne[0] == T_ref && y_ref->ne[1] == c.OC);
128        GGML_ASSERT(y32->ne[0] == T_out && y32->ne[1] == c.OC);
129
130        struct ggml_cgraph * gf = ggml_new_graph(ctx);
131        ggml_build_forward_expand(gf, y_ref);
132        ggml_build_forward_expand(gf, y32);
133        ggml_build_forward_expand(gf, y16);
134        ggml_build_forward_expand(gf, ybf);
135        ggml_graph_compute_with_ctx(ctx, gf, 4);
136
137        const std::vector<float> f32 = tensor_to_f32(y32);
138        const std::vector<float> f16 = tensor_to_f32(y16);
139        const std::vector<float> fbf = tensor_to_f32(ybf);
140        const float * ref = (const float *) y_ref->data;
141
142        const double e32 = nmse_cropped(f32.data(), ref, T_out, T_ref, c.OC, c.p0);
143        const double e16 = nmse_cropped(f16.data(), ref, T_out, T_ref, c.OC, c.p0);
144        const double ebf = nmse_cropped(fbf.data(), ref, T_out, T_ref, c.OC, c.p0);
145
146        // Same thresholds as test-backend-ops: 1e-7 full precision, 5e-4 half
147        const bool ok = e32 <= 1e-7 && e16 <= 5e-4 && ebf <= 5e-4;
148        if (!ok) {
149            fails++;
150        }
151        printf("col2im_1d K=%2d OC=%2d T_in=%3d s0=%d p0=%d: nmse f32=%.2e f16=%.2e bf16=%.2e %s\n",
152            (int) c.K, (int) c.OC, (int) c.T_in, c.s0, c.p0, e32, e16, ebf, ok ? "OK" : "FAIL");
153
154        ggml_free(ctx);
155    }
156
157    printf(fails == 0 ? "all col2im_1d checks passed\n" : "%d col2im_1d checks FAILED\n", fails);
158    return fails == 0 ? 0 : 1;
159}