diff --git a/conversion/__init__.py b/conversion/__init__.py index 0c6204b87..5bd957bd1 100644 --- a/conversion/__init__.py +++ b/conversion/__init__.py @@ -19,6 +19,7 @@ __all__ = [ TEXT_MODEL_MAP: dict[str, str] = { "NEOChatModel": "sensenova_u1", "AfmoeForCausalLM": "afmoe", + "RicursiveForCausalLM": "ricursive", "LagunaForCausalLM": "laguna", "FlintForCausalLM": "flint", "ApertusForCausalLM": "llama", diff --git a/conversion/ricursive.py b/conversion/ricursive.py new file mode 100644 index 000000000..e4ab321c0 --- /dev/null +++ b/conversion/ricursive.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from typing import Iterable, TYPE_CHECKING + +if TYPE_CHECKING: + from torch import Tensor + +from .base import ModelBase, TextModel, gguf + + +@ModelBase.register("RicursiveForCausalLM") +class RicursiveModel(TextModel): + model_arch = gguf.MODEL_ARCH.RICURSIVE + + def set_vocab(self): + tokens, toktypes, tokpre = self.get_vocab_base() + self.gguf_writer.add_tokenizer_model("gpt2") + self.gguf_writer.add_tokenizer_pre(tokpre) + self.gguf_writer.add_token_list(tokens) + self.gguf_writer.add_token_types(toktypes) + + special_vocab = gguf.SpecialVocab(self.dir_model, load_merges=True) + # tokenizer_config.json carries a plain ChatML stub; the vendor serves the tools-aware + # repo-root chat_template.jinja, which SpecialVocab would otherwise lose to the stub + if (template := self.dir_model / "chat_template.jinja").is_file(): + special_vocab.chat_template = template.read_text(encoding="utf-8") + special_vocab.add_to_gguf(self.gguf_writer) + + # tokenizer.json has no post_processor: the HF tokenizer never prepends <|endoftext|> + self.gguf_writer.add_add_bos_token(False) + + def set_gguf_parameters(self): + super().set_gguf_parameters() + hparams = self.hparams + + head_dim = hparams["head_dim"] # 128, while hidden_size / num_attention_heads is 64 + self.gguf_writer.add_key_length(head_dim) + self.gguf_writer.add_value_length(head_dim) + self.gguf_writer.add_rope_dimension_count(head_dim) + + # sliding_attention layers use RoPE, full_attention layers use no positional encoding + self.gguf_writer.add_sliding_window(hparams["sliding_window"]) + self.gguf_writer.add_sliding_window_pattern([t == "sliding_attention" for t in hparams["layer_types"]]) + + if (embed_scale := hparams.get("embed_scale")) is not None: + self.gguf_writer.add_embedding_scale(embed_scale) + if (clamp := hparams.get("ffn_clamp_threshold")) is not None: + self.gguf_writer.add_swiglu_clamp(clamp) + + assert hparams.get("use_gated_attention", True), "only gated-attention checkpoints are supported" + assert hparams.get("use_valres", False), "only value-residual checkpoints are supported" + + def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]: + if name.endswith("self_attn.value_res_lambda"): + assert bid is not None + yield (self.format_tensor_name(gguf.MODEL_TENSOR.ATTN_VALUE_RES, bid), data_torch) + return + if name.endswith("self_attn.g_proj.weight"): + assert bid is not None + yield (self.format_tensor_name(gguf.MODEL_TENSOR.ATTN_GATE, bid), data_torch) + return + + yield from super().modify_tensors(data_torch, name, bid) diff --git a/gguf-py/gguf/constants.py b/gguf-py/gguf/constants.py index 930714f30..bc18372eb 100644 --- a/gguf-py/gguf/constants.py +++ b/gguf-py/gguf/constants.py @@ -162,6 +162,7 @@ class Keys: 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" @@ -708,6 +709,7 @@ class MODEL_ARCH(IntEnum): DOTS3NOTE = auto() ARCEE = auto() AFMOE = auto() + RICURSIVE = auto() LAGUNA = auto() ERNIE4_5 = auto() ERNIE4_5_MOE = auto() @@ -807,6 +809,7 @@ class MODEL_TENSOR(IntEnum): ATTN_SINKS = auto() ATTN_SSMAX = auto() ATTN_GATE = auto() + ATTN_VALUE_RES = auto() # ricursive: per-layer value-residual mix logit ATTN_RESID_ALPHA = auto() ATTN_RESID_BETA = auto() FFN_GATE_INP = auto() @@ -1621,6 +1624,7 @@ MODEL_ARCH_NAMES: dict[MODEL_ARCH, str] = { MODEL_ARCH.DOTS3NOTE: "dots3note", MODEL_ARCH.ARCEE: "arcee", MODEL_ARCH.AFMOE: "afmoe", + MODEL_ARCH.RICURSIVE: "ricursive", MODEL_ARCH.LAGUNA: "laguna", MODEL_ARCH.ERNIE4_5: "ernie4_5", MODEL_ARCH.ERNIE4_5_MOE: "ernie4_5-moe", @@ -1717,6 +1721,7 @@ TENSOR_NAMES: dict[MODEL_TENSOR, str] = { MODEL_TENSOR.ATTN_SINKS: "blk.{bid}.attn_sinks", MODEL_TENSOR.ATTN_SSMAX: "blk.{bid}.attn_ssmax", MODEL_TENSOR.ATTN_GATE: "blk.{bid}.attn_gate", + MODEL_TENSOR.ATTN_VALUE_RES: "blk.{bid}.attn_value_res", MODEL_TENSOR.ATTN_RESID_ALPHA: "blk.{bid}.attn_resid_alpha", MODEL_TENSOR.ATTN_RESID_BETA: "blk.{bid}.attn_resid_beta", MODEL_TENSOR.ATTN_Q_NORM: "blk.{bid}.attn_q_norm", @@ -5505,6 +5510,26 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = { MODEL_TENSOR.FFN_DOWN, MODEL_TENSOR.FFN_UP, ], + MODEL_ARCH.RICURSIVE: [ + MODEL_TENSOR.TOKEN_EMBD, + MODEL_TENSOR.OUTPUT_NORM, + MODEL_TENSOR.OUTPUT, + MODEL_TENSOR.ATTN_NORM, + MODEL_TENSOR.ATTN_POST_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_VALUE_RES, + MODEL_TENSOR.FFN_PRE_NORM, + MODEL_TENSOR.FFN_POST_NORM, + MODEL_TENSOR.FFN_GATE, + MODEL_TENSOR.FFN_DOWN, + MODEL_TENSOR.FFN_UP, + ], MODEL_ARCH.AFMOE: [ MODEL_TENSOR.TOKEN_EMBD, MODEL_TENSOR.OUTPUT_NORM, diff --git a/gguf-py/gguf/gguf_writer.py b/gguf-py/gguf/gguf_writer.py index 0e7aa5012..a2e646c84 100644 --- a/gguf-py/gguf/gguf_writer.py +++ b/gguf-py/gguf/gguf_writer.py @@ -915,6 +915,9 @@ class GGUFWriter: def add_swiglu_clamp_shexp(self, values: Sequence[float]) -> None: self.add_array(Keys.LLM.SWIGLU_CLAMP_SHEXP.format(arch=self.arch), values) + def add_swiglu_clamp(self, value: float) -> None: + self.add_float32(Keys.LLM.SWIGLU_CLAMP.format(arch=self.arch), value) + def add_hidden_act(self, value: str) -> None: self.add_string(Keys.LLM.HIDDEN_ACT.format(arch=self.arch), value) diff --git a/src/llama-arch.cpp b/src/llama-arch.cpp index 92a13e985..3b53ec39a 100644 --- a/src/llama-arch.cpp +++ b/src/llama-arch.cpp @@ -125,6 +125,7 @@ static const std::map LLM_ARCH_NAMES = { { LLM_ARCH_DOTS3NOTE, "dots3note" }, { LLM_ARCH_ARCEE, "arcee" }, { LLM_ARCH_AFMOE, "afmoe" }, + { LLM_ARCH_RICURSIVE, "ricursive" }, { LLM_ARCH_LAGUNA, "laguna" }, { LLM_ARCH_ERNIE4_5, "ernie4_5" }, { LLM_ARCH_ERNIE4_5_MOE, "ernie4_5-moe" }, @@ -225,6 +226,7 @@ static const std::map 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" }, @@ -540,6 +542,7 @@ static const std::map LLM_TENSOR_NAMES = { { LLM_TENSOR_ATTN_Q_NORM, "blk.%d.attn_q_norm" }, { LLM_TENSOR_ATTN_K_NORM, "blk.%d.attn_k_norm" }, { LLM_TENSOR_ATTN_GATE, "blk.%d.attn_gate" }, + { LLM_TENSOR_ATTN_VALUE_RES, "blk.%d.attn_value_res" }, { LLM_TENSOR_ATTN_RESID_ALPHA, "blk.%d.attn_resid_alpha" }, { LLM_TENSOR_ATTN_RESID_BETA, "blk.%d.attn_resid_beta" }, { LLM_TENSOR_FFN_POST_NORM, "blk.%d.post_ffw_norm" }, @@ -934,6 +937,7 @@ static const std::map LLM_TENSOR_INFOS = { {LLM_TENSOR_ATTN_QKV, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}}, {LLM_TENSOR_ATTN_OUT, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}}, {LLM_TENSOR_ATTN_GATE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}}, + {LLM_TENSOR_ATTN_VALUE_RES, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}}, {LLM_TENSOR_ATTN_RESID_ALPHA, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}}, {LLM_TENSOR_ATTN_RESID_BETA, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL}}, {LLM_TENSOR_FFN_GATE, {LLM_TENSOR_LAYER_REPEATING, GGML_OP_MUL_MAT}}, diff --git a/src/llama-arch.h b/src/llama-arch.h index f811607b6..5a2d8272d 100644 --- a/src/llama-arch.h +++ b/src/llama-arch.h @@ -129,6 +129,7 @@ enum llm_arch { LLM_ARCH_DOTS3NOTE, LLM_ARCH_ARCEE, LLM_ARCH_AFMOE, + LLM_ARCH_RICURSIVE, LLM_ARCH_LAGUNA, LLM_ARCH_ERNIE4_5, LLM_ARCH_ERNIE4_5_MOE, @@ -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, @@ -540,6 +542,7 @@ enum llm_tensor { LLM_TENSOR_ATTN_SINKS, LLM_TENSOR_ATTN_SSMAX, LLM_TENSOR_ATTN_GATE, + LLM_TENSOR_ATTN_VALUE_RES, LLM_TENSOR_ATTN_RESID_ALPHA, LLM_TENSOR_ATTN_RESID_BETA, LLM_TENSOR_FFN_GATE_INP, diff --git a/src/llama-hparams.h b/src/llama-hparams.h index 95daf482d..4a9a2cf75 100644 --- a/src/llama-hparams.h +++ b/src/llama-hparams.h @@ -254,6 +254,7 @@ struct llama_hparams { // Additional scale factors (Granite/Granite MoE) float f_residual_scale = 0.0f; float f_embedding_scale = 0.0f; + float f_swiglu_clamp = 0.0f; // dense FFN: silu(min(gate, c)) * clamp(up, -c, c) float f_attention_scale = 0.0f; // grok-2 diff --git a/src/llama-model.cpp b/src/llama-model.cpp index 3c4e598f1..ed395b0b2 100644 --- a/src/llama-model.cpp +++ b/src/llama-model.cpp @@ -290,6 +290,8 @@ static llama_model * llama_model_mapping(llm_arch arch, const llama_model_params return new llama_model_arcee(params); case LLM_ARCH_AFMOE: return new llama_model_afmoe(params); + case LLM_ARCH_RICURSIVE: + return new llama_model_ricursive(params); case LLM_ARCH_LAGUNA: return new llama_model_laguna(params); case LLM_ARCH_ERNIE4_5: @@ -3753,6 +3755,7 @@ llama_rope_type llama_model_rope_type(const llama_model * model) { case LLM_ARCH_COGVLM: case LLM_ARCH_PANGU_EMBED: case LLM_ARCH_AFMOE: + case LLM_ARCH_RICURSIVE: case LLM_ARCH_LAGUNA: case LLM_ARCH_QWEN3NEXT: case LLM_ARCH_CLOVER_MOE: diff --git a/src/llama-model.h b/src/llama-model.h index 45d2b8666..2c3d6b15d 100644 --- a/src/llama-model.h +++ b/src/llama-model.h @@ -312,6 +312,7 @@ struct llama_layer { struct ggml_tensor * wv_enc = nullptr; struct ggml_tensor * wo_enc = nullptr; struct ggml_tensor * wqkv_gate = nullptr; + struct ggml_tensor * attn_value_res = nullptr; // ricursive: value-residual mix logit, [1] struct ggml_tensor * wqkv_gate_b = nullptr; // K2 Horizon MoVA struct ggml_tensor * attn_v_gate = nullptr; diff --git a/src/models/models.h b/src/models/models.h index dd3b7b1ed..449ac3c37 100644 --- a/src/models/models.h +++ b/src/models/models.h @@ -2238,6 +2238,19 @@ struct llama_model_afmoe : 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 build_arch_graph(const llm_graph_params & params) const override; +}; + + struct llama_model_beam : public llama_model_base { llama_model_beam(const struct llama_model_params & params) : llama_model_base(params) {} void load_arch_hparams(llama_model_loader & ml) override; diff --git a/src/models/ricursive.cpp b/src/models/ricursive.cpp new file mode 100644 index 000000000..d11255d8f --- /dev/null +++ b/src/models/ricursive.cpp @@ -0,0 +1,216 @@ +#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); + ml.get_key(LLM_KV_SWIGLU_CLAMP, hparams.f_swiglu_clamp, false); + + // 4 sliding (RoPE) : 1 full (NoPE), the window includes the current token + ml.get_key(LLM_KV_ATTENTION_SLIDING_WINDOW, hparams.n_swa); + hparams.swa_type = LLAMA_SWA_TYPE_STANDARD; + load_swa_pattern(ml, 5); + + 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 + 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 is NULL, init from the input tok embed + if (output == NULL) { + output = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab}, TENSOR_DUPLICATED); + } + + for (int i = 0; i < n_layer; ++i) { + auto & layer = layers[i]; + + // sandwich attention normalization + layer.attn_norm = create_tensor(tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}, 0); + layer.attn_post_norm = create_tensor(tn(LLM_TENSOR_ATTN_POST_NORM, "weight", i), {n_embd}, 0); + + create_tensor_qkv(layer, i, n_embd, n_embd_head_k * n_head, n_embd_k_gqa, n_embd_v_gqa, 0); + layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_embd_head_k * n_head, 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); + + // elementwise sigmoid gate on the attention output + layer.wqkv_gate = create_tensor(tn(LLM_TENSOR_ATTN_GATE, "weight", i), {n_embd, n_embd_head_k * n_head}, 0); + + // value residual: v_proj sees lerp(scaled token embeddings, attn_norm output, sigmoid(lambda)) + layer.attn_value_res = create_tensor(tn(LLM_TENSOR_ATTN_VALUE_RES, "weight", i), {1}, 0); + + // sandwich ffn normalization + layer.ffn_norm = create_tensor(tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}, 0); + layer.ffn_post_norm = create_tensor(tn(LLM_TENSOR_FFN_POST_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_down = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "weight", i), {n_ff, n_embd}, 0); + layer.ffn_up = create_tensor(tn(LLM_TENSOR_FFN_UP, "weight", i), {n_embd, n_ff}, 0); + } +} + +std::unique_ptr llama_model_ricursive::build_arch_graph(const llm_graph_params & params) const { + return std::make_unique(*this, params); +} + +llama_model_ricursive::graph::graph(const llama_model & model, const llm_graph_params & params) : llm_graph_context(params) { + const int64_t n_embd_head = hparams.n_embd_head_v(); + + GGML_ASSERT(n_embd_head == hparams.n_embd_head_k()); + GGML_ASSERT(n_embd_head == n_rot); + + ggml_tensor * cur; + ggml_tensor * inpL; + + // build_inp_embd applies hparams.f_embedding_scale (sqrt(n_embd) here) itself + inpL = build_inp_embd(model.tok_embd); + cb(inpL, "inp_scaled", -1); + + // the value-residual source for every layer, including layer 0 + ggml_tensor * inp_embd = inpL; + + // inp_pos - contains the positions + 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) { + res->t_layer_inp[il] = inpL; + + const auto & layer = model.layers[il]; + + const bool is_swa = hparams.is_swa(il); + + ggml_tensor * inpSA = inpL; + + cur = build_norm(inpL, layer.attn_norm, NULL, LLM_NORM_RMS, il); + cb(cur, "attn_norm", il); + + // self-attention + { + ggml_tensor * attn_inp = cur; + + ggml_tensor * Qcur = build_lora_mm(layer.wq, cur, layer.wq_s); + cb(Qcur, "Qcur", il); + ggml_tensor * Kcur = build_lora_mm(layer.wk, cur, layer.wk_s); + cb(Kcur, "Kcur", il); + + ggml_tensor * mix = ggml_sigmoid(ctx0, layer.attn_value_res); + ggml_tensor * vinp = ggml_add(ctx0, inp_embd, ggml_mul(ctx0, ggml_sub(ctx0, cur, inp_embd), mix)); + cb(vinp, "vres_inp", il); + ggml_tensor * Vcur = build_lora_mm(layer.wv, vinp, layer.wv_s); + cb(Vcur, "Vcur", il); + + 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); + + // full-attention layers carry no positional encoding + if (is_swa) { + 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_rope", il); + cb(Kcur, "Kcur_rope", il); + } + + cur = build_attn(inp_attn, + NULL, NULL, NULL, // wo is applied after gating + Qcur, Kcur, Vcur, nullptr, nullptr, nullptr, kq_scale, il); + cb(cur, "attn_out", il); + + // attention gating: attn_out * sigmoid(g_proj(attn_norm)) BEFORE o_proj + ggml_tensor * gate = build_lora_mm(layer.wqkv_gate, attn_inp); + gate = ggml_sigmoid(ctx0, gate); + cb(gate, "attn_gate_sig", il); + cur = ggml_mul(ctx0, cur, gate); + cb(cur, "attn_gated", il); + + cur = build_lora_mm(layer.wo, cur, layer.wo_s); + cb(cur, "attn_o_proj", il); + } + + cur = build_norm(cur, layer.attn_post_norm, NULL, LLM_NORM_RMS, il); + cb(cur, "attn_post_norm", il); + + 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); + } + + 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); + + // dense SwiGLU with pre-activation clamping + { + ggml_tensor * up = build_lora_mm(layer.ffn_up, cur, layer.ffn_up_s); + cb(up, "ffn_up", il); + ggml_tensor * gate = build_lora_mm(layer.ffn_gate, cur, layer.ffn_gate_s); + cb(gate, "ffn_gate", il); + + if (hparams.f_swiglu_clamp > 0.0f) { + cur = ggml_swiglu_clamp(ctx0, gate, up, hparams.f_swiglu_clamp); + } else { + cur = 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); + + // input for next layer + inpL = cur; + } + + cur = inpL; + + cur = build_norm(cur, model.output_norm, NULL, LLM_NORM_RMS, -1); + cb(cur, "result_norm", -1); + + res->t_embd = cur; + + // lm_head + cur = build_lora_mm(model.output, cur, model.output_s); + cb(cur, "result_output", -1); + res->t_logits = cur; +}