model: add openPangu-2.0-Flash (92B-A6B) with MLA-latent cache, DSA/SWA, mHC, and multi-head MTP (#2065)

* openpangu: Stage-1 converter probe for openPangu-2.0-Flash

Add OpenPanguV2ForCausalLM conversion support (converter-only; runtime graph
is Stage-2). Registers a new LLM_ARCH_OPENPANGU on the Python/gguf-py side:

- gguf-py/constants.py: MODEL_ARCH.OPENPANGU + name, indexer KV keys, 22 new
  tensor enums (DSA indexer x4, MoME convs x3, param-sink x2, mHC/Hyper-
  Connections x12, block-post-norm), and the full MODEL_TENSORS list reusing
  the deepseek MLA + MoE + NextN bricks.
- tensor_mapping.py: arch-specific block mappings that disambiguate the
  sandwich norms (post_attention/pre_mlp/post_mlp) and pin every Pangu-only
  tensor; non-block global mHC merge module.
- convert_hf_to_gguf.py: OpenPanguV2Model (subclasses DeepseekV2Model) with
  set_gguf_parameters (MLA/MoE/indexer/mHC/param-sink/DSA+SWA metadata),
  modify_tensors (expert merge, kv_b split, no MTP skip), and the
  OpenPanguV2Tokenizer pre-tokenizer hash.

Validated offline against the real 50-shard safetensors index: all 37,587
tensors map to a GGUF target (0 unmapped), and set_gguf_parameters reads only
hparams present in config.json. No weights downloaded; no GPU. Pinned on the
ik/dsa_loop_hadamard_blend DSA substrate.

* openpangu: Stage-2 arch scaffold (LLM_ARCH_OPENPANGU) — loadable, compiles

New arch on main (DSA-decoupled). Declares openPangu-2.0-Flash to the runtime so
the model loads into memory; the compute graph is the next step.

- llama-arch.{h,cpp}: LLM_ARCH_OPENPANGU + name; 3 KV keys (mhc_num_stream,
  mhc_recur_norm, param_sink_number); 18 tensor enums (mHC x12, MoME conv x3,
  param-sink x2, block-post-norm).
- llama-model.cpp: OPENPANGU tensor-name block, strings matched to the converter.
- llama-model.h: layer + model struct fields (mHC / conv / sink / block-post / merge).
- llama-hparams.{h,cpp}: reader (MLA + MoE + sigmoid gate + indexer + mHC +
  param-sink + NextN); n_layer_kv_from_start = n_layer - nextn (MTP skipped).
- llama-load-tensors.cpp: create_openpangu_tensors (GLM-DSA MLA/MoE base + Pangu
  tensors; indexer loaded-but-unused for dense fallback); dispatch + is_mla_attn.

Builds clean (CPU-only libllama). Dense-fallback design: no DSA indexer / SWA
windowing / MTP for first generation (exact <=512 tokens). Graph is Stage-2b.

* openpangu: fix compresskv_conv dim (kv_lora_rank, not +rope); pin attention order in spec

* openpangu: end-to-end runtime — build_openpangu graph runs, generates (garbled)

First full forward pass of openPangu-2.0-Flash on ik_llama. Pipeline works end to
end: new LLM_ARCH_OPENPANGU loads the Q4 GGUF, the graph executes, and llama-cli
generates 40 tokens (EXIT=0). Output is currently garbled (tensor-layout bug to
debug), but the structure is proven.

graphs/build_openpangu.cpp: dense decompressed-MHA attention + 4-stream mHC
(Hyper-Connections) with 20-iter Sinkhorn + MoE(sigmoid+shared) + sandwich norms
+ entry stream-repeat/tail-merge + inp_out_ids selection.

Bring-up fixes to load+run:
- llama-vocab.cpp: register 'openpangu' pre-tokenizer (QWEN2 family)
- llama.cpp: OPENPANGU -> LLAMA_ROPE_TYPE_NORM (was defaulting to NONE=-1)
- llama-load-tensors.cpp: full wkv_b load; k_b/v_b as flattened 2D; block_post_norm
  dim = S*H (10240); conv weights 2D {3,C}; mHC alpha/beta/gamma + param_sink +
  merge params use bare (no-.weight) tensor names
- llama-model.cpp: OPENPANGU is NOT is_mla_attn (decompressed MHA, standard KV cache)
- graph loop bounded to base layers (skip NextN/MTP)

v0 deferrals (need conv-state cache / manual attention path, all documented):
MoME convs (passthrough), o_conv, param_sink. Next: fix the layout bug to coherence.

* openpangu: COHERENT generation — NEOX rope, Sinkhorn orientation, MoME convs, param_sink

Four correctness fixes on top of the end-to-end scaffold, verified checkpoint-by-
checkpoint against a Python golden reference running on the GGUF's own dequantized
weights (block-0 activations now match to rounding at full fidelity):

- rope: NORM -> NEOX. Pangu config rope_interleave=false; the Infer source maps it
  as is_neox_style = not rope_interleave (rotary_mode='half').
- mHC Sinkhorn: the flat h_res block is torch-[r,c] row-major, so a bare ggml
  reshape lands column-fastest; the doubly-stochastic iteration ran transposed
  (Sinkhorn is not transpose-symmetric). One transpose at input fixes the whole
  chain including the mhc_post application.
- MoME convs (qa/compresskv/o): were passthrough stubs. Implemented as
  out = x + causal_conv1d(x) (every Infer call site uses residual_connection=1;
  tap stats confirm the perturbation form). Taps cast f16->f32 for ggml_mul.
  Batch-local v0: exact for fresh-sequence prefill; decode steps miss the
  t-1/t-2 taps until a conv-state cache exists.
- param_sink: 128 learned latent-KV entries prepended per layer via a manual
  attention path (kv_store + explicit soft_max over [sinks ++ cache]); huge
  effect at short context. o_conv now applied pre-o_proj on the same path.
  flash_attn forced off for OPENPANGU (FA kernel cannot see the sinks).
- converter: add_bos_token=true (HF prepends <|pangu_text_start|> via the
  post-processor; the key was absent so ik dropped BOS).

Greedy Q4_K_M smoke, chat template + <think>: coherent CoT reasoning and a
correct answer. Layer-0 instrumentation (opg0_* names) kept for now.

* openpangu: MoME conv-state cache — decode steps get real t-1/t-2 taps

Allocate a per-layer cache_s_l tensor for OPENPANGU base layers holding the last
two pre-conv latents of the three MoME sites, packed
[qa 2*1024 | compresskv 2*512 | o 2*6144] f32 (~60KB/layer). The conv helper
reads the [C,2] history window (zeros at sequence start, kv_head==0), builds
xx = [hist ++ x], and writes the last two columns back each ubatch — the concat
naturally handles both prefill chaining and the T==1 shift. Read precedes write
in graph order; the fixed-offset copy is graph-reuse safe.

Verified: prefill anchors unchanged (bit-path identical, zero-history branch);
-ub 1 token-by-token run matches the golden reference at t4 (qlora_conv 0.084,
R_block 0.008 rel; attn_out 0.15 on one channel = f16 KV-cache rounding, washes
out by post-norm); final logits differ from full-batch only by a common-mode
shift that softmax cancels. Chat-template greedy smoke: think-block repetition
is gone — clean structured CoT and correct answer.

v0 limits documented in the helper: one state slot (single sequence); cache
rewinds leave the state stale.

* openpangu: NextN/MTP speculative decoding — 1.7-1.8x TG on CPU

Wire the three NextN layers (46-48) into ik's MTP speculative framework
(--spec-type mtp). v0 drafts with head 1 (layer 46), self-chained by the
framework.

- llama.cpp: add OPENPANGU to the cparams.mtp arch allowlist (it was silently
  zeroed, which left the target context without a logits buffer once the server
  enabled embeddings -> GGML_ASSERT(lctx.logits) in speculative_is_compat).
- load-tensors: MTP layers carry no mHC tensors (tail_use_mhc=false in the
  reference) — create them only for base layers. nextn.* tensors were already
  wired by the Stage-1 probe.
- build_openpangu: extract the attention sublayer into
  build_openpangu_attention (shared base/MTP); add build_openpangu_mtp:
  eh_proj(cat(enorm(embed), hnorm(prev_hidden))) -> one plain-residual Pangu
  block (sandwich norms, convs+param_sink, MoE+shexp, no mHC/block_post_norm)
  -> shared_head norm+head. MTP branch returns the draft graph when
  mtp_op_type != NONE; main graph keeps all-token outputs under cparams.mtp.
  MTP convs run batch-local (no conv-state slot) — affects acceptance only.

A/B (Q4_K_M, CPU, greedy, 192-token chat CoT completion, warm back-to-back,
medians of 3, bracketed B/A/B):
  no-spec:              2.44 t/s  (2.34-2.86)
  --spec-type mtp:n_max=3: 4.23 / 4.49 t/s  (brackets)  => ~1.7-1.8x
Draft acceptance 34% on CoT prose (46% on repetitive text); spec and no-spec
greedy outputs are byte-identical. Headroom: conv-state for MTP drafts, n_max
tuning, true 3-head chaining (spec_step_idx).

* server: include draft_n/draft_n_accepted in /completion timings

get_formated_timings() (the /completion path) omitted the speculative
counters that get_timings() (the OAI path) already reports; add them,
guarded by n_draft_total > 0 like the OAI path.

* openpangu: position-indexed MoME conv-state ring — rollback-safe spec decoding + MTP draft chaining

The v0 single-slot conv state held the last-2 pre-conv latents of the most
recent batch, so any speculative draft rejection left latents of REJECTED
positions in the state and every later decode ran with wrong t-1/t-2 taps
(3 conv sites x 46 layers). At 192-token greedy runs every spec config
diverged from no-spec, each differently (rejection-pattern dependent).

Replace it with a per-layer ring cache_s_l [n_lora_q+n_lora_kv+n_head*v_dim, 16]:
column pos%16 holds position pos's pre-conv latents ([qa|ckv|o] packed).
Invariant: reads target only positions before the first batch token, which
are committed, and committed latents depend only on the committed prefix -
rollback-safe by construction, no checkpointing. Writes cover the last
min(T,16) batch positions in <=2 contiguous cpy segments; the copy sources
are views of the [hist ++ x] concat so the history read is an ancestor of
every write (read-before-write by graph dependency).

The ring is also allocated for the NextN/MTP layers, so the draft head
chains real conv taps across WARMUP -> sequential DRAFT_GEN steps (was
batch-local zero-history per draft token).

graph_reuse is forced off for the arch: ring view offsets are position-
baked and the reuse patcher only updates the standard K/V-store copies.
Measured cost on the CPU server path: none visible. ggml_set_rows driven
by an input index tensor is the future reuse-safe shape.

Verified (Q4_K_M, CPU, greedy 192-tok chat-CoT, warm single process):
- no-spec output byte-identical to pre-ring build
- spec output byte-identical to no-spec below the n_predict cap, for all
  of n_max in {1,2,3,4,6} x p_min in {0,0.3,0.6} (old build: all diverged)
- acceptance n3-p0: 33.9% -> 60.9%; n3-p0.3: 58.1% -> 68.9%
- TG medians: no-spec 3.19-3.32 t/s; mtp:n_max=3,p_min=0.3 6.97 t/s (~2.1x)

* openpangu: DSA lightning indexer + SWA schedule — long-context correctness past the dense fallback

The dense fallback was exact only <=512 tokens (SWA window). This wires the real
DSA/SWA hybrid schedule, self-contained from GGUF keys the converter already
writes (openpangu.swa_layers + sliding_window_list; absent keys keep the old
dense fallback):

- SWA layers (30 base @512): the generic inp_KQ_mask_swa path, per-layer mask
  choice in the builder. The NextN/MTP layers are SWA @2048 in the checkpoint
  schedule; MTP graphs run in their own context, so the mask fill picks
  hparams.n_swa_mtp when built with an MTP op type.
- DSA layers (16, every 3rd): lightning indexer implemented in-graph from the
  Infer reference semantics (jointfix _pangu_torch_calib): q_idx = wq_b on the
  post-conv post-norm q-lora latent (24x128), k_idx = rms-normed wk(x) shared
  across heads, both NEOX-roped on the FIRST n_rot channels; score =
  sum_g w_g * relu(q_g . k) in f32, causal-masked, exact top-k via
  argsort + ggml_set_rows scatter into a -1e30 base -> additive selection mask
  on the existing manual soft_max seam. Selection engages only when the causal
  window exceeds index_top_k (2048); below that the layer is exactly dense.
- Indexer keys cached per position (cache_idx_l, f32 [128, kv_size], DSA layers
  only) with the same committed-position invariant as the conv-state ring, so
  speculative rollbacks stay safe.
- param sinks remain outside both the window and the selection budget, matching
  the reference.

Verified (Q4_K_M, CPU):
- <=512 tokens: byte-identical to the dense build (96/160-token greedy)
- indexer scores vs a GGUF-dequant golden reference at 2101 tokens: 1e-3 rel
  (f16 weight rounding); top-3 selection indices exact on all compared queries
- >512 coherence clean; 3.4K-token needle retrieval through active selection
  (needle outside every SWA window, ~1300 positions pruned) answers exactly

* openpangu: MLA-latent KV cache — attention absorbed into the 512-latent, 14x smaller cache, ~2.2x TG

Store per position only [ckv_norm 512 | roped k_pe 64] (f32, k_l) plus the
transposed 512-latent (f32, v_l, v_trans layout); per-head K/V are never
materialized. q_nope is absorbed through attn_k_b (loaded 2D from the
converter split for base layers; derived at load via llm_prepare_mla for the
NextN layers - now guarded for layers without attention weights, e.g. the
idle NextN heads 2/3). The value side is the latent itself, up-projected
through attn_v_b after the weighted sum, matching the Infer _forward_dsa
reference. param sinks are native latent-space entries, which removes the
per-step full-cache concat+cast that dominated long-context decode.

llama_state row sizes now come from llama_kv_k_row_embd/llama_kv_v_row_embd
(arch-aware), fixing an out-of-bounds crash in the server prompt-cache save
path (hparams-derived 9216-wide rows vs actual 576-wide latent rows).

Verified (Q4_K_M, CPU): layer-0 attention output matches an f32 golden
reference computed from the same GGUF weight encodings (~1e-2 on O(1)
values); MTP spec output byte-identical to no-spec; 3.4K needle retrieval
through active DSA selection exact under greedy. Output differs from the
materialized build at the token level because attn_k_b/attn_v_b are
independently quantized tensors - both are legitimate Q4-fidelity encodings.

Perf (CPU, warm): no-spec TG 3.2-3.3 -> 6.9-7.1 t/s; mtp:n_max=3,p_min=0.3
-> 11.1 t/s (byte-exact, 67% acceptance); prefill 30.5 t/s at 3.4K; KV self
size at 4K ctx: 5.5 GiB -> 391 MiB. Not yet supported on the latent cache:
K-shift/defrag (context shifting) - unreached in current usage.

* openpangu: fence unsupported serving modes, truth-pass comments, drop dead weight/keys

Post-audit hardening. The cache's position-indexed side state (MoME conv ring,
DSA indexer keys) made several generic serving paths silently unsound; they are
now fenced loudly instead of documented as unsupported:

- s_l_position_ring flag on llama_kv_cache: the qnext-state predicate no longer
  claims the conv ring, so per-seq state save, seq_cp and the s_copy graph skip it
- state save/restore refused for the arch at every llama_state_* entry (the ring
  and idx_l are not in the state format; restoring without them diverges silently)
- K-shift/self-extend assert, defrag skips with a warning, server ctx_shift off
  via new llama_model_supports_ctx_shift()
- single sequence enforced at context creation (n_seq_max > 1 refused)
- server prompt-cache reuse limited to pure extension via new
  llama_model_supports_partial_kv_reuse(): mid-cache divergence reprocesses from
  scratch (the 16-column ring cannot rewind); multi-turn continuation stays fast
- MTP draft length clamped to 13 via new llama_model_max_draft_tokens() so a
  rejected draft can never overwrite the ring columns the next decode reads
- K/V cache types forced to f32 for the arch so the KV size log reports the truth
- cache_size(): real latent-cache branch (was falling through to the ~14x larger
  materialized estimate used for offload planning)
- unused fused wkv_b no longer loaded (TENSOR_SKIP; the graph runs entirely on the
  pre-split k_b/v_b), llm_prepare_mla openPangu special-case removed (it was a no-op)
- stale v0 comments rewritten to describe the shipped graph; converter stops
  writing dead keys (dsa_layers, block_post_layernorm_idx) and the tokenizer
  pre-hash is registered in convert_hf_to_gguf_update.py

Gates on this build: greedy spec output byte-identical to no-spec (EOS-terminated,
sha-equal); 3.4K needle retrieved exactly; -np 2 / state save / n_max=20 / stale
prefix reuse all refused or clamped with clear messages.

* openpangu: assert kv_head == first batch position at graph build

The ring, indexer and latent stores are addressed by absolute position through
kv_head; the fences make append-only decode the only reachable mode, but the
invariant was unchecked. Assert it at both graph entries (base and MTP) so any
future cache plumbing that breaks it fails at build instead of corrupting
output. Worst-case measurement builds pass pos = null and are exempt.

* openpangu: cont h_pre before the mHC broadcast mul (CUDA binbcast misreads strided views)

h_pre is a row-slice view of the fused mixes tensor. The CPU mul handles the
strides; the CUDA broadcast path reads the view as if contiguous, so token 0
mixes correctly and every later token gets h_post/h_res rows instead. First
divergent node in the whole graph (oracle rel 0.36 at opg0_attn_mhcpre_x,
fixed to 7.5e-5). Sibling views h_post/h_res were already cont-wrapped, which
is why only h_pre was exposed.

* openpangu: keep DSA zero-trick sources finite (CUDA clamp propagates the 0*(-inf) NaN)

The selection-mask base and zeros were built by scaling the MASKED scores by
zero, but post-mask sc contains -inf and 0 * -inf = NaN. The CPU clamp launders
NaN back to -1e30 (fminf/fmaxf ignore NaN); the CUDA clamp propagates it, so
every DSA layer emitted NaN masks at n_kv > top_k and logits collapsed
(observed: eval-callback CLAMP sum -1.3e36 on CPU vs nan on CUDA, 11748 NaNs
downstream). Scale the pre-mask finite scores instead, which is correct on any
backend regardless of clamp NaN semantics. Also defensively cont the strided
KQ_mask slice feeding the score add (same strided-view kernel class as the mHC
h_pre fix; unproven here but cheap). Gates after fix: 2600-token probe coherent,
3.4K needle exact ('7391') with and without MTP speculation, PP ~120 t/s.

* openpangu: f16 latent KV cache option (explicit -ctk/-ctv f16 halves cache memory, f32 stays default)

Track explicit cache-type requests through CLI/env; openPangu resolves no-request
to f32 (unchanged), accepts explicit f32/f16, warns and falls back to f32 for
BF16/quantized. Sink and cached-token KQ paths stay separate until after KQ so
the latent cache is read directly without the f32-only concat; value is the sum
of the sink and cache matmuls. Ring and DSA indexer caches stay f32; cache_size()
follows the resolved types.

* openpangu: enable graph reuse

* openpangu: wire multi-head MTP drafting

* openpangu: add MTP heads override

* openpangu: keep MTP update logits last

* openpangu: scope MTP warmup heads

* speculative: apply per-request MTP heads before warmup

* openpangu: fix multi-head MTP warmup computing on unwritten inputs

Each chained head called the build_inp_* helpers itself, so the warmup and
update graphs held one inp_tokens/inp_pos/inp_out_ids/KQ_mask tensor per
head while llama_set_inputs only fills the tensors the lctx pointers
reference, i.e. the last head's copies. Every head but the last read
unwritten compute-buffer memory: with heads=3 active even head 1's ring,
latent cache, and cached one-token draft were computed from garbage, which
is why depth-1 acceptance measured 4% against 98% for the heads=1 control.

Create the batch inputs once in build_openpangu and pass them to every
build_openpangu_mtp call, and fix the two chaining errors that were hiding
behind the garbage inputs:

- Shift the chained hidden: head k+1's row at position p consumes head k's
  output row at p-1, the same convention head 1 uses for the target's
  conditioned hidden rows. The predecessor of a batch's first row lives in
  the previous warmup/update, carried across decodes through a new
  inp_mtp_carry input backed by lctx.mtp_carry (written back per ubatch,
  zeroed when a prompt warmup restarts from position 0).
- Fill head 3's cache row at draft step 2: each draft step runs one head,
  so head 3's own decode at step 3 attended over a never-written row at
  the step-2 position. Pre-write it from the committed carry.

Also include the active head count in the graph-reuse key next to the
existing step index (reuse stays forced off for this arch).

* speculative: default MTP drafting to a single head

A stage without an explicit heads= override previously resolved to 0,
meaning all model heads, so multi-head drafting was silently on by
default for models that carry more than one NextN layer. Keep it opt-in
(heads=N or heads=0 for all) until multi-head measures a win over the
single-head config; single-head models are unaffected either way.

* speculative: fence MTP head upshift over a warmed prefix

Deeper NextN heads only hold valid cache rows for spans that were warmed
with them. A request drafting with more MTP heads than the cached prefix
was warmed with (e.g. a heads=1 conversation continued with heads=3, a
pure extension the divergence fence deliberately allows) would read
never-written deeper-head rows: verification keeps the output correct,
but acceptance quietly collapses and any measurement taken there is
misleading.

Track the minimum head count the committed context has been warmed with
since position 0 and have the server reprocess from scratch when a
request asks for more. Also announce the model's NextN head count and
the single-head default once at MTP context setup.

* openpangu: skip dead MTP chain compute and stall-free carry readback

The update chain's last head and the draft-time row fill only matter for
their latent-cache and conv-ring writes; their FFN, norms, and shared
head fed nothing. Add a cache-writes-only mode to the MTP block builder
that returns after the attention block, and use it at both sites.

The multi-head carry readback previously synchronized the scheduler
after every warmup/update decode, a hard stall on CUDA. Issue the
device-to-host copy async on the backend stream instead (stream order
protects the source buffer from later graphs) and synchronize lazily
when the host buffer is next consumed or resized.

* openpangu: stop emitting fused kv_b tensor

* openpangu: default latent cache to f16

* openpangu: refuse unsupported latent cache types

* Window OpenPangu SWA cache reads

* Gather OpenPangu DSA decode reads

Gather DSA decode attention over the selected latent rows for OpenPangu base-model decode and verify graphs. The gathered branch now uses ggml_top_k order directly, runs maskless softmax over sinks plus selected rows for T <= 14, and derives values from the gathered k_l rows instead of the transposed latent cache.

* Chunk OpenPangu indexer prefill scoring

* Chunk OpenPangu prefill attention

* Gather OpenPangu sparse prefill attention

* Drop OpenPangu value cache

* Add OpenPangu indexer cache type flag

* Add OpenPangu q8_0 latent cache type

Store the OpenPangu MLA latent K cache as q8_0 via -ctk q8_0 (about 0.53x of
f16); the default stays f16 so behavior is unchanged without the flag. Latent V
stays f16/f32.

The q8 latent cache is a storage format only: it is dequanted to F32 before all
compute. K reads go through openpangu_build_k_latent_for_read, V derivation
through openpangu_build_v_latent_from_k (full 576-wide row to F32, then slice),
and the DSA gather paths already dequant via get_rows. Feeding a q8 latent view
directly into the KQ mul_mat corrupts large-context prefill, so that path is
removed for quantized caches. The cache write stages ckv and kpe through F32 and
writes one full 576-wide q8 row per token.

Verified on a small discriminator model: the default f16 path is byte-identical
to the prior code; the first-DSA-layer attention envelope is within 0.6% of the
f16 cache (linf_rel 0.0057); top-k selection is bit-identical between cache
types; the q8 latent cache is 0.531x the f16 size at 8K and 32K context; and
generation stays coherent on both the dense and DSA-gather paths at all tested
context lengths.

* Remove OpenPangu debug trace env knobs and redundant DSA_TOPK override

Drop the five LLAMA_OPENPANGU_*_TRACE debug-logging knobs (DSA_GATHER_TRACE,
IDX_CHUNK_TRACE, ATT_CHUNK_TRACE, PREFILL_GATHER_TRACE, SWA_WINDOW_TRACE) and the
LLAMA_OPENPANGU_DSA_TOPK override, which duplicated the -dsatk / --dsa-top-k CLI
flag; top-k now comes solely from cparams.dsa_top_k. The five perf-tuning knobs
(DSA_GATHER, IDX_CHUNK, ATT_CHUNK, ATT_KQ_MAX_MIB, PREFILL_GATHER) are retained
pending the perf battery. No change to default behavior.

* Subchunk OpenPangu DSA prefill gather to fit CUDA grid limit

The prefill gathered-attention ggml_get_rows produced dst rows = topk *
token_chunk (2048 * 256 = 524288) mapped to the CUDA grid.y dimension, which
caps at 65535, crashing with GET_ROWS invalid argument at long context (N_KV
around 10.5K with the natural topk of 2048). Split the prefill gather into token
subchunks so topk * subchunk_tokens stays within the grid limit, and guard the
decode gather with the same fit check (falling back to the dense masked path if
a pathological topk would not fit). The subchunking is over the token dimension
only, so per-token attention is unchanged and the result is numerically
identical. Verified: the GPU sweep runs past the old crash boundary to 22K+ with
zero CUDA errors; CPU and -ctk q8_0 paths unaffected.

* openpangu: fix scheduler node budget for chunked DSA prefill; drop unused attn_kv_b; remove env tunables

- Size the scheduler graph node budget for the chunked DSA prefill so 32K/ub2048 no
  longer trips the hash-set reservation assert; derive the extra budget from the
  builder's chunk/top-k/window structure with a fixed safety margin.
- Remove LLAMA_OPENPANGU_* environment tunables from both the node-budget estimator
  and build_openpangu.cpp; use fixed constants in both so they stay in sync.
- Converter: emit only the split attn_k_b/attn_v_b projections and drop the unused
  fused attn_kv_b tensor.

* openpangu: restore DeepSeek converter kv_b; drop trace env + dead code; fix dense-fallback node budget

- convert_hf_to_gguf.py: restore fused attn_kv_b in DeepseekV2Model (shared
  parent); openPangu subclass keeps split-only k_b/v_b. Stops newly-converted
  DeepSeek GGUFs from failing to load.
- src/llama.cpp: remove LLAMA_GRAPH_REUSE_TRACE getenv, hit/miss counters, and
  the unconditional destructor log (no getenv or behavior change for any arch);
  node-budget estimator now covers the dense-fallback (n_swa==0) attention-chunk
  loop while skipping absent idx/top-k terms, preserving a strict overcount;
  remove unreachable openPangu split-cache block.
- src/llama-context.h: drop now-dead graph_reuse_hits/misses members.
- include/llama.h: move type_k/type_v/idx_type_k *_explicit bools to struct end
  to avoid a mid-struct ABI shift for out-of-tree consumers.
- src/graphs/build_openpangu.cpp: replace vestigial env-struct singletons with
  the OPENPANGU_* constants; drop a redundant Sinkhorn permute round-trip
  (one transpose; greedy output verified byte-identical).

Decode output unchanged (byte-identical greedy generation verified); shared-file
changes are openPangu-gated or restore the pre-PR baseline.

* openpangu: chat-parser support (reasoning split + thinking toggle)

Two openPangu-only fixes, both gated on the arch-unique token
<|pangu_text_start|> so no other model's parsing changes.

- chat-diff-analyzer: add a workarounds entry that force-sets TAG_BASED
  reasoning with an empty start and a </think> end. openPangu prefills
  <think> in the generation prompt, so the output is delimited only by
  </think>; the differential detector otherwise learns start="<think>"
  from the assistant-history form and fails to split, leaking reasoning
  into content. Same shape as the existing Laguna prefill patch.

- chat.cpp: bridge enable_thinking to the template's `thinking` variable.
  openPangu's template gates reasoning on `thinking` rather than the
  ecosystem-standard `enable_thinking`, so the standard toggle was inert.
  An explicit `thinking` chat_template_kwarg still overrides via the
  extra_context merge.

Blast radius: test-chat-auto-parser 437/437 unchanged; the sole
test-chat-template diff is a pre-existing GLM trailing-newline.

* openpangu: use ggml_cast for latent dequant reads

Replace ggml_cpy(view, ggml_new_tensor_2d(F32, ...)) with ggml_cast in the MLA
latent V-from-K and K-read helpers. ggml_cast emits the identical GGML_OP_CPY
node into a fresh f32 tensor, so behavior is unchanged; it is the idiomatic
form. Per review.

* openpangu: narrow SWA reuse-key fields to 32-bit

The openpangu_swa_window_view reuse key stored n_kv/n_tokens/window/pad as
int64_t, but these are bounded well under 2^31 (window/pad are uint32_t at
source; n_kv/n_tokens <= context length). Narrow to int32_t/uint32_t and drop
the widening casts. w_view/win_off stay int64_t: they feed ggml view
dims/offsets. Per review.

* openpangu: precompute param_sink derived tensors at load

The per-layer attention-sink block (sink_blk [576,NS]) and its transposed
latent (s_lat_t [NS,512]) are pure functions of the layer weights, yet were
rebuilt every eval across all 49 layers (RMS-norm + cast + concat + transpose).
Compute them once at load, mirroring the wk_b derived-weight precompute, and
read the stored tensors in build_openpangu_attention. Numerically identical;
removes per-token work at decode.

* openpangu: replace conv position-ring with ggml_ssm_conv + spec-rollback checkpoint

Migrate the MoME depthwise causal conv (three sites per attention sublayer:
qa-lora, compressed-kv, attn-out) from the bespoke 16-column position-indexed
ring onto the core ggml_ssm_conv op with a recurrent conv-state slot.

Cache: s_l becomes [2*conv_col_ne, qnext_state_slots], holding the (d_conv-1)=2
history taps per channel for the three sites (float offsets 0 / 2*n_lora_q /
2*(n_lora_q+n_lora_kv)). Drops the conv_hist_idx / conv_write_idx graph inputs
and their fill in llama_set_inputs; adds one single-sequence sq input for
ggml_ssm_conv shared across the three sites and the MTP head.

Speculative rollback: the position ring self-healed rejected draft columns by
absolute position; a recurrent slot does not, since seq_rm is a no-op for
recurrent state. openPangu is admitted at the three spec-checkpoint save/init
gates so the whole-slot shadow checkpoint (gpu-fallback) snapshots the conv
slot before drafting and restores it before the accepted-token replay. The
restore path is already keyed on ckpt.valid, so no gate change is needed there.
Per-step checkpoint mode is declined for openPangu, which has no SSM recurrent
term, so auto mode resolves to the whole-slot shadow.

Gated: non-spec needle unchanged; MTP-spec needle correct with healthy draft
acceptance (rollback verified via the acceptance canary).

* openpangu: single ggml_concat copy for the latent cache store

The non-quantized latent store split the [ckv | roped k_pe] row into two views
and two cache copies, with a base_offset field on the CacheCopy struct to place
the second one. Match the quantized path: concat the two parts and do one copy
into the cache row. This drops the second cache-copy slot (OPENPANGU_COPY_K_KPE)
and removes base_offset from CacheCopy entirely.

Cache contents are unchanged: the concat writes the same [ckv 512 | k_pe 64]
bytes to the same row. Gated on the needle for both the f16 latent path (the one
that changed) and the q8 latent path, plus coherence.

* openpangu: reuse the shared kr_l indexer cache instead of a separate idx_l

The DSA lightning indexer stored its per-position keys in an openPangu-only idx_l
cache, parallel to the kr_l indexer cache GLM-DSA already uses. Both have the same
storage contract: [indexer_head_size, kv_size], idx_type_k dtype, one row per KV
cell, written at kv_head and read [dim, n_kv] from zero. openPangu now allocates
its indexer keys into kr_l and shares the dsa_cache_copies graph-reuse fixup.

The fixup patch is factored into a helper that both the generic path and the
openPangu update_cache_copies branch call, so the openPangu indexer copy is
repointed to the current kv_head on graph reuse like every other cache write.
This drops the idx_l vector, its allocation and memory accounting, and the
openPangu third cache-copy slot (now one latent copy per layer).

Per-arch allocation predicates stay separate (GLM uses indexer_is_full, openPangu
uses the window==0 DSA schedule); only the kr_l storage and the copy fixup are
shared. openPangu keeps its no-shift/no-defrag/no-state-I/O behavior, and the GLM
Hadamard/k-shift logic stays GLM-gated.

Gated: needle correct on f16 and q8 latent caches and under MTP speculation
(acceptance unchanged at 0.67), plus coherence.

* openpangu: discard pos-0 graphs from reuse; retire stale conv-state comments

The ggml_ssm_conv refactor bakes the pos-0 conv-state reset into graph
topology (a scale-by-zero node on the state view). A graph built at pos 0
could be reused at pos > 0 when the batch shape and padded n_kv match (a
1-token prompt followed by TG is the concrete case), zeroing the conv
history on every reused decode. Admit openPangu at the existing
reset_previous gate so pos-0 graphs are discarded from reuse, the same
guard the qnext recurrent state relies on.

Also retire the internal phase-plan comments the conv refactor left
behind: they claimed the spec-checkpoint wiring had not landed in the
commit that landed it, and misdescribed the s_l slot as awaiting rollback
support.

Gated: needle 8457 on f16 and q8 latent, MTP-spec needle (drafts fully
accepted), coherence.

* openpangu: drop the _explicit cache-type plumbing; validate unconditionally

Review follow-up (item 1 of the second review). The explicit/default
distinction carried less than claimed: the latent K/V fallback was f16,
which is already the -ctk/-ctv and API default, so distinguishing unset
from set-to-the-default bought nothing, and the two bools were behaviorally
redundant. The only load-bearing use was the indexer cache, where openPangu
defaulted to f32 while -ictk defaults to f16. Gating the f16 indexer
directly (needle on f16 and q8 latent paths, MTP speculation, coherence)
shows no quality difference, so openPangu now takes the standard f16
indexer default and the f32 special case is gone. Default indexer cache
memory halves (64 -> 32 MiB at c 8192).

Removes type_k_explicit/type_v_explicit/idx_type_k_explicit from llama.h,
the cparams/mparams plumbing, and common; the resolve helpers become plain
unconditional validators, so -ctk q8_0 is honored and an unsupported type
errors out at load instead of silently coercing.

Gated: needle 8457 on the new f16-indexer default, on q8 latent with MTP
speculation, and with -ictk f32 explicitly honored (64 MiB f32 buffer in
the load log); -ctk q4_0 and -ictk q4_1 refused with a clear error.

* openpangu: keep MTP draft decodes position-contiguous under speculation

The MTP framework's one-token draft shortcut caches a prediction one row
past the accepted prefix during the accepted-token update, then skips
re-decoding the last sampled token at the next draft round. A
mask-addressed cache tolerates the resulting position gap; openPangu's
position-addressed append-only cache (cell == position) does not: after a
rollback the next draft decode lands one cell behind its position, and
after a full acceptance the cache head sits one row ahead of the next
draft base, either way tripping the kv_head == pos[0] invariant and
aborting the server. The checkpoint admission in the conv refactor made
this the standard openPangu speculative flow; the needle-first gates
never generated enough draft rounds against a short prompt to reach it.

Decline the shortcut re-seed for openPangu in mtp_accept_batch (restoring
the drafting behavior all measured acceptance numbers were taken on) and
trim rows at or beyond the draft base in mtp_speculative_gen_draft, so
every draft decode stays position-contiguous with the cache head.

Gated: the crashing flow (short prompt, 512-token spec generation, then a
second request) completes with acceptance 0.60 prose / 0.87 code,
matching the pre-checkpoint baseline profile; needle 8457 plus coherence
on f16+spec and q8+spec.

* openpangu: remove stale ring limits and fix MTP graph reuse

* cli: preserve speculative carry on fallback

Decode an already-emitted pending token when a draft cannot be used instead of sampling unchanged logits and duplicating output. Document single-head MTP as the default and multi-head modes as experimental.

---------

Co-authored-by: Joel Farthing <262452229+joelfarthing@users.noreply.github.com>
This commit is contained in:
Joel Farthing
2026-07-11 12:29:20 +03:00
committed by GitHub
co-authored by Joel Farthing
parent 6a909f4ff6
commit b90939934a
33 changed files with 3073 additions and 107 deletions
+21
View File
@@ -130,6 +130,27 @@ static std::vector<std::function<void(const common_chat_template & tmpl, autopar
analysis.tools.function.close = "```";
LOG_DBG(ANSI_ORANGE "[Patch: DeepSeek-R1-Distill-Qwen]\n" ANSI_RESET);
}
},
// openPangu-2.0 - prefills <think> in the generation prompt (like the Laguna case above),
// so the generated reasoning starts immediately and is delimited only by </think>. The
// <think> is concatenated into a larger literal ('...assistant\n<think>'), so the
// standalone-literal reasoning detector does not pick it up; set the markers explicitly.
// Tool calls (<|tool_call_start|>[{...}]<|tool_call_end|>) are already handled by the auto-parser.
[](const common_chat_template & tmpl, autoparser & analysis) -> void {
if (tmpl.src.find("<|pangu_text_start|>") != std::string::npos) {
// Force-set (do not gate on mode==NONE): the differential detector sees the
// assistant-history form <think>reasoning</think> and sets start="<think>", but at
// generation time <think> is prompt-prefilled, so the output is delimited only by
// </think> (start=""). Same shape as the Laguna patch above.
analysis.reasoning.mode = reasoning_mode::TAG_BASED;
analysis.reasoning.start = "";
analysis.reasoning.end = "</think>";
if (std::find(analysis.preserved_tokens.begin(), analysis.preserved_tokens.end(), "</think>") ==
analysis.preserved_tokens.end()) {
analysis.preserved_tokens.push_back("</think>");
}
LOG_DBG(ANSI_ORANGE "[Patch: openPangu-2.0 thinking template]\n" ANSI_RESET);
}
}
});
+7
View File
@@ -781,6 +781,13 @@ static std::string common_chat_template_direct_apply_impl(
{"eos_token", tmpl.eos_token()},
{"enable_thinking", inputs.enable_thinking},
};
// openPangu's chat template gates reasoning on a `thinking` variable rather than the
// ecosystem-standard `enable_thinking`, so the normal toggle never reaches it. Bridge the
// standard control to it here so reasoning works on and off through `enable_thinking`. An
// explicit `thinking` chat_template_kwarg still wins (merged via extra_context below).
if (tmpl.source().find("<|pangu_text_start|>") != std::string::npos) {
inp["thinking"] = inputs.enable_thinking;
}
if (tools_override.has_value() || !inputs.tools.empty()) {
inp["tools"] = tools_override.has_value() ? *tools_override : inputs.tools;
}
+15 -3
View File
@@ -157,6 +157,9 @@ common_params_speculative common_params_speculative::with_stage_overrides(const
if (stage.has_p_min_override()) {
result.p_min = stage.p_min;
}
if (stage.has_mtp_heads_override()) {
result.mtp_heads = stage.mtp_heads;
}
if (stage.has_dflash_cross_ctx_override()) {
result.dflash_cross_ctx = stage.dflash_cross_ctx;
}
@@ -182,6 +185,7 @@ common_params_speculative common_params_speculative::with_stage_overrides(const
result.n_max = std::max(result.n_max, 0);
result.n_min = std::max(0, std::min(result.n_min, result.n_max));
result.mtp_heads = std::max(result.mtp_heads, 0);
result.stages.clear();
return result;
@@ -792,8 +796,8 @@ void gpt_params_parse_from_env(gpt_params & params) {
get_env("LLAMA_ARG_CONT_BATCHING", params.cont_batching);
get_env("LLAMA_ARG_HOST", params.hostname);
get_env("LLAMA_ARG_PORT", params.port);
get_env("LLAMA_ARG_CACHE_TYPE_K", params.cache_type_k);
get_env("LLAMA_ARG_CACHE_TYPE_V", params.cache_type_v);
get_env("LLAMA_ARG_CACHE_TYPE_K", params.cache_type_k);
get_env("LLAMA_ARG_CACHE_TYPE_V", params.cache_type_v);
get_env("LLAMA_ARG_MLOCK", params.use_mlock);
get_env("LLAMA_ARG_K_CACHE_HADAMARD", params.k_cache_hadamard);
get_env("LLAMA_ARG_V_CACHE_HADAMARD", params.v_cache_hadamard);
@@ -926,6 +930,13 @@ static void common_speculative_stage_apply_kv(
}
return;
}
if (key == "heads" || key == "mtp_heads") {
stage.mtp_heads = std::stoi(value_raw);
if (stage.mtp_heads < 0) {
throw std::invalid_argument("speculative stage mtp_heads must be >= 0");
}
return;
}
if (key == "cross_ctx" || key == "dflash_cross_ctx") {
stage.dflash_cross_ctx = std::stoi(value_raw);
if (stage.dflash_cross_ctx < 1) {
@@ -3320,7 +3331,8 @@ void gpt_params_print_usage(int /*argc*/, char ** argv, const gpt_params & param
" cpu serialise state via llama_state_seq; re-decode on rejection" });
options.push_back({ "*", "--spec-type SPEC[:k=v,...]", "canonical speculative stage entry; repeat for a supported two-stage chain.\n"
"types: none, draft, dflash, mtp, ngram-cache, ngram-simple, ngram-map-k, ngram-map-k4v, ngram-mod, suffix\n"
"canonical keys: n_max,n_min,p_min,cross_ctx,ngram_size_n,ngram_size_m,ngram_min_hits,suffix_min_match_len,suffix_max_depth,suffix_corpus\n"
"canonical keys: n_max,n_min,p_min,heads,cross_ctx,ngram_size_n,ngram_size_m,ngram_min_hits,suffix_min_match_len,suffix_max_depth,suffix_corpus\n"
"MTP heads: heads=1 is the default; heads>1 and heads=0 (all model heads) are experimental\n"
"for comma-bearing string values, quote the value inside the stage payload for normal shell use\n"
"if argv is passed directly without shell unescaping, the parser also accepts escaped commas as \\,\n"
"examples: --spec-type mtp:n_max=1,p_min=0.0\n"
+3
View File
@@ -172,6 +172,7 @@ struct common_speculative_stage_params {
int32_t n_max = -1;
int32_t n_min = -1;
float p_min = -1.0f;
int32_t mtp_heads = -1;
int32_t dflash_cross_ctx = -1;
uint16_t ngram_size_n = 0;
@@ -185,6 +186,7 @@ struct common_speculative_stage_params {
bool has_n_max_override() const { return n_max >= 0; }
bool has_n_min_override() const { return n_min >= 0; }
bool has_p_min_override() const { return p_min >= 0.0f; }
bool has_mtp_heads_override() const { return mtp_heads >= 0; }
bool has_dflash_cross_ctx_override() const { return dflash_cross_ctx >= 0; }
bool has_ngram_size_n_override() const { return ngram_size_n > 0; }
bool has_ngram_size_m_override() const { return ngram_size_m > 0; }
@@ -218,6 +220,7 @@ struct common_params_speculative {
int32_t n_max = 16; // number of tokens to draft during speculative decoding
int32_t n_min = 0; // minimum number of tokens to draft during speculative decoding
std::vector<common_speculative_stage_params> stages; // explicit stage chain for single-spec or self-spec + model fallback
int32_t mtp_heads = 1; // MTP heads to use; 1 is the default, while >1 and 0 (all model heads) are experimental
int32_t dflash_cross_ctx = 512; // target-feature context window for DFlash
float p_split = 0.1f; // speculative decoding split probability
+112 -6
View File
@@ -24,6 +24,8 @@
#define SPEC_VOCAB_CHECK_START_TOKEN_ID 5
void llama_set_mtp_target_context(struct llama_context * ctx, struct llama_context * target_ctx);
void llama_set_mtp_step_idx(struct llama_context * ctx, int32_t mtp_step_idx);
void llama_set_mtp_n_heads(struct llama_context * ctx, int32_t mtp_n_heads);
const std::vector<enum common_speculative_type> common_speculative_types = {
COMMON_SPECULATIVE_TYPE_NONE,
@@ -212,12 +214,13 @@ static std::vector<llama_token> mtp_speculative_gen_draft(
struct llama_context * ctx,
int n_draft,
float p_min,
int32_t mtp_heads,
llama_token id_last,
llama_pos n_past,
llama_seq_id seq_id,
bool constant_draft_positions = false);
static int32_t mtp_update_kv_cache(struct llama_context * ctx, const llama_batch & batch, bool is_prompt_warmup);
static int32_t mtp_update_kv_cache(struct llama_context * ctx, const llama_batch & batch, bool is_prompt_warmup, int32_t mtp_heads);
struct mtp_last_embd {
std::vector<float> embd;
@@ -228,7 +231,19 @@ struct mtp_last_embd {
struct common_speculative_state_mtp : public common_speculative_state {
llama_context * ctx_tgt;
llama_context * ctx_mtp = nullptr;
int32_t mtp_heads_active = 0;
// number of NextN heads the model carries, and the minimum head count the committed
// context has been warmed with since position 0 (deeper heads' caches only hold valid
// rows for spans warmed with them; a request drafting with MORE heads than the cached
// prefix was warmed with must reprocess from scratch). Single-sequence by design, like
// the rest of the openPangu MTP state.
int32_t n_heads_model = 1;
int32_t mtp_warmed_heads = 0;
common_sampler * smpl;
int32_t resolved_heads() const {
return mtp_heads_active > 0 ? std::min(mtp_heads_active, n_heads_model) : n_heads_model;
}
// For Gemma 4 external MTP assistant: draft positions are held constant
bool constant_draft_positions = false;
int n_embd = 0;
@@ -252,9 +267,15 @@ struct common_speculative_state_mtp : public common_speculative_state {
smpl = common_sampler_init(llama_get_model(ctx_mtp), sparams);
llama_set_mtp_target_context(ctx_mtp, ctx_tgt);
n_embd = llama_mtp_state_n_embd(ctx_mtp);
n_heads_model = std::max(1, llama_model_n_nextn_layer(llama_get_model(ctx_mtp)));
LOG_INF("%s: MTP context ready (n_ctx=%d, constant_draft_positions=%s)\n", __func__,
llama_n_ctx(ctx_mtp), constant_draft_positions ? "true" : "false");
if (n_heads_model > 1) {
LOG_INF("%s: model carries %d NextN/MTP heads; heads=1 is the default, while "
"heads>1 and heads=0 (all model heads) are experimental\n",
__func__, n_heads_model);
}
}
~common_speculative_state_mtp() override {
@@ -304,6 +325,7 @@ struct common_speculative_state_mtp : public common_speculative_state {
}
llama_context * ctx = ctx_mtp;
mtp_heads_active = std::max<int32_t>(0, params.mtp_heads);
const auto hidden_it = target_hidden_by_seq.find(seq_id);
if (hidden_it == target_hidden_by_seq.end() || (int) hidden_it->second.size() != n_embd) {
@@ -323,6 +345,7 @@ struct common_speculative_state_mtp : public common_speculative_state {
ctx,
params.n_max,
params.p_min,
params.mtp_heads,
id_last,
n_past,
seq_id,
@@ -1072,6 +1095,7 @@ static common_params_speculative common_speculative_get_runtime_params(
result.n_max = stage.has_n_max_override() ? stage.n_max : params.n_max;
result.n_min = stage.has_n_min_override() ? stage.n_min : params.n_min;
result.p_min = stage.has_p_min_override() ? stage.p_min : params.p_min;
result.mtp_heads = stage.has_mtp_heads_override() ? stage.mtp_heads : params.mtp_heads;
if (config.type == COMMON_SPECULATIVE_TYPE_SUFFIX) {
result.suffix_min_match_len = stage.has_suffix_min_match_len_override()
@@ -1081,11 +1105,43 @@ static common_params_speculative common_speculative_get_runtime_params(
result.n_max = std::max(result.n_max, 0);
result.n_min = std::max(0, std::min(result.n_min, result.n_max));
result.mtp_heads = std::max(result.mtp_heads, 0);
result.stages.clear();
return result;
}
bool common_speculative_mtp_requires_fresh_warmup(const common_speculative * spec) {
const auto * mtp_state = common_speculative_get_mtp_state(spec);
if (mtp_state == nullptr || mtp_state->n_heads_model <= 1) {
return false;
}
// drafting with more heads than the cached prefix was warmed with would read
// never-written deeper-head cache rows; the caller must reprocess from position 0
return mtp_state->resolved_heads() > mtp_state->mtp_warmed_heads && mtp_state->mtp_warmed_heads > 0;
}
void common_speculative_prepare_request(common_speculative * spec, common_params_speculative & params) {
if (spec == nullptr) {
return;
}
const auto runtime_stages = params.get_resolved_stages();
const bool use_runtime_stage_overrides = common_speculative_stage_chain_matches(runtime_stages, spec->configs);
for (size_t i = 0; i < spec->impls.size(); ++i) {
auto * mtp_state = dynamic_cast<common_speculative_state_mtp *>(spec->impls[i].get());
if (mtp_state == nullptr) {
continue;
}
const auto & runtime_stage = use_runtime_stage_overrides ? runtime_stages[i] : spec->configs[i].stage;
common_params_speculative impl_params = common_speculative_get_runtime_params(spec->configs[i], params, runtime_stage);
mtp_state->mtp_heads_active = std::max<int32_t>(0, impl_params.mtp_heads);
}
}
static common_ngram_map get_common_ngram_map(const common_speculative_config & config) {
uint16_t size_key = config.params.ngram_size_n;
uint16_t size_value = config.params.ngram_size_m;
@@ -1281,7 +1337,8 @@ common_speculative * common_speculative_init(
configs.push_back(common_speculative_config(stage, stage_params));
}
if (!configs.empty() && llama_model_has_recurrent(llama_get_model(ctx_tgt))) {
if (!configs.empty() && (llama_model_has_recurrent(llama_get_model(ctx_tgt)) ||
llama_model_is_openpangu(llama_get_model(ctx_tgt)))) {
const int ckpt_tokens = std::max(1, params.get_max_stage_n_max() + 1);
const int actual_mode = llama_spec_ckpt_init(ctx_tgt, params.recurrent_ckpt_mode, ckpt_tokens);
if (actual_mode == LLAMA_SPEC_CKPT_NONE) {
@@ -1407,6 +1464,7 @@ common_speculative * common_speculative_init(
/* .configs = */ std::move(configs),
/* .impls = */ std::move(impls)
};
common_speculative_prepare_request(result, params);
// initialize autotune if requested
if (params.autotune && params.has_composite_stage_chain()) {
@@ -2683,10 +2741,20 @@ static int32_t mtp_accept_batch(
if (!llama_set_draft_input_hidden_state_copy(state.ctx_mtp, hidden_rows, hidden_rows_floats)) {
return -1;
}
if (mtp_update_kv_cache(state.ctx_mtp, accepted_batch, false) != 0) {
if (mtp_update_kv_cache(state.ctx_mtp, accepted_batch, false, state.mtp_heads_active) != 0) {
return -1;
}
if (llama_model_is_openpangu(llama_get_model(state.ctx_mtp))) {
// The one-token draft shortcut re-seeded below would skip re-decoding the last
// sampled token next round, leaving a hole at its position. openPangu's KV cache
// is position-addressed append-only (cell == position), so draft decodes must be
// position-contiguous; decline the shortcut and let the next round decode the
// sampled token normally.
mtp_invalidate_cached_draft(state, seq_id);
return 0;
}
auto & last = mtp_get_last_embd(state, seq_id);
const float * embd = llama_get_embeddings_ith(state.ctx_mtp, accepted_batch.n_tokens - 1);
if (embd != nullptr) {
@@ -2767,6 +2835,18 @@ int32_t common_speculative_on_target_batch(
const float * last_hidden = hidden_rows_storage.data() + (size_t) (batch.n_tokens - 1) * features.width;
mtp_store_target_hidden(*mtp_state, seq_id, last_hidden, features.width);
// track the minimum head count the committed context has been warmed with: a fresh
// position-0 warmup resets it, everything after can only narrow it
{
const int32_t resolved = mtp_state->resolved_heads();
if (is_prompt_warmup && batch.pos != nullptr && batch.n_tokens > 0 && batch.pos[0] == 0) {
mtp_state->mtp_warmed_heads = resolved;
} else {
mtp_state->mtp_warmed_heads = mtp_state->mtp_warmed_heads > 0
? std::min(mtp_state->mtp_warmed_heads, resolved) : resolved;
}
}
if (mtp_state->constant_draft_positions) {
mtp_invalidate_cached_draft(*mtp_state, seq_id);
return 0;
@@ -2804,7 +2884,7 @@ int32_t common_speculative_on_target_batch(
if (!llama_set_draft_input_hidden_state_copy(mtp_state->ctx_mtp, conditioned_hidden_rows, hidden_rows_storage.size())) {
return -1;
}
const int32_t ret = mtp_update_kv_cache(mtp_state->ctx_mtp, batch, true);
const int32_t ret = mtp_update_kv_cache(mtp_state->ctx_mtp, batch, true, mtp_state->mtp_heads_active);
mtp_invalidate_cached_draft(*mtp_state, seq_id);
return ret;
}
@@ -2839,6 +2919,7 @@ std::vector<llama_token> mtp_speculative_gen_draft(
struct llama_context * ctx,
int n_draft,
float p_min,
int32_t mtp_heads,
llama_token id_last,
llama_pos n_past,
llama_seq_id seq_id,
@@ -2856,7 +2937,22 @@ std::vector<llama_token> mtp_speculative_gen_draft(
common_sampler_reset(smpl);
if (llama_model_is_openpangu(llama_get_model(ctx)) &&
llama_kv_cache_seq_pos_max(ctx, seq_id) >= n_past) {
// Position-addressed cache: drafting restarts at n_past, so any rows at or beyond
// it (the accepted-update writes one row past the accepted prefix) must be dropped
// first to keep the draft decode position-contiguous with the cache head.
llama_kv_cache_seq_rm(ctx, seq_id, n_past, -1);
}
const int n_embd = llama_mtp_state_n_embd(ctx);
const int n_mtp_heads_model = std::max(1, llama_model_n_nextn_layer(llama_get_model(ctx)));
const int n_mtp_heads = mtp_heads > 0
? std::max(1, std::min((int) mtp_heads, n_mtp_heads_model))
: n_mtp_heads_model;
llama_batch mtp_batch = llama_batch_init(1, 0, 1);
llama_set_mtp_n_heads(ctx, n_mtp_heads);
llama_set_mtp_op_type(ctx, MTP_OP_DRAFT_GEN);
float prob;
@@ -2864,7 +2960,6 @@ std::vector<llama_token> mtp_speculative_gen_draft(
llama_token current_input_id = id_last;
llama_pos current_n_past = n_past;
const int n_embd = llama_mtp_state_n_embd(ctx);
auto & last = mtp_get_last_embd(state, seq_id);
int i0 = 0;
@@ -2878,6 +2973,8 @@ std::vector<llama_token> mtp_speculative_gen_draft(
current_n_past++;
if (!llama_set_draft_input_hidden_state_copy(ctx, last.embd.data(), last.embd.size())) {
llama_batch_free(mtp_batch);
llama_set_mtp_step_idx(ctx, 0);
llama_set_mtp_n_heads(ctx, 0);
llama_set_mtp_op_type(ctx, MTP_OP_NONE);
return drafts;
}
@@ -2889,6 +2986,7 @@ std::vector<llama_token> mtp_speculative_gen_draft(
mtp_batch.n_tokens = 0;
const llama_pos draft_pos = constant_draft_positions ? n_past : current_n_past;
common_batch_add(mtp_batch, current_input_id, draft_pos, {seq_id}, true);
llama_set_mtp_step_idx(ctx, std::min(i, n_mtp_heads - 1));
++n_decode;
if (llama_decode(ctx, mtp_batch) != 0) {
@@ -2922,6 +3020,8 @@ std::vector<llama_token> mtp_speculative_gen_draft(
}
}
llama_batch_free(mtp_batch);
llama_set_mtp_step_idx(ctx, 0);
llama_set_mtp_n_heads(ctx, 0);
llama_set_mtp_op_type(ctx, MTP_OP_NONE);
// Purge the metadata for the draft tokens.
@@ -2938,7 +3038,7 @@ std::vector<llama_token> mtp_speculative_gen_draft(
}
int32_t mtp_update_kv_cache(struct llama_context * ctx, const llama_batch& batch, bool is_prompt_warmup) {
int32_t mtp_update_kv_cache(struct llama_context * ctx, const llama_batch& batch, bool is_prompt_warmup, int32_t mtp_heads) {
if (batch.n_tokens == 0) {
return 0;
}
@@ -2962,12 +3062,18 @@ int32_t mtp_update_kv_cache(struct llama_context * ctx, const llama_batch& batch
}
mtp_batch.logits[mtp_batch.n_tokens-1] = true;
if (is_prompt_warmup) {
llama_set_mtp_n_heads(ctx, mtp_heads);
llama_set_mtp_step_idx(ctx, 0);
llama_set_mtp_op_type(ctx, MTP_OP_WARMUP);
} else {
llama_set_mtp_n_heads(ctx, mtp_heads);
llama_set_mtp_step_idx(ctx, 0);
llama_set_mtp_op_type(ctx, MTP_OP_UPDATE_ACCEPTED);
}
const int32_t ret = llama_decode(ctx, mtp_batch);
llama_set_mtp_step_idx(ctx, 0);
llama_set_mtp_n_heads(ctx, 0);
llama_set_mtp_op_type(ctx, MTP_OP_NONE);
return ret;
}
+7
View File
@@ -82,6 +82,13 @@ void common_speculative_free(common_speculative * spec);
// optionally call once at the beginning of a new generation
void common_speculative_begin(common_speculative * spec, const llama_tokens & prompt);
// apply per-request runtime parameters before prompt warmup can touch companion state
void common_speculative_prepare_request(common_speculative * spec, common_params_speculative & params);
// true when the active request drafts with more MTP heads than the cached prefix was
// warmed with; the caller must then reprocess the prompt from position 0
bool common_speculative_mtp_requires_fresh_warmup(const common_speculative * spec);
// sample up to n_draft tokens and add them to the batch using the draft model
// draft_base_pos/draft_seq_id override the MTP position for id_last
llama_tokens common_speculative_draft(
+145
View File
@@ -592,6 +592,9 @@ class Model:
if chkhsh == "66b8d4e19ab16c3bfd89bce5d785fb7e0155e8648708a1f42077cb9fe002c273":
# ref: https://huggingface.co/alvarobartt/grok-2-tokenizer
res = "grok-2"
if chkhsh == "65df2fe396b537a53433301848c0a739f56d56f67ad3d35eba27961ac33c12bb":
# ref: https://huggingface.co/openpangu/openPangu-2.0-Flash
res = "openpangu"
if chkhsh == "972da7b59cec44d1f0a490a86c96df53859e486e481563e5dddac155013d87ac":
# ref: https://huggingface.co/poolside/Laguna-XS.2
res = "laguna"
@@ -4588,6 +4591,148 @@ class DeepseekV2Model(Model):
raise ValueError(f"Unprocessed experts: {experts}")
@Model.register("OpenPanguV2ForCausalLM")
class OpenPanguV2Model(DeepseekV2Model):
# openPangu-2.0-Flash: MLA + DSA/SWA hybrid + MoE + mHC(Hyper-Connections) + MoME convs.
# Emits a complete, self-contained GGUF: weights (incl. pre-split attn_k_b/attn_v_b for
# the latent-attention graph) plus the DSA/SWA schedule and mHC/MoME/sink metadata.
model_arch = gguf.MODEL_ARCH.OPENPANGU
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# MTP/NextN layers (num_hidden_layers .. +num_nextn_predict_layers-1) are real blocks
# in ik_llama's layout (n_layer includes NextN; n_layer_kv_from_start excludes them).
self._nextn = int(self.hparams.get("num_nextn_predict_layers", 0) or 0)
self.block_count = int(self.hparams["num_hidden_layers"]) + self._nextn
self.tensor_map = gguf.get_tensor_name_map(self.model_arch, self.block_count)
def set_vocab(self):
# OpenPanguV2Tokenizer is a GPT2/BPE-style tokenizer. The pre-tokenizer hash is
# registered in Model.get_vocab_base_pre (see the openpangu entry).
self._set_vocab_gpt2()
# HF prepends <|pangu_text_start|> via the tokenizer post-processor; tokenizer_config
# has no add_bos_token key, so state it explicitly for the GGUF.
self.gguf_writer.add_add_bos_token(True)
def set_gguf_parameters(self):
# Base transformer params (block_count now includes NextN layers).
Model.set_gguf_parameters(self)
hparams = self.hparams
arch = gguf.MODEL_ARCH_NAMES[self.model_arch]
self.gguf_writer.add_leading_dense_block_count(hparams["first_k_dense_replace"])
self.gguf_writer.add_vocab_size(hparams["vocab_size"])
self.gguf_writer.add_q_lora_rank(hparams["q_lora_rank"])
self.gguf_writer.add_kv_lora_rank(hparams["kv_lora_rank"])
self.gguf_writer.add_key_length(hparams["qk_nope_head_dim"] + hparams["qk_rope_head_dim"])
self.gguf_writer.add_value_length(hparams["v_head_dim"])
# MoE
self.gguf_writer.add_expert_feed_forward_length(hparams["moe_intermediate_size"])
self.gguf_writer.add_expert_count(hparams["n_routed_experts"])
self.gguf_writer.add_expert_used_count(hparams["num_experts_per_tok"])
self.gguf_writer.add_expert_shared_count(hparams["n_shared_experts"])
self.gguf_writer.add_expert_weights_scale(hparams["routed_scaling_factor"])
self.gguf_writer.add_expert_weights_norm(hparams["norm_topk_prob"])
# router_enable_expert_bias => sigmoid gating with e_score_correction bias
self.gguf_writer.add_expert_gating_func(gguf.ExpertGatingFuncType.SIGMOID)
# RoPE
self.gguf_writer.add_rope_dimension_count(hparams["qk_rope_head_dim"])
self.gguf_writer.add_rope_freq_base(hparams["rope_theta"])
# NextN / MTP
self.gguf_writer.add_uint32(
gguf.Keys.LLM.NEXTN_PREDICT_LAYERS.format(arch=arch), self._nextn
)
# DSA lightning indexer
self.gguf_writer.add_uint32(
gguf.Keys.Attention.INDEXER_HEAD_COUNT.format(arch=arch), hparams["index_n_heads"]
)
self.gguf_writer.add_uint32(
gguf.Keys.Attention.INDEXER_KEY_LENGTH.format(arch=arch), hparams["index_head_dim"]
)
self.gguf_writer.add_uint32(
gguf.Keys.Attention.INDEXER_TOP_K.format(arch=arch), hparams["index_topk"]
)
# SWA (window; last few layers widen to 2048 per sliding_window_list)
if hparams.get("sliding_window") is not None:
self.gguf_writer.add_sliding_window(hparams["sliding_window"])
# Pangu-specific structural metadata (consumed by the OPENPANGU graph). The runtime
# derives everything else from these plus tensor presence: DSA layers are the
# windowless base layers (dsa_layers is redundant with swa_layers, so it is not
# written), and block_post_norm placement follows the tensors themselves.
self.gguf_writer.add_uint32(f"{arch}.mhc_num_stream", hparams["mhc_num_stream"])
self.gguf_writer.add_uint32(f"{arch}.mhc_recur_norm", hparams["mhc_recur_norm"])
self.gguf_writer.add_uint32(f"{arch}.param_sink_number", hparams["param_sink_number"])
self.gguf_writer.add_array(f"{arch}.swa_layers", hparams["swa_layers"])
if hparams.get("sliding_window_list") is not None:
self.gguf_writer.add_array(f"{arch}.sliding_window_list", hparams["sliding_window_list"])
_experts: list[dict[str, Tensor]] | None = None
def modify_tensors(self, data_torch: Tensor, name: str, bid: int | None) -> Iterable[tuple[str, Tensor]]:
# Sigmoid router bias: rename to the deepseek-style ".bias" the tensor map expects.
if name.endswith("e_score_correction_bias"):
name = name.replace("e_score_correction_bias", "e_score_correction.bias")
# NOTE: unlike DeepseekV2, we do NOT skip MTP layers — they are real blocks here.
# Merge routed experts into stacked 3D tensors (same layout as deepseek/glm4moe).
if name.find("mlp.experts") != -1:
n_experts = self.hparams["n_routed_experts"]
assert bid is not None
if self._experts is None:
self._experts = [{} for _ in range(self.block_count)]
self._experts[bid][name] = data_torch
if len(self._experts[bid]) >= n_experts * 3:
tensors: list[tuple[str, Tensor]] = []
for w_name in ["down_proj", "gate_proj", "up_proj"]:
datas: list[Tensor] = []
for xid in range(n_experts):
ename = f"model.layers.{bid}.mlp.experts.{xid}.{w_name}.weight"
datas.append(self._experts[bid][ename])
del self._experts[bid][ename]
data_torch = torch.stack(datas, dim=0)
merged_name = f"model.layers.{bid}.mlp.experts.{w_name}.weight"
tensors.append((self.map_tensor_name(merged_name), data_torch))
return tensors
else:
return []
# Split the fused MLA kv_b into k_b / v_b (deepseek MLA layout). OpenPangu's
# graph consumes the pre-split tensors directly, so do not emit the fused copy.
if name.endswith("kv_b_proj.weight"):
name_kb = name.replace("kv_b_proj", "k_b_proj")
name_vb = name.replace("kv_b_proj", "v_b_proj")
n_head_kv = self.hparams["num_attention_heads"]
v_head_dim = self.hparams["v_head_dim"]
qk_nope_head_dim = self.hparams["qk_nope_head_dim"]
assert data_torch.shape[0] == n_head_kv * (v_head_dim + qk_nope_head_dim)
kv_b = data_torch.view(n_head_kv, v_head_dim + qk_nope_head_dim, data_torch.shape[-1])
k_b, v_b = torch.split(kv_b, [qk_nope_head_dim, v_head_dim], dim=1)
k_b = k_b.transpose(1, 2)
k_b = k_b.reshape(n_head_kv * data_torch.shape[-1], qk_nope_head_dim)
v_b = v_b.reshape(n_head_kv * v_head_dim, data_torch.shape[-1])
return [
(self.map_tensor_name(name_kb), k_b),
(self.map_tensor_name(name_vb), v_b),
]
# Everything else (attn/norms/mHC/MoME conv/param-sink/indexer/nextn) maps by name.
return [(self.map_tensor_name(name), data_torch)]
@Model.register("T5WithLMHeadModel")
@Model.register("T5ForConditionalGeneration")
@Model.register("MT5ForConditionalGeneration")
+1
View File
@@ -107,6 +107,7 @@ models = [
{"name": "minimax-m2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/MiniMaxAI/MiniMax-M2", },
{"name": "mellum2", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/JetBrains/Mellum2-12B-A2.5B-Base", },
{"name": "gpt-4o", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/openai/gpt-oss-20b", "chkhsh": "ccc2ef013c104be7bae2965776d611e1d7a8a2a9c547dd93a682c9a9fc80352e", }, # o200k_harmony shares the GPT-4o pre-tokenizer
{"name": "openpangu", "tokt": TOKENIZER_TYPE.BPE, "repo": "https://huggingface.co/openpangu/openPangu-2.0-Flash", "chkhsh": "65df2fe396b537a53433301848c0a739f56d56f67ad3d35eba27961ac33c12bb", }, # primary source: https://gitcode.com/ascend-tribe/openPangu-2.0-Flash
]
+2 -2
View File
@@ -132,7 +132,7 @@ Check the details [here](./speculative.md).
| `-ctkd, --cache-type-k-draft TYPE` | KV cache data type for K for the draft model | - | For draft model, see: `-ctk` |
| `-ctvd, --cache-type-v-draft TYPE` | KV cache data type for V for the draft model | - | For draft model, see: `-ctk` |
| `-draft, --draft-params` | Comma-separated list of draft model parameters | - | |
| `--spec-type SPEC[:k=v,...]` | Canonical speculative stage entry; repeat to configure the supported two-stage chain | - | Types: `none`, `draft`, `dflash`, `mtp`, `ngram-cache`, `ngram-simple`, `ngram-map-k`, `ngram-map-k4v`, `ngram-mod`, `suffix`. Canonical keys: `n_max`, `n_min`, `p_min`, `cross_ctx`, `ngram_size_n`, `ngram_size_m`, `ngram_min_hits`, `suffix_min_match_len`, `suffix_max_depth`, `suffix_corpus`. String values may escape commas as `\,` or quote the value inside the stage payload. Examples: `--spec-type ngram-mod:n_max=64,n_min=2,ngram_size_n=8 --spec-type mtp:n_max=1,p_min=0.0`, `--model-draft draft.gguf --spec-type dflash:n_max=4,cross_ctx=512` |
| `--spec-type SPEC[:k=v,...]` | Canonical speculative stage entry; repeat to configure the supported two-stage chain | - | Types: `none`, `draft`, `dflash`, `mtp`, `ngram-cache`, `ngram-simple`, `ngram-map-k`, `ngram-map-k4v`, `ngram-mod`, `suffix`. Canonical keys include `n_max`, `n_min`, `p_min`, `heads`, `cross_ctx`, `ngram_size_n`, `ngram_size_m`, `ngram_min_hits`, `suffix_min_match_len`, `suffix_max_depth`, `suffix_corpus`. For MTP, `heads=1` is the default; values above `1` and `heads=0` (all model heads) are experimental. String values may escape commas as `\,` or quote the value inside the stage payload. Examples: `--spec-type ngram-mod:n_max=64,n_min=2,ngram_size_n=8 --spec-type mtp:n_max=1,p_min=0.0`, `--model-draft draft.gguf --spec-type dflash:n_max=4,cross_ctx=512` |
| `--spec-autotune` | Automatically tune speculative params to maximize tokens/sec | - | Automatically determines the near-optimal arguments for the type of speculation being performed [PR 1595](https://github.com/ikawrakow/ik_llama.cpp/pull/1595) |
| `--recurrent-ckpt-mode MODE` | Checkpoint strategy for recurrent/hybrid speculative decoding | auto | One of: - `auto` auto-select: per-step if CUDA full-GPU, gpu-fallback otherwise - `per-step` save SSM state per draft step in VRAM; no re-decode on rejection - `gpu-fallback` copy state to GPU buffer; re-decode on rejection - `cpu` serialise state via llama_state_seq; re-decode on rejection [PR 1669](https://github.com/ikawrakow/ik_llama.cpp/pull/1669) [PR 1774](https://github.com/ikawrakow/ik_llama.cpp/pull/1774) |
@@ -383,7 +383,7 @@ WIP
| `--override-kv KEY=TYPE:VALUE` | Override model metadata by key | - | Advanced option to override model metadata by key. May be specified multiple times. types: int, float, bool, str. Example: `--override-kv tokenizer.ggml.add_bos_token=bool:false` |
| `-m, --model FNAME` | Model path | models/$filename | Mandatory, the GGUF model file to be served. |
| `-md, --model-draft FNAME` | Draft model for speculative decoding | unused | Required when an explicit `draft` stage is used. |
| `--spec-type SPEC[:k=v,...]` | Canonical speculative stage entry; repeat for the supported two-stage chain | none | Use stage-local keys like `n_max`, `n_min`, `p_min`, `ngram_size_n`, `ngram_size_m`, `ngram_min_hits`, `suffix_min_match_len`, `suffix_max_depth`, and `suffix_corpus`. |
| `--spec-type SPEC[:k=v,...]` | Canonical speculative stage entry; repeat for the supported two-stage chain | none | Use stage-local keys like `n_max`, `n_min`, `p_min`, `heads`, `ngram_size_n`, `ngram_size_m`, `ngram_min_hits`, `suffix_min_match_len`, `suffix_max_depth`, and `suffix_corpus`. For MTP, `heads=1` is the default; values above `1` and `heads=0` (all model heads) are experimental. |
### Request-Level Speculative Overrides
+1
View File
@@ -128,6 +128,7 @@ Canonical stage keys:
| `n_max` | Maximum drafted tokens for that stage |
| `n_min` | Minimum usable drafted tokens for that stage |
| `p_min` | Minimum speculative probability threshold |
| `heads` | MTP heads to use; `1` is the default, while values above `1` and `0` (all model heads) are experimental |
| `ngram_size_n` | Lookup n-gram size |
| `ngram_size_m` | Draft m-gram size |
| `ngram_min_hits` | Minimum matching hits for n-gram map stages |
+42 -20
View File
@@ -941,17 +941,20 @@ int main(int argc, char ** argv) {
const int n_predict_budget = n_remain < 0 ? std::numeric_limits<int>::max() : n_remain;
bool used_speculative = false;
const bool sampled_before_from_carry = have_speculative_sampled;
llama_token sampled_before = LLAMA_TOKEN_NULL;
if (sampled_before_from_carry) {
sampled_before = speculative_sampled;
have_speculative_sampled = false;
speculative_sampled = LLAMA_TOKEN_NULL;
}
bool sampled_before_ready = sampled_before_from_carry;
if (spec != nullptr && n_predict_budget != 1) {
const bool sampled_before_from_carry = have_speculative_sampled;
llama_token sampled_before = LLAMA_TOKEN_NULL;
if (sampled_before_from_carry) {
sampled_before = speculative_sampled;
have_speculative_sampled = false;
speculative_sampled = LLAMA_TOKEN_NULL;
} else {
if (!sampled_before_ready) {
sampled_before = common_sampler_sample_legacy(ctx_sampling, ctx, ctx_guidance);
common_sampler_accept(ctx_sampling, ctx, sampled_before, /* apply_grammar= */ true);
sampled_before_ready = true;
}
static const llama_tokens empty_speculative_tokens;
const llama_tokens & draft_history =
@@ -981,7 +984,7 @@ int main(int argc, char ** argv) {
const int min_usable_draft = params.speculative.get_min_usable_stage_n_min();
if ((int) draft.size() >= min_usable_draft && (!draft.empty() || n_predict_budget > 1)) {
if (llama_model_has_recurrent(model)) {
if (llama_model_has_recurrent(model) || llama_model_is_openpangu(model)) {
if (!common_speculative_before_draft(
spec,
model,
@@ -1066,23 +1069,42 @@ int main(int argc, char ** argv) {
}
if (!used_speculative) {
const llama_token id = common_sampler_sample_legacy(ctx_sampling, ctx, ctx_guidance);
common_sampler_accept(ctx_sampling, ctx, id, /* apply_grammar= */ true);
if (sampled_before_ready) {
// A carried token was already displayed by the previous speculative step but
// still needs to be decoded. If a new draft is unusable, stage that token instead
// of sampling the unchanged logits again and emitting it twice.
embd.push_back(sampled_before);
embd_is_prompt = false;
input_echo = true;
LOG("last: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, ctx_sampling->prev).c_str());
if (sampled_before_from_carry) {
emitted_generated = false;
} else {
emitted = embd;
emitted_generated = true;
--n_remain;
}
embd.push_back(id);
emitted = embd;
embd_is_prompt = false;
emitted_generated = true;
LOG("n_remain: %d\n", n_remain);
} else {
const llama_token id = common_sampler_sample_legacy(ctx_sampling, ctx, ctx_guidance);
common_sampler_accept(ctx_sampling, ctx, id, /* apply_grammar= */ true);
// echo this to console
input_echo = true;
LOG("last: %s\n", LOG_TOKENS_TOSTR_PRETTY(ctx, ctx_sampling->prev).c_str());
// decrement remaining sampling budget
--n_remain;
embd.push_back(id);
emitted = embd;
embd_is_prompt = false;
emitted_generated = true;
LOG("n_remain: %d\n", n_remain);
// echo this to console
input_echo = true;
// decrement remaining sampling budget
--n_remain;
LOG("n_remain: %d\n", n_remain);
}
} else {
input_echo = true;
}
+84 -4
View File
@@ -164,7 +164,15 @@ static common_speculative_stage_params server_parse_speculative_stage_json(const
continue;
}
throw std::runtime_error("Error: per-request speculative.stages only support type, n_max, n_min, and p_min; structural stage overrides are startup-only");
if (item.key() == "heads" || item.key() == "mtp_heads") {
stage.mtp_heads = item.value().get<int32_t>();
if (stage.mtp_heads < 0) {
throw std::runtime_error("Error: speculative.stages[].heads must be >= 0");
}
continue;
}
throw std::runtime_error("Error: per-request speculative.stages only support type, n_max, n_min, p_min, and heads; structural stage overrides are startup-only");
}
return stage;
@@ -509,6 +517,8 @@ void server_slot::reset() {
// Reset speculative decoding stats
n_draft_total = 0;
n_draft_accepted = 0;
n_draft_by_depth.clear();
n_draft_accepted_by_depth.clear();
chat_msg = {};
json_schema = json();
generated_tool_call_ids.clear();
@@ -608,7 +618,7 @@ void server_slot::release() {
json server_slot::get_formated_timings() const {
return json{
json timings = json{
{"prompt_n", n_prompt_tokens_processed},
{"prompt_ms", t_prompt_processing},
{"prompt_per_token_ms", t_prompt_processing / n_prompt_tokens_processed},
@@ -622,6 +632,27 @@ json server_slot::get_formated_timings() const {
{"n_ctx", n_ctx},
{"n_past", n_past},
};
if (n_draft_total > 0) {
timings["draft_n"] = n_draft_total;
timings["draft_n_accepted"] = n_draft_accepted;
json by_depth = json::array();
for (size_t i = 0; i < n_draft_by_depth.size(); ++i) {
if (n_draft_by_depth[i] <= 0) {
continue;
}
const int32_t accepted = i < n_draft_accepted_by_depth.size()
? n_draft_accepted_by_depth[i] : 0;
by_depth.push_back({
{"depth", (int32_t) i + 1},
{"draft_n", n_draft_by_depth[i]},
{"draft_n_accepted", accepted},
});
}
if (!by_depth.empty()) {
timings["draft_by_depth"] = by_depth;
}
}
return timings;
}
result_timings server_slot::get_timings() const {
@@ -644,6 +675,8 @@ result_timings server_slot::get_timings() const {
if (n_draft_total > 0) {
timings.draft_n = n_draft_total;
timings.draft_n_accepted = n_draft_accepted;
timings.draft_n_by_depth = n_draft_by_depth;
timings.draft_n_accepted_by_depth = n_draft_accepted_by_depth;
}
return timings;
@@ -1158,6 +1191,9 @@ bool server_context::launch_slot_with_task(server_slot& slot, server_task& task)
if (stage_override.has_p_min_override()) {
slot.params.speculative.stages[i].p_min = stage_override.p_min;
}
if (stage_override.has_mtp_heads_override()) {
slot.params.speculative.stages[i].mtp_heads = stage_override.mtp_heads;
}
}
const auto resolved = slot.params.speculative.get_resolved_stages();
@@ -1195,6 +1231,7 @@ bool server_context::launch_slot_with_task(server_slot& slot, server_task& task)
if (!common_speculative_validate_chain(slot.params.speculative, &spec_error)) {
throw std::runtime_error("Error: invalid speculative request configuration: " + spec_error);
}
common_speculative_prepare_request(slot.spec, slot.params.speculative);
} catch (const std::exception & e) {
send_error(task, e.what(), ERROR_TYPE_INVALID_REQUEST);
return false;
@@ -1762,6 +1799,12 @@ bool server_context::launch_slot_with_task(server_slot& slot, server_task& task)
LOG_WARNING("%s\n", "ctx_shift is not implemented for split mode graph, it will be disabled");
}
}
if (!llama_model_supports_ctx_shift(llama_get_model(slot.ctx))) {
if (params_base.ctx_shift) {
params_base.ctx_shift = false;
LOG_WARNING("%s\n", "ctx_shift is not supported by this model's KV cache, it will be disabled");
}
}
{
const auto& stop = data.find("stop");
if (stop != data.end() && stop->is_array()) {
@@ -3488,6 +3531,12 @@ void server_context::add_sampled_tokens() {
} else {
// keep track of total number of drafted tokens tested
slot.n_draft_total += draft.size();
if (slot.n_draft_by_depth.size() < draft.size()) {
slot.n_draft_by_depth.resize(draft.size(), 0);
}
for (size_t i = 0; i < draft.size(); ++i) {
slot.n_draft_by_depth[i]++;
}
// add all drafted tokens to the batch
for (size_t i = 0; i < draft.size(); i++) {
@@ -3822,6 +3871,30 @@ void server_context::batch_pending_prompt(const int32_t n_ubatch, const int32_t
slot.n_past = prefix.first;
slot.n_past_prompt = prefix.second;
slot.n_past_offset = slot.n_past_prompt - slot.n_past;
if (!llama_model_supports_partial_kv_reuse(model) &&
slot.n_past < (int32_t) slot.cache_tokens.size()) {
// the cache diverges from the new prompt mid-sequence; this
// model can only extend or reset a cached sequence (per-position
// side state past the divergence point is already lost)
LLAMA_LOG_INFO("%s: cached sequence diverges at %d/%d and this model does not support partial KV reuse - reprocessing from scratch\n",
__func__, (int) slot.n_past, (int) slot.cache_tokens.size());
slot.n_past = 0;
slot.n_past_prompt = 0;
slot.n_past_offset = 0;
}
if (slot.n_past > 0 && slot.spec != nullptr &&
common_speculative_mtp_requires_fresh_warmup(slot.spec)) {
// the request drafts with more MTP heads than the cached
// prefix was warmed with; deeper-head cache rows for the
// reused span were never written
LLAMA_LOG_INFO("%s: request drafts with more MTP heads than the cached prefix was warmed with - reprocessing from scratch\n",
__func__);
slot.n_past = 0;
slot.n_past_prompt = 0;
slot.n_past_offset = 0;
}
if ((slot.n_past + size_threshold < slot.cache_tokens.size()))
{
int32_t back = 4;
@@ -4118,7 +4191,14 @@ void server_context::speculative_decoding_accept() {
slot.t_token_generation = std::max<int64_t>(1, t_current - slot.t_start_generation) / 1e3;
// update how many tokens out of those tested were accepted
slot.n_draft_accepted += ids.size() - 1;
const size_t n_draft_accepted = ids.size() - 1;
slot.n_draft_accepted += n_draft_accepted;
if (slot.n_draft_accepted_by_depth.size() < n_draft_accepted) {
slot.n_draft_accepted_by_depth.resize(n_draft_accepted, 0);
}
for (size_t i = 0; i < n_draft_accepted; ++i) {
slot.n_draft_accepted_by_depth[i]++;
}
// rollback to the state before sampling the draft tokens
slot.cache_tokens.keep_first(slot.cache_tokens.n_tokens() - n_draft);
@@ -4722,7 +4802,7 @@ void server_context::update_slots() {
// make sure we're in the right embedding mode
llama_set_embeddings(ctx, batch_type == 1);
if (llama_model_has_recurrent(model)) {
if (llama_model_has_recurrent(model) || llama_model_is_openpangu(model)) {
const int ckpt_mode = params_base.speculative.recurrent_ckpt_mode;
for (auto & slot : slots) {
+3
View File
@@ -6,6 +6,7 @@
#include <cstddef>
#include <memory>
#include <vector>
@@ -163,6 +164,8 @@ struct server_slot {
// speculative decoding stats
int32_t n_draft_total = 0; // Total draft tokens generated
int32_t n_draft_accepted = 0; // Draft tokens actually accepted
std::vector<int32_t> n_draft_by_depth;
std::vector<int32_t> n_draft_accepted_by_depth;
int32_t n_past_se = 0; // self-extend
+18
View File
@@ -20,6 +20,24 @@ json result_timings::to_json() const {
if (draft_n > 0) {
base["draft_n"] = draft_n;
base["draft_n_accepted"] = draft_n_accepted;
if (!draft_n_by_depth.empty()) {
json by_depth = json::array();
for (size_t i = 0; i < draft_n_by_depth.size(); ++i) {
if (draft_n_by_depth[i] <= 0) {
continue;
}
const int32_t accepted = i < draft_n_accepted_by_depth.size()
? draft_n_accepted_by_depth[i] : 0;
by_depth.push_back({
{"depth", (int32_t) i + 1},
{"draft_n", draft_n_by_depth[i]},
{"draft_n_accepted", accepted},
});
}
if (!by_depth.empty()) {
base["draft_by_depth"] = by_depth;
}
}
}
return base;
+4
View File
@@ -8,6 +8,8 @@
// TODO: prevent including the whole server-common.h as we only use server_tokens
#include "server-common.h"
#include <vector>
using json = nlohmann::ordered_json;
enum stop_type {
@@ -130,6 +132,8 @@ struct result_timings {
// Optional speculative metrics - only included when > 0
int32_t draft_n = 0;
int32_t draft_n_accepted = 0;
std::vector<int32_t> draft_n_by_depth;
std::vector<int32_t> draft_n_accepted_by_depth;
json to_json() const;
};
+119
View File
@@ -113,6 +113,9 @@ class Keys:
CAUSAL = "{arch}.attention.causal"
Q_LORA_RANK = "{arch}.attention.q_lora_rank"
KV_LORA_RANK = "{arch}.attention.kv_lora_rank"
INDEXER_HEAD_COUNT = "{arch}.attention.indexer.head_count"
INDEXER_KEY_LENGTH = "{arch}.attention.indexer.key_length"
INDEXER_TOP_K = "{arch}.attention.indexer.top_k"
REL_BUCKETS_COUNT = "{arch}.attention.relative_buckets_count"
SLIDING_WINDOW = "{arch}.attention.sliding_window"
SLIDING_WINDOW_PATTERN = "{arch}.attention.sliding_window_pattern"
@@ -260,6 +263,7 @@ class MODEL_ARCH(IntEnum):
ARCTIC = auto()
DEEPSEEK2 = auto()
GLM4_MOE = auto()
OPENPANGU = auto()
CHATGLM = auto()
BITNET = auto()
BITNET_25 = auto()
@@ -386,6 +390,33 @@ class MODEL_TENSOR(IntEnum):
MTP_CENTROIDS = auto()
DFLASH_FC = auto()
DFLASH_HIDDEN_NORM = auto()
# openPangu-2.0 (DSA lightning indexer)
INDEXER_K_NORM = auto()
INDEXER_PROJ = auto() # weights_proj
INDEXER_ATTN_K = auto() # wk
INDEXER_ATTN_Q_B = auto() # wq_b
# openPangu-2.0 (MoME causal-conv on MLA latents)
ATTN_QA_CONV = auto()
ATTN_KV_CONV = auto() # compresskv_conv
ATTN_O_CONV = auto()
# openPangu-2.0 (learned static param sink)
ATTN_PARAM_SINK_KV = auto() # param_sink_compressed_kv
ATTN_PARAM_SINK_K_PE = auto() # param_sink_k_pe
# openPangu-2.0 (mHC / Hyper-Connections: per-attn, per-mlp, global merge)
MHC_ATTN_PHI = auto()
MHC_ATTN_ALPHA = auto()
MHC_ATTN_BETA = auto()
MHC_ATTN_GAMMA = auto()
MHC_MLP_PHI = auto()
MHC_MLP_ALPHA = auto()
MHC_MLP_BETA = auto()
MHC_MLP_GAMMA = auto()
MHC_MERGE_PHI = auto()
MHC_MERGE_ALPHA = auto() # branch_alpha_pre
MHC_MERGE_BETA = auto() # branch_beta_pre
MHC_MERGE_GAMMA = auto()
# openPangu-2.0 (sandwich norm: extra whole-block post-norm on a layer subset)
BLOCK_POST_NORM = auto()
MODEL_ARCH_NAMES: dict[MODEL_ARCH, str] = {
@@ -437,6 +468,7 @@ MODEL_ARCH_NAMES: dict[MODEL_ARCH, str] = {
MODEL_ARCH.DEEPSEEK2: "deepseek2",
MODEL_ARCH.CHATGLM: "chatglm",
MODEL_ARCH.GLM4_MOE: "glm4moe",
MODEL_ARCH.OPENPANGU: "openpangu",
MODEL_ARCH.BITNET: "bitnet",
MODEL_ARCH.BITNET_25: "bitnet-25",
MODEL_ARCH.T5: "t5",
@@ -563,6 +595,29 @@ TENSOR_NAMES: dict[MODEL_TENSOR, str] = {
MODEL_TENSOR.MTP_CENTROIDS: "mtp_centroids",
MODEL_TENSOR.DFLASH_FC: "dflash_fc",
MODEL_TENSOR.DFLASH_HIDDEN_NORM: "dflash_hidden_norm",
# openPangu-2.0
MODEL_TENSOR.INDEXER_K_NORM: "blk.{bid}.attn_indexer_k_norm",
MODEL_TENSOR.INDEXER_PROJ: "blk.{bid}.attn_indexer_weights_proj",
MODEL_TENSOR.INDEXER_ATTN_K: "blk.{bid}.attn_indexer_k",
MODEL_TENSOR.INDEXER_ATTN_Q_B: "blk.{bid}.attn_indexer_q_b",
MODEL_TENSOR.ATTN_QA_CONV: "blk.{bid}.attn_qa_conv",
MODEL_TENSOR.ATTN_KV_CONV: "blk.{bid}.attn_compresskv_conv",
MODEL_TENSOR.ATTN_O_CONV: "blk.{bid}.attn_o_conv",
MODEL_TENSOR.ATTN_PARAM_SINK_KV: "blk.{bid}.attn_param_sink_kv",
MODEL_TENSOR.ATTN_PARAM_SINK_K_PE: "blk.{bid}.attn_param_sink_k_pe",
MODEL_TENSOR.MHC_ATTN_PHI: "blk.{bid}.attn_mhc_phi",
MODEL_TENSOR.MHC_ATTN_ALPHA: "blk.{bid}.attn_mhc_alpha",
MODEL_TENSOR.MHC_ATTN_BETA: "blk.{bid}.attn_mhc_beta",
MODEL_TENSOR.MHC_ATTN_GAMMA: "blk.{bid}.attn_mhc_gamma",
MODEL_TENSOR.MHC_MLP_PHI: "blk.{bid}.mlp_mhc_phi",
MODEL_TENSOR.MHC_MLP_ALPHA: "blk.{bid}.mlp_mhc_alpha",
MODEL_TENSOR.MHC_MLP_BETA: "blk.{bid}.mlp_mhc_beta",
MODEL_TENSOR.MHC_MLP_GAMMA: "blk.{bid}.mlp_mhc_gamma",
MODEL_TENSOR.MHC_MERGE_PHI: "merge_mhc_phi",
MODEL_TENSOR.MHC_MERGE_ALPHA: "merge_mhc_alpha",
MODEL_TENSOR.MHC_MERGE_BETA: "merge_mhc_beta",
MODEL_TENSOR.MHC_MERGE_GAMMA: "merge_mhc_gamma",
MODEL_TENSOR.BLOCK_POST_NORM: "blk.{bid}.block_post_norm",
}
MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
@@ -1298,6 +1353,70 @@ MODEL_TENSORS: dict[MODEL_ARCH, list[MODEL_TENSOR]] = {
MODEL_TENSOR.NEXTN_SHARED_HEAD_HEAD,
MODEL_TENSOR.NEXTN_SHARED_HEAD_NORM,
],
MODEL_ARCH.OPENPANGU: [
MODEL_TENSOR.TOKEN_EMBD,
MODEL_TENSOR.OUTPUT_NORM,
MODEL_TENSOR.OUTPUT,
# MLA attention (deepseek-style)
MODEL_TENSOR.ATTN_NORM,
MODEL_TENSOR.ATTN_Q_A,
MODEL_TENSOR.ATTN_Q_B,
MODEL_TENSOR.ATTN_KV_A_MQA,
MODEL_TENSOR.ATTN_KV_B,
MODEL_TENSOR.ATTN_K_B,
MODEL_TENSOR.ATTN_V_B,
MODEL_TENSOR.ATTN_Q_A_NORM,
MODEL_TENSOR.ATTN_KV_A_NORM,
MODEL_TENSOR.ATTN_OUT,
MODEL_TENSOR.ATTN_POST_NORM, # post_attention_layernorm (sandwich)
# DSA lightning indexer
MODEL_TENSOR.INDEXER_K_NORM,
MODEL_TENSOR.INDEXER_PROJ,
MODEL_TENSOR.INDEXER_ATTN_K,
MODEL_TENSOR.INDEXER_ATTN_Q_B,
# MoME causal convs + param sink
MODEL_TENSOR.ATTN_QA_CONV,
MODEL_TENSOR.ATTN_KV_CONV,
MODEL_TENSOR.ATTN_O_CONV,
MODEL_TENSOR.ATTN_PARAM_SINK_KV,
MODEL_TENSOR.ATTN_PARAM_SINK_K_PE,
# MoE (routed + shared + sigmoid bias) and dense-lead FFN
MODEL_TENSOR.FFN_NORM, # pre_mlp_layernorm
MODEL_TENSOR.FFN_POST_NORM, # post_mlp_layernorm (sandwich)
MODEL_TENSOR.FFN_GATE,
MODEL_TENSOR.FFN_DOWN,
MODEL_TENSOR.FFN_UP,
MODEL_TENSOR.FFN_GATE_INP,
MODEL_TENSOR.FFN_GATE_EXP,
MODEL_TENSOR.FFN_DOWN_EXP,
MODEL_TENSOR.FFN_UP_EXP,
MODEL_TENSOR.FFN_GATE_SHEXP,
MODEL_TENSOR.FFN_DOWN_SHEXP,
MODEL_TENSOR.FFN_UP_SHEXP,
MODEL_TENSOR.FFN_EXP_PROBS_B,
# mHC / Hyper-Connections
MODEL_TENSOR.MHC_ATTN_PHI,
MODEL_TENSOR.MHC_ATTN_ALPHA,
MODEL_TENSOR.MHC_ATTN_BETA,
MODEL_TENSOR.MHC_ATTN_GAMMA,
MODEL_TENSOR.MHC_MLP_PHI,
MODEL_TENSOR.MHC_MLP_ALPHA,
MODEL_TENSOR.MHC_MLP_BETA,
MODEL_TENSOR.MHC_MLP_GAMMA,
MODEL_TENSOR.MHC_MERGE_PHI,
MODEL_TENSOR.MHC_MERGE_ALPHA,
MODEL_TENSOR.MHC_MERGE_BETA,
MODEL_TENSOR.MHC_MERGE_GAMMA,
# sandwich extra block post-norm (layer subset)
MODEL_TENSOR.BLOCK_POST_NORM,
# NextN / MTP tail (3 layers)
MODEL_TENSOR.NEXTN_EH_PROJ,
MODEL_TENSOR.NEXTN_EMBED_TOKENS,
MODEL_TENSOR.NEXTN_ENORM,
MODEL_TENSOR.NEXTN_HNORM,
MODEL_TENSOR.NEXTN_SHARED_HEAD_HEAD,
MODEL_TENSOR.NEXTN_SHARED_HEAD_NORM,
],
MODEL_ARCH.DFLASH: [
MODEL_TENSOR.OUTPUT_NORM,
MODEL_TENSOR.ATTN_NORM,
+88
View File
@@ -87,6 +87,20 @@ class TensorNameMap:
MODEL_TENSOR.ROPE_FACTORS_LONG: (),
MODEL_TENSOR.ROPE_FACTORS_SHORT: (),
# openPangu-2.0 global mHC stream-merge module (non-block)
MODEL_TENSOR.MHC_MERGE_PHI: (
"model.merge_mhc_module.phi",
),
MODEL_TENSOR.MHC_MERGE_ALPHA: (
"model.merge_mhc_module.branch_alpha_pre",
),
MODEL_TENSOR.MHC_MERGE_BETA: (
"model.merge_mhc_module.branch_beta_pre",
),
MODEL_TENSOR.MHC_MERGE_GAMMA: (
"model.merge_mhc_module.norm_gamma",
),
}
block_mappings_cfg: dict[MODEL_TENSOR, tuple[str, ...]] = {
@@ -697,6 +711,80 @@ class TensorNameMap:
"model.layers.{bid}.post_attention_layernorm",
),
},
# openPangu-2.0: disambiguate the sandwich norms (post_attention/pre_mlp/post_mlp)
# from the generic collisions, and pin all Pangu-only block tensors.
MODEL_ARCH.OPENPANGU: {
MODEL_TENSOR.ATTN_POST_NORM: (
"model.layers.{bid}.post_attention_layernorm",
),
MODEL_TENSOR.FFN_NORM: (
"model.layers.{bid}.pre_mlp_layernorm",
),
MODEL_TENSOR.FFN_POST_NORM: (
"model.layers.{bid}.post_mlp_layernorm",
),
MODEL_TENSOR.BLOCK_POST_NORM: (
"model.layers.{bid}.block_post_layernorm",
),
MODEL_TENSOR.FFN_EXP_PROBS_B: (
"model.layers.{bid}.mlp.e_score_correction",
),
# DSA lightning indexer
MODEL_TENSOR.INDEXER_K_NORM: (
"model.layers.{bid}.self_attn.indexer.k_norm",
),
MODEL_TENSOR.INDEXER_PROJ: (
"model.layers.{bid}.self_attn.indexer.weights_proj",
),
MODEL_TENSOR.INDEXER_ATTN_K: (
"model.layers.{bid}.self_attn.indexer.wk",
),
MODEL_TENSOR.INDEXER_ATTN_Q_B: (
"model.layers.{bid}.self_attn.indexer.wq_b",
),
# MoME causal convs
MODEL_TENSOR.ATTN_QA_CONV: (
"model.layers.{bid}.self_attn.qa_conv",
),
MODEL_TENSOR.ATTN_KV_CONV: (
"model.layers.{bid}.self_attn.compresskv_conv",
),
MODEL_TENSOR.ATTN_O_CONV: (
"model.layers.{bid}.self_attn.o_conv",
),
# learned static param sink
MODEL_TENSOR.ATTN_PARAM_SINK_KV: (
"model.layers.{bid}.self_attn.param_sink_compressed_kv",
),
MODEL_TENSOR.ATTN_PARAM_SINK_K_PE: (
"model.layers.{bid}.self_attn.param_sink_k_pe",
),
# mHC / Hyper-Connections (per attn + per mlp)
MODEL_TENSOR.MHC_ATTN_PHI: (
"model.layers.{bid}.attn_mhc_module.phi",
),
MODEL_TENSOR.MHC_ATTN_ALPHA: (
"model.layers.{bid}.attn_mhc_module.branch_alpha",
),
MODEL_TENSOR.MHC_ATTN_BETA: (
"model.layers.{bid}.attn_mhc_module.branch_beta",
),
MODEL_TENSOR.MHC_ATTN_GAMMA: (
"model.layers.{bid}.attn_mhc_module.norm_gamma",
),
MODEL_TENSOR.MHC_MLP_PHI: (
"model.layers.{bid}.mlp_mhc_module.phi",
),
MODEL_TENSOR.MHC_MLP_ALPHA: (
"model.layers.{bid}.mlp_mhc_module.branch_alpha",
),
MODEL_TENSOR.MHC_MLP_BETA: (
"model.layers.{bid}.mlp_mhc_module.branch_beta",
),
MODEL_TENSOR.MHC_MLP_GAMMA: (
"model.layers.{bid}.mlp_mhc_module.norm_gamma",
),
},
}
mapping: dict[str, tuple[MODEL_TENSOR, str]]
+12
View File
@@ -698,6 +698,9 @@ extern "C" {
LLAMA_API bool llama_model_has_recurrent(const struct llama_model * model);
// Returns true if the model is openPangu (conv-only recurrent state that rides the spec-rollback checkpoint)
LLAMA_API bool llama_model_is_openpangu(const struct llama_model * model);
// Returns true if the model is a Gemma 4 MTP assistant (external frozen-KV speculative drafter)
LLAMA_API bool llama_model_is_gemma4_mtp_assistant(const struct llama_model * model);
@@ -705,6 +708,15 @@ extern "C" {
LLAMA_API bool llama_model_is_split_mode_graph(const struct llama_model * model);
// Returns false for models whose KV cache cannot be re-positioned after the fact
// (K-shift / context shift / self-extend), e.g. openPangu's latent cache.
LLAMA_API bool llama_model_supports_ctx_shift(const struct llama_model * model);
// Returns false for models that can only reuse a cached sequence as a pure extension:
// rewinding into the middle of a decoded sequence loses per-position side state
// (e.g. openPangu keeps only the current recurrent conv state).
LLAMA_API bool llama_model_supports_partial_kv_reuse(const struct llama_model * model);
LLAMA_API const char * llama_model_arch_string(const struct llama_model * model);
// Returns 0 on success
+1
View File
@@ -111,6 +111,7 @@ add_library(llama
graphs/build_gptneox.cpp
graphs/build_arctic.cpp
graphs/build_deepseek2.cpp
graphs/build_openpangu.cpp
graphs/build_glm4.cpp
graphs/build_bitnet.cpp
graphs/build_cohere2.cpp
File diff suppressed because it is too large Load Diff
+5
View File
@@ -86,6 +86,7 @@ static const std::map<llm_arch, const char *> LLM_ARCH_NAMES = {
{ LLM_ARCH_GEMMA4_MTP, "gemma4_mtp" },
{ LLM_ARCH_DFLASH_DRAFT, "dflash-draft" },
{ LLM_ARCH_GEMMA4_ASSISTANT,"gemma4-assistant" },
{ LLM_ARCH_OPENPANGU, "openpangu" },
{ LLM_ARCH_UNKNOWN, "(unknown)" },
};
@@ -264,6 +265,10 @@ static const std::map<llm_kv, const char *> LLM_KV_NAMES = {
{ LLM_KV_ADAPTER_TYPE, "adapter.type" },
{ LLM_KV_ADAPTER_LORA_ALPHA, "adapter.lora.alpha" },
{ LLM_KV_OPENPANGU_MHC_NUM_STREAM, "%s.mhc_num_stream" },
{ LLM_KV_OPENPANGU_MHC_RECUR_NORM, "%s.mhc_recur_norm" },
{ LLM_KV_OPENPANGU_PARAM_SINK_NUMBER, "%s.param_sink_number" },
};
LLM_KV::LLM_KV(llm_arch arch, const char* suffix) : arch(arch), suffix(suffix) {}
+26
View File
@@ -84,6 +84,7 @@ enum llm_arch {
LLM_ARCH_GEMMA4_MTP,
LLM_ARCH_DFLASH_DRAFT,
LLM_ARCH_GEMMA4_ASSISTANT,
LLM_ARCH_OPENPANGU,
LLM_ARCH_UNKNOWN,
};
@@ -247,6 +248,11 @@ enum llm_kv {
LLM_KV_ADAPTER_TYPE,
LLM_KV_ADAPTER_LORA_ALPHA,
// openPangu-2.0 (mHC / Hyper-Connections + param sink)
LLM_KV_OPENPANGU_MHC_NUM_STREAM,
LLM_KV_OPENPANGU_MHC_RECUR_NORM,
LLM_KV_OPENPANGU_PARAM_SINK_NUMBER,
};
struct LLM_KV {
@@ -381,6 +387,26 @@ enum llm_tensor {
LLM_TENSOR_DFLASH_FC,
LLM_TENSOR_DFLASH_HIDDEN_NORM,
// openPangu-2.0
LLM_TENSOR_ATTN_QA_CONV, // MoME causal conv on q-lora latent
LLM_TENSOR_ATTN_KV_CONV, // MoME causal conv on compressed-kv latent
LLM_TENSOR_ATTN_O_CONV, // MoME causal conv on attn output
LLM_TENSOR_ATTN_PARAM_SINK_KV, // learned static latent-kv sink
LLM_TENSOR_ATTN_PARAM_SINK_K_PE,
LLM_TENSOR_BLOCK_POST_NORM, // sandwich whole-block post-norm (layer subset)
LLM_TENSOR_MHC_ATTN_PHI, // mHC / Hyper-Connections (per attn sublayer)
LLM_TENSOR_MHC_ATTN_ALPHA,
LLM_TENSOR_MHC_ATTN_BETA,
LLM_TENSOR_MHC_ATTN_GAMMA,
LLM_TENSOR_MHC_MLP_PHI, // mHC (per mlp sublayer)
LLM_TENSOR_MHC_MLP_ALPHA,
LLM_TENSOR_MHC_MLP_BETA,
LLM_TENSOR_MHC_MLP_GAMMA,
LLM_TENSOR_MHC_MERGE_PHI, // mHC global stream-merge (non-block)
LLM_TENSOR_MHC_MERGE_ALPHA,
LLM_TENSOR_MHC_MERGE_BETA,
LLM_TENSOR_MHC_MERGE_GAMMA,
LLM_TENSOR_UNKNOWN,
};
+26
View File
@@ -106,6 +106,8 @@ void llm_build_context::init() {
lctx.inp_out_ids = nullptr;
lctx.inp_KQ_mask = nullptr;
lctx.inp_KQ_mask_swa = nullptr;
lctx.inp_KQ_mask_swa_win = nullptr;
lctx.openpangu_swa_window_view = {};
lctx.inp_K_shift = nullptr;
lctx.inp_mean = nullptr;
lctx.inp_cls = nullptr;
@@ -117,6 +119,7 @@ void llm_build_context::init() {
lctx.inp_embd_enc = nullptr;
lctx.inp_KQ_mask_cross = nullptr;
lctx.inp_dsa_sink = nullptr;
lctx.inp_mtp_carry = nullptr;
lctx.dflash.inputs.target_features = nullptr;
lctx.dflash.inputs.pos_ctx = nullptr;
lctx.dflash.inputs.kq_mask = nullptr;
@@ -521,6 +524,25 @@ ggml_tensor * llm_build_context::build_inp_KQ_mask_swa(bool causal) {
return flash_attn ? ggml_cast(ctx0, lctx.inp_KQ_mask_swa, GGML_TYPE_F16) : lctx.inp_KQ_mask_swa;
}
ggml_tensor * llm_build_context::build_inp_KQ_mask_swa_win(int64_t n_kv_win, bool causal) {
GGML_ASSERT(hparams.n_swa > 0);
GGML_ASSERT(n_kv_win > 0);
if (causal && flash_attn) {
lctx.inp_KQ_mask_swa_win = ggml_new_tensor_2d(ctx0, GGML_TYPE_F16, n_kv_win, GGML_PAD(n_tokens, GGML_KQ_MASK_PAD));
cb(lctx.inp_KQ_mask_swa_win, "KQ_mask_swa_win", -1);
ggml_set_input(lctx.inp_KQ_mask_swa_win);
return lctx.inp_KQ_mask_swa_win;
}
lctx.inp_KQ_mask_swa_win = causal
? ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_kv_win, GGML_PAD(n_tokens, GGML_KQ_MASK_PAD))
: ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_tokens, GGML_PAD(n_tokens, GGML_KQ_MASK_PAD));
cb(lctx.inp_KQ_mask_swa_win, "KQ_mask_swa_win", -1);
ggml_set_input(lctx.inp_KQ_mask_swa_win);
return flash_attn ? ggml_cast(ctx0, lctx.inp_KQ_mask_swa_win, GGML_TYPE_F16) : lctx.inp_KQ_mask_swa_win;
}
ggml_tensor * llm_build_context::build_inp_mean() {
lctx.inp_mean = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_tokens, n_tokens);
cb(lctx.inp_mean, "inp_mean", -1);
@@ -2623,6 +2645,10 @@ ggml_cgraph * llm_build_context::llama_build_graph(
{
result = llm.build_deepseek2();
} break;
case LLM_ARCH_OPENPANGU:
{
result = llm.build_openpangu();
} break;
case LLM_ARCH_CHATGLM:
{
result = llm.build_chatglm();
+35
View File
@@ -139,6 +139,8 @@ struct llm_build_context {
ggml_tensor * build_inp_KQ_mask_swa(bool causal = true);
ggml_tensor * build_inp_KQ_mask_swa_win(int64_t n_kv_win, bool causal = true);
ggml_tensor * build_inp_mean();
ggml_tensor * build_inp_cls();
@@ -273,6 +275,39 @@ struct llm_build_context {
ggml_cgraph * build_arctic();
ggml_cgraph * build_deepseek2();
ggml_cgraph * build_openpangu();
// openPangu attention sublayer body (shared by base layers and the NextN/MTP head):
// input is the already-input-normed hidden; returns the post-o_proj attention output.
ggml_tensor * build_openpangu_attention(
ggml_cgraph * gf,
const struct llama_layer & layer,
int il,
ggml_tensor * x_normed,
ggml_tensor * KQ_mask,
ggml_tensor * inp_pos,
ggml_tensor * conv_state,
ggml_tensor * seq_qnext,
float kq_scale,
bool KQ_mask_swa_windowed = false);
// openPangu NextN/MTP head (plain-residual block, no mHC): eh_proj stitching ->
// attention -> MoE -> shared head. Returns the draft logits tensor.
ggml_tensor * build_openpangu_mtp(
const struct llama_layer & mtp_layer,
ggml_tensor * prev_embeddings,
ggml_cgraph * gf,
int il,
ggml_tensor * inp_pos,
ggml_tensor * KQ_mask,
ggml_tensor * inp_out_ids,
ggml_tensor * inp_tokens,
ggml_tensor * seq_qnext,
ggml_tensor ** full_hidden_out = nullptr,
bool select_outputs = true,
bool build_logits = true,
bool cache_writes_only = false,
bool KQ_mask_swa_windowed = false);
ggml_tensor * build_deepseek2_tp_attention(
ggml_cgraph * gf, int il,
+57 -4
View File
@@ -13,6 +13,28 @@ struct llama_model;
#include <set>
#include <memory>
struct llama_openpangu_swa_window_view {
int64_t w_view = 0;
int64_t win_off = 0;
bool engaged = false;
};
static inline llama_openpangu_swa_window_view llama_openpangu_calc_swa_window_view(
int64_t n_kv, int64_t n_tokens, int64_t window, int64_t pad) {
llama_openpangu_swa_window_view result;
if (window <= 0 || n_kv <= 0) {
result.w_view = n_kv;
return result;
}
const int64_t unpadded = window + pad + n_tokens;
const int64_t overcovered = pad > 1 ? ((unpadded + pad - 1) / pad) * pad : unpadded;
result.w_view = overcovered < n_kv ? overcovered : n_kv;
result.win_off = n_kv - result.w_view;
result.engaged = result.w_view < n_kv;
return result;
}
struct llama_kv_cell {
llama_pos pos = -1;
llama_pos delta = 0;
@@ -42,6 +64,11 @@ struct llama_kv_cache {
bool hybrid = false;
bool v_trans = true; // the value tensor is transposed
// openPangu s_l holds position-strict MoME conv state, not per-sequence recurrent
// slots; Qwen3Next-style s_l handling (seq ops, state serialization, s_copy) must
// skip it. Speculative rollback snapshots/restores it via the whole-slot spec checkpoint.
bool s_l_position_strict = false;
// Note: The value of head isn't only used to optimize searching
// for a free KV slot. llama_decode_internal also uses it, so it
// cannot be freely changed after a slot has been allocated.
@@ -61,10 +88,10 @@ struct llama_kv_cache {
std::vector<struct ggml_tensor *> v_l;
std::vector<struct ggml_tensor *> s_l; // per layer recurrent state storage (Qwen3Next)
// DSA lightning-indexer key cache (GLM-5.2 / DeepSeek-V3.2). One per layer, MQA single
// head: [indexer_head_size, kv_size]. Mirrors k_l but stores the (Hadamard-rotated)
// indexer keys so a decoded token scores against ALL past indexer keys, not just the
// current batch. Empty unless the model has the DSA indexer.
// Persistent DSA indexer-key cache. One per indexer layer, MQA single head:
// [indexer_head_size, kv_size]. Stores architecture-specific indexer keys in their
// scoring representation so a decoded token scores against all past indexer keys.
// Empty unless the model has the DSA indexer.
std::vector<struct ggml_tensor *> kr_l;
// When true, the delta_net graph builder will enable per-step SSM state saves
@@ -372,6 +399,7 @@ struct llama_context {
struct ggml_tensor * inp_out_ids; // I32 [n_outputs]
struct ggml_tensor * inp_KQ_mask; // F32 [kv_size, n_batch]
struct ggml_tensor * inp_KQ_mask_swa; // F32 [kv_size, n_batch]
struct ggml_tensor * inp_KQ_mask_swa_win = nullptr; // F32 [openPangu SWA W_view, n_batch]
struct ggml_tensor * inp_K_shift; // I32 [kv_size]
struct ggml_tensor * inp_mean; // F32 [n_batch, n_batch]
struct ggml_tensor * inp_cls; // I32 [n_batch]
@@ -384,14 +412,35 @@ struct llama_context {
struct ggml_tensor * inp_KQ_mask_cross; // F32 [n_outputs_enc, n_batch]
struct ggml_tensor * inp_scale = nullptr; // F32 [n_tokens]
struct ggml_tensor * inp_mtp_states = nullptr;
struct ggml_tensor * inp_mtp_carry = nullptr; // F32 [n_embd, nextn-1] per-head hidden at the last committed position
struct ggml_tensor * inp_dsa_sink = nullptr; // F32 [n_kv, n_tokens] per-sequence attention-sink boost for DSA indexer top-k
struct ggml_tensor * inp_mask_inf = nullptr;
struct openpangu_swa_window_view_state {
bool active = false;
int32_t n_kv = 0;
int32_t n_tokens = 0;
uint32_t window = 0;
uint32_t pad = 0;
int64_t w_view = 0;
int64_t win_off = 0;
} openpangu_swa_window_view;
// multi-head MTP chaining state: head k's output row at the last committed position,
// written back after each warmup/update decode and fed into the next MTP graph through
// inp_mtp_carry (zeroed when a prompt warmup restarts from position 0). The readback is
// issued async after compute; mtp_carry_pending marks a copy that must be synchronized
// before the host buffer is read or resized.
std::vector<float> mtp_carry;
bool mtp_carry_pending = false;
ggml_backend_t ggml_backend_by_name(const char * name);
struct Prev;
std::unique_ptr<Prev> prev;
std::unique_ptr<Prev> prev_mtp;
int32_t mtp_step_idx = 0;
int32_t mtp_n_heads = 0;
void reset_scheduler();
bool can_reuse_graph(const llama_batch & u_batch);
@@ -409,6 +458,8 @@ struct llama_context {
// uninitialized -> wrong block-max-pool/top-k -> degraded/NaN sparse-FA decode). Register the
// kr_l cpy per layer here and patch its offset in update_cache_copies(), exactly like K/V.
std::vector<CacheCopy> dsa_cache_copies;
std::vector<CacheCopy> openpangu_cache_copies;
std::vector<CacheCopy> openpangu_cache_copies_mtp;
bool update_cache_copies();
@@ -418,6 +469,8 @@ struct llama_context {
bool prepare_mtp_graph_inputs(
struct llama_context & lctx);
void set_mtp_op_type(llama_mtp_op_type value);
void set_mtp_step_idx(int32_t value);
void set_mtp_n_heads(int32_t value);
int max_nodes(int n_tokens, int n_kv) const;
};
+72
View File
@@ -1131,6 +1131,78 @@ void llm_load_hparams(
default: model.type = e_model::MODEL_UNKNOWN;
}
} break;
case LLM_ARCH_OPENPANGU:
{
ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps);
ml.get_key(LLM_KV_LEADING_DENSE_BLOCK_COUNT, hparams.n_layer_dense_lead);
ml.get_key(LLM_KV_ATTENTION_Q_LORA_RANK, hparams.n_lora_q);
ml.get_key(LLM_KV_ATTENTION_KV_LORA_RANK, hparams.n_lora_kv);
ml.get_key(LLM_KV_EXPERT_FEED_FORWARD_LENGTH, hparams.n_ff_exp);
ml.get_key(LLM_KV_EXPERT_SHARED_COUNT, hparams.n_expert_shared);
ml.get_key(LLM_KV_EXPERT_WEIGHTS_SCALE, hparams.expert_weights_scale);
ml.get_key(LLM_KV_EXPERT_WEIGHTS_NORM, hparams.expert_weights_norm, false);
// openPangu routes with a sigmoid gate + e_score_correction bias
hparams.expert_gating_func = LLM_EXPERT_GATING_FUNC_TYPE_NONE;
ml.get_key(LLM_KV_EXPERT_GATING_FUNC, hparams.expert_gating_func, false);
if (hparams.expert_gating_func == LLM_EXPERT_GATING_FUNC_TYPE_NONE) {
hparams.expert_gating_func = LLM_EXPERT_GATING_FUNC_SIGMOID;
}
// DSA lightning indexer
ml.get_key(LLM_KV_ATTENTION_INDEXER_HEAD_COUNT, hparams.indexer_n_head, false);
ml.get_key(LLM_KV_ATTENTION_INDEXER_KEY_LENGTH, hparams.indexer_head_size, false);
ml.get_key(LLM_KV_ATTENTION_INDEXER_TOP_K, hparams.indexer_top_k, false);
// mHC / Hyper-Connections + learned param sink
ml.get_key(LLM_KV_OPENPANGU_MHC_NUM_STREAM, hparams.mhc_num_stream);
ml.get_key(LLM_KV_OPENPANGU_MHC_RECUR_NORM, hparams.mhc_recur_norm);
ml.get_key(LLM_KV_OPENPANGU_PARAM_SINK_NUMBER, hparams.param_sink_number);
// NextN / MTP layers are appended at the end and skipped for base generation
ml.get_key(LLM_KV_NEXTN_PREDICT_LAYERS, hparams.nextn_predict_layers, false);
if (hparams.nextn_predict_layers > 0 && hparams.nextn_predict_layers < hparams.n_layer) {
hparams.n_layer_kv_from_start = hparams.n_layer - hparams.nextn_predict_layers;
}
// DSA/SWA schedule: openpangu.swa_layers lists the sliding-window layer ids and
// openpangu.sliding_window_list the per-entry window; the remaining base layers
// are DSA (indexer + top-k, no window). The NextN/MTP layers appear in the SWA
// list with their own (larger) window, used by the MTP graphs. Absent keys keep
// every window at 0 = dense fallback (pre-DSA GGUFs keep working).
{
std::vector<uint32_t> swa_ids, swa_windows;
const bool have_ids = ml.get_arr("openpangu.swa_layers", swa_ids, false);
const bool have_win = ml.get_arr("openpangu.sliding_window_list", swa_windows, false);
if (have_ids && have_win && swa_ids.size() == swa_windows.size()) {
const uint32_t n_base = hparams.n_layer > hparams.nextn_predict_layers
? hparams.n_layer - hparams.nextn_predict_layers : hparams.n_layer;
for (size_t i = 0; i < swa_ids.size(); ++i) {
const uint32_t il = swa_ids[i];
if (il >= hparams.n_layer) {
throw std::runtime_error(format("openpangu.swa_layers contains out-of-range layer %u", il));
}
hparams.openpangu_window[il] = swa_windows[i];
if (il < n_base) {
if (hparams.n_swa != 0 && hparams.n_swa != swa_windows[i]) {
throw std::runtime_error("openpangu: non-uniform base sliding windows are not supported");
}
hparams.n_swa = swa_windows[i];
} else {
if (hparams.n_swa_mtp != 0 && hparams.n_swa_mtp != swa_windows[i]) {
throw std::runtime_error("openpangu: non-uniform MTP sliding windows are not supported");
}
hparams.n_swa_mtp = swa_windows[i];
}
}
} else if (have_ids || have_win) {
LLAMA_LOG_WARN("%s: openpangu SWA schedule keys are inconsistent - keeping dense fallback\n", __func__);
}
}
model.type = e_model::MODEL_UNKNOWN; // 92B-A6B (46 + 3 MTP layers)
} break;
case LLM_ARCH_CHATGLM:
{
ml.get_key(LLM_KV_ATTENTION_LAYERNORM_RMS_EPS, hparams.f_norm_rms_eps);
+10
View File
@@ -131,6 +131,16 @@ struct llama_hparams {
// indexer_types metadata if present, else derived from the GLM-5.2 config rule at load time.
std::array<bool, LLAMA_MAX_LAYERS> indexer_is_full = {};
// openPangu-2.0 (mHC / Hyper-Connections + learned param sink)
uint32_t mhc_num_stream = 1;
uint32_t mhc_recur_norm = 0;
uint32_t param_sink_number = 0;
// openPangu DSA/SWA schedule: per-layer sliding window (0 = DSA layer, full causal
// attention over the indexer's top-k selection). The NextN/MTP layers carry their own
// (larger) window, applied when the graph is built with an MTP op type.
uint32_t n_swa_mtp = 0;
std::array<uint32_t, LLAMA_MAX_LAYERS> openpangu_window = {};
// qwen3vl deepstack
uint32_t n_deepstack_layers = 0;
+148
View File
@@ -119,6 +119,7 @@ struct create_tensors_helper : public create_tensors_helper_interface {
bool create_arctix_tensors(const LLM_TN & tn);
bool create_deepseek2_tensors(const LLM_TN & tn);
bool create_openpangu_tensors(const LLM_TN & tn);
bool create_glm_dsa_tensors(const LLM_TN & tn);
@@ -2870,6 +2871,151 @@ bool create_tensors_helper::create_glm_dsa_tensors(const LLM_TN & tn) {
return use_mmap_buffer;
}
// openPangu-2.0-Flash: GLM-DSA-style MLA + MoE base, plus mHC / MoME conv / param-sink /
// sandwich norms / DSA lightning indexer. The graph runs absorbed MLA over a latent KV
// cache from the converter's pre-split attn_k_b/attn_v_b; the indexer tensors feed the
// DSA top-k selection on windowless layers when the GGUF carries a DSA/SWA schedule.
// Conv weights keep their torch [C,1,kernel] layout (ggml ne = {kernel,1,C}).
bool create_tensors_helper::create_openpangu_tensors(const LLM_TN & tn) {
LOADING_PRELUDE
const int64_t n_embd_head_qk_rope = hparams.n_rot;
const int64_t n_embd_head_qk_nope = hparams.n_embd_head_k(0) - hparams.n_rot;
const int64_t q_lora_rank = hparams.n_lora_q;
const int64_t kv_lora_rank = hparams.n_lora_kv;
const int64_t n_ff_exp = hparams.n_ff_exp;
const int64_t n_expert_shared = hparams.n_expert_shared;
const int64_t S = hparams.mhc_num_stream; // 4
const int64_t SH = S * n_embd; // concatenated multi-stream width
const int64_t phi_out = (S + 2) * S; // mHC phi output width
const int64_t beta_len = S * (S + 2); // mHC beta length
const int64_t sink_n = hparams.param_sink_number; // 128
const int64_t kernel = 3; // MoME causal conv width
model.tok_embd = create_tensor(ctx_input, tn(LLM_TENSOR_TOKEN_EMBD, "weight"), {n_embd, n_vocab});
// output
{
model.output_norm = create_tensor(ctx_output, tn(LLM_TENSOR_OUTPUT_NORM, "weight"), {n_embd});
model.output = create_tensor(ctx_output, tn(LLM_TENSOR_OUTPUT, "weight"), {n_embd, n_vocab});
}
// global mHC stream-merge module (non-block)
model.mhc_merge_phi = create_tensor(ctx_output, tn(LLM_TENSOR_MHC_MERGE_PHI, "weight"), {SH, S});
model.mhc_merge_alpha = create_tensor(ctx_output, tn(LLM_TENSOR_MHC_MERGE_ALPHA), {1});
model.mhc_merge_beta = create_tensor(ctx_output, tn(LLM_TENSOR_MHC_MERGE_BETA), {S});
model.mhc_merge_gamma = create_tensor(ctx_output, tn(LLM_TENSOR_MHC_MERGE_GAMMA), {SH});
for (int i = 0; i < n_layer; ++i) {
const bool is_mtp_layer = hparams.nextn_predict_layers > 0 &&
static_cast<uint32_t>(i) >= n_layer - hparams.nextn_predict_layers;
int flags = 0;
if (!model.mtp && is_mtp_layer) {
flags |= llama_model_loader::TENSOR_SKIP | llama_model_loader::TENSOR_NOT_REQUIRED;
}
ggml_context * ctx_layer = ctx_for_layer(i);
ggml_context * ctx_split = ctx_for_layer_split(i);
auto & layer = model.layers[i];
const auto graph_or_attn = (model.split_mode == LLAMA_SPLIT_MODE_GRAPH ||
model.split_mode == LLAMA_SPLIT_MODE_ATTN);
auto norm_ctx = graph_or_attn ? ctx_split : ctx_layer;
auto moe_ctx = graph_or_attn ? ctx_split : ctx_layer;
// --- norms (sandwich): input / post-attn / pre-mlp(ffn) / post-mlp ---
layer.attn_norm = create_tensor(norm_ctx, tn(LLM_TENSOR_ATTN_NORM, "weight", i), {n_embd}, flags);
layer.attn_post_norm = create_tensor(norm_ctx, tn(LLM_TENSOR_ATTN_POST_NORM, "weight", i), {n_embd}, flags);
layer.attn_q_a_norm = create_tensor(norm_ctx, tn(LLM_TENSOR_ATTN_Q_A_NORM, "weight", i), {q_lora_rank}, flags);
layer.attn_kv_a_norm = create_tensor(norm_ctx, tn(LLM_TENSOR_ATTN_KV_A_NORM, "weight", i), {kv_lora_rank}, flags);
// block_post_norm only present on a layer subset -> optional
// block_post_layernorm is RMSNorm over the concatenated S*H (mhc_num_stream * hidden)
layer.block_post_norm = create_tensor(norm_ctx, tn(LLM_TENSOR_BLOCK_POST_NORM, "weight", i), {SH}, flags | llama_model_loader::TENSOR_NOT_REQUIRED);
// --- MLA projections (ik-native, pre-split k_b/v_b) ---
layer.wq_a = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_Q_A, "weight", i), {n_embd, q_lora_rank}, flags);
layer.wq_b = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_Q_B, "weight", i), {q_lora_rank, n_head * n_embd_head_k}, flags);
layer.wkv_a_mqa = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_KV_A_MQA, "weight", i), {n_embd, kv_lora_rank + n_embd_head_qk_rope}, flags);
// older GGUFs include the fused kv_b_proj, but the absorbed-MLA graph has no
// consumer for it (attention runs entirely on pre-split k_b/v_b below) -> skip
// when present and allow it to be absent from new conversions
layer.wkv_b = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_KV_B, "weight", i), {kv_lora_rank, n_head * (n_embd_head_qk_nope + n_embd_head_v)}, flags | llama_model_loader::TENSOR_SKIP | llama_model_loader::TENSOR_NOT_REQUIRED);
// converter-emitted pre-split k_b/v_b, loaded 2D as written (head-major rows) and
// reshaped in-graph: k_b absorbs q_nope into latent space, v_b up-projects the output
layer.wk_b = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_K_B, "weight", i), {n_embd_head_qk_nope, n_head * kv_lora_rank}, flags);
layer.wv_b = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_V_B, "weight", i), {kv_lora_rank, n_head * n_embd_head_v}, flags);
layer.wo = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_OUT, "weight", i), {n_head * n_embd_head_v, n_embd}, flags);
// --- MoME causal convs (torch [C,1,3] -> gguf squeezes to 2D {3, C}) ---
layer.qa_conv = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_QA_CONV, "weight", i), {kernel, q_lora_rank}, flags);
// compresskv_conv acts on the compressed-kv latent only (kv_lora_rank), NOT the k_pe part
layer.kv_conv = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_KV_CONV, "weight", i), {kernel, kv_lora_rank}, flags);
layer.o_conv = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_O_CONV, "weight", i), {kernel, n_head * n_embd_head_v}, flags);
// --- learned static param sink (latent-kv prepended to attention) ---
layer.param_sink_kv = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_PARAM_SINK_KV, i), {kv_lora_rank, sink_n}, flags);
layer.param_sink_k_pe = create_tensor(ctx_split, tn(LLM_TENSOR_ATTN_PARAM_SINK_K_PE, i), {n_embd_head_qk_rope, sink_n}, flags);
// --- DSA indexer (loaded-but-unused in the dense-fallback graph) ---
layer.indexer_k_norm = create_tensor(ctx_split, tn(LLM_TENSOR_INDEXER_K_NORM, "weight", i), {hparams.indexer_head_size}, flags | llama_model_loader::TENSOR_NOT_REQUIRED);
layer.indexer_proj = create_tensor(ctx_split, tn(LLM_TENSOR_INDEXER_PROJ, "weight", i), {n_embd, hparams.indexer_n_head}, flags | llama_model_loader::TENSOR_NOT_REQUIRED);
layer.indexer_attn_k = create_tensor(ctx_split, tn(LLM_TENSOR_INDEXER_ATTN_K, "weight", i), {n_embd, hparams.indexer_head_size}, flags | llama_model_loader::TENSOR_NOT_REQUIRED);
layer.indexer_attn_q_b = create_tensor(ctx_split, tn(LLM_TENSOR_INDEXER_ATTN_Q_B, "weight", i), {q_lora_rank, hparams.indexer_n_head * hparams.indexer_head_size}, flags | llama_model_loader::TENSOR_NOT_REQUIRED);
// --- mHC / Hyper-Connections (per attn + per mlp sublayer, float32) ---
// MTP/NextN layers run WITHOUT mHC (tail_use_mhc=false in the reference; the
// checkpoint has no mhc tensors for them), so only create these for base layers.
if (!is_mtp_layer) {
layer.mhc_attn_phi = create_tensor(norm_ctx, tn(LLM_TENSOR_MHC_ATTN_PHI, "weight", i), {SH, phi_out}, flags);
layer.mhc_attn_alpha = create_tensor(norm_ctx, tn(LLM_TENSOR_MHC_ATTN_ALPHA, i), {3}, flags);
layer.mhc_attn_beta = create_tensor(norm_ctx, tn(LLM_TENSOR_MHC_ATTN_BETA, i), {beta_len}, flags);
layer.mhc_attn_gamma = create_tensor(norm_ctx, tn(LLM_TENSOR_MHC_ATTN_GAMMA, i), {SH}, flags);
layer.mhc_mlp_phi = create_tensor(norm_ctx, tn(LLM_TENSOR_MHC_MLP_PHI, "weight", i), {SH, phi_out}, flags);
layer.mhc_mlp_alpha = create_tensor(norm_ctx, tn(LLM_TENSOR_MHC_MLP_ALPHA, i), {3}, flags);
layer.mhc_mlp_beta = create_tensor(norm_ctx, tn(LLM_TENSOR_MHC_MLP_BETA, i), {beta_len}, flags);
layer.mhc_mlp_gamma = create_tensor(norm_ctx, tn(LLM_TENSOR_MHC_MLP_GAMMA, i), {SH}, flags);
}
// --- FFN: dense-lead then MoE (routed + shared, sigmoid bias) ---
layer.ffn_norm = create_tensor(norm_ctx, tn(LLM_TENSOR_FFN_NORM, "weight", i), {n_embd}, flags);
layer.ffn_post_norm = create_tensor(norm_ctx, tn(LLM_TENSOR_FFN_POST_NORM, "weight", i), {n_embd}, flags);
if (i < (int) hparams.n_layer_dense_lead) {
layer.ffn_gate = create_tensor(ctx_split, tn(LLM_TENSOR_FFN_GATE, "weight", i), {n_embd, n_ff}, flags);
layer.ffn_down = create_tensor(ctx_split, tn(LLM_TENSOR_FFN_DOWN, "weight", i), { n_ff, n_embd}, flags);
layer.ffn_up = create_tensor(ctx_split, tn(LLM_TENSOR_FFN_UP, "weight", i), {n_embd, n_ff}, flags);
} else {
layer.ffn_gate_inp = create_tensor(moe_ctx, tn(LLM_TENSOR_FFN_GATE_INP, "weight", i), {n_embd, n_expert}, flags);
layer.ffn_exp_probs_b = create_tensor(moe_ctx, tn(LLM_TENSOR_FFN_EXP_PROBS_B, "bias", i), {n_expert}, flags);
GGML_ASSERT(n_expert > 0);
GGML_ASSERT(n_expert_used > 0);
layer.ffn_gate_exps = create_tensor(ctx_split, tn(LLM_TENSOR_FFN_GATE_EXPS, "weight", i), { n_embd, n_ff_exp, n_expert}, flags);
layer.ffn_down_exps = create_tensor(ctx_split, tn(LLM_TENSOR_FFN_DOWN_EXPS, "weight", i), {n_ff_exp, n_embd, n_expert}, flags);
layer.ffn_up_exps = create_tensor(ctx_split, tn(LLM_TENSOR_FFN_UP_EXPS, "weight", i), { n_embd, n_ff_exp, n_expert}, flags);
layer.ffn_gate_shexp = create_tensor(ctx_split, tn(LLM_TENSOR_FFN_GATE_SHEXP, "weight", i), {n_embd, n_ff_exp * n_expert_shared}, flags);
layer.ffn_down_shexp = create_tensor(ctx_split, tn(LLM_TENSOR_FFN_DOWN_SHEXP, "weight", i), { n_ff_exp * n_expert_shared, n_embd}, flags);
layer.ffn_up_shexp = create_tensor(ctx_split, tn(LLM_TENSOR_FFN_UP_SHEXP, "weight", i), {n_embd, n_ff_exp * n_expert_shared}, flags);
}
if (is_mtp_layer) {
layer.nextn.eh_proj = create_tensor(ctx_split, tn(LLM_TENSOR_NEXTN_EH_PROJ, "weight", i), { 2 * n_embd, n_embd }, flags);
layer.nextn.enorm = create_tensor(ctx_split, tn(LLM_TENSOR_NEXTN_ENORM, "weight", i), { n_embd }, flags);
layer.nextn.hnorm = create_tensor(ctx_split, tn(LLM_TENSOR_NEXTN_HNORM, "weight", i), { n_embd }, flags);
layer.nextn.embed_tokens = create_tensor(ctx_split, tn(LLM_TENSOR_NEXTN_EMBED_TOKENS, "weight", i), { n_embd, n_vocab }, flags | llama_model_loader::TENSOR_NOT_REQUIRED);
layer.nextn.shared_head_head = create_tensor(ctx_split, tn(LLM_TENSOR_NEXTN_SHARED_HEAD_HEAD, "weight", i), { n_embd, n_vocab }, flags | llama_model_loader::TENSOR_NOT_REQUIRED);
layer.nextn.shared_head_norm = create_tensor(ctx_split, tn(LLM_TENSOR_NEXTN_SHARED_HEAD_NORM, "weight", i), { n_embd }, flags);
}
}
return use_mmap_buffer;
}
bool create_tensors_helper::create_glm4_moe_tensors(const LLM_TN & tn) {
LOADING_PRELUDE
@@ -4514,6 +4660,8 @@ bool create_tensors_helper::create_tensors() {
use_mmap_buffer = create_deepseek2_tensors(tn); break;
case LLM_ARCH_GLM_DSA:
use_mmap_buffer = create_glm_dsa_tensors(tn); break;
case LLM_ARCH_OPENPANGU:
use_mmap_buffer = create_openpangu_tensors(tn); break;
case LLM_ARCH_GLM4_MOE:
use_mmap_buffer = create_glm4_moe_tensors(tn); break;
case LLM_ARCH_BITNET:
+1
View File
@@ -1376,3 +1376,4 @@ template std::enable_if<std::is_integral<unsigned int>::value, bool>::type llama
template std::enable_if<std::is_integral<unsigned int>::value, bool>::type llama_model_loader::get_arr_n<unsigned int>(enum llm_kv, unsigned int&, bool);
template bool llama_model_loader::get_arr<int32_t, 8>(const std::string &, std::array<int32_t, 8> &, bool);
template bool llama_model_loader::get_arr<uint32_t, 8>(const std::string &, std::array<uint32_t, 8> &, bool);
template bool llama_model_loader::get_arr<uint32_t>(const std::string &, std::vector<uint32_t> &, bool);
+94 -1
View File
@@ -1775,6 +1775,71 @@ static const std::map<llm_arch, std::map<llm_tensor, std::string>> LLM_TENSOR_NA
},
},
{
LLM_ARCH_OPENPANGU,
{
{ LLM_TENSOR_TOKEN_EMBD, "token_embd" },
{ LLM_TENSOR_OUTPUT_NORM, "output_norm" },
{ LLM_TENSOR_OUTPUT, "output" },
{ LLM_TENSOR_ATTN_NORM, "blk.%d.attn_norm" },
{ LLM_TENSOR_ATTN_Q_A, "blk.%d.attn_q_a" },
{ LLM_TENSOR_ATTN_Q_A_NORM, "blk.%d.attn_q_a_norm" },
{ LLM_TENSOR_ATTN_Q_B, "blk.%d.attn_q_b" },
{ LLM_TENSOR_ATTN_KV_A_MQA, "blk.%d.attn_kv_a_mqa" },
{ LLM_TENSOR_ATTN_KV_A_NORM, "blk.%d.attn_kv_a_norm" },
{ LLM_TENSOR_ATTN_KV_B, "blk.%d.attn_kv_b" },
{ LLM_TENSOR_ATTN_K_B, "blk.%d.attn_k_b" },
{ LLM_TENSOR_ATTN_V_B, "blk.%d.attn_v_b" },
{ LLM_TENSOR_ATTN_OUT, "blk.%d.attn_output" },
{ LLM_TENSOR_ATTN_POST_NORM, "blk.%d.post_attention_norm" },
// DSA lightning indexer (loaded; unused in the dense-fallback graph)
{ LLM_TENSOR_INDEXER_K_NORM, "blk.%d.attn_indexer_k_norm" },
{ LLM_TENSOR_INDEXER_PROJ, "blk.%d.attn_indexer_weights_proj" },
{ LLM_TENSOR_INDEXER_ATTN_K, "blk.%d.attn_indexer_k" },
{ LLM_TENSOR_INDEXER_ATTN_Q_B, "blk.%d.attn_indexer_q_b" },
// MoME causal convs + param sink
{ LLM_TENSOR_ATTN_QA_CONV, "blk.%d.attn_qa_conv" },
{ LLM_TENSOR_ATTN_KV_CONV, "blk.%d.attn_compresskv_conv" },
{ LLM_TENSOR_ATTN_O_CONV, "blk.%d.attn_o_conv" },
{ LLM_TENSOR_ATTN_PARAM_SINK_KV, "blk.%d.attn_param_sink_kv" },
{ LLM_TENSOR_ATTN_PARAM_SINK_K_PE, "blk.%d.attn_param_sink_k_pe" },
// FFN (dense-lead + MoE, sandwich norms)
{ LLM_TENSOR_FFN_NORM, "blk.%d.ffn_norm" },
{ LLM_TENSOR_FFN_POST_NORM, "blk.%d.post_ffw_norm" },
{ LLM_TENSOR_FFN_GATE, "blk.%d.ffn_gate" },
{ LLM_TENSOR_FFN_UP, "blk.%d.ffn_up" },
{ LLM_TENSOR_FFN_DOWN, "blk.%d.ffn_down" },
{ LLM_TENSOR_FFN_GATE_INP, "blk.%d.ffn_gate_inp" },
{ LLM_TENSOR_FFN_GATE_EXPS, "blk.%d.ffn_gate_exps" },
{ LLM_TENSOR_FFN_DOWN_EXPS, "blk.%d.ffn_down_exps" },
{ LLM_TENSOR_FFN_UP_EXPS, "blk.%d.ffn_up_exps" },
{ LLM_TENSOR_FFN_GATE_SHEXP, "blk.%d.ffn_gate_shexp" },
{ LLM_TENSOR_FFN_DOWN_SHEXP, "blk.%d.ffn_down_shexp" },
{ LLM_TENSOR_FFN_UP_SHEXP, "blk.%d.ffn_up_shexp" },
{ LLM_TENSOR_FFN_EXP_PROBS_B, "blk.%d.exp_probs_b" },
// mHC / Hyper-Connections
{ LLM_TENSOR_MHC_ATTN_PHI, "blk.%d.attn_mhc_phi" },
{ LLM_TENSOR_MHC_ATTN_ALPHA, "blk.%d.attn_mhc_alpha" },
{ LLM_TENSOR_MHC_ATTN_BETA, "blk.%d.attn_mhc_beta" },
{ LLM_TENSOR_MHC_ATTN_GAMMA, "blk.%d.attn_mhc_gamma" },
{ LLM_TENSOR_MHC_MLP_PHI, "blk.%d.mlp_mhc_phi" },
{ LLM_TENSOR_MHC_MLP_ALPHA, "blk.%d.mlp_mhc_alpha" },
{ LLM_TENSOR_MHC_MLP_BETA, "blk.%d.mlp_mhc_beta" },
{ LLM_TENSOR_MHC_MLP_GAMMA, "blk.%d.mlp_mhc_gamma" },
{ LLM_TENSOR_MHC_MERGE_PHI, "merge_mhc_phi" },
{ LLM_TENSOR_MHC_MERGE_ALPHA, "merge_mhc_alpha" },
{ LLM_TENSOR_MHC_MERGE_BETA, "merge_mhc_beta" },
{ LLM_TENSOR_MHC_MERGE_GAMMA, "merge_mhc_gamma" },
{ LLM_TENSOR_BLOCK_POST_NORM, "blk.%d.block_post_norm" },
// NextN / MTP tail
{ LLM_TENSOR_NEXTN_EH_PROJ, "blk.%d.nextn.eh_proj" },
{ LLM_TENSOR_NEXTN_EMBED_TOKENS, "blk.%d.nextn.embed_tokens" },
{ LLM_TENSOR_NEXTN_ENORM, "blk.%d.nextn.enorm" },
{ LLM_TENSOR_NEXTN_HNORM, "blk.%d.nextn.hnorm" },
{ LLM_TENSOR_NEXTN_SHARED_HEAD_HEAD, "blk.%d.nextn.shared_head_head" },
{ LLM_TENSOR_NEXTN_SHARED_HEAD_NORM, "blk.%d.nextn.shared_head_norm" },
},
},
{
LLM_ARCH_UNKNOWN,
{
@@ -2050,6 +2115,10 @@ bool llama_model_has_recurrent(const llama_model * model) {
return llm_arch_is_hybrid(model->arch) || llm_arch_is_recurrent(model->arch);
}
bool llama_model_is_openpangu(const llama_model * model) {
return model && model->arch == LLM_ARCH_OPENPANGU;
}
bool llama_model_is_gemma4_mtp_assistant(const llama_model * model) {
return model && (model->arch == LLM_ARCH_GEMMA4_MTP || model->arch == LLM_ARCH_GEMMA4_ASSISTANT);
}
@@ -2075,6 +2144,18 @@ bool llama_model_is_split_mode_graph(const struct llama_model * model) {
return model && (model->split_mode == LLAMA_SPLIT_MODE_GRAPH || model->split_mode == LLAMA_SPLIT_MODE_ATTN);
}
bool llama_model_supports_ctx_shift(const struct llama_model * model) {
// openPangu's latent K rows carry baked-in rope (k_pe) and the DSA indexer cache is
// keyed by absolute position; neither survives K-shift/defrag-style repositioning.
return model && model->arch != LLM_ARCH_OPENPANGU;
}
bool llama_model_supports_partial_kv_reuse(const struct llama_model * model) {
// openPangu keeps only the current MoME conv state, so a sequence can be extended or
// reset, but rewinding into its decoded middle cannot reconstruct the state at that point.
return model && model->arch != LLM_ARCH_OPENPANGU;
}
llm_tensor llm_tensor_type(llm_arch arch, const std::string & tensor_name, int il) {
auto it = LLM_TENSOR_NAMES.find(arch);
if (it == LLM_TENSOR_NAMES.end()) {
@@ -2106,12 +2187,24 @@ llm_tensor llm_tensor_type(llm_arch arch, const std::string & tensor_name, int i
return LLM_TENSOR_UNKNOWN;
}
size_t llama_model::cache_size(int il, ggml_type type_k, ggml_type type_v, uint32_t kv_size, int mla_attn, int n_seq_max, bool flash_attn) const {
size_t llama_model::cache_size(int il, ggml_type type_k, ggml_type type_v, ggml_type idx_type_k, uint32_t kv_size, int mla_attn, int n_seq_max, bool flash_attn) const {
if (il < 0 || il >= hparams.n_layer) return 0;
if (hparams.recurrent_layer_arr[il]) {
auto state_sots = std::min<uint32_t>(std::max<uint32_t>(1, n_seq_max), kv_size);
return hparams.n_embd_v_s() * state_sots * sizeof(float);
}
if (arch == LLM_ARCH_OPENPANGU) {
// MLA-latent cache: K row [ckv | roped k_pe]. The value-side latent is
// rederived from K per graph. DSA layers also cache one indexer key per
// position. The recurrent conv slot is constant-size and negligible here.
size_t size = ggml_row_size(type_k, hparams.n_lora_kv + hparams.n_rot) * kv_size;
if (hparams.indexer_head_size > 0 && hparams.n_swa > 0 &&
il < (int) hparams.n_layer - (int) hparams.nextn_predict_layers &&
hparams.openpangu_window[il] == 0) {
size += ggml_row_size(idx_type_k, hparams.indexer_head_size) * kv_size;
}
return size;
}
bool is_mla_attn = arch == LLM_ARCH_DEEPSEEK2 || arch == LLM_ARCH_GLM_DSA || arch == LLM_ARCH_MISTRAL4;
if (is_mla_attn && mla_attn) {
auto n_embd_head_qk_rope = hparams.n_rot;
+27 -1
View File
@@ -388,10 +388,30 @@ struct llama_layer {
struct llama_layer_nextn nextn;
// openPangu-2.0: MoME causal convs + learned static param sink + mHC + block post-norm
struct ggml_tensor * qa_conv = nullptr;
struct ggml_tensor * kv_conv = nullptr; // compresskv_conv
struct ggml_tensor * o_conv = nullptr;
struct ggml_tensor * param_sink_kv = nullptr;
struct ggml_tensor * param_sink_k_pe = nullptr;
struct ggml_tensor * param_sink_blk = nullptr;
struct ggml_tensor * param_sink_lat_t = nullptr;
struct ggml_tensor * block_post_norm = nullptr;
struct ggml_tensor * mhc_attn_phi = nullptr;
struct ggml_tensor * mhc_attn_alpha = nullptr;
struct ggml_tensor * mhc_attn_beta = nullptr;
struct ggml_tensor * mhc_attn_gamma = nullptr;
struct ggml_tensor * mhc_mlp_phi = nullptr;
struct ggml_tensor * mhc_mlp_alpha = nullptr;
struct ggml_tensor * mhc_mlp_beta = nullptr;
struct ggml_tensor * mhc_mlp_gamma = nullptr;
std::unique_ptr<ggml_tensor> computed_wk_b;
std::unique_ptr<ggml_tensor> computed_wk_b_pp;
std::unique_ptr<ggml_tensor> computed_wv_b;
std::unique_ptr<ggml_tensor> computed_wkv_b;
std::unique_ptr<ggml_tensor> computed_param_sink_blk;
std::unique_ptr<ggml_tensor> computed_param_sink_lat_t;
// Per-device replicas of computed wk_b/wv_b (-sm graph). Buffers owned via model.bufs.
std::vector<std::unique_ptr<ggml_tensor>> computed_wk_b_replicas;
@@ -442,6 +462,12 @@ struct llama_model {
struct ggml_tensor * output_norm_enc;
struct ggml_tensor * output_mtp = nullptr;
// openPangu-2.0: global mHC stream-merge module (non-block)
struct ggml_tensor * mhc_merge_phi = nullptr;
struct ggml_tensor * mhc_merge_alpha = nullptr;
struct ggml_tensor * mhc_merge_beta = nullptr;
struct ggml_tensor * mhc_merge_gamma = nullptr;
std::unique_ptr<ggml_tensor> output_mtp_ptr;
llama_split_tensor split_output;
@@ -545,7 +571,7 @@ struct llama_model {
return hadamard_size(hparams.n_embd_head_v(il));
}
size_t cache_size(int il, ggml_type type_k, ggml_type type_v, uint32_t kv_size, int mla_attn, int n_seq_max, bool flash_attn) const;
size_t cache_size(int il, ggml_type type_k, ggml_type type_v, ggml_type idx_type_k, uint32_t kv_size, int mla_attn, int n_seq_max, bool flash_attn) const;
void set_tensor_overrides(const llama_model_params& params);
+1
View File
@@ -2028,6 +2028,7 @@ void llama_vocab::impl::load(llama_model_loader & ml, const LLM_KV & kv) {
tokenizer_pre == "qwen2" ||
tokenizer_pre == "deepseek-r1-qwen" ||
tokenizer_pre == "kormo" ||
tokenizer_pre == "openpangu" ||
tokenizer_pre == "f2llmv2") {
pre_type = LLAMA_VOCAB_PRE_TYPE_QWEN2;
clean_spaces = false;
+688 -66
View File
File diff suppressed because it is too large Load Diff