Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 101 additions & 5 deletions ggml/src/ggml-cpu/ggml-cpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -1384,7 +1384,7 @@ UseGgmlGemm1:;
const size_t nbw3 = nbw2*ne12;

assert(params->wsize >= ne13*nbw3);
GGML_ASSERT(src1->type == GGML_TYPE_F32);
GGML_ASSERT(src1->type == GGML_TYPE_F32 || src1->type == GGML_TYPE_F16);

#if 0
for (int64_t i13 = 0; i13 < ne13; ++i13) {
Expand All @@ -1403,9 +1403,28 @@ UseGgmlGemm1:;
size_t bs = ggml_blck_size(vec_dot_type);
int64_t ne10_block_start = (ith * ne10/bs) / nth;
int64_t ne10_block_end = ((ith + 1) * ne10/bs) / nth;
from_float((float *)((char *) src1->data + i13*nb13 + i12*nb12 + i11*nb11 + ne10_block_start*bs*nb10),
(void *) (wdata + i13*nbw3 + i12*nbw2 + i11*nbw1 + ne10_block_start*nbw0),
(ne10_block_end - ne10_block_start) * bs);
if (src1->type == GGML_TYPE_F32) {
from_float((float *)((char *) src1->data + i13*nb13 + i12*nb12 + i11*nb11 + ne10_block_start*bs*nb10),
(void *) (wdata + i13*nbw3 + i12*nbw2 + i11*nbw1 + ne10_block_start*nbw0),
(ne10_block_end - ne10_block_start) * bs);
} else {
// F16 src1 feeding a quantized src0 (e.g. an f16
// im2col against Q8_0 conv weights): convert to f32
// in cache-sized chunks, then quantize into wdata.
const ggml_fp16_t * x16 = (const ggml_fp16_t *)((const char *) src1->data + i13*nb13 + i12*nb12 + i11*nb11) + ne10_block_start*bs;
char * wrow = wdata + i13*nbw3 + i12*nbw2 + i11*nbw1 + ne10_block_start*nbw0;
const int64_t n = (ne10_block_end - ne10_block_start) * bs;
float tmp_f32[1024]; // multiple of every block size
int64_t done = 0;
while (done < n) {
const int64_t chunk = MIN(n - done, (int64_t) 1024);
for (int64_t k = 0; k < chunk; ++k) {
tmp_f32[k] = GGML_CPU_FP16_TO_FP32(x16[done + k]);
}
from_float(tmp_f32, (void *)(wrow + (done/bs)*nbw0), chunk);
done += chunk;
}
}
}
}
}
Expand Down Expand Up @@ -3103,7 +3122,84 @@ static thread_ret_t ggml_graph_compute_thread(void * data) {
continue;
}

ggml_compute_forward(&params, node);
// Collapse CONV_2D -> ADD(per-channel bias) [-> UNARY(relu|hardswish)]
// chains into one fused kernel call: the bias add and activation are
// applied while the GEMM result is still hot instead of as separate
// full-tensor passes. The decision depends only on the (shared) graph,
// so every thread takes the same branch and the per-node barriers stay
// in sync; the skipped nodes' outputs are never read (single use,
// checked by ggml_can_fuse).
int fuse_extra = 0;
int32_t fuse_act = -1;
const struct ggml_tensor * fuse_bias = NULL;
struct ggml_tensor * fuse_out = NULL;
if (node->op == GGML_OP_CONV_2D) {
static const enum ggml_op fuse_ops3[3] = {GGML_OP_CONV_2D, GGML_OP_ADD, GGML_OP_UNARY};
static const enum ggml_op fuse_ops2[2] = {GGML_OP_CONV_2D, GGML_OP_ADD};
if (ggml_can_fuse(cgraph, node_n, fuse_ops3, 3)) {
struct ggml_tensor * unary_node = cgraph->nodes[node_n + 2];
const enum ggml_unary_op uop = ggml_get_unary_op(unary_node);
if ((uop == GGML_UNARY_OP_RELU || uop == GGML_UNARY_OP_HARDSWISH) &&
unary_node->type == GGML_TYPE_F32) {
fuse_extra = 2;
fuse_act = (int32_t) uop;
fuse_out = unary_node;
}
}
if (fuse_extra == 0 && ggml_can_fuse(cgraph, node_n, fuse_ops2, 2)) {
fuse_extra = 1;
fuse_out = cgraph->nodes[node_n + 1];
}
if (fuse_extra > 0) {
const struct ggml_tensor * add_node = cgraph->nodes[node_n + 1];
const struct ggml_tensor * bias =
add_node->src[0] == node ? add_node->src[1] : add_node->src[0];
// require ADD to broadcast a contiguous F32 [1,1,OC,1] row over
// the conv output (the conv must be the full-shape operand)
if (add_node->src[0] == node && add_node->type == GGML_TYPE_F32 &&
bias->type == GGML_TYPE_F32 && ggml_is_contiguous(bias) &&
bias->ne[0] == 1 && bias->ne[1] == 1 &&
bias->ne[2] == node->ne[2] && bias->ne[3] == 1) {
fuse_bias = bias;
} else {
fuse_extra = 0;
fuse_act = -1;
fuse_out = NULL;
}
}
}

// Standalone ADD(per-channel bias) -> UNARY(relu|hardswish) pairs (e.g.
// after the explicit im2col+mul_mat conv lowering) fuse into one pass.
if (fuse_extra == 0 && node->op == GGML_OP_ADD) {
static const enum ggml_op fuse_au[2] = {GGML_OP_ADD, GGML_OP_UNARY};
if (ggml_can_fuse(cgraph, node_n, fuse_au, 2)) {
struct ggml_tensor * unary_node = cgraph->nodes[node_n + 1];
const enum ggml_unary_op uop = ggml_get_unary_op(unary_node);
const struct ggml_tensor * a0 = node->src[0];
const struct ggml_tensor * a1 = node->src[1];
if ((uop == GGML_UNARY_OP_RELU || uop == GGML_UNARY_OP_HARDSWISH) &&
node->type == GGML_TYPE_F32 && unary_node->type == GGML_TYPE_F32 &&
a0->type == GGML_TYPE_F32 && a1->type == GGML_TYPE_F32 &&
ggml_are_same_shape(node, a0) &&
ggml_is_contiguous(a0) && ggml_is_contiguous(a1) &&
ggml_is_contiguous(unary_node) &&
a1->ne[0] == 1 && a1->ne[1] == 1 &&
a1->ne[2] == a0->ne[2] && a1->ne[3] == 1) {
fuse_extra = 1;
fuse_act = (int32_t) uop;
fuse_out = unary_node;
ggml_compute_forward_add_unary_fused(&params, node, fuse_act, fuse_out);
node_n += fuse_extra;
}
}
} else if (fuse_extra > 0) {
ggml_compute_forward_conv_2d_fused(&params, node, fuse_bias, fuse_act, fuse_out);
node_n += fuse_extra;
}
if (fuse_extra == 0) {
ggml_compute_forward(&params, node);
}

if (state->ith == 0 && cplan->abort_callback &&
cplan->abort_callback(cplan->abort_callback_data)) {
Expand Down
Loading
Loading