voice_audio_multi/tests/unittests/test_metal_im2col_3d.cpp
Fabio c36510c21e
Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
Initial import: audio.cpp with Orange Pi documentation
2026-11-04 19:05:22 +01:00

165 lines
8.7 KiB
C++

#include <ggml.h>
#include <ggml-backend.h>
#include <ggml-metal.h>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <stdexcept>
#include <vector>
namespace {
void require(bool value, const char * message) {
if (!value) throw std::runtime_error(message);
}
struct Shape {
int w, h, d, ic, oc, n;
int kw, kh, kd, sw, sh, sd, pw, ph, pd, dw, dh, dd;
bool view = false;
};
struct Storage {
ggml_context * ctx = ggml_init({8 * 1024 * 1024, nullptr, true});
ggml_backend_buffer_t buffer = nullptr;
~Storage() {
if (buffer) ggml_backend_buffer_free(buffer);
ggml_free(ctx);
}
};
void run(ggml_backend_t backend, Shape s, ggml_type type) {
Storage storage;
auto * ctx = storage.ctx;
auto * xbase = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, s.w + (s.view ? 3 : 0), s.h + (s.view ? 1 : 0),
s.d + (s.view ? 1 : 0), s.ic * s.n);
auto * x = s.view ? ggml_view_4d(ctx, xbase, s.w, s.h, s.d, s.ic * s.n,
xbase->nb[1], xbase->nb[2], xbase->nb[3], sizeof(float)) : xbase;
auto * w = ggml_new_tensor_4d(ctx, type, s.kw, s.kh, s.kd, s.ic * s.oc);
auto * packed = ggml_im2col_3d(ctx, w, x, s.ic, s.sw, s.sh, s.sd, s.pw, s.ph, s.pd,
s.dw, s.dh, s.dd, type);
// Independent gold: the existing Metal direct convolution. No CPU convolution
// and no graph containing the new operation participates in this reference.
auto * gold = ggml_conv_3d_direct(ctx, w, s.view ? ggml_cont(ctx, x) : x, s.sw, s.sh, s.sd, s.pw, s.ph, s.pd,
s.dw, s.dh, s.dd, s.ic, s.n, s.oc);
// Materialize views for the legacy convolution reference and the public
// helper. The standalone packing comparison below still reads the raw view.
auto * actual = ggml_conv_3d(ctx, w, s.view ? ggml_cont(ctx, x) : x, s.ic,
s.sw, s.sh, s.sd, s.pw, s.ph, s.pd, s.dw, s.dh, s.dd);
// Check packing itself against existing Metal gather + cast, including zero
// padding. The host builds gather indices, not simulated floating-point output.
auto * indices = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, ggml_nelements(packed));
auto * flat = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, ggml_nelements(xbase) + 1);
auto * gathered = ggml_get_rows(ctx, flat, indices);
if (type == GGML_TYPE_F16) gathered = ggml_cast(ctx, gathered, type);
auto * graph = ggml_new_graph_custom(ctx, 128, false);
for (auto * out : {gold, actual, packed, gathered}) ggml_build_forward_expand(graph, out);
for (int i = 0; i < ggml_graph_n_nodes(graph); ++i) {
auto * node = ggml_graph_node(graph, i);
if (!ggml_backend_supports_op(backend, node)) {
std::cerr << "unsupported " << ggml_op_name(node->op) << " type=" << ggml_type_name(type) << " view=" << s.view << '\n';
throw std::runtime_error("unsupported node");
}
}
storage.buffer = ggml_backend_alloc_ctx_tensors(ctx, backend);
require(storage.buffer, "allocation failed");
std::vector<int32_t> map(ggml_nelements(packed));
size_t index = 0;
const int od_count = packed->ne[3] / s.n;
for (int n = 0; n < s.n; ++n)
for (int od = 0; od < od_count; ++od)
for (int oh = 0; oh < packed->ne[2]; ++oh)
for (int ow = 0; ow < packed->ne[1]; ++ow)
for (int ic = 0; ic < s.ic; ++ic)
for (int kd = 0; kd < s.kd; ++kd)
for (int kh = 0; kh < s.kh; ++kh)
for (int kw = 0; kw < s.kw; ++kw) {
int iw = ow*s.sw + kw*s.dw - s.pw, ih = oh*s.sh + kh*s.dh - s.ph, id = od*s.sd + kd*s.dd - s.pd;
map[index++] = iw < 0 || iw >= s.w || ih < 0 || ih >= s.h || id < 0 || id >= s.d ?
ggml_nelements(xbase) : (s.view ? 1 : 0) + iw +
(ih*x->nb[1] + id*x->nb[2] + (n*s.ic+ic)*x->nb[3])/sizeof(float);
}
ggml_backend_tensor_set(indices, map.data(), 0, ggml_nbytes(indices));
std::vector<float> first;
for (int pattern = 0; pattern < 3; ++pattern) {
std::vector<float> xv(ggml_nelements(xbase) + 1), wv(ggml_nelements(w));
for (size_t i = 0; i + 1 < xv.size(); ++i)
xv[i] = ggml_fp16_to_fp32(ggml_fp32_to_fp16(float(int((i*17 + pattern*7)%31)-15) / (pattern ? 137.F : 16.F)));
for (size_t i = 0; i < wv.size(); ++i)
wv[i] = ggml_fp16_to_fp32(ggml_fp32_to_fp16(float(int((i*13 + pattern*3)%23)-11) / (pattern ? 157.F : 32.F)));
ggml_backend_tensor_set(xbase, xv.data(), 0, ggml_nbytes(xbase));
ggml_backend_tensor_set(flat, xv.data(), 0, ggml_nbytes(flat));
if (type == GGML_TYPE_F16) {
std::vector<ggml_fp16_t> half(wv.size());
ggml_fp32_to_fp16_row(wv.data(), half.data(), half.size());
ggml_backend_tensor_set(w, half.data(), 0, ggml_nbytes(w));
} else ggml_backend_tensor_set(w, wv.data(), 0, ggml_nbytes(w));
require(ggml_backend_graph_compute(backend, graph) == GGML_STATUS_SUCCESS, "compute failed");
std::vector<unsigned char> p(ggml_nbytes(packed)), g(p.size());
ggml_backend_tensor_get(packed, p.data(), 0, p.size());
ggml_backend_tensor_get(gathered, g.data(), 0, g.size());
require(p == g, "packing differs from legacy Metal gather/cast");
std::vector<float> a(ggml_nelements(actual)), b(a.size());
ggml_backend_tensor_get(actual, a.data(), 0, ggml_nbytes(actual));
ggml_backend_tensor_get(gold, b.data(), 0, ggml_nbytes(gold));
for (size_t i = 0; i < a.size(); ++i)
require(std::isfinite(a[i]) && std::isfinite(b[i]) && std::abs(a[i]-b[i]) <= (pattern ? 2e-5F : 0.F),
"convolution differs from legacy Metal direct convolution");
if (!pattern) first = a;
else require(a != first, "updated input did not affect output");
}
// F32 packing must preserve values outside half range and half mantissa.
if (type == GGML_TYPE_F32) {
std::vector<float> xv(ggml_nelements(xbase) + 1);
for (size_t i = 0; i + 1 < xv.size(); ++i) xv[i] = i%2 ? 65536.F : 1.0001F;
ggml_backend_tensor_set(xbase, xv.data(), 0, ggml_nbytes(xbase));
ggml_backend_tensor_set(flat, xv.data(), 0, ggml_nbytes(flat));
auto * pack_graph = ggml_new_graph_custom(ctx, 128, false);
ggml_build_forward_expand(pack_graph, packed);
ggml_build_forward_expand(pack_graph, gathered);
require(ggml_backend_graph_compute(backend, pack_graph) == GGML_STATUS_SUCCESS, "range compute failed");
std::vector<float> a(ggml_nelements(packed)), b(a.size());
ggml_backend_tensor_get(packed, a.data(), 0, ggml_nbytes(packed));
ggml_backend_tensor_get(gathered, b.data(), 0, ggml_nbytes(gathered));
require(a == b, "F32 packing lost range or precision");
}
}
} // namespace
int main() {
auto * backend = ggml_backend_metal_init();
if (!backend) return 77;
try {
const Shape shapes[] = {
{7,5,3,3,5,2, 1,1,1, 1,1,1, 0,0,0, 1,1,1},
{7,5,4,3,5,2, 3,3,3, 1,1,1, 1,1,1, 1,1,1},
{9,7,5,2,4,2, 2,3,2, 2,1,2, 1,2,1, 2,1,2},
{5,4,1,3,2,1, 3,3,3, 1,1,1, 1,1,1, 1,1,1},
{4,3,2,2,3,1, 2,2,2, 1,2,1, 0,0,0, 1,1,1},
{9,7,5,2,4,2, 2,3,2, 2,1,2, 1,2,1, 2,1,2, true},
{16,16,5,64,64,1, 3,3,3, 1,1,1, 1,1,1, 1,1,1},
// More elements than a single bounded dispatch grid: exercise its loop.
{32,32,9,128,8,1, 3,3,3, 1,1,1, 1,1,1, 1,1,1},
};
{
Storage storage;
auto * w = ggml_new_tensor_4d(storage.ctx, GGML_TYPE_F16, 3, 3, 3, 2);
auto * x = ggml_new_tensor_4d(storage.ctx, GGML_TYPE_F32, 8, 8, 3, 2);
auto * op = ggml_im2col_3d(storage.ctx, w, x, 2, 1,1,1, 1,1,1, 1,1,1, GGML_TYPE_F16);
require(ggml_backend_supports_op(backend, op), "valid packing rejected");
x->type = GGML_TYPE_F16;
require(!ggml_backend_supports_op(backend, op), "unsupported input type admitted");
x->type = GGML_TYPE_F32;
x->nb[0] = 2*sizeof(float);
require(!ggml_backend_supports_op(backend, op), "unsupported innermost stride admitted");
x->nb[0] = sizeof(float);
op->type = GGML_TYPE_I32;
require(!ggml_backend_supports_op(backend, op), "unsupported output type admitted");
}
for (const auto & shape : shapes)
for (auto type : {GGML_TYPE_F16, GGML_TYPE_F32}) run(backend, shape, type);
std::cout << "PASS: 16 shapes/types, changing inputs, strided offset views, batches, stride/dilation/padding, legacy Metal gold\n";
ggml_backend_free(backend);
return 0;
} catch (const std::exception & e) {
std::cerr << e.what() << '\n';
ggml_backend_free(backend);
return 1;
}
}