CoolFace
Modelpublic

Felipe97/llama-cpp-compiled

sourceHugging Faceupdated 2d agoView on Hugging Face
0likes1.1kdownloads
qwen4exp.cpp1298 linesDownload Raw Back to models
1#include "models.h"2#include "llama-impl.h"3#include "llama-memory-hybrid-idx.h"4#include "llama-memory-recurrent.h"5 6#include <algorithm>7#include <cinttypes>8 9// bad metadata must be catchable: GGML_ASSERT aborts the whole process10static void qwen4exp_require_nonzero(const llama_model_loader & ml, llm_kv kid, uint32_t value) {11    if (value == 0) {12        throw std::runtime_error(format("%s must be greater than zero, got %u", ml.llm_kv(kid).c_str(), value));13    }14}15 16// get_arr() copies a short array as-is, leaving a zero tail the n-gram hash silently drops17static void qwen4exp_require_arr_len(llama_model_loader & ml, llm_kv kid, uint32_t n_min) {18    uint32_t n_arr = 0;19    ml.get_arr_n(kid, n_arr, true);20    if (n_arr < n_min) {21        throw std::runtime_error(format("%s has %u entries, but at least %u are required",22                                        ml.llm_kv(kid).c_str(), n_arr, n_min));23    }24}25 26void llama_model_qwen4exp::load_arch_hparams(llama_model_loader & ml) {27    ml.get_key_or_arr(LLM_KV_EXPERT_FEED_FORWARD_LENGTH, hparams.n_ff_exp_arr, hparams.n_layer_all, false);28    ml.get_key(LLM_KV_EXPERT_SHARED_FEED_FORWARD_LENGTH, hparams.n_ff_shexp, false);29    ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS,       hparams.f_norm_rms_eps);30 31    ml.get_key_or_arr(LLM_KV_ROPE_DIMENSION_SECTIONS,    hparams.rope_sections, 4, true);32 33    ml.get_key(LLM_KV_SSM_CONV_KERNEL,    hparams.ssm_d_conv);34    ml.get_key(LLM_KV_SSM_INNER_SIZE,     hparams.ssm_d_inner);35    ml.get_key(LLM_KV_SSM_STATE_SIZE,     hparams.ssm_d_state);36    ml.get_key(LLM_KV_SSM_TIME_STEP_RANK, hparams.ssm_dt_rank);37    ml.get_key(LLM_KV_SSM_GROUP_COUNT,    hparams.ssm_n_group);38    qwen4exp_require_nonzero(ml, LLM_KV_SSM_CONV_KERNEL,    hparams.ssm_d_conv);39    qwen4exp_require_nonzero(ml, LLM_KV_SSM_INNER_SIZE,     hparams.ssm_d_inner);40    qwen4exp_require_nonzero(ml, LLM_KV_SSM_STATE_SIZE,     hparams.ssm_d_state);41    qwen4exp_require_nonzero(ml, LLM_KV_SSM_TIME_STEP_RANK, hparams.ssm_dt_rank);42    qwen4exp_require_nonzero(ml, LLM_KV_SSM_GROUP_COUNT,    hparams.ssm_n_group);43 44    // HC; low_rank is qwen4exp-specific, DeepSeek-V4 leaves it absent (full rank)45    ml.get_key(LLM_KV_HYPER_CONNECTION_COUNT,    hparams.dsv4_hc_mult);46    ml.get_key(LLM_KV_HYPER_CONNECTION_LOW_RANK, hparams.hc_low_rank);47    // a count of 1 has nothing to mix: transformers configuration_qwen4_exp.py:196, vLLM48    // config.py:49 and SGLang configs/qwen4_exp.py:38 all raise on hc_count <= 149    if (hparams.dsv4_hc_mult <= 1) {50        throw std::runtime_error(format("%s must be greater than one, got %u",51                                        ml.llm_kv(LLM_KV_HYPER_CONNECTION_COUNT).c_str(), hparams.dsv4_hc_mult));52    }53    qwen4exp_require_nonzero(ml, LLM_KV_HYPER_CONNECTION_LOW_RANK, hparams.hc_low_rank);54    hparams.n_embd_out_impl = hparams.dsv4_hc_mult * hparams.n_embd;55 56    ml.get_key(LLM_KV_ATTENTION_INDEXER_HEAD_COUNT, hparams.indexer_n_head);57    ml.get_key(LLM_KV_ATTENTION_INDEXER_KEY_LENGTH, hparams.indexer_head_size);58    ml.get_key(LLM_KV_ATTENTION_INDEXER_TOP_K,      hparams.indexer_top_k);59    qwen4exp_require_nonzero(ml, LLM_KV_ATTENTION_INDEXER_HEAD_COUNT, hparams.indexer_n_head);60    qwen4exp_require_nonzero(ml, LLM_KV_ATTENTION_INDEXER_KEY_LENGTH, hparams.indexer_head_size);61    qwen4exp_require_nonzero(ml, LLM_KV_ATTENTION_INDEXER_TOP_K,      hparams.indexer_top_k);62    ml.get_key_or_arr(LLM_KV_ATTENTION_COMPRESS_RATIOS, hparams.dsv4_compress_ratios, hparams.n_layer_all, false);63 64    // PLE n-gram hash embeddings; if the key group is absent every field stays zero65    hparams.is_ple_impl.reset();66    hparams.ple_n_heads = 0;67 68    uint32_t n_ple = 0;69    ml.get_arr_n(LLM_KV_PLE_LAYERS, n_ple, false);70    if (n_ple > 0) {71        std::vector<uint32_t> ple_layers;72        ml.get_arr(LLM_KV_PLE_LAYERS, ple_layers);73        if (n_ple != 1) {74            // hparams holds one set of hash constants, so several PLE modules cannot be represented75            throw std::runtime_error(format("%s lists %u layers, but only one PLE layer is supported",76                                            ml.llm_kv(LLM_KV_PLE_LAYERS).c_str(), n_ple));77        }78        for (uint32_t il : ple_layers) {79            if (il >= hparams.n_layer_all) {80                throw std::runtime_error(format("PLE layer %u is out of range", il));81            }82            hparams.is_ple_impl.set(il);83        }84 85        ml.get_key(LLM_KV_PLE_NGRAM_SIZE,      hparams.ple_ngram_size);86        ml.get_key(LLM_KV_PLE_HEADS_PER_NGRAM, hparams.ple_heads_per_ngram);87        ml.get_key(LLM_KV_PLE_CONV_KERNEL,     hparams.ple_conv_kernel);88        ml.get_key(LLM_KV_PLE_EOS_TOKEN_ID,    hparams.ple_eos_token_id);89        // optional: files written before this key fall back to the EOS token90        ml.get_key(LLM_KV_PLE_IMAGE_TOKEN_ID,  hparams.ple_image_token_id, false);91        ml.get_key(LLM_KV_EMBEDDING_LENGTH_PER_LAYER, hparams.n_embd_per_layer);92        qwen4exp_require_nonzero(ml, LLM_KV_PLE_CONV_KERNEL,             hparams.ple_conv_kernel);93        qwen4exp_require_nonzero(ml, LLM_KV_EMBEDDING_LENGTH_PER_LAYER,  hparams.n_embd_per_layer);94 95        hparams.ple_n_heads  = (hparams.ple_ngram_size - 1) * hparams.ple_heads_per_ngram;96        hparams.ple_head_dim = hparams.n_embd_per_layer;97        if (hparams.ple_ngram_size < 2 || hparams.ple_ngram_size > LLAMA_MAX_PLE_NGRAM) {98            throw std::runtime_error(format("PLE n-gram size %u is out of range", hparams.ple_ngram_size));99        }100        if (hparams.ple_n_heads == 0 || hparams.ple_n_heads > LLAMA_MAX_PLE_HEADS) {101            throw std::runtime_error(format("PLE head count %u is out of range", hparams.ple_n_heads));102        }103 104        qwen4exp_require_arr_len(ml, LLM_KV_PLE_LAYER_MULTIPLIERS, hparams.ple_ngram_size);105        qwen4exp_require_arr_len(ml, LLM_KV_PLE_HEAD_OFFSETS,      hparams.ple_n_heads);106        qwen4exp_require_arr_len(ml, LLM_KV_PLE_HEAD_VOCAB_SIZES,  hparams.ple_n_heads);107 108        ml.get_arr(LLM_KV_PLE_LAYER_MULTIPLIERS, hparams.ple_layer_multipliers);109 110        // the file stores the head ranges as uint64, so read at that width and narrow to the int32 the gather uses111        std::array<uint64_t, LLAMA_MAX_PLE_HEADS> head_offsets     = {};112        std::array<uint64_t, LLAMA_MAX_PLE_HEADS> head_vocab_sizes = {};113        ml.get_arr(LLM_KV_PLE_HEAD_OFFSETS,     head_offsets);114        ml.get_arr(LLM_KV_PLE_HEAD_VOCAB_SIZES, head_vocab_sizes);115        for (uint32_t h = 0; h < hparams.ple_n_heads; ++h) {116            if (head_vocab_sizes[h] == 0 ||117                head_offsets[h]     > INT32_MAX ||118                head_vocab_sizes[h] > INT32_MAX ||119                head_offsets[h] + head_vocab_sizes[h] > INT32_MAX) {120                throw std::runtime_error(format("PLE head %u range does not fit the int32 row index", h));121            }122            hparams.ple_head_offsets[h]     = (uint32_t) head_offsets[h];123            hparams.ple_head_vocab_sizes[h] = (uint32_t) head_vocab_sizes[h];124        }125    }126 127    // linear attention everywhere except every full_attention_interval-th layer128    if (!ml.get_key_or_arr(LLM_KV_ATTENTION_RECURRENT_LAYERS, hparams.is_recr_impl, hparams.n_layer_all, false)) {129        uint32_t full_attn_interval = 4;130        ml.get_key(LLM_KV_FULL_ATTENTION_INTERVAL, full_attn_interval, false);131        qwen4exp_require_nonzero(ml, LLM_KV_FULL_ATTENTION_INTERVAL, full_attn_interval);132        for (uint32_t i = 0; i < hparams.n_layer_all; ++i) {133            hparams.is_recr_impl[i] = (i < hparams.n_layer()) && ((i + 1) % full_attn_interval != 0);134        }135    }136 137    // the PLE conv history is a row of the recurrent cache, which linear layers alone have138    for (uint32_t i = 0; i < hparams.n_layer_all; ++i) {139        if (hparams.is_ple(i) && !hparams.is_recr(i)) {140            throw std::runtime_error(format("PLE layer %u is not a linear attention layer", i));141        }142    }143 144    switch (hparams.n_layer()) {145        case 48: type = LLM_TYPE_A3B; break;146        default: type = LLM_TYPE_UNKNOWN;147    }148}149 150void llama_model_qwen4exp::load_arch_tensors(llama_model_loader & ml) {151    LLAMA_LOAD_LOCALS;152 153    const int64_t hc     = hparams.dsv4_hc_mult;154    const int64_t hc_dim = hc * n_embd;155    const int64_t hc_lr  = hparams.hc_low_rank;156 157    tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), { n_embd, n_vocab }, 0);158 159    // there is no output_norm: the final hyper-connection mixer carries it160    // the gammas load as [n_embd, hc] so the grouped norm multiplies them without a graph reshape161    hc_head_norm = create_tensor(tn(LLM_TENSOR_HC_HEAD_NORM, "weight"), { n_embd, hc }, TENSOR_ALLOW_RESHAPE);162    hc_head_down = create_tensor(tn(LLM_TENSOR_HC_HEAD_DOWN, "weight"), { hc_dim, hc_lr }, 0);163    hc_head_up   = create_tensor(tn(LLM_TENSOR_HC_HEAD_UP,   "weight"), { hc_lr, hc_dim }, 0);164 165    output = create_tensor(tn(LLM_TENSOR_OUTPUT, "weight"), { n_embd, n_vocab }, TENSOR_NOT_REQUIRED);166    if (output == NULL) {167        output = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), { n_embd, n_vocab }, TENSOR_DUPLICATED);168    }169 170    // flat [ple_head_dim, n_rows] gather target171    if (hparams.ple_n_heads > 0) {172        // the head ranges are what the gather indexes, so they set the minimum row count173        int64_t ple_rows = 0;174        for (uint32_t h = 0; h < hparams.ple_n_heads; ++h) {175            ple_rows = std::max(ple_rows, (int64_t) hparams.ple_head_offsets[h] + hparams.ple_head_vocab_sizes[h]);176        }177 178        // the converter pads the table; a model synthesised from metadata has no tensor to ask179        const std::string ple_name = tn(LLM_TENSOR_PER_LAYER_TOKEN_EMBD, "weight").str();180        if (const auto * ple_w = ml.get_weight(ple_name.c_str())) {181            if (ple_w->tensor->ne[1] < ple_rows) {182                throw std::runtime_error(format("%s has %" PRId64 " rows, too few for the PLE head ranges (%" PRId64 ")",183                                                ple_name.c_str(), ple_w->tensor->ne[1], ple_rows));184            }185            ple_rows = ple_w->tensor->ne[1];186        }187 188        per_layer_tok_embd = create_tensor(tn(LLM_TENSOR_PER_LAYER_TOKEN_EMBD, "weight"),189                                           { hparams.ple_head_dim, ple_rows }, TENSOR_READ_LAZY);190    }191 192    for (int il = 0; il < n_layer; ++il) {193        auto & layer = layers[il];194 195        const int64_t n_ff_exp   = hparams.n_ff_exp() ? hparams.n_ff_exp() : n_ff / n_expert_used;196        const int64_t n_ff_shexp = hparams.n_ff_shexp ? hparams.n_ff_shexp : n_ff;197 198        const int64_t head_k_dim = hparams.ssm_d_state;199        const int64_t head_v_dim = hparams.ssm_d_state;200        const int64_t n_k_heads  = hparams.ssm_n_group;201        const int64_t n_v_heads  = hparams.ssm_dt_rank;202        const int64_t key_dim    = head_k_dim * n_k_heads;203        const int64_t value_dim  = head_v_dim * n_v_heads;204        const int64_t conv_dim   = key_dim * 2 + value_dim;205 206        // two HC modules per layer: before the token mixer, before the MoE207        layer.hc_attn_norm   = create_tensor(tn(LLM_TENSOR_HC_ATTN_NORM,   "weight", il), { n_embd, hc }, TENSOR_ALLOW_RESHAPE);208        layer.hc_attn_down   = create_tensor(tn(LLM_TENSOR_HC_ATTN_DOWN,   "weight", il), { hc_dim, hc_lr }, 0);209        layer.hc_attn_up     = create_tensor(tn(LLM_TENSOR_HC_ATTN_UP,     "weight", il), { hc_lr, hc_dim }, 0);210        layer.hc_attn_inject = create_tensor(tn(LLM_TENSOR_HC_ATTN_INJECT, "weight", il), { hc_dim, hc }, 0);211        layer.hc_ffn_norm    = create_tensor(tn(LLM_TENSOR_HC_FFN_NORM,    "weight", il), { n_embd, hc }, TENSOR_ALLOW_RESHAPE);212        layer.hc_ffn_down    = create_tensor(tn(LLM_TENSOR_HC_FFN_DOWN,    "weight", il), { hc_dim, hc_lr }, 0);213        layer.hc_ffn_up      = create_tensor(tn(LLM_TENSOR_HC_FFN_UP,      "weight", il), { hc_lr, hc_dim }, 0);214        layer.hc_ffn_inject  = create_tensor(tn(LLM_TENSOR_HC_FFN_INJECT,  "weight", il), { hc_dim, hc }, 0);215 216        if (!hparams.is_recr(il)) {217            // full attention: wq holds [q|gate] interleaved per head218            create_tensor_qkv(layer, il, n_embd, n_embd_head_k * n_head * 2, n_embd_k_gqa, n_embd_v_gqa, 0);219            layer.wo = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "weight", il), { n_embd_head_k * n_head, n_embd }, 0);220 221            layer.attn_q_norm = create_tensor(tn(LLM_TENSOR_ATTN_Q_NORM, "weight", il), { n_embd_head_k }, 0);222            layer.attn_k_norm = create_tensor(tn(LLM_TENSOR_ATTN_K_NORM, "weight", il), { n_embd_head_k }, 0);223 224            const int64_t idx_dim = hparams.indexer_head_size;225            layer.index_q_proj = create_tensor(tn(LLM_TENSOR_INDEXER_Q_PROJ, "weight", il), { n_embd, hparams.indexer_n_head * idx_dim }, 0);226            layer.index_k_proj = create_tensor(tn(LLM_TENSOR_INDEXER_K_PROJ, "weight", il), { n_embd, idx_dim }, 0);227            layer.index_q_norm = create_tensor(tn(LLM_TENSOR_INDEXER_Q_NORM, "weight", il), { idx_dim }, 0);228            layer.index_k_norm = create_tensor(tn(LLM_TENSOR_INDEXER_K_NORM, "weight", il), { idx_dim }, 0);229        } else {230            layer.wqkv       = create_tensor(tn(LLM_TENSOR_ATTN_QKV,   "weight", il), { n_embd, key_dim * 2 + value_dim }, 0);231            layer.wqkv_gate  = create_tensor(tn(LLM_TENSOR_ATTN_GATE,  "weight", il), { n_embd, value_dim }, 0);232            layer.ssm_conv1d = create_tensor(tn(LLM_TENSOR_SSM_CONV1D, "weight", il), { hparams.ssm_d_conv, conv_dim }, 0);233            layer.ssm_dt     = create_tensor(tn(LLM_TENSOR_SSM_DT,     "bias",   il), { hparams.ssm_dt_rank }, 0);234            layer.ssm_a      = create_tensor(tn(LLM_TENSOR_SSM_A_NOSCAN,         il), { hparams.ssm_dt_rank }, 0);235            layer.ssm_beta   = create_tensor(tn(LLM_TENSOR_SSM_BETA,   "weight", il), { n_embd, n_v_heads }, 0);236            layer.ssm_alpha  = create_tensor(tn(LLM_TENSOR_SSM_ALPHA,  "weight", il), { n_embd, n_v_heads }, 0);237            layer.ssm_norm   = create_tensor(tn(LLM_TENSOR_SSM_NORM,   "weight", il), { head_v_dim }, 0);238            layer.ssm_out    = create_tensor(tn(LLM_TENSOR_SSM_OUT,    "weight", il), { value_dim, n_embd }, 0);239        }240 241        if (hparams.is_ple(il)) {242            layer.ple_key        = create_tensor(tn(LLM_TENSOR_PLE_KEY,        "weight", il), { n_embd, hc_dim }, 0);243            layer.ple_value      = create_tensor(tn(LLM_TENSOR_PLE_VALUE,      "weight", il), { n_embd, n_embd }, 0);244            layer.ple_norm_key   = create_tensor(tn(LLM_TENSOR_PLE_NORM_KEY,   "weight", il), { n_embd, hc }, TENSOR_ALLOW_RESHAPE);245            layer.ple_norm_query = create_tensor(tn(LLM_TENSOR_PLE_NORM_QUERY, "weight", il), { n_embd, hc }, TENSOR_ALLOW_RESHAPE);246            layer.ple_norm_conv  = create_tensor(tn(LLM_TENSOR_PLE_NORM_CONV,  "weight", il), { n_embd, hc }, TENSOR_ALLOW_RESHAPE);247            layer.ple_conv1d     = create_tensor(tn(LLM_TENSOR_PLE_CONV1D,     "weight", il), { hparams.ple_conv_kernel, hc_dim }, 0);248        }249 250        layer.ffn_gate_inp  = create_tensor(tn(LLM_TENSOR_FFN_GATE_INP,  "weight", il), { n_embd, n_expert }, 0);251        layer.ffn_down_exps = create_tensor(tn(LLM_TENSOR_FFN_DOWN_EXPS, "weight", il), { n_ff_exp, n_embd, n_expert }, 0);252        create_tensor_gate_up_exps(layer, il, n_embd, n_ff_exp, n_expert, 0);253 254        layer.ffn_gate_inp_shexp = create_tensor(tn(LLM_TENSOR_FFN_GATE_INP_SHEXP, "weight", il), { n_embd }, 0);255        layer.ffn_gate_shexp     = create_tensor(tn(LLM_TENSOR_FFN_GATE_SHEXP,     "weight", il), { n_embd, n_ff_shexp }, 0);256        layer.ffn_up_shexp       = create_tensor(tn(LLM_TENSOR_FFN_UP_SHEXP,       "weight", il), { n_embd, n_ff_shexp }, 0);257        layer.ffn_down_shexp     = create_tensor(tn(LLM_TENSOR_FFN_DOWN_SHEXP,     "weight", il), { n_ff_shexp, n_embd }, 0);258    }259}260 261std::unique_ptr<llm_graph_context> llama_model_qwen4exp::build_arch_graph(const llm_graph_params & params) const {262    return std::make_unique<graph>(*this, params);263}264 265// Hyper-connections keep hc parallel residual streams [n_embd, hc, T] in place of layer norms.266// Returns the mixed [n_embd, T] stream; `inject` gets the [hc, T] scatter weights.267ggml_tensor * llama_model_qwen4exp::graph::build_hc_mix(268        ggml_tensor *  x,269        ggml_tensor *  w_norm,270        ggml_tensor *  w_down,271        ggml_tensor *  w_up,272        ggml_tensor *  w_inject,273        ggml_tensor ** inject,274        int            il) {275    const int64_t hc     = hparams.dsv4_hc_mult;276    const int64_t hc_dim = hc * n_embd;277    const int64_t nt     = x->ne[2];278 279    // grouped RMSNorm: reduce over one stream, then scale all streams with the [n_embd, hc] gamma280    // the converter folded each gamma to (1 + w)281    ggml_tensor * xn = ggml_mul(ctx0, ggml_rms_norm(ctx0, x, hparams.f_norm_rms_eps), w_norm);282    xn = ggml_reshape_2d(ctx0, xn, hc_dim, nt);283    cb(xn, "hc_norm", il);284 285    ggml_tensor * lo = build_lora_mm(w_down, xn);286    lo = ggml_silu(ctx0, ggml_scale(ctx0, lo, 1.0f / (float) hc));287    ggml_tensor * gate = build_lora_mm(w_up, lo);288    cb(gate, "hc_gate", il);289 290    ggml_tensor * mixed = nullptr;291    if (cparams.fused_dsv4_hc_pre && il >= 0) {292        // sigmoid gate and mean over the streams in one op293        mixed = ggml_dsv4_hc_pre_gated(ctx0,294                ggml_reshape_3d(ctx0, xn,   n_embd, hc, nt),295                ggml_reshape_3d(ctx0, gate, n_embd, hc, nt), 1.0f / (float) hc);296        res->add_fused_node({LLM_FUSED_OP_DSV4_HC_PRE, mixed, il});297    } else {298        ggml_tensor * gated = ggml_mul(ctx0, xn, ggml_sigmoid(ctx0, gate));299        gated = ggml_reshape_3d(ctx0, gated, n_embd, hc, nt);300 301        // collapse the streams by their mean302        mixed = ggml_view_2d(ctx0, gated, n_embd, nt,303                ggml_row_size(gated->type, n_embd) * hc, 0);304        mixed = ggml_cont(ctx0, mixed);305        for (int64_t c = 1; c < hc; ++c) {306            ggml_tensor * s = ggml_view_2d(ctx0, gated, n_embd, nt,307                    ggml_row_size(gated->type, n_embd) * hc,308                    ggml_row_size(gated->type, n_embd) * c);309            mixed = ggml_add(ctx0, mixed, s);310        }311        mixed = ggml_scale(ctx0, mixed, 1.0f / (float) hc);312    }313    cb(mixed, "hc_mixed", il);314 315    if (inject) {316        *inject = build_lora_mm(w_inject, xn);317        cb(*inject, "hc_inject", il);318    }319 320    return mixed;321}322 323ggml_tensor * llama_model_qwen4exp::graph::build_hc_combine(324        ggml_tensor * residual,325        ggml_tensor * block_out,326        ggml_tensor * inject,327        int           il) {328    const int64_t hc = hparams.dsv4_hc_mult;329    const int64_t nt = residual->ne[2];330 331    // 2*sigmoid centres the scatter weights on 1, so a zero injection is a plain residual add332    ggml_tensor * w = ggml_sigmoid(ctx0, ggml_scale(ctx0, inject, 1.0f / (float) hc));333    w = ggml_scale(ctx0, w, 2.0f);334 335    ggml_tensor * cur = nullptr;336    if (cparams.fused_dsv4_hc_post && il >= 0) {337        // identity comb: every stream adds the same block output, scaled by its own weight338        cur = ggml_dsv4_hc_post(ctx0, block_out, residual, w, nullptr);339        res->add_fused_node({LLM_FUSED_OP_DSV4_HC_POST, cur, il});340    } else {341        w = ggml_reshape_3d(ctx0, w, 1, hc, nt);342 343        ggml_tensor * b = ggml_reshape_3d(ctx0, block_out, n_embd, 1, nt);344        b = ggml_repeat_4d(ctx0, b, n_embd, hc, nt, 1);345 346        cur = ggml_add(ctx0, residual, ggml_mul(ctx0, b, w));347    }348    cb(cur, "hc_combine", il);349 350    return cur;351}352 353llama_model_qwen4exp::graph::graph(const llama_model & model, const llm_graph_params & params) :354    llm_build_delta_net_base(params), model(model) {355    const int64_t hc = hparams.dsv4_hc_mult;356 357    GGML_ASSERT(hparams.n_embd_head_v() == hparams.n_embd_head_k());358 359    int sections[4];360    std::copy(std::begin(hparams.rope_sections), std::begin(hparams.rope_sections) + 4, sections);361 362    ggml_tensor * inpL = build_inp_embd(model.tok_embd);363    cb(inpL, "model.input_embed", -1);364    ggml_build_forward_expand(gf, inpL);365 366    auto * inp = build_inp_mem_hybrid();367 368    // qwen4exp always builds llama_memory_hybrid_idx, so this downcast is safe369    // the indexer cache inside it is absent when the GGUF has no indexer tensors370    const auto * mctx_hyb = static_cast<const llama_memory_hybrid_idx_context *>(inp->mctx);371 372    const llama_kv_cache_context * mctx_idx = mctx_hyb->get_idx();373    if (mctx_idx) {374        GGML_ASSERT(mctx_idx->get_n_kv() == inp->mctx->get_attn()->get_n_kv() &&375                "the indexer cache must track the attention cache cell for cell");376    }377 378    ggml_tensor * inp_pos     = build_inp_pos();379    ggml_tensor * inp_out_ids = build_inp_out_ids();380 381    ggml_tensor * ple_emb = nullptr;382    if (hparams.ple_n_heads > 0) {383        ple_emb = build_inp_ple(mctx_hyb);384        // make sure ple_emb and build_inp_embd are in the same graph split385        ggml_build_forward_expand(gf, ple_emb);386    }387 388    // the wide residual starts as hc identical copies of the embedding389    ggml_tensor * res_hc = ggml_repeat_4d(ctx0,390            ggml_reshape_3d(ctx0, inpL, n_embd, 1, n_tokens),391            n_embd, hc, n_tokens, 1);392    cb(res_hc, "hc_init", -1);393 394    for (int il = 0; il < n_layer; ++il) {395        res->t_layer_inp[il] = res_hc;396 397        if (hparams.is_ple(il)) {398            res_hc = build_ple(inp->get_recr(), ple_emb, res_hc, il);399        }400 401        ggml_tensor * inject = nullptr;402        ggml_tensor * cur = build_hc_mix(res_hc,403                model.layers[il].hc_attn_norm,404                model.layers[il].hc_attn_down,405                model.layers[il].hc_attn_up,406                model.layers[il].hc_attn_inject,407                &inject, il);408 409        ggml_build_forward_expand(gf, cur);410 411        if (hparams.is_recr(il)) {412            cur = build_layer_attn_linear(inp->get_recr(), cur, il);413        } else {414            cur = build_layer_attn(inp->get_attn(), mctx_hyb, cur, inp_pos, sections, il);415        }416 417        if (il == n_layer - 1 && inp_out_ids) {418            // everything below is per token, so drop the rows that produce no output419            cur    = ggml_get_rows(ctx0, cur,    inp_out_ids);420            inject = ggml_get_rows(ctx0, inject, inp_out_ids);421 422            res_hc = ggml_reshape_2d(ctx0, res_hc, n_embd*hc, res_hc->ne[2]);423            res_hc = ggml_get_rows(ctx0, res_hc, inp_out_ids);424            res_hc = ggml_reshape_3d(ctx0, res_hc, n_embd, hc, res_hc->ne[1]);425        }426 427        res_hc = build_hc_combine(res_hc, cur, inject, il);428 429        cur = build_hc_mix(res_hc,430                model.layers[il].hc_ffn_norm,431                model.layers[il].hc_ffn_down,432                model.layers[il].hc_ffn_up,433                model.layers[il].hc_ffn_inject,434                &inject, il);435 436        cur = build_layer_ffn(cur, il);437        cb(cur, "ffn_out", il);438 439        res_hc = build_hc_combine(res_hc, cur, inject, il);440 441        // "l_last" is the layer output name that build_cvec and imatrix look for442        cb(res_hc, "l_last", il);443    }444 445    // the final mixer is the output norm: there is no separate one446    ggml_tensor * cur = build_hc_mix(res_hc,447            model.hc_head_norm, model.hc_head_down, model.hc_head_up,448            nullptr, nullptr, -1);449 450    cb(cur, "result_norm", -1);451    res->t_embd = cur;452 453    cur = build_lora_mm(model.output, cur, model.output_s);454    cb(cur, "result_output", -1);455    res->t_logits = cur;456 457    ggml_build_forward_expand(gf, cur);458}459 460std::pair<ggml_tensor *, ggml_tensor *> llama_model_qwen4exp::graph::build_qkvz(461                ggml_tensor * input,462                        int   il) {463    const int64_t n_seqs       = ubatch.n_seqs;464    const int64_t n_seq_tokens = ubatch.n_seq_tokens;465 466    ggml_tensor * qkv_mixed = build_lora_mm(model.layers[il].wqkv, input, model.layers[il].wqkv_s);467    qkv_mixed = ggml_reshape_3d(ctx0, qkv_mixed, qkv_mixed->ne[0], n_seq_tokens, n_seqs);468    cb(qkv_mixed, "linear_attn_qkv_mixed", il);469 470    ggml_tensor * z = build_lora_mm(model.layers[il].wqkv_gate, input, model.layers[il].wqkv_gate_s);471    cb(z, "z", il);472 473    return { qkv_mixed, z };474}475 476ggml_tensor * llama_model_qwen4exp::graph::build_norm_gated(477        ggml_tensor * input,478        ggml_tensor * weights,479        ggml_tensor * gate,480        int           layer) {481    // the one numerical difference from Qwen3.5's GDN: sigmoid output gate, not silu482    ggml_tensor * normalized = build_norm(input, weights, nullptr, LLM_NORM_RMS, layer);483    ggml_tensor * gated = ggml_sigmoid(ctx0, gate);484 485    return ggml_mul(ctx0, normalized, gated);486}487 488// QSA attends to a budget of whole blocks of compress_ratio tokens, plus the incomplete tail489// one mean-pooled indexer key scores each block; set_input resolves the cache layout490class llama_model_qwen4exp::llm_graph_input_qsa : public llm_graph_input_i {491public:492    llm_graph_input_qsa(const llama_memory_hybrid_idx_context * mctx, uint32_t ratio, bool blk_bias) :493        mctx(mctx), ratio(ratio), blk_bias(blk_bias) {}494    virtual ~llm_graph_input_qsa() = default;495 496    void set_input(const llama_ubatch * ubatch) override {497        mctx->get_idx()->set_input_k_idxs(k_idxs, ubatch);498        mctx->set_input_qsa(cell_blk, blk_cells, blk_pos, bias, ubatch, ratio, blk_bias);499    }500 501    bool can_reuse(const llm_graph_params & params) override {502        mctx = static_cast<const llama_memory_hybrid_idx_context *>(params.mctx);503 504        const auto * idx = mctx->get_idx();505        if (idx == nullptr) {506            return false;507        }508 509        const int64_t n_kv     = idx->get_n_kv();510        const int64_t n_stream = mctx->get_n_stream();511        const int64_t n_blocks = (n_kv + ratio - 1)/ratio;512 513        bool res = true;514 515        res &= params.ubatch.n_tokens % n_stream == 0;516 517        res &= k_idxs->ne[0]    == params.ubatch.n_tokens;518        res &= cell_blk->ne[0]  == n_kv;519        res &= cell_blk->ne[1]  == n_stream;520        res &= blk_cells->ne[0] == (int64_t) ratio*n_blocks;521        res &= blk_pos->ne[0]   == 4*n_blocks*n_stream;522        res &= bias->ne[0]      == (blk_bias ? n_blocks : n_kv);523        res &= bias->ne[1]      == params.ubatch.n_tokens/n_stream;524 525        return res;526    }527 528    // per stream: a cell index names a different token in each stream529    ggml_tensor * k_idxs    = nullptr;   // I32 [n_tokens]530    ggml_tensor * cell_blk  = nullptr;   // I32 [n_kv, n_stream]531    ggml_tensor * blk_cells = nullptr;   // I32 [ratio*n_blocks, n_stream]532    ggml_tensor * blk_pos   = nullptr;   // I32 [4*n_blocks*n_stream]533    ggml_tensor * bias      = nullptr;   // F32 [n_blocks or n_kv, n_tokens/n_stream, n_stream]534 535    const llama_memory_hybrid_idx_context * mctx;536    const uint32_t ratio;537 538    // the per-cell half of the bias is the attention mask, so only the per-block half is uploaded539    const bool blk_bias;540};541 542ggml_tensor * llama_model_qwen4exp::graph::build_qsa_top_k(543        const llama_memory_hybrid_idx_context * mctx_hyb,544        ggml_tensor *                           cur,545        ggml_tensor *                           inp_pos,546        ggml_tensor *                           kq_mask,547        int *                                   sections,548        int                                     il) {549    const llama_kv_cache_context * mctx_idx = mctx_hyb->get_idx();550 551    const int64_t idx_dim  = hparams.indexer_head_size;552    const int64_t n_idx_h  = hparams.indexer_n_head;553    const int64_t r        = hparams.dsv4_compress_ratios[il];554    const int64_t n_kv     = mctx_idx->get_n_kv();555 556    GGML_ASSERT(r > 0);557 558    const int64_t n_blocks = (n_kv + r - 1)/r;559 560    // build_attn_qsa and the KQ mask need the tokens to divide evenly across the streams561    const int64_t n_stream = mctx_hyb->get_n_stream();562    GGML_ASSERT(n_tokens % n_stream == 0);563    const int64_t n_tps = n_tokens/n_stream;564 565    // only the "which block is visible" half of the bias varies per block566    // the rest is the visible/not test the attention mask already carries, so upload the per-block half only: 1/ratio of the cells567    // alibi writes distances instead of a mask and non-causal keeps future cells, so both opt out568    // the mask also holds an mrope rule for the query's own position, but only 2d image positions can differ there569    const bool blk_bias = kq_mask != nullptr &&570        kq_mask->ne[0] == n_kv && kq_mask->ne[1] == n_tps && kq_mask->ne[3] == n_stream &&571        cparams.causal_attn && !hparams.use_alibi;572 573    // nothing above depends on the layer, so the layers sharing a ratio share one input set574    llm_graph_input_qsa * inp = nullptr;575 576    const auto it = qsa_inps.find((uint32_t) r);577    if (it != qsa_inps.end()) {578        inp = it->second;579    } else {580        auto qsa = std::make_unique<llm_graph_input_qsa>(mctx_hyb, (uint32_t) r, blk_bias);581 582        qsa->k_idxs    = mctx_idx->build_input_k_idxs(ctx0, ubatch);583        qsa->cell_blk  = ggml_new_tensor_2d(ctx0, GGML_TYPE_I32, n_kv, n_stream);584        qsa->blk_cells = ggml_new_tensor_2d(ctx0, GGML_TYPE_I32, r*n_blocks, n_stream);585        qsa->blk_pos   = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, 4*n_blocks*n_stream);586        qsa->bias      = ggml_new_tensor_3d(ctx0, GGML_TYPE_F32, blk_bias ? n_blocks : n_kv, n_tps, n_stream);587 588        ggml_set_input(qsa->cell_blk);589        ggml_set_input(qsa->blk_cells);590        ggml_set_input(qsa->blk_pos);591        ggml_set_input(qsa->bias);592 593        inp = qsa.get();594        res->add_input(std::move(qsa));595        qsa_inps.emplace((uint32_t) r, inp);596    }597 598    // cached indexer keys are raw: pooling precedes norm and rotation, so apply neither599    ggml_tensor * k_raw = build_lora_mm(model.layers[il].index_k_proj, cur);600    k_raw = ggml_reshape_3d(ctx0, k_raw, idx_dim, 1, n_tokens);601    cb(k_raw, "indexer_k_raw", il);602 603    ggml_build_forward_expand(gf, mctx_idx->cpy_k(ctx0, k_raw, inp->k_idxs, il));604 605    // one key head, so rows are contiguous. get_k gives [idx_dim, n_head_kv, n_kv, n_stream].606    ggml_tensor * k_all = mctx_idx->get_k(ctx0, il);607    k_all = ggml_view_3d(ctx0, k_all, idx_dim, n_kv, n_stream, k_all->nb[2], k_all->nb[3], 0);608 609    // gathers per stream: blk_cells row s indexes stream s's own cells610    ggml_tensor * members = ggml_get_rows(ctx0, k_all, inp->blk_cells);611    members = ggml_reshape_4d(ctx0, members, idx_dim, r, n_blocks, n_stream);612 613    // mean over the block members; r is small, so summing slices beats a transpose plus sum_rows614    ggml_tensor * pooled = nullptr;615    for (int64_t i = 0; i < r; ++i) {616        ggml_tensor * slice = ggml_cont(ctx0,617                ggml_view_3d(ctx0, members, idx_dim, n_blocks, n_stream,618                        members->nb[2], members->nb[3], i*members->nb[1]));619        pooled = pooled ? ggml_add(ctx0, pooled, slice) : slice;620    }621    pooled = ggml_scale(ctx0, pooled, 1.0f/(float) r);622    cb(pooled, "indexer_k_pooled", il);623 624    // count blocks along ne1: rms_norm launches gridDim.y = ne2, capped at 65535, and 262144/4 = 65536625    pooled = ggml_reshape_3d(ctx0, pooled, idx_dim, n_blocks*n_stream, 1);626    pooled = build_norm(pooled, model.layers[il].index_k_norm, nullptr, LLM_NORM_RMS, il);627 628    // rope wants [n_dims, n_head, n_tokens]: lay every stream's blocks flat, split after.629    pooled = ggml_reshape_3d(ctx0, pooled, idx_dim, 1, n_blocks*n_stream);630    pooled = ggml_rope_multi(ctx0, pooled, inp->blk_pos, nullptr,631            n_rot, sections, rope_type, n_ctx_orig, freq_base, freq_scale,632            ext_factor, attn_factor, beta_fast, beta_slow);633    pooled = ggml_reshape_3d(ctx0, pooled, idx_dim, n_blocks, n_stream);634    cb(pooled, "indexer_k", il);635 636    ggml_tensor * q = build_lora_mm(model.layers[il].index_q_proj, cur);637    q = ggml_reshape_3d(ctx0, q, idx_dim, n_idx_h, n_tokens);638    q = build_norm(q, model.layers[il].index_q_norm, nullptr, LLM_NORM_RMS, il);639    q = ggml_rope_multi(ctx0, q, inp_pos, nullptr,640            n_rot, sections, rope_type, n_ctx_orig, freq_base, freq_scale,641            ext_factor, attn_factor, beta_fast, beta_slow);642    cb(q, "indexer_q", il);643 644    // rectify each head dot product before the sum, as in the DeepSeek lightning indexer645    // mul_mat matches ne[2], so the queries of stream s only meet the blocks of stream s646    ggml_tensor * score = ggml_mul_mat(ctx0, pooled,647            ggml_reshape_3d(ctx0, q, idx_dim, n_idx_h*n_tps, n_stream));648    score = ggml_reshape_4d(ctx0, score, n_blocks, n_idx_h, n_tps, n_stream);649    score = ggml_relu(ctx0, score);650 651    // the heads sit side by side on ne[1] and there are only a few of them652    ggml_tensor * summed = nullptr;653    for (int64_t h = 0; h < n_idx_h; ++h) {654        ggml_tensor * slice = ggml_view_3d(ctx0, score, n_blocks, n_tps, n_stream,655                score->nb[2], score->nb[3], h*score->nb[1]);656        summed = summed ? ggml_add(ctx0, summed, slice) : ggml_cont(ctx0, slice);657    }658 659    score = summed;660    cb(score, "indexer_score", il);661 662    // one value per block, so it is cheaper to bias here than after the cells are expanded663    if (blk_bias) {664        score = ggml_add(ctx0, score, inp->bias);665    }666 667    // every token of a block gets the block score; the budget is whole blocks, so top-k cuts on a block boundary668    ggml_tensor * expanded = ggml_get_rows(ctx0,669            ggml_cont(ctx0, ggml_permute(ctx0, score, 1, 0, 2, 3)), inp->cell_blk);670    expanded = ggml_cont(ctx0, ggml_permute(ctx0, expanded, 1, 0, 2, 3));671 672    if (blk_bias) {673        // flash attention keeps the mask in f16; the scores are f32674        ggml_tensor * mask = kq_mask->type == GGML_TYPE_F32 ? kq_mask : ggml_cast(ctx0, kq_mask, GGML_TYPE_F32);675        expanded = ggml_add(ctx0, expanded, ggml_reshape_3d(ctx0, mask, n_kv, n_tps, n_stream));676    } else {677        expanded = ggml_add(ctx0, expanded, inp->bias);678    }679    cb(expanded, "indexer_score_tokens", il);680 681    // the reference returns indexer_top_k + compress_ratio - 1: whole blocks plus the tail682    const int64_t width = std::min<int64_t>(n_kv, (int64_t) hparams.indexer_top_k + r - 1);683 684    ggml_tensor * top_k = ggml_cont(ctx0, ggml_top_k(ctx0, expanded, width));685 686    // build_attn_qsa reads [n_top_k, n_batch, 1, n_stream], matching the KQ mask.687    top_k = ggml_reshape_4d(ctx0, top_k, width, n_tps, 1, n_stream);688    cb(top_k, "indexer_top_k", il);689 690    return top_k;691}692 693// Dense GQA self-attention restricted to the cells that top_k names.694// The mask build below copies the MLA sparse path in llm_graph_context::build_attn.695ggml_tensor * llama_model_qwen4exp::graph::build_attn_qsa(696        llm_graph_input_attn_kv * inp,697        ggml_tensor *             q_cur,698        ggml_tensor *             k_cur,699        ggml_tensor *             v_cur,700        ggml_tensor *             top_k,701        float                     kq_scale,702        int                       il) {703    // rotate q/k/v before they reach a quantized cache, as the dense path does. the indexer704    // has already scored with its own query in build_qsa_top_k, so top_k is unaffected.705    if (inp->self_k_rot) {706        q_cur = llama_mul_mat_hadamard(ctx0, q_cur, inp->self_k_rot);707        k_cur = llama_mul_mat_hadamard(ctx0, k_cur, inp->self_k_rot);708    }709 710    if (inp->self_v_rot) {711        v_cur = llama_mul_mat_hadamard(ctx0, v_cur, inp->self_v_rot);712    }713 714    // these nodes are added to the graph together so that they are not reordered715    // by doing so, the number of splits in the graph is reduced716    // expand k later to enable rope fusion which directly writes into k-v cache717    ggml_build_forward_expand(gf, q_cur);718    ggml_build_forward_expand(gf, v_cur);719    ggml_build_forward_expand(gf, k_cur);720 721    const auto * mctx_cur = inp->mctx;722 723    // store to KV cache724    {725        const auto & k_idxs = inp->get_k_idxs();726        const auto & v_idxs = inp->get_v_idxs();727 728        ggml_build_forward_expand(gf, mctx_cur->cpy_k(ctx0, k_cur, k_idxs, il));729        ggml_build_forward_expand(gf, mctx_cur->cpy_v(ctx0, v_cur, v_idxs, il));730    }731 732    ggml_tensor * kq_mask = inp->get_kq_mask();733 734    // prepare new kq mask - starts filled with -INFINITY735    ggml_tensor * kq_mask_all = ggml_fill(ctx0, kq_mask, -INFINITY);736 737    // reshape KQ mask into tensor with rows of size 1:738    // [n_kv, n_batch, 1, n_stream] -> [1, n_kv, n_batch, n_stream]739    kq_mask_all = ggml_view_4d(ctx0, kq_mask_all, 1, kq_mask_all->ne[0], kq_mask_all->ne[1], kq_mask_all->ne[3], kq_mask_all->nb[0], kq_mask_all->nb[1], kq_mask_all->nb[2], 0);740 741    // reshape top_k indices: [n_top_k, n_batch, 1, n_stream] -> [n_top_k, n_batch, n_stream, 1]742    ggml_tensor * top_k_3d = ggml_view_4d(ctx0, top_k, top_k->ne[0], top_k->ne[1], top_k->ne[3], 1, top_k->nb[1], top_k->nb[2], top_k->ne[3]*top_k->nb[3], 0);743 744    // prepare zero-filled tensor with rows of size 1: [1, n_top_k, n_batch, n_stream]745    // this will be our source of zero values for unmasking top k mask elements746    ggml_tensor * zeros = ggml_new_tensor_4d(ctx0, GGML_TYPE_F32, 1, top_k_3d->ne[0], top_k_3d->ne[1], top_k_3d->ne[2]);747    zeros = ggml_fill(ctx0, zeros, 0.0f);748 749    // modify KQ mask by unmasking elements that are in top_k indices750    // ggml_set_rows([1, n_kv, n_batch, n_stream], [1, n_top_k, n_batch, n_stream], [n_top_k, n_batch, n_stream, 1])751    ggml_tensor * kq_mask_top_k = ggml_set_rows(ctx0, kq_mask_all, zeros, top_k_3d);752 753    // reshape to restore the original shape of KQ mask:754    // [1, n_kv, n_batch, n_stream] -> [n_kv, n_batch, 1, n_stream]755    kq_mask_top_k = ggml_view_4d(ctx0, kq_mask_top_k, kq_mask_top_k->ne[1], kq_mask_top_k->ne[2], 1, kq_mask_top_k->ne[3], kq_mask_top_k->nb[2], kq_mask_top_k->nb[3], kq_mask_top_k->nb[3], 0);756 757    // combine with the original kq mask758    kq_mask_top_k = ggml_add(ctx0, kq_mask_top_k, kq_mask);759 760    ggml_tensor * q = q_cur;761    ggml_tensor * k = mctx_cur->get_k(ctx0, il);762    ggml_tensor * v = mctx_cur->get_v(ctx0, il);763 764    // TODO: enable sparse attention when we are ready765    // ref: https://github.com/ggml-org/llama.cpp/pull/27970766    //ggml_tensor * cur = build_attn_mha(q, k, v, nullptr, kq_mask_top_k, nullptr, nullptr, top_k->ne[0], kq_scale, il);767    ggml_tensor * cur = build_attn_mha(q, k, v, nullptr, kq_mask_top_k, nullptr, nullptr, 0, kq_scale, il);768    cb(cur, "kqv_out", il);769 770    // the rotation is its own inverse, so undo it on the value side of the output771    if (inp->self_v_rot) {772        cur = llama_mul_mat_hadamard(ctx0, cur, inp->self_v_rot);773    }774 775    return cur;776}777 778ggml_tensor * llama_model_qwen4exp::graph::build_layer_attn(779        llm_graph_input_attn_kv * inp,780        const llama_memory_hybrid_idx_context * mctx_hyb,781        ggml_tensor *             cur,782        ggml_tensor *             inp_pos,783        int *                     sections,784        int                       il) {785    const int64_t n_embd_head = hparams.n_embd_head_v();786    GGML_ASSERT(n_embd_head == hparams.n_embd_head_k());787 788    // indexer reads the same block input as q/k/v; no cache or no ratio means dense789    const bool qsa = mctx_hyb->get_idx() != nullptr && hparams.dsv4_compress_ratios[il] > 0;790 791    ggml_tensor * top_k = qsa ? build_qsa_top_k(mctx_hyb, cur, inp_pos, inp->get_kq_mask(), sections, il) : nullptr;792 793    // Qwen3Next uses a single Q projection that outputs query + gate794    ggml_tensor * Qcur_full = build_lora_mm(model.layers[il].wq, cur, model.layers[il].wq_s); // [ (n_embd_head * 2) * n_head, n_tokens ]795    cb(Qcur_full, "Qcur_full", il);796 797    ggml_tensor * Qcur = ggml_view_3d(ctx0, Qcur_full, n_embd_head, n_head, n_tokens,798        ggml_element_size(Qcur_full) * n_embd_head * 2,799        ggml_element_size(Qcur_full) * n_embd_head * 2 * n_head, 0);800    cb(Qcur, "Qcur_reshaped", il);801 802    Qcur = build_norm(Qcur, model.layers[il].attn_q_norm, nullptr, LLM_NORM_RMS, il);803    cb(Qcur, "Qcur_normed", il);804 805    ggml_tensor * Kcur = build_lora_mm(model.layers[il].wk, cur, model.layers[il].wk_s);806    cb(Kcur, "Kcur", il);807 808    ggml_tensor * Vcur = build_lora_mm(model.layers[il].wv, cur, model.layers[il].wv_s);809    cb(Vcur, "Vcur", il);810 811    Kcur = ggml_reshape_3d(ctx0, Kcur, n_embd_head, n_head_kv, n_tokens);812    Kcur = build_norm(Kcur, model.layers[il].attn_k_norm, nullptr, LLM_NORM_RMS, il);813    cb(Kcur, "Kcur_normed", il);814 815    ggml_tensor * gate = ggml_view_3d(ctx0, Qcur_full, n_embd_head, n_head, n_tokens,816        ggml_element_size(Qcur_full) * n_embd_head * 2,817        ggml_element_size(Qcur_full) * n_embd_head * 2 * n_head,818        ggml_element_size(Qcur_full) * n_embd_head);819    gate = ggml_cont_2d(ctx0, gate, n_embd_head * n_head, n_tokens);820    cb(gate, "gate_reshaped", il);821 822    Vcur = ggml_reshape_3d(ctx0, Vcur, n_embd_head, n_head_kv, n_tokens);823 824    // Apply IMRoPE825    Qcur = ggml_rope_multi(826            ctx0, Qcur, inp_pos, nullptr,827            n_rot, sections, rope_type, n_ctx_orig, freq_base, freq_scale,828            ext_factor, attn_factor, beta_fast, beta_slow829            );830 831    Kcur = ggml_rope_multi(832            ctx0, Kcur, inp_pos, nullptr,833            n_rot, sections, rope_type, n_ctx_orig, freq_base, freq_scale,834            ext_factor, attn_factor, beta_fast, beta_slow835            );836 837    cb(Qcur, "Qcur", il);838    cb(Kcur, "Kcur", il);839    cb(Vcur, "Vcur", il);840 841    const float kq_scale = hparams.f_attention_scale == 0.0f ? 1.0f / sqrtf(float(n_embd_head)) : hparams.f_attention_scale;842 843    if (top_k) {844        cur = build_attn_qsa(inp, Qcur, Kcur, Vcur, top_k, kq_scale, il);845    } else {846        cur = build_attn(inp,847                    nullptr, nullptr, nullptr,848                    Qcur, Kcur, Vcur, nullptr, nullptr, nullptr, kq_scale, il);849    }850    cb(cur, "attn_pregate", il);851 852    ggml_tensor * gate_sigmoid = ggml_sigmoid(ctx0, gate);853    cb(gate_sigmoid, "gate_sigmoid", il);854 855    cur = ggml_mul(ctx0, cur, gate_sigmoid);856    cb(cur, "attn_gated", il);857 858    cur = build_lora_mm(model.layers[il].wo, cur, model.layers[il].wo_s);859    cb(cur, "attn_output", il);860 861    return cur;862}863 864ggml_tensor * llama_model_qwen4exp::graph::build_layer_attn_linear(865        llm_graph_input_rs * inp,866        ggml_tensor *        cur,867        int                  il) {868    const auto * mctx_cur = inp->mctx;869 870    const int64_t d_inner      = hparams.ssm_d_inner;871    const int64_t n_seqs       = ubatch.n_seqs;872    const int64_t head_k_dim   = hparams.ssm_d_state;873    const int64_t num_k_heads  = hparams.ssm_n_group;874    const int64_t num_v_heads  = hparams.ssm_dt_rank;875    const int64_t head_v_dim   = hparams.ssm_d_state;876    const int64_t n_seq_tokens = ubatch.n_seq_tokens;877 878    GGML_ASSERT(n_seqs != 0);879    GGML_ASSERT(ubatch.equal_seqs());880    GGML_ASSERT(ubatch.n_tokens == n_seq_tokens * n_seqs);881    GGML_ASSERT(head_v_dim * num_v_heads == d_inner);882 883    auto qkvz = build_qkvz(cur, il);884    ggml_tensor * qkv_mixed = qkvz.first;885    ggml_tensor * z         = qkvz.second;886 887    ggml_tensor * beta = build_lora_mm(model.layers[il].ssm_beta, cur, model.layers[il].ssm_beta_s);888    beta = ggml_reshape_4d(ctx0, beta, 1, num_v_heads, n_seq_tokens, n_seqs);889    cb(beta, "beta", il);890 891    beta = ggml_sigmoid(ctx0, beta);892    cb(beta, "beta_sigmoid", il);893 894    ggml_tensor * alpha = build_lora_mm(model.layers[il].ssm_alpha, cur, model.layers[il].ssm_alpha_s);895    alpha = ggml_reshape_3d(ctx0, alpha, num_v_heads, n_seq_tokens, n_seqs);896    cb(alpha, "alpha", il);897 898    ggml_tensor * alpha_biased   = ggml_add(ctx0, alpha, model.layers[il].ssm_dt);899    ggml_tensor * alpha_softplus = ggml_softplus(ctx0, alpha_biased);900    cb(alpha_softplus, "a_softplus", il);901 902    ggml_tensor * gate = ggml_mul(ctx0, alpha_softplus, model.layers[il].ssm_a);  // -A_log.exp() * softplus903    cb(gate, "gate", il);904 905    gate = ggml_reshape_4d(ctx0, gate, 1, num_v_heads, n_seq_tokens, n_seqs);906 907    ggml_tensor * conv_states_all = mctx_cur->get_r_l(il);908    ggml_tensor * ssm_states_all  = mctx_cur->get_s_l(il);909 910    ggml_tensor * conv_kernel      = model.layers[il].ssm_conv1d;911    const int64_t conv_kernel_size = conv_kernel->ne[0];912 913    // the channels must match how load_arch_tensors sizes wqkv, not ssm_d_inner914    const int64_t conv_channels    = head_k_dim * num_k_heads * 2 + head_v_dim * num_v_heads;915 916    ggml_tensor * conv_input = build_conv_state_at(inp, conv_states_all, qkv_mixed,917            conv_kernel_size - 1, conv_channels, il);918 919    ggml_tensor * state = build_rs(inp, ssm_states_all, hparams.n_embd_s(), n_seqs);920    state = ggml_reshape_4d(ctx0, state, head_v_dim, head_v_dim, num_v_heads, n_seqs);921    cb(state, "state_predelta", il);922 923    ggml_tensor * conv_output_proper = ggml_ssm_conv(ctx0, conv_input, conv_kernel);924    cb(conv_output_proper, "conv_output_raw", il);925 926    ggml_tensor * conv_output_silu = ggml_silu(ctx0, conv_output_proper);927    cb(conv_output_silu, "conv_output_silu", il);928 929    ggml_tensor * conv_qkv_mix = conv_output_silu;930 931    int64_t nb1_qkv = ggml_row_size(conv_qkv_mix->type, conv_channels);932 933    // Extract the convolved Q, K, V from conv_output934    ggml_tensor * q_conv = ggml_view_4d(ctx0, conv_qkv_mix, head_k_dim, num_k_heads, n_seq_tokens, n_seqs,935            ggml_row_size(conv_qkv_mix->type, head_k_dim),936            nb1_qkv,937            nb1_qkv * n_seq_tokens,938            0);939 940    ggml_tensor * k_conv = ggml_view_4d(ctx0, conv_qkv_mix, head_k_dim, num_k_heads, n_seq_tokens, n_seqs,941            ggml_row_size(conv_qkv_mix->type, head_k_dim),942            nb1_qkv,943            nb1_qkv * n_seq_tokens,944            head_k_dim * num_k_heads * ggml_element_size(conv_qkv_mix));945 946    ggml_tensor * v_conv = ggml_view_4d(ctx0, conv_qkv_mix, head_v_dim, num_v_heads, n_seq_tokens, n_seqs,947            ggml_row_size(conv_qkv_mix->type, head_v_dim),948            nb1_qkv,949            nb1_qkv * n_seq_tokens,950            ggml_row_size(conv_qkv_mix->type, 2 * head_k_dim * num_k_heads));951 952    cb(q_conv, "q_conv", il);953    cb(k_conv, "k_conv", il);954    cb(v_conv, "v_conv", il);955 956 957    const float eps_norm = hparams.f_norm_rms_eps;958 959    q_conv = build_gdn_l2_norm(ctx0, q_conv, eps_norm);960    k_conv = build_gdn_l2_norm(ctx0, k_conv, eps_norm);961 962    // repeat to match shapes when head keys != value keys; unneeded with the fused GDN963    if (num_k_heads != num_v_heads && (!cparams.fused_gdn_ar || !cparams.fused_gdn_ch)) {964        GGML_ASSERT(num_v_heads % num_k_heads == 0);965        q_conv = ggml_repeat_4d(ctx0, q_conv, head_k_dim, num_v_heads, n_seq_tokens, n_seqs);966        k_conv = ggml_repeat_4d(ctx0, k_conv, head_k_dim, num_v_heads, n_seq_tokens, n_seqs);967    }968 969    cb(q_conv, "q_conv_predelta", il);970    cb(k_conv, "k_conv_predelta", il);971    cb(v_conv, "v_conv_predelta", il);972 973    ggml_tensor * output = build_recurrent_attn(inp, ssm_states_all, q_conv, k_conv, v_conv, gate, beta, state, il);974 975    ggml_tensor * z_2d = ggml_reshape_4d(ctx0, z, head_v_dim, num_v_heads, n_seq_tokens, n_seqs);976 977    // gated normalization, as self.norm(core_attn_out, z) in the reference978    ggml_tensor * attn_out_norm = build_norm_gated(output, model.layers[il].ssm_norm, z_2d, il);979 980    ggml_tensor * final_output = ggml_reshape_3d(ctx0, attn_out_norm, head_v_dim * num_v_heads, n_seq_tokens, n_seqs);981    cb(final_output, "final_output", il);982 983    cur = build_lora_mm(model.layers[il].ssm_out, final_output, model.layers[il].ssm_out_s);984    cb(cur, "linear_attn_out", il);985 986    cur = ggml_reshape_2d(ctx0, cur, n_embd, n_seq_tokens * n_seqs);987 988    return cur;989}990 991ggml_tensor * llama_model_qwen4exp::graph::build_layer_ffn(ggml_tensor * cur, const int il) {992    GGML_ASSERT(model.layers[il].ffn_gate_inp != nullptr);993 994    ggml_tensor * moe_out =995        build_moe_ffn(cur,996            model.layers[il].ffn_gate_inp,997            model.layers[il].ffn_up_exps,998            model.layers[il].ffn_gate_exps,999            model.layers[il].ffn_down_exps,1000            nullptr,1001            n_expert, n_expert_used,1002            LLM_FFN_SILU, true,1003            hparams.expert_weights_scale,1004            LLAMA_EXPERT_GATING_FUNC_TYPE_SOFTMAX, il,1005            nullptr, model.layers[il].ffn_gate_up_exps,1006            model.layers[il].ffn_up_exps_s,1007            model.layers[il].ffn_gate_exps_s,1008            model.layers[il].ffn_down_exps_s);1009    cb(moe_out, "ffn_moe_out", il);1010 1011    // shared experts, as in the Qwen3Next reference1012    if (model.layers[il].ffn_up_shexp != nullptr) {1013        ggml_tensor * ffn_shexp =1014            build_ffn(cur,1015                model.layers[il].ffn_up_shexp, NULL, model.layers[il].ffn_up_shexp_s,1016                model.layers[il].ffn_gate_shexp, NULL, model.layers[il].ffn_gate_shexp_s,1017                model.layers[il].ffn_down_shexp, NULL, model.layers[il].ffn_down_shexp_s,1018                NULL,1019                LLM_FFN_SILU, LLM_FFN_PAR, il);1020        cb(ffn_shexp, "ffn_shexp", il);1021 1022        // shared expert has its own sigmoided gate (ffn_gate_inp_shexp, one value per token)1023        ggml_tensor * shared_gate = build_lora_mm(model.layers[il].ffn_gate_inp_shexp, cur);1024        cb(shared_gate, "shared_expert_gate", il);1025 1026        shared_gate = ggml_sigmoid(ctx0, shared_gate);1027        cb(shared_gate, "shared_expert_gate_sigmoid", il);1028 1029        ffn_shexp = ggml_mul(ctx0, ffn_shexp, shared_gate);1030        cb(ffn_shexp, "ffn_shexp_gated", il);1031 1032        cur = ggml_add(ctx0, moe_out, ffn_shexp);1033        cb(cur, "ffn_out", il);1034    } else {1035        cur = moe_out;1036    }1037 1038    return cur;1039}1040 1041// PLE n-gram hash embedding: each token gathers ple_n_heads rows of a shared table.1042//   mixed_n = (t[p]*m[0]) ^ ... ^ (t[p-n+1]*m[n-1]);  row = mixed_n % vocab[h] + offset[h]1043// The hash runs host-side because ggml has no int64 and no xor. EOS resets the window.1044 1045class llm_graph_input_ple : public llm_graph_input_i {1046public:1047    llm_graph_input_ple(const llama_model_qwen4exp & pmodel,1048                        const llama_kv_cache_context * mctx) : pmodel(pmodel), mctx(mctx) {}1049    virtual ~llm_graph_input_ple() = default;1050 1051    void set_input(const llama_ubatch * ubatch) override;1052 1053    bool can_reuse(const llm_graph_params & params) override {1054        mctx = static_cast<const llama_memory_hybrid_idx_context *>(params.mctx)->get_attn();1055        return rows->ne[0] == (int64_t) pmodel.hparams.ple_n_heads * params.ubatch.n_tokens;1056    }1057 1058    ggml_tensor * rows = nullptr;   // I32 [ple_n_heads * n_tokens]1059 1060    const llama_model_qwen4exp & pmodel;1061 1062    // the predecessor tokens live in the attention KV cells (ext.tok)1063    const llama_kv_cache_context * mctx;1064 1065    // scratch, reused across set_input() calls1066    std::vector<llama_token> prev;1067};1068 1069void llm_graph_input_ple::set_input(const llama_ubatch * ubatch) {1070    const auto & hp = pmodel.hparams;1071 1072    // an image arrives as an embd batch, so ubatch->token is null, but every position still needs a row for ggml_get_rows1073    // stand in the image token id that the reference hashes, or EOS if the file has no such key1074    // gemma3n and gemma4 do the same with a hardcoded row 0 of per_layer_token_embd.1075    const llama_token img_tok = hp.ple_image_token_id != 01076        ? (llama_token) hp.ple_image_token_id1077        : (llama_token) hp.ple_eos_token_id;1078    auto tok_of = [&](int64_t k) -> llama_token {1079        return ubatch->token ? ubatch->token[k] : img_tok;1080    };1081 1082    const int64_t n_tokens = ubatch->n_tokens;1083    const int64_t n_gram   = hp.ple_ngram_size;1084    const int64_t n_heads  = hp.ple_n_heads;1085    const int64_t per_gram = hp.ple_heads_per_ngram;1086    const int64_t eos      = hp.ple_eos_token_id;1087    const int64_t n_prev   = n_gram - 1;1088 1089    std::vector<int32_t> idx(n_heads * n_tokens);1090 1091    GGML_ASSERT(mctx != nullptr);1092 1093    for (int64_t i = 0; i < n_tokens; ++i) {1094        // the preceding tokens would be ambiguous, see get_prev_tokens()1095        GGML_ASSERT(ubatch->n_seq_id[i] == 1 && "PLE n-gram embeddings do not support tokens shared by multiple sequences");1096    }1097 1098    // predecessors come from the KV cells (ext.tok); apply_ubatch() already stored this ubatch, so its own tokens count too1099    mctx->get_prev_tokens(*ubatch, n_prev, prev);1100 1101    for (int64_t i = 0; i < n_tokens; ++i) {1102        // an EOS in the window resets everything at or before it1103        // a missing predecessor (before the sequence start, or no cached cell) reads as EOS1104        // the EOS of the token itself does not cut its own context, as in the reference1105        std::vector<int64_t> ctx(n_gram);1106        ctx[0] = tok_of(i);1107        bool cut = false;1108        for (int64_t s = 1; s < n_gram; ++s) {1109            // predecessor s positions back; prev[] is oldest-first, missing entries are LLAMA_TOKEN_NULL1110            const llama_token t = cut ? LLAMA_TOKEN_NULL : prev[i*n_prev + (n_prev - s)];1111            cut = cut || t < 0 || t == eos;1112            ctx[s] = cut ? eos : t;1113        }1114 1115        for (int64_t n = 2; n <= n_gram; ++n) {1116            uint64_t mixed = (uint64_t) ctx[0] * hp.ple_layer_multipliers[0];1117            for (int64_t j = 1; j < n; ++j) {1118                mixed ^= (uint64_t) ctx[j] * hp.ple_layer_multipliers[j];1119            }1120            const int64_t base = (n - 2) * per_gram;1121            for (int64_t g = 0; g < per_gram; ++g) {1122                const int64_t h_i = base + g;1123                idx[i * n_heads + h_i] =1124                    (int32_t) (mixed % hp.ple_head_vocab_sizes[h_i] + hp.ple_head_offsets[h_i]);1125            }1126        }1127    }1128 1129    ggml_backend_tensor_set(rows, idx.data(), 0, idx.size()*ggml_element_size(rows));1130}1131 1132// Read a conv history out of its own recurrent row and write the new tail back.1133// The shared build_conv_state cannot do this: qwen4exp has two such rows per layer.1134ggml_tensor * llama_model_qwen4exp::graph::build_conv_state_at(1135        llm_graph_input_rs * inp,1136        ggml_tensor *        conv_states_all,1137        ggml_tensor *        x,1138        int64_t              state_cols,1139        int64_t              channels,1140        int                  il) {1141    const auto * mctx_cur = inp->mctx;1142 1143    const auto kv_head = mctx_cur->get_head();1144 1145    const int64_t n_seqs    = ubatch.n_seqs;1146    const int64_t row_total = conv_states_all->ne[0];1147 1148    // the row is exactly this convolution's state, so the gather is reused as a whole1149    GGML_ASSERT(state_cols * channels == row_total);1150 1151    auto it = rs_rows.find(conv_states_all);1152    if (it == rs_rows.end()) {1153        it = rs_rows.emplace(conv_states_all, build_rs(inp, conv_states_all, row_total, n_seqs)).first;1154    }1155    ggml_tensor * rows = it->second;1156 1157    ggml_tensor * state = ggml_reshape_3d(ctx0, rows, state_cols, channels, n_seqs);1158    cb(state, "conv_state_at", il);1159 1160    ggml_tensor * conv_input = ggml_concat(ctx0, state, ggml_transpose(ctx0, x), 0);1161 1162    // [TAG_RECURRENT_ROLLBACK_SPLITS] keep the last state_cols columns once per rollback slot,1163    // slot s ending s tokens earlier so a rollback of s tokens reads a history that never saw them1164    const size_t row_size = ggml_row_size(conv_states_all->type, row_total);1165    const uint32_t mem_size = mctx_cur->get_size();1166 1167    const int64_t n_slots = (int64_t) cparams.n_rs_seq + 1;1168 1169    for (int64_t slot = 0; slot < n_slots; ++slot) {1170        const int64_t s_idx = std::max<int64_t>(0, conv_input->ne[0] - state_cols - slot);1171 1172        ggml_tensor * tail = ggml_view_3d(ctx0, conv_input,1173                state_cols, channels, n_seqs,1174                conv_input->nb[1], conv_input->nb[2],1175                ggml_row_size(conv_input->type, s_idx));1176 1177        ggml_tensor * dst = ggml_view_2d(ctx0, conv_states_all,1178                state_cols * channels, n_seqs,1179                conv_states_all->nb[1],1180                (slot * mem_size + kv_head) * row_size);1181 1182        ggml_build_forward_expand(gf, ggml_cpy(ctx0, ggml_cont(ctx0, tail), dst));1183    }1184 1185    return conv_input;1186}1187 1188ggml_tensor * llama_model_qwen4exp::graph::build_inp_ple(1189        const llama_memory_hybrid_idx_context * mctx_hyb) {1190    const int64_t n_heads = hparams.ple_n_heads;1191 1192    // the attention cells see every ubatch regardless of the layer types1193    auto ple_inp = std::make_unique<llm_graph_input_ple>(1194            static_cast<const llama_model_qwen4exp &>(model), mctx_hyb->get_attn());1195 1196    ple_inp->rows = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, n_heads * n_tokens);1197    ggml_set_input(ple_inp->rows);1198    ggml_tensor * rows = ple_inp->rows;1199    res->add_input(std::move(ple_inp));1200 

Showing the first 1,200 of 1298 lines. Download the file for the rest.