ggml-org/llama.cpp · #31692

model : add Ripple (ricursiveai)

notforu777 · merged Nov 5, 202615 files · 406 + / 3 −
conversion/__init__.py1 + / 0 −
@@ -20,6 +20,7 @@     "NEOChatModel": "sensenova_u1",     "AfmoeForCausalLM": "afmoe",     "LagunaForCausalLM": "laguna",+    "RicursiveForCausalLM": "ricursive",     "FlintForCausalLM": "flint",     "ApertusForCausalLM": "llama",     "ArceeForCausalLM": "llama",
conversion/ricursive.pyadded92 + / 0 −
@@ -0,0 +1,92 @@+from __future__ import annotations++import math++from typing import Iterable, TYPE_CHECKING++if TYPE_CHECKING:+    from torch import Tensor++from .base import ModelBase, TextModel, gguf, logger+++@ModelBase.register("RicursiveForCausalLM")+@ModelBase.example("ricursiveAI/Ripple-65B-Preview")+class RicursiveModel(TextModel):+    model_arch = gguf.MODEL_ARCH.RICURSIVE++    def __init__(self, *args, **kwargs):+        super().__init__(*args, **kwargs)+        hparams = self.hparams++        if (act := hparams.get("hidden_act", "silu")) != "silu":+            raise ValueError(f"Ricursive: unsupported hidden_act {act!r}")++        # RicursiveConfig derives the layer pattern from layer_types alone, by default full attention iff i % 5 == 4+        if (layer_types := hparams.get("layer_types")) is None:+            layer_types = ["full_attention" if i % 5 == 4 else "sliding_attention" for i in range(self.block_count)]+        if len(layer_types) != self.block_count:+            raise ValueError(f"Ricursive: layer_types has {len(layer_types)} entries, expected {self.block_count}")+        if unknown := set(layer_types) - {"sliding_attention", "full_attention"}:+            raise ValueError(f"Ricursive: unsupported layer_types {sorted(unknown)}")+        self.is_swa = [t == "sliding_attention" for t in layer_types]+        # only sliding layers use RoPE, an all-full-attention model would have no positional encoding+        if not any(self.is_swa):+            raise ValueError("Ricursive: layer_types has no sliding_attention layer, all-full-attention models are not supported")+        # RicursiveConfig pops a stored hybrid_layer_pattern and recomputes it from layer_types+        if (pattern := hparams.get("hybrid_layer_pattern")) is not None and list(pattern) != [int(swa) for swa in self.is_swa]:+            logger.warning("Ricursive: hybrid_layer_pattern does not match layer_types, ignoring it")++        if not hparams.get("gate_full_precision", True):+            logger.warning("Ricursive: gate_full_precision is false, llama.cpp computes the attention gate in f32")++        # RicursiveConfig defaults; the reference ignores the tensors of a disabled feature+        self.use_valres = hparams.get("use_valres", False)+        self.use_gate = hparams.get("use_gated_attention", True)+        names = list(self.model_tensors)+        if bias := [name for name in names if name.endswith("bias")]:+            raise ValueError(f"Ricursive: bias tensors are not supported, found {bias[0]!r}")+        for enabled, suffix in ((self.use_valres, ".self_attn.value_res_lambda"), (self.use_gate, ".self_attn.g_proj.weight")):+            if enabled and sum(name.endswith(suffix) for name in names) != self.block_count:+                raise ValueError(f"Ricursive: expected one {suffix!r} tensor per layer")++    def set_vocab(self):+        self._set_vocab_gpt2()+        # eos_token_id is [<|im_end|>, <|endoftext|>]: register the turn end as EOT so generation stops on it+        eos = self.hparams.get("eos_token_id")+        if isinstance(eos, list):+            extra = [e for e in eos if e != self.hparams.get("bos_token_id")]+            if extra:+                self.gguf_writer.add_eot_token_id(extra[0])+                logger.info(f"gguf: eot_token_id = {extra[0]}")++    def set_gguf_parameters(self):+        super().set_gguf_parameters()+        hparams = self.hparams+        self.gguf_writer.add_vocab_size(hparams["vocab_size"])+        self.gguf_writer.add_rope_dimension_count(hparams["head_dim"])++        self.gguf_writer.add_sliding_window(hparams["sliding_window"])+        self.gguf_writer.add_sliding_window_pattern(self.is_swa)+        # sliding layers use RoPE, full attention layers use no positional encoding+        self.gguf_writer.add_rope_pattern(self.is_swa)++        if (embed_scale := hparams.get("embed_scale")) is not None:+            if embed_scale == 0:+                raise ValueError(f"Ricursive: unsupported embed_scale {embed_scale!r}")+            self.gguf_writer.add_embedding_scale(float(embed_scale))+        # absent and 0 both mean no clamp (the reference tests `if clamp_threshold:`)+        if (clamp := hparams.get("ffn_clamp_threshold")) is not None:+            if clamp < 0 or not math.isfinite(clamp):+                raise ValueError(f"Ricursive: unsupported ffn_clamp_threshold {clamp!r}")+            self.gguf_writer.add_swiglu_clamp([float(clamp)] * self.block_count)++    def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:+        # value_res_lambda has no .weight suffix in the checkpoint+        if name.endswith(".value_res_lambda"):+            if not self.use_valres:+                return+            name += ".weight"+        elif name.endswith(".g_proj.weight") and not self.use_gate:+            return+        yield from super().modify_tensors(data_torch, name, bid)
gguf-py/gguf/constants.py25 + / 0 −
@@ -162,6 +162,7 @@ class LLM:         EMBD_LENGTH_PER_LAYER_INP         = "{arch}.embedding_length_per_layer_input"         SWIGLU_CLAMP_EXP                  = "{arch}.swiglu_clamp_exp"         SWIGLU_CLAMP_SHEXP                = "{arch}.swiglu_clamp_shexp"+        SWIGLU_CLAMP                      = "{arch}.swiglu_clamp"         HIDDEN_ACT                        = "{arch}.hidden_activation"         DENSE_FEAT_IN_SIZE                = "{arch}.{dense}_feat_in"         DENSE_FEAT_OUT_SIZE               = "{arch}.{dense}_feat_out"@@ -709,6 +710,7 @@ class MODEL_ARCH(IntEnum):     ARCEE            = auto()     AFMOE            = auto()     LAGUNA           = auto()+    RICURSIVE        = auto()     ERNIE4_5         = auto()     ERNIE4_5_MOE     = auto()     HUNYUAN_MOE      = auto()@@ -877,6 +879,7 @@ class MODEL_TENSOR(IntEnum):     ATTN_K_NORM          = auto()     LAYER_OUT_NORM       = auto()     LAYER_OUT_SCALE      = auto()+    ATTN_VRES_LAMBDA     = auto()     PER_LAYER_TOKEN_EMBD = auto() # gemma3n     PER_LAYER_MODEL_PROJ = auto() # gemma3n     PER_LAYER_INP_GATE   = auto() # gemma3n@@ -1622,6 +1625,7 @@ class MODEL_TENSOR(IntEnum):     MODEL_ARCH.ARCEE:            "arcee",     MODEL_ARCH.AFMOE:            "afmoe",     MODEL_ARCH.LAGUNA:           "laguna",+    MODEL_ARCH.RICURSIVE:        "ricursive",     MODEL_ARCH.ERNIE4_5:         "ernie4_5",     MODEL_ARCH.ERNIE4_5_MOE:     "ernie4_5-moe",     MODEL_ARCH.FALCON_H1:        "falcon-h1",@@ -1789,6 +1793,7 @@ class MODEL_TENSOR(IntEnum):     MODEL_TENSOR.FFN_POST_EXP_NORM:         "blk.{bid}.ffn_post_exp_norm",     MODEL_TENSOR.LAYER_OUT_NORM:            "blk.{bid}.layer_output_norm",     MODEL_TENSOR.LAYER_OUT_SCALE:           "blk.{bid}.layer_output_scale",+    MODEL_TENSOR.ATTN_VRES_LAMBDA:          "blk.{bid}.attn_vres_lambda",     MODEL_TENSOR.PER_LAYER_TOKEN_EMBD:      "per_layer_token_embd",           # gemma3n     MODEL_TENSOR.PER_LAYER_MODEL_PROJ:      "per_layer_model_proj",           # gemma3n     MODEL_TENSOR.PER_LAYER_PROJ_NORM:       "per_layer_proj_norm",            # gemma3n@@ -5533,6 +5538,26 @@ class MODEL_TENSOR(IntEnum):         MODEL_TENSOR.FFN_POST_NORM,         MODEL_TENSOR.FFN_EXP_PROBS_B,     ],+    MODEL_ARCH.RICURSIVE: [+        MODEL_TENSOR.TOKEN_EMBD,+        MODEL_TENSOR.OUTPUT_NORM,+        MODEL_TENSOR.OUTPUT,+        MODEL_TENSOR.ATTN_NORM,+        MODEL_TENSOR.ATTN_Q,+        MODEL_TENSOR.ATTN_K,+        MODEL_TENSOR.ATTN_V,+        MODEL_TENSOR.ATTN_OUT,+        MODEL_TENSOR.ATTN_Q_NORM,+        MODEL_TENSOR.ATTN_K_NORM,+        MODEL_TENSOR.ATTN_GATE,+        MODEL_TENSOR.ATTN_VRES_LAMBDA,+        MODEL_TENSOR.ATTN_POST_NORM,+        MODEL_TENSOR.FFN_PRE_NORM,+        MODEL_TENSOR.FFN_GATE,+        MODEL_TENSOR.FFN_DOWN,+        MODEL_TENSOR.FFN_UP,+        MODEL_TENSOR.FFN_POST_NORM,+    ],     MODEL_ARCH.LAGUNA: [         MODEL_TENSOR.TOKEN_EMBD,         MODEL_TENSOR.OUTPUT_NORM,
gguf-py/gguf/gguf_writer.py3 + / 0 −
@@ -912,6 +912,9 @@ def add_expert_gating_func(self, value: ExpertGatingFuncType) -> None:     def add_swiglu_clamp_exp(self, values: Sequence[float]) -> None:         self.add_array(Keys.LLM.SWIGLU_CLAMP_EXP.format(arch=self.arch), values) +    def add_swiglu_clamp(self, values: Sequence[float]) -> None:+        self.add_array(Keys.LLM.SWIGLU_CLAMP.format(arch=self.arch), values)+     def add_swiglu_clamp_shexp(self, values: Sequence[float]) -> None:         self.add_array(Keys.LLM.SWIGLU_CLAMP_SHEXP.format(arch=self.arch), values) 
gguf-py/gguf/tensor_mapping.py4 + / 0 −
@@ -451,6 +451,10 @@ class TensorNameMap:             "model.layers.{bid}.self_attn.attn_gate_proj",  # k2-horizon         ), +        MODEL_TENSOR.ATTN_VRES_LAMBDA: (+            "model.layers.{bid}.self_attn.value_res_lambda",  # ricursive+        ),+         # Feed-forward norm         MODEL_TENSOR.FFN_NORM: (             "layers.{bid}.moe_connection.norm",                              # metis16
src/llama-arch.cpp4 + / 0 −
@@ -176,6 +176,7 @@ static const std::map<llm_arch, const char *> LLM_ARCH_NAMES = {     { LLM_ARCH_K2_HORIZON,       "k2-horizon"       },     { LLM_ARCH_BEAM,             "beam"             },     { LLM_ARCH_KOLIBRI1,         "kolibri1"         },+    { LLM_ARCH_RICURSIVE,        "ricursive"        },     { LLM_ARCH_UNKNOWN,          "(unknown)"        }, }; @@ -225,6 +226,7 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {     { LLM_KV_EXPERT_CHUNK_FEED_FORWARD_LENGTH,  "%s.expert_chunk_feed_forward_length"  },     { LLM_KV_SWIGLU_CLAMP_EXP,                  "%s.swiglu_clamp_exp"                  },     { LLM_KV_SWIGLU_CLAMP_SHEXP,                "%s.swiglu_clamp_shexp"                },+    { LLM_KV_SWIGLU_CLAMP,                      "%s.swiglu_clamp"                      },     { LLM_KV_USE_PARALLEL_RESIDUAL,             "%s.use_parallel_residual"             },     { LLM_KV_TENSOR_DATA_LAYOUT,                "%s.tensor_data_layout"                },     { LLM_KV_EXPERT_COUNT,                      "%s.expert_count"                      },@@ -593,6 +595,7 @@ static const std::map<llm_tensor, const char *> LLM_TENSOR_NAMES = {     { LLM_TENSOR_ATTN_QKV,                               "blk.%d.attn_qkv" },     { LLM_TENSOR_LAYER_OUT_NORM,                         "blk.%d.layer_output_norm" },     { LLM_TENSOR_LAYER_OUT_SCALE,                        "blk.%d.layer_output_scale" },+    { LLM_TENSOR_ATTN_VRES_LAMBDA,                       "blk.%d.attn_vres_lambda" },     { LLM_TENSOR_ATTN_OUT_NORM,                          "blk.%d.attn_output_norm" },     { LLM_TENSOR_POS_EMBD,                               "position_embd" },     { LLM_TENSOR_FFN_ACT,                                "blk.%d.ffn.act" },@@ -1098,6 +1101,7 @@ static const std::map<llm_tensor, llm_tensor_info> LLM_TENSOR_INFOS = {     {LLM_TENSOR_ATTN_K_NORM,                {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},     {LLM_TENSOR_LAYER_OUT_NORM,             {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},     {LLM_TENSOR_LAYER_OUT_SCALE,            {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},+    {LLM_TENSOR_ATTN_VRES_LAMBDA,           {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},     {LLM_TENSOR_ATTN_Q_A_NORM,              {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},     {LLM_TENSOR_ATTN_KV_A_NORM,             {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},     {LLM_TENSOR_ATTN_SUB_NORM,              {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}},
src/llama-arch.h3 + / 0 −
@@ -180,6 +180,7 @@ enum llm_arch {     LLM_ARCH_BEAM,     LLM_ARCH_KOLIBRI1,     LLM_ARCH_LIMITE,+    LLM_ARCH_RICURSIVE,     LLM_ARCH_UNKNOWN, }; @@ -229,6 +230,7 @@ enum llm_kv {     LLM_KV_EXPERT_CHUNK_FEED_FORWARD_LENGTH,     LLM_KV_SWIGLU_CLAMP_EXP,     LLM_KV_SWIGLU_CLAMP_SHEXP,+    LLM_KV_SWIGLU_CLAMP,     LLM_KV_USE_PARALLEL_RESIDUAL,     LLM_KV_TENSOR_DATA_LAYOUT,     LLM_KV_EXPERT_COUNT,@@ -611,6 +613,7 @@ enum llm_tensor {     LLM_TENSOR_ATTN_K_NORM,     LLM_TENSOR_LAYER_OUT_NORM,     LLM_TENSOR_LAYER_OUT_SCALE,+    LLM_TENSOR_ATTN_VRES_LAMBDA,     LLM_TENSOR_POST_ATTN_NORM,     LLM_TENSOR_POST_MLP_NORM,     LLM_TENSOR_PER_LAYER_TOKEN_EMBD, // gemma3n
src/llama-hparams.h1 + / 0 −
@@ -431,6 +431,7 @@ struct llama_hparams {     // Step35: optional per-layer clamps for (Swi)GLU     std::array<float, LLAMA_MAX_LAYERS> swiglu_clamp_exp; // clamping for expert FFN     std::array<float, LLAMA_MAX_LAYERS> swiglu_clamp_shexp; // shared expert+    std::array<float, LLAMA_MAX_LAYERS> swiglu_clamp;       // dense FFN      // this value n_pattern means that every nth layer is dense (i.e. non-SWA)     // dense_first means whether the pattern is start with a dense layer
src/llama-model-saver.cpp2 + / 0 −
@@ -210,6 +210,8 @@ void llama_model_saver::add_kv_from_model() {     add_kv(LLM_KV_MOE_LATENT_SIZE,                   hparams.moe_latent_size);     add_kv(LLM_KV_EXPERT_SHARED_FEED_FORWARD_LENGTH, hparams.n_ff_shexp);     add_kv(LLM_KV_EXPERT_CHUNK_FEED_FORWARD_LENGTH,  hparams.n_ff_chexp);+    add_kv(LLM_KV_SWIGLU_CLAMP, std::vector<float>(+            hparams.swiglu_clamp.begin(), hparams.swiglu_clamp.begin() + hparams.n_layer_all));     add_kv(LLM_KV_SWIGLU_CLAMP_EXP, std::vector<float>(             hparams.swiglu_clamp_exp.begin(), hparams.swiglu_clamp_exp.begin() + hparams.n_layer_all));     add_kv(LLM_KV_SWIGLU_CLAMP_SHEXP, std::vector<float>(
src/llama-model.cpp4 + / 0 −
@@ -292,6 +292,8 @@ static llama_model * llama_model_mapping(llm_arch arch, const llama_model_params             return new llama_model_afmoe(params);         case LLM_ARCH_LAGUNA:             return new llama_model_laguna(params);+        case LLM_ARCH_RICURSIVE:+            return new llama_model_ricursive(params);         case LLM_ARCH_ERNIE4_5:             return new llama_model_ernie4_5(params);         case LLM_ARCH_ERNIE4_5_MOE:@@ -1546,6 +1548,7 @@ void llama_model_base::load_hparams(llama_model_loader & ml) {      std::fill(hparams.swiglu_clamp_exp.begin(),   hparams.swiglu_clamp_exp.end(),   0.0f);     std::fill(hparams.swiglu_clamp_shexp.begin(), hparams.swiglu_clamp_shexp.end(), 0.0f);+    std::fill(hparams.swiglu_clamp.begin(),       hparams.swiglu_clamp.end(),       0.0f);      ml.get_key_or_arr(LLM_KV_FEED_FORWARD_LENGTH,  hparams.n_ff_arr,   hparams.n_layer_all, false);     ml.get_key_or_arr(LLM_KV_ATTENTION_HEAD_COUNT, hparams.n_head_arr, hparams.n_layer_all, false);@@ -3706,6 +3709,7 @@ llama_rope_type llama_model_rope_type(const llama_model * model) {         case LLM_ARCH_QWEN3MOE:         case LLM_ARCH_LLADA_MOE:         case LLM_ARCH_RND1:+        case LLM_ARCH_RICURSIVE:         case LLM_ARCH_OLMO2:         case LLM_ARCH_OLMOE:         case LLM_ARCH_PHI2:
src/llama-model.h3 + / 0 −
@@ -377,6 +377,9 @@ struct llama_layer {     struct ggml_tensor * ffn_resid_alpha  = nullptr;     struct ggml_tensor * ffn_resid_beta   = nullptr; +    // value residual mix+    struct ggml_tensor * attn_vres_lambda = nullptr;+     // ff shared expert (shexp)     struct ggml_tensor * ffn_gate_inp_shexp = nullptr;     struct ggml_tensor * ffn_gate_shexp     = nullptr;
src/models/models.h12 + / 0 −
@@ -2251,6 +2251,18 @@ struct llama_model_beam : public llama_model_base { };  +struct llama_model_ricursive : public llama_model_base {+    llama_model_ricursive(const struct llama_model_params & params) : llama_model_base(params) {}+    void load_arch_hparams(llama_model_loader & ml) override;+    void load_arch_tensors(llama_model_loader & ml) override;++    struct graph : public llm_graph_context {+        graph(const llama_model & model, const llm_graph_params & params);+    };++    std::unique_ptr<llm_graph_context> build_arch_graph(const llm_graph_params & params) const override;+};+ struct llama_model_laguna : public llama_model_base {     llama_model_laguna(const struct llama_model_params & params) : llama_model_base(params) {}     void load_arch_hparams(llama_model_loader & ml) override;
src/models/ricursive.cppadded236 + / 0 −
@@ -0,0 +1,236 @@+#include "models.h"++void llama_model_ricursive::load_arch_hparams(llama_model_loader & ml) {+    ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps);+    ml.get_key(LLM_KV_EMBEDDING_SCALE,             hparams.f_embedding_scale, false);+    // optional: absent or 0 means no clamp (the reference tests `if clamp_threshold:`); a scalar applies to every layer+    ml.get_key_or_arr(LLM_KV_SWIGLU_CLAMP, hparams.swiglu_clamp, hparams.n_layer_all, false);+    for (uint32_t il = 0; il < hparams.n_layer_all; ++il) {+        if (!std::isfinite(hparams.swiglu_clamp[il]) || hparams.swiglu_clamp[il] < 0.0f) {+            throw std::runtime_error(format("ricursive: swiglu_clamp for layer %u must be finite and non-negative, got %g", il, hparams.swiglu_clamp[il]));+        }+    }++    if (hparams.n_embd_head_k() != hparams.n_embd_head_v()) {+        throw std::runtime_error("ricursive: key_length and value_length must be equal");+    }++    // RoPE is only applied on the SWA layers, so a model without SWA would have no positional encoding+    ml.get_key(LLM_KV_ATTENTION_SLIDING_WINDOW, hparams.n_swa);+    if (hparams.n_swa == 0) {+        throw std::runtime_error("ricursive: sliding_window must be > 0");+    }+    hparams.swa_type = LLAMA_SWA_TYPE_STANDARD;+    if (ml.get_arr(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, hparams.is_swa_impl, false)) {+        // load_swa_pattern only bounds the array by LLAMA_MAX_LAYERS: a short one would leave the last layers dense and without RoPE+        uint32_t n_entries = 0;+        ml.get_arr_n(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, n_entries);+        if (n_entries != hparams.n_layer()) {+            throw std::runtime_error(format("ricursive: sliding_window_pattern has %u entries, expected %u", n_entries, hparams.n_layer()));+        }+    } else {+        // default: every 5th layer is global (RicursiveConfig layer_types: full attention iff i % 5 == 4)+        load_swa_pattern(ml, 5);+    }+    if (!hparams.is_swa_any()) {+        throw std::runtime_error("ricursive: no sliding window layer, the model would have no positional encoding");+    }++    // RoPE exactly on the SWA layers; rope_pattern is optional because the first published GGUFs do not carry it+    hparams.rope_pattern = hparams.is_swa_impl;+    ml.get_key_or_arr(LLM_KV_ATTENTION_ROPE_PATTERN, hparams.rope_pattern, hparams.n_layer(), false);+    for (uint32_t il = 0; il < hparams.n_layer(); ++il) {+        if (hparams.has_rope(il) != hparams.is_swa(il)) {+            throw std::runtime_error(format("ricursive: layer %u has %s but %s", il,+                    hparams.has_rope(il) ? "RoPE" : "no RoPE", hparams.is_swa(il) ? "a sliding window" : "no sliding window"));+        }+    }++    // the SWA layers are the only RoPE layers, they use the model RoPE parameters+    hparams.rope_freq_base_train_swa  = hparams.rope_freq_base_train;+    hparams.rope_freq_scale_train_swa = hparams.rope_freq_scale_train;+    ml.get_key(LLM_KV_ROPE_FREQ_BASE_SWA, hparams.rope_freq_base_train_swa, false);++    switch (hparams.n_layer()) {+        case 80: type = LLM_TYPE_65B; break;+        default: type = LLM_TYPE_UNKNOWN;+    }+}++void llama_model_ricursive::load_arch_tensors(llama_model_loader &) {+    LLAMA_LOAD_LOCALS;++    tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, 0);++    output_norm = create_tensor(tn(LLM_TENSOR_OUTPUT_NORM, "weight"), {n_embd}, 0);+    output      = create_tensor(tn(LLM_TENSOR_OUTPUT,      "weight"), {n_embd, n_vocab}, TENSOR_NOT_REQUIRED);+    if (output == NULL) {+        output = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, TENSOR_DUPLICATED);+    }++    const int64_t n_embd_q = n_embd_head_k * n_head;++    for (int i = 0; i < n_layer; ++i) {+        auto & layer = layers[i];++        layer.attn_norm = create_tensor(tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}, 0);++        layer.wq = create_tensor(tn(LLM_TENSOR_ATTN_Q,   "weight", i), {n_embd, n_embd_q},     0);+        layer.wk = create_tensor(tn(LLM_TENSOR_ATTN_K,   "weight", i), {n_embd, n_embd_k_gqa}, 0);+        layer.wv = create_tensor(tn(LLM_TENSOR_ATTN_V,   "weight", i), {n_embd, n_embd_v_gqa}, 0);+        layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd_q, n_embd},     0);++        layer.attn_q_norm = create_tensor(tn(LLM_TENSOR_ATTN_Q_NORM, "weight", i), {n_embd_head_k}, 0);+        layer.attn_k_norm = create_tensor(tn(LLM_TENSOR_ATTN_K_NORM, "weight", i), {n_embd_head_k}, 0);++        layer.wqkv_gate        = create_tensor(tn(LLM_TENSOR_ATTN_GATE,        "weight", i), {n_embd, n_embd_q}, TENSOR_NOT_REQUIRED);+        layer.attn_vres_lambda = create_tensor(tn(LLM_TENSOR_ATTN_VRES_LAMBDA, "weight", i), {1},                TENSOR_NOT_REQUIRED);++        layer.attn_post_norm = create_tensor(tn(LLM_TENSOR_ATTN_POST_NORM, "weight", i), {n_embd}, 0);++        layer.ffn_norm      = create_tensor(tn(LLM_TENSOR_FFN_NORM,      "weight", i), {n_embd},       0);+        layer.ffn_gate      = create_tensor(tn(LLM_TENSOR_FFN_GATE,      "weight", i), {n_embd, n_ff}, 0);+        layer.ffn_up        = create_tensor(tn(LLM_TENSOR_FFN_UP,        "weight", i), {n_embd, n_ff}, 0);+        layer.ffn_down      = create_tensor(tn(LLM_TENSOR_FFN_DOWN,      "weight", i), {n_ff, n_embd}, 0);+        layer.ffn_post_norm = create_tensor(tn(LLM_TENSOR_FFN_POST_NORM, "weight", i), {n_embd},       0);+    }++    // the gate and the value residual are per-model switches (use_gated_attention, use_valres)+    for (int i = 1; i < n_layer; ++i) {+        if ((layers[i].wqkv_gate == nullptr) != (layers[0].wqkv_gate == nullptr)) {+            throw std::runtime_error(format("ricursive: attn_gate must be present in all layers or in none (layers 0 and %d differ)", i));+        }+        if ((layers[i].attn_vres_lambda == nullptr) != (layers[0].attn_vres_lambda == nullptr)) {+            throw std::runtime_error(format("ricursive: attn_vres_lambda must be present in all layers or in none (layers 0 and %d differ)", i));+        }+    }+}++std::unique_ptr<llm_graph_context> llama_model_ricursive::build_arch_graph(const llm_graph_params & params) const {+    return std::make_unique<graph>(params);+}++llama_model_ricursive::graph::graph(const llm_graph_params & params) : llm_graph_context(params) {+    const int64_t n_embd_head = hparams.n_embd_head_v();++    ggml_tensor * cur;+    ggml_tensor * inpL;++    inpL = build_inp_embd(model.tok_embd);++    // value residual: x0 = token embeddings * embedding_scale, the same tensor for all layers (modeling_ricursive.py:380-381,405)+    ggml_tensor * inp_vres = inpL;++    ggml_tensor * inp_pos = build_inp_pos();++    auto * inp_attn = build_attn_inp_kv_iswa();++    ggml_tensor * inp_out_ids = build_inp_out_ids();++    const float kq_scale = 1.0f / sqrtf(float(n_embd_head));++    for (int il = 0; il < n_layer; ++il) {+        const auto & layer = model.layers[il];++        ggml_tensor * inpSA = inpL;++        cur = build_norm(inpL, layer.attn_norm, NULL, LLM_NORM_RMS, il);+        cb(cur, "attn_norm", il);++        ggml_tensor * attn_in = cur;++        {+            ggml_tensor * Qcur = build_lora_mm(layer.wq, attn_in, layer.wq_s);+            ggml_tensor * Kcur = build_lora_mm(layer.wk, attn_in, layer.wk_s);++            // v_in = lerp(x0, attn_norm(x), sigmoid(lambda)), lambda is learned per layer (modeling_ricursive.py:182-185)+            ggml_tensor * v_in = attn_in;+            if (layer.attn_vres_lambda) {+                ggml_tensor * mix = ggml_sigmoid(ctx0, layer.attn_vres_lambda);+                v_in = ggml_add(ctx0, inp_vres, ggml_mul(ctx0, ggml_sub(ctx0, attn_in, inp_vres), mix));+                cb(v_in, "vres_in", il);+            }+            ggml_tensor * Vcur = build_lora_mm(layer.wv, v_in, layer.wv_s);++            Qcur = ggml_reshape_3d(ctx0, Qcur, n_embd_head, n_head,    n_tokens);+            Kcur = ggml_reshape_3d(ctx0, Kcur, n_embd_head, n_head_kv, n_tokens);+            Vcur = ggml_reshape_3d(ctx0, Vcur, n_embd_head, n_head_kv, n_tokens);++            Qcur = build_norm(Qcur, layer.attn_q_norm, NULL, LLM_NORM_RMS, il);+            Kcur = build_norm(Kcur, layer.attn_k_norm, NULL, LLM_NORM_RMS, il);+            cb(Qcur, "Qcur_normed", il);+            cb(Kcur, "Kcur_normed", il);++            // global layers use no RoPE (NoPE): the reference rotates only sliding_attention layers (modeling_ricursive.py:127-129,203-205)+            if (hparams.has_rope(il)) {+                const float freq_base_l  = model.get_rope_freq_base (cparams, il);+                const float freq_scale_l = model.get_rope_freq_scale(cparams, il);++                Qcur = ggml_rope_ext(ctx0, Qcur, inp_pos, nullptr,+                        n_rot, rope_type, n_ctx_orig, freq_base_l, freq_scale_l,+                        ext_factor, attn_factor, beta_fast, beta_slow);+                Kcur = ggml_rope_ext(ctx0, Kcur, inp_pos, nullptr,+                        n_rot, rope_type, n_ctx_orig, freq_base_l, freq_scale_l,+                        ext_factor, attn_factor, beta_fast, beta_slow);+            }+            cb(Qcur, "Qcur", il);+            cb(Kcur, "Kcur", il);+            cb(Vcur, "Vcur", il);++            cur = build_attn(inp_attn, NULL, NULL, NULL, Qcur, Kcur, Vcur, nullptr, nullptr, nullptr, kq_scale, il);+            cb(cur, "attn_out", il);++            if (layer.wqkv_gate) {+                ggml_tensor * gate = build_lora_mm(layer.wqkv_gate, attn_in, layer.wqkv_gate_s);+                gate = ggml_sigmoid(ctx0, gate);+                cb(gate, "attn_gate", il);+                cur = ggml_mul(ctx0, cur, gate);+                cb(cur, "attn_gated", il);+            }++            cur = build_lora_mm(layer.wo, cur, layer.wo_s);+        }++        if (il == n_layer - 1 && inp_out_ids) {+            cur   = ggml_get_rows(ctx0,   cur, inp_out_ids);+            inpSA = ggml_get_rows(ctx0, inpSA, inp_out_ids);+        }++        cur = build_norm(cur, layer.attn_post_norm, NULL, LLM_NORM_RMS, il);+        cb(cur, "attn_post_norm", il);++        ggml_tensor * ffn_inp = ggml_add(ctx0, cur, inpSA);+        cb(ffn_inp, "ffn_inp", il);++        cur = build_norm(ffn_inp, layer.ffn_norm, NULL, LLM_NORM_RMS, il);+        cb(cur, "ffn_norm", il);++        {+            ggml_tensor * gate = build_lora_mm(layer.ffn_gate, cur, layer.ffn_gate_s);+            ggml_tensor * up   = build_lora_mm(layer.ffn_up,   cur, layer.ffn_up_s);+            cur = hparams.swiglu_clamp[il] > 0.0f+                ? ggml_swiglu_clamp(ctx0, gate, up, hparams.swiglu_clamp[il])+                : ggml_swiglu_split(ctx0, gate, up);+            cb(cur, "ffn_swiglu", il);+            cur = build_lora_mm(layer.ffn_down, cur, layer.ffn_down_s);+            cb(cur, "ffn_out", il);+        }++        cur = build_norm(cur, layer.ffn_post_norm, NULL, LLM_NORM_RMS, il);+        cb(cur, "ffn_post_norm", il);++        cur = ggml_add(ctx0, cur, ffn_inp);+        cur = build_cvec(cur, il);+        cb(cur, "l_out", il);++        inpL = cur;+    }++    cur = build_norm(inpL, model.output_norm, NULL, LLM_NORM_RMS, -1);+    cb(cur, "result_norm", -1);+    res->t_embd = cur;++    cur = build_lora_mm(model.output, cur, model.output_s);+    cb(cur, "result_output", -1);+    res->t_logits = cur;+}
tests/fusion/MTL.csv2 + / 0 −
@@ -291,6 +291,8 @@ qwen4exp            ,0   ,any     ,SOFT_MAX+ARGSORT+GET_ROWS+SUM_ROWS+CLAMP+DIV, qwen4exp            ,0   ,any     ,SSM_CONV+UNARY              ,      1 refact              ,0   ,any     ,RMS_NORM+MUL                ,      5 refact              ,0   ,any     ,RMS_NORM+MUL                ,      5+ricursive           ,0   ,any     ,RMS_NORM+MUL                ,      9+ricursive           ,0   ,any     ,RMS_NORM+MUL+ADD            ,      4 rnd1                ,0   ,any     ,MUL+ADD                     ,      4 rnd1                ,0   ,any     ,RMS_NORM+MUL                ,      9 rnd1                ,0   ,any     ,SOFT_MAX+ARGSORT+GET_ROWS+SUM_ROWS+CLAMP+DIV,      2
tests/test-llama-archs.cpp14 + / 3 −
@@ -267,7 +267,7 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {         n_layer = 6;     } else if (arch == LLM_ARCH_STEP35 || arch == LLM_ARCH_LAGUNA) {         n_embd = 160; // exercise per-head tensor split granularity with head size 80-    } else if (arch == LLM_ARCH_QWEN3 || arch == LLM_ARCH_MUSE_GLIMMER || arch == LLM_ARCH_AFMOE) {+    } else if (arch == LLM_ARCH_QWEN3 || arch == LLM_ARCH_MUSE_GLIMMER || arch == LLM_ARCH_AFMOE || arch == LLM_ARCH_RICURSIVE) {         n_head = 4;     } else if (arch == LLM_ARCH_LIMITE) {         n_head  = 4;@@ -310,7 +310,7 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {     if (arch == LLM_ARCH_QWEN3) {         n_head_kv = 1; // MQA coverage     } else if (arch == LLM_ARCH_MUSE_GLIMMER || arch == LLM_ARCH_MUSE_SPARK || arch == LLM_ARCH_AFMOE || arch == LLM_ARCH_MAION_CODER ||-            arch == LLM_ARCH_KOLIBRI1 || arch == LLM_ARCH_LIMITE || arch == LLM_ARCH_GEMMA4) {+            arch == LLM_ARCH_KOLIBRI1 || arch == LLM_ARCH_LIMITE || arch == LLM_ARCH_GEMMA4 || arch == LLM_ARCH_RICURSIVE) {         n_head_kv = 2; // GQA coverage     }     if (arch == LLM_ARCH_BEAM) {@@ -457,7 +457,7 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {         ms.add_kv(LLM_KV_ATTENTION_SLIDING_WINDOW_PATTERN, pattern);     } else if (arch == LLM_ARCH_COHERE2MOE || arch == LLM_ARCH_MIMO2 || arch == LLM_ARCH_STEP35 || arch == LLM_ARCH_SPARK2_5 ||             arch == LLM_ARCH_MUSE_GLIMMER || arch == LLM_ARCH_MUSE_SPARK || arch == LLM_ARCH_GRANITE_SWA || arch == LLM_ARCH_DOTS3NOTE ||-            arch == LLM_ARCH_KOLIBRI1 || arch == LLM_ARCH_MAPLE || arch == LLM_ARCH_FLINT || arch == LLM_ARCH_BEAM) {+            arch == LLM_ARCH_KOLIBRI1 || arch == LLM_ARCH_MAPLE || arch == LLM_ARCH_FLINT || arch == LLM_ARCH_BEAM || arch == LLM_ARCH_RICURSIVE) {         std::vector<uint32_t> pattern;         pattern.reserve(n_layer);         for (uint32_t il = 0; il < n_layer; il++) {@@ -627,6 +627,17 @@ static gguf_context_ptr get_gguf_ctx(const llm_arch arch, const bool moe) {         ms.add_kv(LLM_KV_SWIGLU_CLAMP_EXP, 7.0f);     } +    if (arch == LLM_ARCH_RICURSIVE) {+        std::vector<float> swiglu_clamp;+        swiglu_clamp.reserve(n_layer);+        for (uint32_t il = 0; il < n_layer; il++) {+            // the random weight scales keep the FFN activations small, a small limit still clamps so the roundtrip checks the per-layer values+            swiglu_clamp.push_back(il % 3 == 1 ? 0.0f : 1e-3f);+        }+        ms.add_kv(LLM_KV_EMBEDDING_SCALE, 16.0f);+        ms.add_kv(LLM_KV_SWIGLU_CLAMP,    swiglu_clamp);+    }+     if (arch == LLM_ARCH_METIS16) {         ms.add_kv(LLM_KV_METIS_PASS_COUNT,               uint32_t(2));         ms.add_kv(LLM_KV_HYPER_CONNECTION_COUNT,         uint32_t(4));