#include "ggml-metal-fusion.h"
#include "ggml-backend-impl.h"
#include "ggml-metal-device.h"
#include <algorithm>
#include <cstddef>
#include <cstring>
#include <set>
#include <string>
#include <vector>
struct ggml_metal_fusion {
ggml_metal_fusion_id id;
std::vector<ggml_op> ops; std::vector<ggml_op> ops_all; std::vector<int> outs;
bool unsafe;
bool (*check)(const struct ggml_metal_fusion * fusion,
const struct ggml_tensor * const * nodes,
const struct ggml_cgraph * gf,
const int * node_idxs,
int idx,
ggml_metal_fusion_mode mode);
};
ggml_metal_fusion_id ggml_metal_fusion_get_id(const ggml_metal_fusion * fusion) {
return fusion->id;
}
static bool ggml_metal_fusion_same_buffer(const ggml_tensor * a, const ggml_tensor * b) {
if (!a || !b) {
return false;
}
ggml_backend_buffer_t ba = a->view_src ? a->view_src->buffer : a->buffer;
ggml_backend_buffer_t bb = b->view_src ? b->view_src->buffer : b->buffer;
ggml_metal_buffer_t ca = (ggml_metal_buffer_t) ba->context;
ggml_metal_buffer_t cb = (ggml_metal_buffer_t) bb->context;
return ggml_metal_buffer_get_id(ca, a).metal == ggml_metal_buffer_get_id(cb, b).metal;
}
static bool ggml_metal_fusion_check_norm(
const ggml_metal_fusion * fusion,
const ggml_tensor * const * nodes,
const ggml_cgraph * gf,
const int * node_idxs,
int idx,
ggml_metal_fusion_mode mode) {
GGML_UNUSED(mode);
GGML_UNUSED(gf);
GGML_UNUSED(node_idxs);
GGML_UNUSED(idx);
GGML_ASSERT(fusion->ops.size() >= 2);
if (fusion->id == GGML_METAL_FUSION_NORM_SCALE) {
GGML_ASSERT(fusion->ops.size() == 2);
const ggml_tensor * scale = nodes[1];
if (scale->op != GGML_OP_SCALE || scale->src[0] != nodes[0] || scale->src[1] ||
scale->type != GGML_TYPE_F32) {
return false;
}
return true;
}
for (int j = 1; j < (int) fusion->ops.size(); j++) {
if (nodes[j]->src[0] != nodes[j - 1]) {
return false;
}
if (nodes[j]->src[1]->ne[0] != nodes[0]->ne[0]) {
return false;
}
if (!ggml_is_contiguous_rows(nodes[j]->src[1])) {
return false;
}
if (nodes[j]->type != GGML_TYPE_F32) {
return false;
}
}
return true;
}
static bool ggml_metal_fusion_check_ssm_conv_silu(
const ggml_metal_fusion * fusion,
const ggml_tensor * const * nodes,
const ggml_cgraph * gf,
const int * node_idxs,
int idx,
ggml_metal_fusion_mode mode) {
GGML_UNUSED(fusion);
GGML_UNUSED(gf);
GGML_UNUSED(node_idxs);
GGML_UNUSED(idx);
GGML_UNUSED(mode);
const ggml_tensor * conv = nodes[0];
const ggml_tensor * un = nodes[1];
if (conv->op != GGML_OP_SSM_CONV || un->op != GGML_OP_UNARY || un->src[0] != conv || un->src[1]) {
return false;
}
if (ggml_get_unary_op(un) != GGML_UNARY_OP_SILU) {
return false;
}
if (conv->type != GGML_TYPE_F32 || un->type != GGML_TYPE_F32 || !ggml_is_contiguous_rows(un)) {
return false;
}
return true;
}
static bool ggml_metal_fusion_check_add_chain(
const ggml_metal_fusion * fusion,
const ggml_tensor * const * nodes,
const ggml_cgraph * gf,
const int * node_idxs,
int idx,
ggml_metal_fusion_mode mode) {
GGML_UNUSED(gf);
GGML_UNUSED(node_idxs);
GGML_UNUSED(idx);
GGML_ASSERT(fusion->ops.size() >= 2);
for (int j = 1; j < (int) fusion->ops.size(); j++) {
if (nodes[j]->src[0] != nodes[j - 1]) {
return false;
}
if (!ggml_are_same_layout(nodes[j]->src[1], nodes[j - 1]->src[1])) {
return false;
}
if (mode == GGML_METAL_FUSION_FULL) {
if (!ggml_metal_fusion_same_buffer(nodes[j]->src[1], nodes[0]->src[1])) {
return false;
}
}
}
return true;
}
static bool ggml_metal_fusion_check_gdn_cache(
const ggml_metal_fusion * fusion,
const ggml_tensor * const * nodes,
const ggml_cgraph * gf,
const int * node_idxs,
int idx,
ggml_metal_fusion_mode mode) {
GGML_UNUSED(fusion);
GGML_UNUSED(gf);
GGML_UNUSED(node_idxs);
GGML_UNUSED(idx);
const ggml_tensor * gdn = nodes[0];
const ggml_tensor * cpy = nodes[1];
if (gdn->type != GGML_TYPE_F32 || (gdn->flags & GGML_TENSOR_FLAG_OUTPUT)) {
return false;
}
if (cpy->op != GGML_OP_CPY || (cpy->flags & GGML_TENSOR_FLAG_OUTPUT)) {
return false;
}
const int64_t S_v = gdn->src[2]->ne[0];
const int64_t H = gdn->src[2]->ne[1];
const int64_t n_tokens = gdn->src[2]->ne[2];
const int64_t n_seqs = gdn->src[2]->ne[3];
const int64_t K = ggml_get_op_params_i32(gdn, 0);
const size_t tail_off = ggml_row_size(GGML_TYPE_F32, S_v * H * n_tokens * n_seqs);
const int64_t D = S_v * S_v * H;
const int64_t n_written = std::min<int64_t>(n_tokens, K);
const ggml_tensor * src = cpy->src[0]; const ggml_tensor * dst = cpy->src[1];
if (src->op != GGML_OP_VIEW || src->view_src != gdn ||
src->view_offs != tail_off || !ggml_is_contiguous(src)) {
return false;
}
const int64_t expected_ne[GGML_MAX_DIMS] = { D, n_seqs, n_written, 1 };
if (dst->type != GGML_TYPE_F32 ||
!std::equal(expected_ne, expected_ne + GGML_MAX_DIMS, dst->ne) ||
dst->nb[0] != ggml_type_size(GGML_TYPE_F32) ||
dst->nb[1] != ggml_row_size(GGML_TYPE_F32, D)) {
return false;
}
if (mode == GGML_METAL_FUSION_FULL) {
if (dst->data == nullptr) {
return false;
}
}
return true;
}
static bool ggml_metal_fusion_check_snake(
const ggml_metal_fusion * fusion,
const ggml_tensor * const * nodes,
const ggml_cgraph * gf,
const int * node_idxs,
int idx,
ggml_metal_fusion_mode mode) {
GGML_UNUSED(fusion);
GGML_UNUSED(mode);
GGML_UNUSED(gf);
GGML_UNUSED(node_idxs);
GGML_UNUSED(idx);
const ggml_tensor * mul0 = nodes[0];
const ggml_tensor * sin_node = nodes[1];
const ggml_tensor * sqr = nodes[2];
const ggml_tensor * mul1 = nodes[3];
const ggml_tensor * add = nodes[4];
const ggml_tensor * x = ggml_are_same_shape(mul0, mul0->src[0]) ? mul0->src[0] : mul0->src[1];
const ggml_tensor * a = (x == mul0->src[0]) ? mul0->src[1] : mul0->src[0];
const ggml_tensor * inv_b = (mul1->src[0] == sqr) ? mul1->src[1] : mul1->src[0];
const ggml_tensor * x_in_add = (add->src[0] == mul1) ? add->src[1] : add->src[0];
const bool types_ok =
(x->type == GGML_TYPE_F32 || x->type == GGML_TYPE_F16 || x->type == GGML_TYPE_BF16) &&
(a->type == GGML_TYPE_F32) && (inv_b->type == GGML_TYPE_F32) &&
(mul0->type == x->type) && (sin_node->type == x->type) &&
(sqr->type == x->type) && (mul1->type == x->type) &&
(add->type == x->type);
const bool shape_ok = ggml_are_same_shape(a, inv_b) && a->ne[0] == 1 && a->ne[1] == x->ne[1];
const bool dim_ok =
(x->ne[2] == 1) && (x->ne[3] == 1) &&
(add->ne[2] == 1) && (add->ne[3] == 1) &&
(a->ne[2] == 1) && (a->ne[3] == 1) &&
(inv_b->ne[2] == 1) && (inv_b->ne[3] == 1);
const bool contig_ok =
ggml_is_contiguous(x) && ggml_is_contiguous(add) &&
ggml_is_contiguous(a) && ggml_is_contiguous(inv_b);
return types_ok && shape_ok && dim_ok && contig_ok && x_in_add == x;
}
#define GGML_METAL_TOPK_MOE_MAX_EXPERTS 1024
static const std::vector<ggml_op> ops_topk_moe_all = {
GGML_OP_SOFT_MAX, GGML_OP_RESHAPE, GGML_OP_ARGSORT, GGML_OP_VIEW, GGML_OP_GET_ROWS
};
static const std::vector<ggml_op> ops_topk_moe_scale_all = {
GGML_OP_SOFT_MAX, GGML_OP_RESHAPE, GGML_OP_ARGSORT, GGML_OP_VIEW, GGML_OP_GET_ROWS, GGML_OP_SCALE
};
static const std::vector<ggml_op> ops_topk_moe_norm_all = {
GGML_OP_SOFT_MAX, GGML_OP_RESHAPE, GGML_OP_ARGSORT, GGML_OP_VIEW, GGML_OP_GET_ROWS,
GGML_OP_RESHAPE, GGML_OP_SUM_ROWS, GGML_OP_CLAMP, GGML_OP_DIV, GGML_OP_RESHAPE
};
static const std::vector<ggml_op> ops_topk_moe_norm_scale_all = {
GGML_OP_SOFT_MAX, GGML_OP_RESHAPE, GGML_OP_ARGSORT, GGML_OP_VIEW, GGML_OP_GET_ROWS,
GGML_OP_RESHAPE, GGML_OP_SUM_ROWS, GGML_OP_CLAMP, GGML_OP_DIV, GGML_OP_RESHAPE, GGML_OP_SCALE
};
static bool ggml_metal_fusion_check_topk_moe(
const ggml_metal_fusion * fusion,
const ggml_tensor * const * nodes,
const ggml_cgraph * gf,
const int * node_idxs,
int idx,
ggml_metal_fusion_mode mode) {
GGML_ASSERT(fusion->ops.size() >= 3);
GGML_UNUSED(nodes);
const int n_ops = (int) fusion->ops.size();
const bool with_norm = n_ops >= 6;
const bool with_scale = n_ops == 4 || n_ops == 7;
const std::vector<ggml_op> & ops_all = fusion->ops_all;
const int raw_start = node_idxs[idx];
int raw_end = node_idxs[idx + n_ops - 1];
if (with_norm && !with_scale) {
if (raw_end + 1 >= gf->n_nodes) {
return false;
}
const ggml_tensor * trailing_reshape = gf->nodes[raw_end + 1];
if (trailing_reshape->op != GGML_OP_RESHAPE || trailing_reshape->src[0] != gf->nodes[raw_end]) {
return false;
}
raw_end++;
}
const int raw_count = raw_end - raw_start + 1;
if (raw_count != (int) ops_all.size()) {
return false;
}
int raw_idxs[GGML_METAL_FUSION_MAX];
for (int i = 0; i < raw_count; ++i) {
raw_idxs[i] = raw_start + i;
if (gf->nodes[raw_start + i]->op != ops_all[i]) {
return false;
}
}
const ggml_tensor * softmax = gf->nodes[raw_start];
const ggml_tensor * probs_reshaped = gf->nodes[raw_start + 1];
const ggml_tensor * argsort = gf->nodes[raw_start + 2];
const ggml_tensor * ids = gf->nodes[raw_start + 3];
const ggml_tensor * get_rows = gf->nodes[raw_start + 4];
const ggml_tensor * out = gf->nodes[raw_end];
const ggml_tensor * logits = softmax->src[0];
float scale = 1.0f;
float max_bias = 0.0f;
memcpy(&scale, ((const int32_t *) softmax->op_params) + 0, sizeof(scale));
memcpy(&max_bias, ((const int32_t *) softmax->op_params) + 1, sizeof(max_bias));
if (scale != 1.0f || max_bias != 0.0f || softmax->src[1] || softmax->src[2]) {
return false;
}
if (logits->type != GGML_TYPE_F32 || softmax->type != GGML_TYPE_F32 ||
out->type != GGML_TYPE_F32 || ids->type != GGML_TYPE_I32) {
return false;
}
const int64_t n_expert = logits->ne[0];
const int64_t n_tokens = logits->ne[1];
const int64_t n_expert_used = ids->ne[0];
if (n_expert <= 0 || n_tokens <= 0 || n_expert_used <= 0 || n_expert_used > n_expert ||
n_expert > GGML_METAL_TOPK_MOE_MAX_EXPERTS || n_expert_used > GGML_METAL_TOPK_MOE_MAX_EXPERTS) {
return false;
}
if (logits->ne[2] != 1 || logits->ne[3] != 1 ||
ids->ne[1] != n_tokens || ids->ne[2] != 1 || ids->ne[3] != 1 ||
out->ne[0] != 1 || out->ne[1] != n_expert_used || out->ne[2] != n_tokens || out->ne[3] != 1) {
return false;
}
if (!ggml_is_contiguous(logits) || !ggml_is_contiguous(out) ||
ids->nb[0] != ggml_type_size(GGML_TYPE_I32) ||
ids->nb[1] != ggml_type_size(GGML_TYPE_I32) * n_expert) {
return false;
}
if (probs_reshaped->src[0] != softmax || argsort->src[0] != softmax ||
ids->src[0] != argsort || get_rows->src[0] != probs_reshaped || get_rows->src[1] != ids) {
return false;
}
if (with_norm) {
const ggml_tensor * weights_reshaped = gf->nodes[raw_start + 5];
const ggml_tensor * sum_rows = gf->nodes[raw_start + 6];
const ggml_tensor * clamp = gf->nodes[raw_start + 7];
const ggml_tensor * div = gf->nodes[raw_start + 8];
const ggml_tensor * out_reshaped = gf->nodes[raw_start + 9];
if (weights_reshaped->src[0] != get_rows || sum_rows->src[0] != weights_reshaped ||
clamp->src[0] != sum_rows || div->src[0] != weights_reshaped || div->src[1] != clamp ||
out_reshaped->src[0] != div) {
return false;
}
if (with_scale) {
const ggml_tensor * scale_node = gf->nodes[raw_start + 10];
if (scale_node->src[0] != out_reshaped) {
return false;
}
}
} else if (with_scale) {
const ggml_tensor * scale_node = gf->nodes[raw_start + 5];
if (scale_node->src[0] != get_rows) {
return false;
}
}
const int outputs[2] = { raw_start + 3, raw_end };
if (!ggml_can_fuse_subgraph_ext(gf, raw_idxs, raw_count, ops_all.data(), outputs, 2)) {
return false;
}
if (mode == GGML_METAL_FUSION_FULL) {
if (!logits->data || !out->data || !ids->data) {
return false;
}
}
return true;
}
#define GGML_METAL_MOE_REDUCE_MAX_EXPERTS 8
struct ggml_metal_moe_reduce_match {
const ggml_tensor * experts;
const ggml_tensor * weights;
const ggml_tensor * dst;
int node_count;
};
static bool ggml_metal_fusion_match_moe_reduce(
const ggml_cgraph * gf, int node_idx, const std::vector<ggml_op> & ops_all,
ggml_metal_moe_reduce_match * match) {
if (match == nullptr || node_idx < 0 || node_idx + (int) ops_all.size() > gf->n_nodes) {
return false;
}
const ggml_tensor * mul = gf->nodes[node_idx];
if (mul->op != GGML_OP_MUL || mul->type != GGML_TYPE_F32) {
return false;
}
const int raw_count = (int) ops_all.size();
const int n_expert_used = raw_count / 2;
if (n_expert_used < 2 || n_expert_used > GGML_METAL_MOE_REDUCE_MAX_EXPERTS ||
raw_count != 2 * n_expert_used) {
return false;
}
int n_views = 0;
while (node_idx + 1 + n_views < gf->n_nodes &&
gf->nodes[node_idx + 1 + n_views]->op == GGML_OP_VIEW) {
n_views++;
}
if (n_views != n_expert_used) {
return false;
}
for (int i = n_expert_used + 1; i < raw_count; ++i) {
if (gf->nodes[node_idx + i]->op != GGML_OP_ADD) {
return false;
}
}
int raw_idxs[GGML_METAL_FUSION_MAX];
for (int i = 0; i < raw_count; ++i) {
raw_idxs[i] = node_idx + i;
if (gf->nodes[node_idx + i]->op != ops_all[i]) {
return false;
}
}
const ggml_tensor * experts = mul->src[0];
const ggml_tensor * weights = mul->src[1];
const ggml_tensor * dst = gf->nodes[node_idx + raw_count - 1];
if (experts->type != GGML_TYPE_F32 || weights->type != GGML_TYPE_F32 || dst->type != GGML_TYPE_F32) {
return false;
}
const int64_t n_embd = experts->ne[0];
const int64_t n_tokens = experts->ne[2];
if (n_embd <= 0 || n_tokens <= 0 || experts->ne[1] != n_expert_used || experts->ne[3] != 1 ||
weights->ne[0] != 1 || weights->ne[1] != n_expert_used || weights->ne[2] != n_tokens || weights->ne[3] != 1 ||
dst->ne[0] != n_embd || dst->ne[1] != n_tokens || dst->ne[2] != 1 || dst->ne[3] != 1) {
return false;
}
if (!ggml_is_contiguous(experts) || !ggml_is_contiguous(weights) || !ggml_is_contiguous(dst)) {
return false;
}
for (int i = 1; i <= n_expert_used; ++i) {
const ggml_tensor * view = gf->nodes[node_idx + i];
if (view->view_src != mul || view->src[0] != mul ||
view->view_offs != (size_t) (i - 1) * mul->nb[1] ||
view->ne[0] != n_embd || view->ne[1] != n_tokens ||
view->nb[1] != mul->nb[2]) {
return false;
}
}
const ggml_tensor * prev_add = nullptr;
for (int j = 1; j < n_expert_used; ++j) {
const ggml_tensor * add = gf->nodes[node_idx + n_expert_used + j];
const ggml_tensor * rhs = gf->nodes[node_idx + j + 1];
const ggml_tensor * lhs = j == 1 ? gf->nodes[node_idx + 1] : prev_add;
if (add->src[0] != lhs || add->src[1] != rhs) {
return false;
}
prev_add = add;
}
const int outputs[1] = { node_idx + raw_count - 1 };
if (!ggml_can_fuse_subgraph_ext(gf, raw_idxs, raw_count, ops_all.data(), outputs, 1)) {
return false;
}
match->experts = experts;
match->weights = weights;
match->dst = dst;
match->node_count = raw_count;
return true;
}
static bool ggml_metal_fusion_check_moe_reduce(
const ggml_metal_fusion * fusion,
const ggml_tensor * const * nodes,
const ggml_cgraph * gf,
const int * node_idxs,
int idx,
ggml_metal_fusion_mode mode) {
GGML_UNUSED(nodes);
ggml_metal_moe_reduce_match match;
if (!ggml_metal_fusion_match_moe_reduce(gf, node_idxs[idx], fusion->ops_all, &match)) {
return false;
}
if ((int) fusion->ops.size() != match.experts->ne[1]) {
return false;
}
const int raw_end = node_idxs[idx] + match.node_count - 1;
if (node_idxs[idx + (int) fusion->ops.size() - 1] != raw_end) {
return false;
}
if (mode == GGML_METAL_FUSION_FULL) {
if (!match.experts->data || !match.weights->data || !match.dst->data) {
return false;
}
}
return true;
}
static const std::vector<ggml_op> ops_norm_mul = { GGML_OP_NORM, GGML_OP_MUL };
static const std::vector<ggml_op> ops_norm_mul_add = { GGML_OP_NORM, GGML_OP_MUL, GGML_OP_ADD };
static const std::vector<ggml_op> ops_norm_scale = { GGML_OP_NORM, GGML_OP_SCALE };
static const std::vector<ggml_op> ops_rms_norm_mul = { GGML_OP_RMS_NORM, GGML_OP_MUL };
static const std::vector<ggml_op> ops_rms_norm_mul_add = { GGML_OP_RMS_NORM, GGML_OP_MUL, GGML_OP_ADD };
static const std::vector<ggml_op> ops_rms_norm_scale = { GGML_OP_RMS_NORM, GGML_OP_SCALE };
static const std::vector<ggml_op> ops_add_2 = { GGML_OP_ADD, GGML_OP_ADD };
static const std::vector<ggml_op> ops_add_3 = { GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD };
static const std::vector<ggml_op> ops_add_4 = { GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD };
static const std::vector<ggml_op> ops_add_5 = { GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD };
static const std::vector<ggml_op> ops_add_6 = { GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD };
static const std::vector<ggml_op> ops_add_7 = { GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD };
static const std::vector<ggml_op> ops_snake = { GGML_OP_MUL, GGML_OP_SIN, GGML_OP_SQR, GGML_OP_MUL, GGML_OP_ADD };
static const std::vector<ggml_op> ops_gdn_cache = { GGML_OP_GATED_DELTA_NET, GGML_OP_CPY };
static const std::vector<ggml_op> ops_topk_moe = {
GGML_OP_SOFT_MAX, GGML_OP_ARGSORT, GGML_OP_GET_ROWS
};
static const std::vector<ggml_op> ops_topk_moe_scale = {
GGML_OP_SOFT_MAX, GGML_OP_ARGSORT, GGML_OP_GET_ROWS, GGML_OP_SCALE
};
static const std::vector<ggml_op> ops_topk_moe_norm = {
GGML_OP_SOFT_MAX, GGML_OP_ARGSORT, GGML_OP_GET_ROWS,
GGML_OP_SUM_ROWS, GGML_OP_CLAMP, GGML_OP_DIV
};
static const std::vector<ggml_op> ops_topk_moe_norm_scale = {
GGML_OP_SOFT_MAX, GGML_OP_ARGSORT, GGML_OP_GET_ROWS,
GGML_OP_SUM_ROWS, GGML_OP_CLAMP, GGML_OP_DIV, GGML_OP_SCALE
};
static const std::vector<ggml_op> ops_ssm_conv_silu = { GGML_OP_SSM_CONV, GGML_OP_UNARY };
static const std::vector<ggml_op> ops_moe_reduce_2 = { GGML_OP_MUL, GGML_OP_ADD };
static const std::vector<ggml_op> ops_moe_reduce_3 = { GGML_OP_MUL, GGML_OP_ADD, GGML_OP_ADD };
static const std::vector<ggml_op> ops_moe_reduce_4 = { GGML_OP_MUL, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD };
static const std::vector<ggml_op> ops_moe_reduce_5 = { GGML_OP_MUL, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD };
static const std::vector<ggml_op> ops_moe_reduce_6 = { GGML_OP_MUL, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD };
static const std::vector<ggml_op> ops_moe_reduce_7 = { GGML_OP_MUL, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD };
static const std::vector<ggml_op> ops_moe_reduce_8 = { GGML_OP_MUL, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD };
static const std::vector<ggml_op> ops_moe_reduce_all_2 = {
GGML_OP_MUL, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_ADD
};
static const std::vector<ggml_op> ops_moe_reduce_all_3 = {
GGML_OP_MUL, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_ADD, GGML_OP_ADD
};
static const std::vector<ggml_op> ops_moe_reduce_all_4 = {
GGML_OP_MUL, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW,
GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD
};
static const std::vector<ggml_op> ops_moe_reduce_all_5 = {
GGML_OP_MUL, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW,
GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD
};
static const std::vector<ggml_op> ops_moe_reduce_all_6 = {
GGML_OP_MUL, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW,
GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD
};
static const std::vector<ggml_op> ops_moe_reduce_all_7 = {
GGML_OP_MUL, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW,
GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD
};
static const std::vector<ggml_op> ops_moe_reduce_all_8 = {
GGML_OP_MUL,
GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW, GGML_OP_VIEW,
GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD, GGML_OP_ADD
};
static const std::vector<ggml_metal_fusion> ggml_metal_fusions = {
{ GGML_METAL_FUSION_NORM_MUL, ops_norm_mul, ops_norm_mul, {}, false, ggml_metal_fusion_check_norm },
{ GGML_METAL_FUSION_NORM_MUL_ADD, ops_norm_mul_add, ops_norm_mul_add, {}, false, ggml_metal_fusion_check_norm },
{ GGML_METAL_FUSION_NORM_SCALE, ops_norm_scale, ops_norm_scale, {}, false, ggml_metal_fusion_check_norm },
{ GGML_METAL_FUSION_NORM_MUL, ops_rms_norm_mul, ops_rms_norm_mul, {}, false, ggml_metal_fusion_check_norm },
{ GGML_METAL_FUSION_NORM_MUL_ADD, ops_rms_norm_mul_add, ops_rms_norm_mul_add, {}, false, ggml_metal_fusion_check_norm },
{ GGML_METAL_FUSION_NORM_SCALE, ops_rms_norm_scale, ops_rms_norm_scale, {}, false, ggml_metal_fusion_check_norm },
{ GGML_METAL_FUSION_ADD_CHAIN, ops_add_2, ops_add_2, {}, false, ggml_metal_fusion_check_add_chain },
{ GGML_METAL_FUSION_ADD_CHAIN, ops_add_3, ops_add_3, {}, false, ggml_metal_fusion_check_add_chain },
{ GGML_METAL_FUSION_ADD_CHAIN, ops_add_4, ops_add_4, {}, false, ggml_metal_fusion_check_add_chain },
{ GGML_METAL_FUSION_ADD_CHAIN, ops_add_5, ops_add_5, {}, false, ggml_metal_fusion_check_add_chain },
{ GGML_METAL_FUSION_ADD_CHAIN, ops_add_6, ops_add_6, {}, false, ggml_metal_fusion_check_add_chain },
{ GGML_METAL_FUSION_ADD_CHAIN, ops_add_7, ops_add_7, {}, false, ggml_metal_fusion_check_add_chain },
{ GGML_METAL_FUSION_SNAKE, ops_snake, ops_snake, {}, false, ggml_metal_fusion_check_snake },
{ GGML_METAL_FUSION_GDN_CACHE, ops_gdn_cache, ops_gdn_cache, {}, true, ggml_metal_fusion_check_gdn_cache },
{ GGML_METAL_FUSION_TOPK_MOE, ops_topk_moe, ops_topk_moe_all, {1}, true, ggml_metal_fusion_check_topk_moe },
{ GGML_METAL_FUSION_TOPK_MOE, ops_topk_moe_scale, ops_topk_moe_scale_all, {1}, true, ggml_metal_fusion_check_topk_moe },
{ GGML_METAL_FUSION_TOPK_MOE, ops_topk_moe_norm, ops_topk_moe_norm_all, {1}, true, ggml_metal_fusion_check_topk_moe },
{ GGML_METAL_FUSION_TOPK_MOE, ops_topk_moe_norm_scale, ops_topk_moe_norm_scale_all, {1}, true, ggml_metal_fusion_check_topk_moe },
{ GGML_METAL_FUSION_MOE_REDUCE, ops_moe_reduce_2, ops_moe_reduce_all_2, {}, true, ggml_metal_fusion_check_moe_reduce },
{ GGML_METAL_FUSION_MOE_REDUCE, ops_moe_reduce_3, ops_moe_reduce_all_3, {}, true, ggml_metal_fusion_check_moe_reduce },
{ GGML_METAL_FUSION_MOE_REDUCE, ops_moe_reduce_4, ops_moe_reduce_all_4, {}, true, ggml_metal_fusion_check_moe_reduce },
{ GGML_METAL_FUSION_MOE_REDUCE, ops_moe_reduce_5, ops_moe_reduce_all_5, {}, true, ggml_metal_fusion_check_moe_reduce },
{ GGML_METAL_FUSION_MOE_REDUCE, ops_moe_reduce_6, ops_moe_reduce_all_6, {}, true, ggml_metal_fusion_check_moe_reduce },
{ GGML_METAL_FUSION_MOE_REDUCE, ops_moe_reduce_7, ops_moe_reduce_all_7, {}, true, ggml_metal_fusion_check_moe_reduce },
{ GGML_METAL_FUSION_MOE_REDUCE, ops_moe_reduce_8, ops_moe_reduce_all_8, {}, true, ggml_metal_fusion_check_moe_reduce },
{ GGML_METAL_FUSION_SSM_CONV_SILU, ops_ssm_conv_silu, ops_ssm_conv_silu, {}, false, ggml_metal_fusion_check_ssm_conv_silu },
};
static bool ggml_metal_fusion_match_raw_pattern(
const ggml_cgraph * gf, int node_idx, const std::vector<ggml_op> & ops) {
if (node_idx < 0 || node_idx + (int) ops.size() > gf->n_nodes) {
return false;
}
for (int i = 0; i < (int) ops.size(); ++i) {
if (gf->nodes[node_idx + i]->op != ops[i]) {
return false;
}
}
return true;
}
static void ggml_metal_fusion_add_pattern_alloc_deps(
void * user_data,
void (*add_alloc_dep)(void *, ggml_tensor *, ggml_tensor *),
ggml_cgraph * gf,
const ggml_metal_fusion * fusion,
int node_idx) {
const int last_node = node_idx + (int) fusion->ops_all.size() - 1;
std::set<ggml_tensor *> seen;
for (int j = 0; j < (int) fusion->ops_all.size(); ++j) {
ggml_tensor * node = gf->nodes[node_idx + j];
for (int s = 0; s < GGML_MAX_SRC; ++s) {
ggml_tensor * src = node->src[s];
if (src && seen.insert(src).second) {
add_alloc_dep(user_data, src, gf->nodes[last_node]);
}
}
seen.insert(node);
}
}
void ggml_metal_fusion_add_alloc_deps(
void * user_data,
void (*add_alloc_dep)(void *, ggml_tensor *, ggml_tensor *),
ggml_cgraph * gf) {
for (int i = 0; i < gf->n_nodes; ++i) {
const ggml_metal_fusion * best = nullptr;
int best_raw = 0;
for (const ggml_metal_fusion & fusion : ggml_metal_fusions) {
if ((int) fusion.ops_all.size() <= best_raw) {
continue;
}
if (ggml_metal_fusion_match_raw_pattern(gf, i, fusion.ops_all)) {
best = &fusion;
best_raw = (int) fusion.ops_all.size();
}
}
if (best) {
ggml_metal_fusion_add_pattern_alloc_deps(user_data, add_alloc_dep, gf, best, i);
i += best_raw - 1;
}
}
}
static std::string ggml_metal_fusion_label(const ggml_metal_fusion * fusion) {
GGML_ASSERT(fusion != nullptr);
std::string label;
for (int j = 0; j < (int) fusion->ops.size(); j++) {
if (j > 0) {
label += '+';
}
label += ggml_op_name(fusion->ops[j]);
}
return label;
}
struct ggml_metal_fusion_info {
std::vector<std::string> labels;
std::vector<uint64_t> counts;
bool enabled;
bool stats;
bool labels_set;
int debug;
};
struct ggml_metal_fusion_info * ggml_metal_fusion_info_init(bool enabled, int debug) {
ggml_metal_fusion_info * finfo = new ggml_metal_fusion_info;
finfo->enabled = enabled;
finfo->stats = debug > 0;
finfo->labels_set = false;
finfo->debug = debug;
if (finfo->stats) {
ggml_metal_fusion_info_labels_init(finfo);
}
return finfo;
}
void ggml_metal_fusion_info_free(ggml_metal_fusion_info * finfo) {
delete finfo;
}
bool ggml_metal_fusion_info_enabled(const ggml_metal_fusion_info * finfo) {
return finfo->enabled;
}
bool ggml_metal_fusion_info_stats(const ggml_metal_fusion_info * finfo) {
return finfo->stats;
}
int ggml_metal_fusion_info_debug(const ggml_metal_fusion_info * finfo) {
return finfo->debug;
}
int ggml_metal_fusion_info_n_fusions(const ggml_metal_fusion_info * finfo) {
return (int) finfo->labels.size();
}
const char * ggml_metal_fusion_info_label(const ggml_metal_fusion_info * finfo, int idx) {
GGML_ASSERT(idx >= 0 && idx < (int) finfo->labels.size());
return finfo->labels[idx].c_str();
}
uint64_t ggml_metal_fusion_info_count(const ggml_metal_fusion_info * finfo, int idx) {
GGML_ASSERT(idx >= 0 && idx < (int) finfo->counts.size());
return finfo->counts[idx];
}
void ggml_metal_fusion_info_count_fusion(ggml_metal_fusion_info * finfo, const ggml_metal_fusion * fusion) {
if (!finfo->stats || fusion == nullptr) {
return;
}
const ptrdiff_t idx = fusion - ggml_metal_fusions.data();
if (idx >= 0 && idx < (ptrdiff_t) finfo->counts.size()) {
finfo->counts[idx]++;
}
}
void ggml_metal_fusion_info_set_enabled(ggml_metal_fusion_info * finfo, bool enabled) {
finfo->enabled = enabled;
}
void ggml_metal_fusion_info_labels_init(ggml_metal_fusion_info * finfo) {
if (finfo->labels_set) {
return;
}
finfo->labels.clear();
finfo->counts.assign(ggml_metal_fusions.size(), 0);
finfo->labels.reserve(ggml_metal_fusions.size());
for (const ggml_metal_fusion & fusion : ggml_metal_fusions) {
finfo->labels.emplace_back(ggml_metal_fusion_label(&fusion));
}
finfo->labels_set = true;
}
void ggml_metal_fusion_info_stats_init(ggml_metal_fusion_info * finfo) {
finfo->stats = true;
ggml_metal_fusion_info_labels_init(finfo);
}
void ggml_metal_fusion_info_stats_reset(ggml_metal_fusion_info * finfo) {
std::fill(finfo->counts.begin(), finfo->counts.end(), 0);
}
int ggml_metal_fusion_info_stats_get(const ggml_metal_fusion_info * finfo, const char ** labels, uint64_t * counts, int n) {
const int n_fusions = (int) finfo->labels.size();
if (labels == nullptr) {
return n_fusions;
}
const int n_fill = std::min(n, n_fusions);
for (int i = 0; i < n_fill; i++) {
labels[i] = finfo->labels[i].c_str();
if (counts != nullptr) {
counts[i] = finfo->counts[i];
}
}
return n_fill;
}
static bool ggml_metal_fusion_check_memory_ranges(
const ggml_metal_fusion * fusion,
const ggml_tensor * const * nodes,
int node_count) {
auto same_memory = [](const ggml_tensor * a, const ggml_tensor * b) {
if (a->data && b->data && a->data == b->data) {
return true;
}
for (const ggml_tensor * v = a; v; v = v->view_src) {
if (v == b) {
return true;
}
}
for (const ggml_tensor * v = b; v; v = v->view_src) {
if (v == a) {
return true;
}
}
return false;
};
auto nodes_overlap = [](const ggml_tensor * a, const ggml_tensor * b) {
if (!a || !b || !a->data || !b->data || !a->buffer || !b->buffer) {
return false;
}
if (a->buffer != b->buffer) {
return false;
}
const int64_t a_start = (int64_t) a->data;
const int64_t a_end = a_start + ggml_backend_buft_get_alloc_size(a->buffer->buft, a);
const int64_t b_start = (int64_t) b->data;
const int64_t b_end = b_start + ggml_backend_buft_get_alloc_size(b->buffer->buft, b);
return (b_start <= a_start && a_start < b_end) ||
(a_start <= b_start && b_start < a_end);
};
auto is_intermediate = [](const ggml_tensor * src, const ggml_tensor * const * nodes, int j) {
for (int k = 0; k < j; ++k) {
if (src == nodes[k]) {
return true;
}
for (const ggml_tensor * view_src = src->view_src; view_src; view_src = view_src->view_src) {
if (view_src == nodes[k]) {
return true;
}
}
}
return false;
};
auto check_dst = [&](const ggml_tensor * dst) {
for (int j = 0; j < node_count; ++j) {
for (int s = 0; s < GGML_MAX_SRC; ++s) {
const ggml_tensor * src = nodes[j]->src[s];
if (!src || src->op == GGML_OP_NONE || same_memory(src, dst)) {
continue;
}
if (nodes_overlap(dst, src) && !is_intermediate(src, nodes, j)) {
return false;
}
}
}
return true;
};
if (!check_dst(nodes[node_count - 1])) {
return false;
}
for (int offset : fusion->outs) {
GGML_ASSERT(offset >= 0 && offset < node_count);
if (!check_dst(nodes[offset])) {
return false;
}
}
return true;
}
const ggml_metal_fusion * ggml_metal_fusion_next(
const ggml_cgraph * gf,
const int * node_idxs,
int n_idxs,
int idx,
ggml_metal_fusion_mode mode,
int * n_out) {
const ggml_metal_fusion * res = nullptr;
int best = 1;
for (const ggml_metal_fusion & fusion : ggml_metal_fusions) {
const int n_ops = (int) fusion.ops.size();
if (n_ops <= best) {
continue;
}
if (idx + n_ops > n_idxs) {
continue;
}
const ggml_tensor * nodes[GGML_METAL_FUSION_MAX];
bool ok = true;
for (int j = 0; j < n_ops; j++) {
nodes[j] = gf->nodes[node_idxs[idx + j]];
if (nodes[j]->op != fusion.ops[j]) {
ok = false;
break;
}
}
if (!ok) {
continue;
}
if (!fusion.unsafe) {
for (int j = 1; j < n_ops && ok; j++) {
if (nodes[j]->src[0] != nodes[j - 1] && nodes[j]->src[1] != nodes[j - 1]) {
ok = false;
break;
}
if (!ggml_are_same_shape(nodes[j], nodes[j - 1])) {
ok = false;
break;
}
}
if (!ok) {
continue;
}
int outputs_buf[GGML_METAL_FUSION_MAX];
outputs_buf[0] = node_idxs[idx + n_ops - 1];
for (size_t i = 0; i < fusion.outs.size(); ++i) {
const int out_offset = fusion.outs[i];
GGML_ASSERT(out_offset >= 0 && out_offset < n_ops);
outputs_buf[i + 1] = node_idxs[idx + out_offset];
}
const int n_outputs = 1 + (int) fusion.outs.size();
if (!ggml_can_fuse_subgraph_ext(gf, node_idxs + idx, n_ops, fusion.ops.data(), outputs_buf, n_outputs)) {
continue;
}
}
if (fusion.check && !fusion.check(&fusion, nodes, gf, node_idxs, idx, mode)) {
continue;
}
if (mode == GGML_METAL_FUSION_FULL &&
!ggml_metal_fusion_check_memory_ranges(&fusion, nodes, n_ops)) {
continue;
}
best = n_ops;
res = &fusion;
}
*n_out = best;
return res;
}
int ggml_metal_fusion_max(const ggml_cgraph * gf, int idx) {
if (ggml_op_is_empty(gf->nodes[idx]->op) || ggml_is_empty(gf->nodes[idx])) {
return 1;
}
int idxs[GGML_METAL_FUSION_MAX];
int n_idxs = 0;
for (int i = idx; i < gf->n_nodes && n_idxs < GGML_METAL_FUSION_MAX; i++) {
if (!ggml_op_is_empty(gf->nodes[i]->op) && !ggml_is_empty(gf->nodes[i])) {
idxs[n_idxs++] = i;
}
}
if (n_idxs == 0) {
return 1;
}
int total = 0;
int i_f = 0;
while (i_f < n_idxs && total < GGML_METAL_FUSION_MAX) {
int len = 1;
const ggml_metal_fusion * fusion = ggml_metal_fusion_next(gf, idxs, n_idxs, i_f, GGML_METAL_FUSION_STRUCTURAL, &len);
if (!fusion || total + len > GGML_METAL_FUSION_MAX) {
break;
}
total += len;
i_f += len;
}
if (i_f == 0) {
return 1;
}
return std::min(GGML_METAL_FUSION_MAX, idxs[i_f - 1] - idx + 1);
}