forked from ggml-org/llama.cpp
-
Notifications
You must be signed in to change notification settings - Fork 85
metal: GDN rows-indexed state read + snapshot write-fold (ring decode path) #61
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
f904581
ggml: rows-indexed state read for the fused GDN op (ring decode path)
bri-prism 9fd3825
ggml: fold recurrent GDN snapshot writes on Metal
bri-prism af81813
metal gdn: always populate snapshot tail on write-fold, handle K==1 rows
bri-prism 0f44bf0
qwen35: gate GDN rows mode to Metal-only GPU device sets
bri-prism ebe0474
ggml: disable OpenMP for Emscripten/WASM builds
bri-prism File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) { | ||
|
|
@@ -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]); | ||
|
|
@@ -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( | ||
|
|
@@ -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: | ||
|
|
@@ -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) { | ||
| 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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
|
||
|
|
@@ -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; | ||
|
|
||
|
|
@@ -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) { | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.