Skip to content
Merged
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
16 changes: 16 additions & 0 deletions ggml/include/ggml.h
Original file line number Diff line number Diff line change
Expand Up @@ -2557,6 +2557,22 @@ extern "C" {
struct ggml_tensor * beta,
struct ggml_tensor * state);

// rows-indexed state read: instead of a gathered/contiguous (D, K, n_seqs)
// scratch, the op reads each sequence's live state directly from `states`
// (2D cache view, D-wide rows) at row `rows[seq]` (I32, n_seqs entries).
// Removes the per-layer get_rows + slot-0 cpy from recurrent decode graphs.
// Output layout is identical to ggml_gated_delta_net with K = n_snap_slots.
GGML_API struct ggml_tensor * ggml_gated_delta_net_rows(
struct ggml_context * ctx,
struct ggml_tensor * q,
struct ggml_tensor * k,
struct ggml_tensor * v,
struct ggml_tensor * g,
struct ggml_tensor * beta,
struct ggml_tensor * states,
struct ggml_tensor * rows,
int n_snap_slots);

// custom operators

typedef void (*ggml_custom1_op_t)(struct ggml_tensor * dst , const struct ggml_tensor * a, int ith, int nth, void * userdata);
Expand Down
9 changes: 9 additions & 0 deletions ggml/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ if (GGML_SCHED_NO_REALLOC)
target_compile_definitions(ggml-base PUBLIC GGML_SCHED_NO_REALLOC)
endif()

if (GGML_OPENMP AND EMSCRIPTEN)
# Emscripten/WASM cannot emit the common symbols that libomp's reduction
# helpers generate (e.g. .gomp_critical_user_.reduction.var), so an OpenMP
# build of ggml-quants.c fails to link. WASM has no host threads to gain
# from OpenMP anyway -- disable it rather than fail the build.
message(STATUS "ggml: disabling OpenMP for Emscripten/WASM target")
set(GGML_OPENMP OFF)
endif()

if (GGML_OPENMP)
find_package(OpenMP)
if (OpenMP_FOUND)
Expand Down
22 changes: 15 additions & 7 deletions ggml/src/ggml-cpu/ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10559,11 +10559,16 @@ static void ggml_compute_forward_gated_delta_net_one_chunk(

const bool kda = (neg0 == S_v);

// state is 3D (S_v*S_v*H, K, n_seqs); K is the snapshot slot count.
const int64_t K = src_state->ne[1];
// K is the snapshot slot count (op_params, shared by both op variants).
const int64_t K = ggml_get_op_params_i32(dst, 0);
GGML_ASSERT(K >= 1);
// per-seq stride in floats (slot 0 of seq s lives at state + s * seq_stride)
const int64_t state_seq_stride = src_state->nb[2] / sizeof(float);
// rows mode (src[6] set): state is a 2D cache view (D, n_rows) and each
// sequence's live state is read at row rows[seq] -- no gathered scratch.
const ggml_tensor * src_rows = dst->src[6];
const int32_t * state_rows_idx = src_rows ? (const int32_t *) src_rows->data : nullptr;
// scratch mode: per-seq stride in floats (slot 0 of seq s at s * seq_stride)
const int64_t state_seq_stride = src_rows ? 0 : (int64_t) (src_state->nb[2] / sizeof(float));
const int64_t state_row_size = src_rows ? (int64_t) (src_state->nb[1] / sizeof(float)) : 0;

const int64_t per_thread = S_v + (K > 1 ? S_v * S_v : 0);
const int ith = params->ith;
Expand Down Expand Up @@ -10608,9 +10613,12 @@ static void ggml_compute_forward_gated_delta_net_one_chunk(
? state_work
: state_out_base + (iv3 * H + iv1) * S_v * S_v;

// copy input state into the working buffer and operate in-place
// state layout (D, K, n_seqs): slot 0 of seq iv3 starts at iv3 * state_seq_stride.
const float * s_in = state_in_base + iv3 * state_seq_stride + iv1 * S_v * S_v;
// copy input state into the working buffer and operate in-place.
// scratch mode: state layout (D, K, n_seqs), slot 0 of seq iv3 at
// iv3 * state_seq_stride. rows mode: cache row state_rows_idx[iv3].
const float * s_in = state_rows_idx
? state_in_base + (int64_t) state_rows_idx[iv3] * state_row_size + iv1 * S_v * S_v
: state_in_base + iv3 * state_seq_stride + iv1 * S_v * S_v;
memcpy(s_out, s_in, S_v * S_v * sizeof(float));

// attn output pointer for first token of this (head, seq)
Expand Down
5 changes: 5 additions & 0 deletions ggml/src/ggml-cuda/ggml-cuda.cu
Original file line number Diff line number Diff line change
Expand Up @@ -5417,6 +5417,11 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g
case GGML_OP_RWKV_WKV7:
return true;
case GGML_OP_GATED_DELTA_NET:
// rows-indexed state read (src[6]) not implemented on CUDA yet;
// reject so it falls back instead of silently reading src[5] as a scratch
if (op->src[6] != NULL) {
return false;
}
//TODO: enable once MUSA compiler is solved https://github.com/ggml-org/llama.cpp/pull/19504#issuecomment-4018634327
#ifdef GGML_USE_MUSA
return false;
Expand Down
3 changes: 2 additions & 1 deletion ggml/src/ggml-hexagon/ggml-hexagon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3710,7 +3710,8 @@ static bool ggml_backend_hexagon_device_supports_op(ggml_backend_dev_t dev, cons
break;

case GGML_OP_GATED_DELTA_NET:
supp = ggml_hexagon_supported_gated_delta_net(sess, op);
// rows-indexed state read (src[6]) not implemented here
supp = op->src[6] == NULL && ggml_hexagon_supported_gated_delta_net(sess, op);
break;

case GGML_OP_CUMSUM:
Expand Down
13 changes: 9 additions & 4 deletions ggml/src/ggml-metal/ggml-metal-device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -583,16 +583,19 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_rwkv(ggml_metal_
return res;
}

ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_gated_delta_net(ggml_metal_library_t lib, const ggml_tensor * op) {
ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_gated_delta_net(ggml_metal_library_t lib, const ggml_tensor * op, bool write_rows) {
char base[256];
char name[256];

// v is src[2], dimensions: S_v = ne[0], H = ne[1]
const int ne20 = op->src[2]->ne[0]; // S_v
const int ne21 = op->src[2]->ne[1]; // H
const int ne30 = op->src[3]->ne[0]; // G
// state is src[5], 3D (S_v*S_v*H, K, n_seqs); K is the snapshot slot count.
const int K = op->src[5]->ne[1];
// K (snapshot slot count) comes from op_params: in rows mode src[5] is the
// 2D cache view, so its ne[1] is the cache row count, not K.
const int K = ggml_get_op_params_i32(op, 0);
// rows mode: src[6] holds per-seq cache row indices for the state read
const bool has_rows = op->src[6] != NULL;

const int nsg = op->src[2]->ne[0]/32;

Expand All @@ -601,7 +604,7 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_gated_delta_net(
GGML_ASSERT(ne20 % 32 == 0);

snprintf(base, 256, "kernel_gated_delta_net_%s_%d", ggml_type_name(op->src[0]->type), nsg);
snprintf(name, 256, "%s_ne20=%d_ne30=%d_K=%d", base, ne20, ne30, K);
snprintf(name, 256, "%s_ne20=%d_ne30=%d_K=%d_rows=%d_write_rows=%d", base, ne20, ne30, K, has_rows ? 1 : 0, write_rows ? 1 : 0);

ggml_metal_pipeline_with_params res = ggml_metal_library_get_pipeline(lib, name);
if (!res.pipeline) {
Expand All @@ -610,6 +613,8 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_gated_delta_net(
ggml_metal_cv_set_int16(cv, ne20, FC_GATED_DELTA_NET + 0);
ggml_metal_cv_set_int16(cv, ne30, FC_GATED_DELTA_NET + 1);
ggml_metal_cv_set_int16(cv, K, FC_GATED_DELTA_NET + 2);
ggml_metal_cv_set_bool (cv, has_rows, FC_GATED_DELTA_NET + 3);
ggml_metal_cv_set_bool (cv, write_rows, FC_GATED_DELTA_NET_WRITE_ROWS);

res = ggml_metal_library_compile_pipeline(lib, base, name, cv);

Expand Down
2 changes: 1 addition & 1 deletion ggml/src/ggml-metal/ggml-metal-device.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_ssm_conv
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_ssm_conv_batched (ggml_metal_library_t lib, const struct ggml_tensor * op, int ssm_conv_bs);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_ssm_scan (ggml_metal_library_t lib, const struct ggml_tensor * op);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_rwkv (ggml_metal_library_t lib, const struct ggml_tensor * op);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_gated_delta_net (ggml_metal_library_t lib, const struct ggml_tensor * op);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_gated_delta_net (ggml_metal_library_t lib, const struct ggml_tensor * op, bool write_rows);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_solve_tri (ggml_metal_library_t lib, const struct ggml_tensor * op);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mv_ext (ggml_metal_library_t lib, const struct ggml_tensor * op, int nsg, int nxpsg, int r1ptg);
struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm (ggml_metal_library_t lib, const struct ggml_tensor * op);
Expand Down
1 change: 1 addition & 0 deletions ggml/src/ggml-metal/ggml-metal-impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
#define FC_SUM_ROWS 1400
#define FC_UPSCALE 1500
#define FC_GATED_DELTA_NET 1600
#define FC_GATED_DELTA_NET_WRITE_ROWS (FC_GATED_DELTA_NET + 4)

// op-specific constants
#define OP_FLASH_ATTN_EXT_NQPSG 8
Expand Down
92 changes: 90 additions & 2 deletions ggml/src/ggml-metal/ggml-metal-ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <algorithm>
#include <limits>
#include <cmath>
#include <unordered_set>

static ggml_metal_buffer_id ggml_metal_get_buffer_id(const ggml_tensor * t) {
if (!t) {
Expand Down Expand Up @@ -73,6 +74,14 @@ struct ggml_metal_op {
return idxs.size();
}

bool is_fused_set_rows(const ggml_tensor * node) const {
return fused_set_rows.find(node) != fused_set_rows.end();
}

void mark_fused_set_rows(const ggml_tensor * node) {
fused_set_rows.insert(node);
}

ggml_tensor * node(int i) const {
assert(i >= 0 && i < (int) idxs.size());
return ggml_graph_node(gf, idxs[i]);
Expand Down Expand Up @@ -109,6 +118,7 @@ struct ggml_metal_op {

// non-empty node indices
std::vector<int> idxs;
std::unordered_set<const ggml_tensor *> fused_set_rows;
};

ggml_metal_op_t ggml_metal_op_init(
Expand Down Expand Up @@ -182,6 +192,13 @@ static int ggml_metal_op_encode_impl(ggml_metal_op_t ctx, int idx) {
return 1;
}

// A rows scatter may be consumed by the preceding fused GDN epilogue.
// Keep the graph node for dependency construction, but do not encode a
// second copy/scatter kernel.
if (node->op == GGML_OP_SET_ROWS && ctx->is_fused_set_rows(node)) {
return 1;
}

switch (node->op) {
case GGML_OP_NONE:
case GGML_OP_RESHAPE:
Expand Down Expand Up @@ -1591,6 +1608,55 @@ int ggml_metal_op_rwkv(ggml_metal_op_t ctx, int idx) {
return 1;
}

// The rows-mode GDN op produces attention output plus a trailing snapshot
// region. In the recurrent ring graph that region is viewed and later
// scattered back into the state cache by SET_ROWS. Keep the graph nodes (and
// therefore the dependency) but let the GDN epilogue perform that scatter so
// the 786K-element SET_ROWS dispatch disappears from the Metal command stream.
static int ggml_metal_gdn_write_rows(
ggml_metal_op_t ctx,
int idx,
ggml_tensor ** write_rows,
ggml_tensor ** state_dst,
ggml_tensor ** fused_set_rows) {
*write_rows = nullptr;
*state_dst = nullptr;
*fused_set_rows = nullptr;

const ggml_tensor * gdn = ctx->node(idx);
if (gdn->op != GGML_OP_GATED_DELTA_NET || gdn->src[6] == nullptr ||
getenv("GGML_GDN_WRITE_FOLD_DISABLE") != nullptr) {
Comment on lines +1626 to +1628

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in fa6b0b1: ggml_metal_gdn_write_rows early-returns when !ctx->use_fusion, so GGML_METAL_FUSION_DISABLE suppresses the scan/mark/compile like the other Metal fusions.

return 1;
}

for (int j = idx + 1; j < ctx->n_nodes(); ++j) {
ggml_tensor * set_rows = ctx->node(j);
if (set_rows->op != GGML_OP_SET_ROWS || set_rows->src[0] == nullptr) {
continue;
}

// SET_ROWS receives a view into the GDN result. Follow the view chain
// because attention normalization and cache maintenance nodes may be
// ordered between the producer and this scatter in the graph.
const ggml_tensor * src = set_rows->src[0];
while (src != nullptr && (src->op == GGML_OP_VIEW || src->op == GGML_OP_RESHAPE)) {
src = src->src[0];
}
if (src != gdn || set_rows->src[1] == nullptr || set_rows->src[2] == nullptr ||
set_rows->src[1]->type != GGML_TYPE_I64 || set_rows->src[2]->type != GGML_TYPE_F32 ||
set_rows->src[2]->buffer == nullptr || set_rows->src[2]->data == nullptr) {
Comment on lines +1641 to +1647

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in fa6b0b1: the fold now verifies the SET_ROWS target is exactly the snapshot tail -- ggml_nelements(view)==D*n_write, index count==n_write, dest row width==D -- before suppressing it, so a mis-sized view (e.g. an attention-output slice) is no longer fused.

continue;
}

*write_rows = set_rows->src[1];
*state_dst = set_rows->src[2];
*fused_set_rows = set_rows;
return 1;
}

return 1;
}

int ggml_metal_op_gated_delta_net(ggml_metal_op_t ctx, int idx) {
ggml_tensor * op = ctx->node(idx);

Expand All @@ -1607,7 +1673,22 @@ int ggml_metal_op_gated_delta_net(ggml_metal_op_t ctx, int idx) {
GGML_TENSOR_LOCALS( int32_t, ne, op, ne);
GGML_TENSOR_LOCALS(uint64_t, nb, op, nb);

auto pipeline = ggml_metal_library_get_pipeline_gated_delta_net(lib, op);
ggml_tensor * write_rows = nullptr;
ggml_tensor * state_dst = nullptr;
ggml_tensor * fused_set_rows = nullptr;
const int n_fuse = ggml_metal_gdn_write_rows(ctx, idx, &write_rows, &state_dst, &fused_set_rows);
const bool has_write_rows = write_rows != nullptr;

if (has_write_rows) {
ctx->mark_fused_set_rows(fused_set_rows);
// The future SET_ROWS is an explicit write dependency. Register its
// destination now and force a barrier before the in-kernel write so
// earlier cache maintenance cannot overlap it.
ggml_metal_op_concurrency_reset(ctx);
ggml_metal_op_concurrency_add(ctx, fused_set_rows);
}

auto pipeline = ggml_metal_library_get_pipeline_gated_delta_net(lib, op, has_write_rows);

int ida = 0;

Expand Down Expand Up @@ -1657,13 +1738,20 @@ int ggml_metal_op_gated_delta_net(ggml_metal_op_t ctx, int idx) {
ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(op->src[3]), ida++); // gate
ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(op->src[4]), ida++); // beta
ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(op->src[5]), ida++); // state
// rows (rows mode; bind state as a never-read placeholder otherwise --
// the function constant compiles the rows path out entirely)
ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(op->src[6] ? op->src[6] : op->src[5]), ida++);
// write rows and destination are only consumed by the fused ring path;
// bind valid placeholders for the ordinary/scratch variants.
ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(has_write_rows ? write_rows : (op->src[6] ? op->src[6] : op->src[5])), ida++);
ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(has_write_rows ? state_dst : op->src[5]), ida++);
ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(op), ida++); // dst

const int nsg = pipeline.nsg;

ggml_metal_encoder_dispatch_threadgroups(enc, op->src[2]->ne[0]/nsg, op->src[2]->ne[1], op->src[2]->ne[3], 32, nsg, 1);

return 1;
return n_fuse;
}

int ggml_metal_op_solve_tri(ggml_metal_op_t ctx, int idx) {
Expand Down
Loading
Loading