ggml-org/llama.cpp · #31692
model : add Ripple (ricursiveai)
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", # metis16src/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, // gemma3nsrc/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 layersrc/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, 2tests/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));