Commit 5356982 for stable-diffusion.cpp

commit 5356982584d5b46a8f5cb0842927aff6443fe174
Author: leejet <leejet714@gmail.com>
Date:   Sun Nov 1 22:08:29 2026 +0800

    fix: chunk MiniMax-H3 MLP to respect backend buffer limits (#2257)

diff --git a/src/model/diffusion/minimax_h3.hpp b/src/model/diffusion/minimax_h3.hpp
index 3dd720c..0915b52 100644
--- a/src/model/diffusion/minimax_h3.hpp
+++ b/src/model/diffusion/minimax_h3.hpp
@@ -140,8 +140,11 @@ namespace MiniMaxH3 {
     };

     struct MLP : public UnaryBlock {
+        int64_t ffn_hidden_size;
+
         MLP(int64_t hidden_size,
-            int64_t ffn_hidden_size) {
+            int64_t ffn_hidden_size)
+            : ffn_hidden_size(ffn_hidden_size) {
             blocks["fc1"] = std::make_shared<Linear>(hidden_size, ffn_hidden_size * 2, false, false, true, 1.f / 128.f);
             blocks["fc2"] = std::make_shared<Linear>(ffn_hidden_size, hidden_size, false, false, true, 1.f / 128.f);
         }
@@ -159,11 +162,38 @@ namespace MiniMaxH3 {
         }

         ggml_tensor* forward(GGMLRunnerContext* ctx, ggml_tensor* x) override {
-            auto fc1       = std::dynamic_pointer_cast<Linear>(blocks["fc1"]);
-            auto fc2       = std::dynamic_pointer_cast<Linear>(blocks["fc2"]);
-            auto projected = fc1->forward(ctx, x);
-            auto activated = ggml_swiglu(ctx->ggml_ctx, projected);
-            return fc2->forward(ctx, activated);
+            auto fc1           = std::dynamic_pointer_cast<Linear>(blocks["fc1"]);
+            auto fc2           = std::dynamic_pointer_cast<Linear>(blocks["fc2"]);
+            auto forward_chunk = [&](ggml_tensor* input) {
+                auto projected = fc1->forward(ctx, input);
+                auto activated = ggml_swiglu(ctx->ggml_ctx, projected);
+                return fc2->forward(ctx, activated);
+            };
+
+            int64_t chunk_size = x->ne[1];
+            if (ctx->backend != nullptr) {
+                auto buft        = ggml_backend_get_default_buffer_type(ctx->backend);
+                size_t alignment = ggml_backend_buft_get_alignment(buft);
+                size_t max_size  = ggml_backend_buft_get_max_size(buft);
+                max_size -= max_size % alignment;
+                // Matmul outputs are F32 even for quantized weights. Split before fc1
+                // so its expanded activation never needs an oversized backend buffer.
+                size_t token_bytes = ggml_row_size(GGML_TYPE_F32, std::max(x->ne[0], 2 * ffn_hidden_size)) * x->ne[2] * x->ne[3];
+                chunk_size         = static_cast<int64_t>(std::min<size_t>(chunk_size, std::max<size_t>(1, max_size / token_bytes)));
+            }
+            if (chunk_size == x->ne[1]) {
+                return forward_chunk(x);
+            }
+
+            std::vector<ggml_tensor*> chunks;
+            for (int64_t start = 0; start < x->ne[1]; start += chunk_size) {
+                auto input = ggml_ext_slice(ctx->ggml_ctx, x, 1, start, std::min(start + chunk_size, x->ne[1]), false);
+                if (!ggml_is_contiguous(input)) {
+                    input = ggml_cont(ctx->ggml_ctx, input);
+                }
+                chunks.push_back(forward_chunk(input));
+            }
+            return ggml_ext_vec_concat(ctx->ggml_ctx, chunks, 1);
         }
     };