mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-09-01 09:37:35 +02:00
* gguf: add qwen4exp (Qwen3.8-Flash-Next) arch and converter
Adds the GGUF-side plumbing for HF model_type qwen4_exp:
- MODEL_ARCH.QWEN4EXP plus tensors for the low-rank hyper-connection
variant (hc_*_norm/down/up/inject) and the PLE n-gram hash embeddings.
The DeepSeek-V4 hc_*_fn/base/scale tensors are a different
parameterisation, so these are separate entries rather than reuse.
- Reuses the existing indexer, per_layer_token_embd, SSM and
compress_ratios keys unchanged.
- conversion/qwen4exp.py inherits the Qwen3.5 linear-attention V-head
reorder and interleaved mrope, concatenates the 128 PLE embedding
shards, and splits index_qk_proj into separate indexer q/k tensors.
The PLE hash multipliers reach ~2.4e13. prepare_tensors() casts every
non-float dtype to float32 before modify_tensors() runs, and GGUF array
writes infer INT32 from Python ints, so both paths are bypassed: the
constants are read from the pre-cast lazy tensors and written as
explicit UINT64 arrays.
Additive only; no existing arch changes behaviour.
* llama: load qwen4exp (Qwen3.8-Flash-Next) hparams and tensors
Adds LLM_ARCH_QWEN4EXP with its hparams and tensor loading. The graph
comes in the next commit; this makes the model load and report correct
metadata.
- hyper-connections set n_embd_out_impl = hc_count * n_embd, so the
residual stream is 4x wide and there is no output_norm: the final
mixer's hc_norm is the last norm in the model.
- registered as hybrid and given the same recurrent/attention memory
filters as Qwen3-Next and Qwen3.5.
- reuses the existing indexer, per_layer_token_embd, SSM and
compress_ratios keys as-is.
- the PLE table row count is read back from the file rather than
recomputing the vocab padding rule.
llama-model-loader gains UINT64 array support. That branch previously
threw, so no existing caller changes behaviour; it is needed because the
PLE hash multipliers do not fit in int32.
* qwen4exp: shorten comments
* llama: qwen4exp text graph with hyper-connections, GDN and MoE
Implements the decode graph for Qwen3.8-Flash-Next: the hyper-connection
residual stream, gated delta net layers, the MoE block with its gated shared
expert, and dense full attention. The QSA indexer and the PLE n-gram embedding
are not wired up yet and land in later commits.
Hyper-connections are implemented here rather than shared with deepseek4.cpp.
The two formulations agree on the [n_embd, hc, n_tokens] layout and little
else: DeepSeek-V4 mixes with a full-rank projection and Sinkhorn-normalises
it, whereas this model uses a low-rank down/silu/up sigmoid gate and collapses
by a plain mean. Only the ~10 line stream mean is genuinely common, so sharing
would mean touching DSV4's hot path and its three fused CUDA ops to reuse very
little. What is reused is the substantive part: the LLM_KV_HYPER_CONNECTION_*
keys, the n_embd_out_impl wide-residual support already in the loader, and the
layout convention.
Also allows a checkpoint to carry no PLE layers at all, which makes it
possible to bring the graph up and validate it in stages.
Validated against vLLM, the only working reference implementation. On a
scaled-down model with an init scale large enough to give non-uniform logits,
agreement with vLLM sits at the numerical noise floor: llama.cpp f32 against
its own bf16 gives 84.3% top-1 agreement over 255 positions, and this graph
against vLLM gives 85.1%. The comparison was calibrated by seeding three
deliberate bugs (silu instead of sigmoid on the delta net gate, dropping the
1/hc scale in the mix, dropping the 2x in the combine); each drops top-1 to
between 0% and 11%, an order of magnitude below the floor.
* llama: qwen4exp PLE n-gram hash embedding
Adds the per-layer embedding: a custom I32 graph input hashes each token with
its ngram_size-1 predecessors host-side and the result is a plain row gather
over the shared table, the same shape gemma3n's per-layer embedding uses. The
hash has to run on the host because the splitmix64-derived multipliers reach
2^45, so the products need 64-bit integers and an xor, neither of which ggml
has.
Predecessors that fall outside the ubatch come from a small per-sequence
history on the model, mirroring the per-request ngram_context the reference
carries. It is only trusted when contiguous with the incoming position, so a
fresh prompt or a rewound cache falls back to EOS padding rather than hashing
against stale tokens.
The depthwise conv is written out as a sum of shifted, per-channel-scaled
copies rather than through ggml_conv_1d_dw, which carries a correctness
warning upstream.
Verified two ways. The row indices match a transcription of the reference's
tensor formulation exactly, 1024 of 1024 rows, including sequences with EOS
tokens sprinkled through them to exercise the segment reset. Separately, with
PLE placed on layer 0 so its input is just the token embedding, ple_embd and
ple_gated_value match a PyTorch computation from the same checkpoint to every
printed digit.
End to end over 1023 scored positions the port sits the same distance from
vLLM with PLE as without it, 6.3 points of top-1 against 6.0, so PLE costs no
accuracy relative to the rest of the model. That common offset is vLLM's bf16
activations, which cannot be removed: its QSA kernel refuses float32.
Two bugs found along the way, both caught by the row-index check. The history
was read and updated in the same pass, so a token early in a ubatch could pick
up an earlier token of that same ubatch as prior context; it is now snapshotted
first. And an EOS token was cutting its own context, where the reference takes
the last EOS strictly before the position, so a boundary only hides tokens from
the positions after it.
Known gap: the conv carries no state across ubatches, so it is exact only for a
prefill that starts at position 0. Chunked prefill and decode need the conv
state wired into the recurrent memory, and the conv branch itself is still
numerically unverified because the fixture zeroes its weights.
* llama: carry the qwen4exp PLE conv state across ubatches
The PLE depthwise conv was zero-padding on the left, which is only right for a
prefill that starts at position 0. Decode and chunked prefill saw a truncated
history for the first (kernel-1)*ngram_size positions of every ubatch.
The PLE module sits on a layer that is also a delta-net layer, so both need a
conv history in the same recurrent row. Rather than plumb a per-layer state
size through build_rs and build_conv_state, the row is widened once and each
convolution addresses its own slice through a local helper. n_embd_r() gains
the extra span, which is zero for every other architecture because it is
derived from ple_n_heads.
Verified by feeding the same 1024 token sequence in chunks instead of one
shot: at 64 tokens per decode the logits are bit-identical to the single-shot
run, 1023 of 1023 top-1 and a maximum logprob deviation of exactly zero. At
one token per decode they differ slightly, but the no-PLE model differs more
under the same test (94.6% against 97.1%), so that is the usual gemv-versus-
gemm accumulation difference and not the state.
The conv branch is also no longer unverified. With non-zero conv weights the
port sits 6.3 points of top-1 below the numerical floor, the same distance as
with the weights zeroed and as the model with no PLE at all, so the branch
adds no error of its own.
test-llama-archs passes every existing architecture at 0.00e+00, including the
delta-net models that share this code path.
* llama: fix the qwen4exp PLE conv state and unblock test-llama-archs
build_rs writes into the state tensor in place, zeroing one row and copying the
carried-over states, so calling it twice for the same layer let the second call
clobber the first write-back. The PLE layer is also a delta-net layer, so that
is exactly what happened: both convolutions gathered the same row. They now
share a single gather per layer.
The earlier claim that the conv state was carried correctly was tested on a
fixture whose conv weights are zero, where the branch contributes nothing and
chunking matches trivially. Re-running with non-zero conv weights showed the
divergence, growing with the number of ubatch boundaries: 97.1% top-1 at one
boundary down to 90.2% at seven. With the shared gather it is bit-identical to
the single-shot run at every chunk size tried, 512, 128 and 64, with a maximum
logprob deviation of exactly zero over 1023 positions. The delta-net-only model
stays bit-identical too, so nothing regressed there.
Also derive the delta-net conv channel count the way load_arch_tensors sizes
wqkv instead of from ssm_d_inner. The two agree for this model, but n_embd_r()
only bounds the row and the convolution has to match the tensor feeding it.
test-llama-archs previously aborted on this architecture and took every later
architecture with it. qwen4exp is marked MoE-only, given the hyper-connection
keys and an ssm_d_inner consistent with its tensor derivation, and skipped for
now: the hyper-connection keys written by get_gguf_ctx are not reaching the
synthesised file, which needs a separate look. The suite completes again, 124
architectures at 0.00e+00.
* llama: optional indexer key cache in llama_memory_hybrid
Groundwork for qwen4exp's QSA sparse attention. Its indexer needs a per-token
key history for the full-attention layers, but a hybrid model cannot use
llama_kv_cache_dsa: that class derives from llama_memory_i rather than
llama_kv_cache, and llama_memory_hybrid constructs its attention cache
directly. No existing architecture pairs recurrent state with a sparse
indexer, so there was nothing to reuse wholesale.
llama_memory_hybrid therefore gains a third, optional cache, shaped the same
way llama_kv_cache_dsa shapes its lightning-indexer cache: a copy of hparams
with n_head_kv forced to 1 and n_embd_head_k_full set to indexer_head_size.
It is built only when a filter_idx callback is passed, which defaults to
nullptr, so every existing architecture gets exactly what it got before. The
per-sequence operations and the batch preparation forward to it under a null
check, matching how the DSA cache prepares its two caches over the same
ubatches.
test-llama-archs passes all 124 architectures at 0.00e+00, including the 12 in
the hybrid family that share this code. The qwen4exp fixtures are unchanged:
same logits against vLLM, and chunked evaluation still bit-identical to
single-shot.
* llama: QSA sparse attention for qwen4exp
The full-attention layers of this model do not attend to everything. An
indexer scores one mean-pooled key per block of compress_ratio tokens and
keeps a budget of the best blocks, plus the tail of tokens that do not yet
form a complete block. Below indexer_top_k + compress_ratio - 1 cached
tokens every block fits in the budget, so the result is exactly dense.
What is reused rather than rebuilt:
- the mask machinery. build_attn's DSA overload already turns a list of
token indices into a KQ mask via ggml_set_rows, so that block is lifted
out verbatim into build_attn_mask_top_k and shared with a new overload
on llm_graph_input_attn_kv. DSA's node sequence is unchanged; the new
overload exists because llama_kv_cache_dsa assumes MLA and cannot be
dropped into a hybrid model.
- the indexer key cache, which is the optional third cache added to
llama_memory_hybrid in the previous commit. It holds raw keys, because
pooling happens before the norm and the rotation.
The graph expands block scores rather than block indices: giving every
token of a block its block's score needs only a gather, where expanding
indices would need an integer multiply-add that ggml has no op for. Since
the budget is a whole number of blocks and a block's members tie exactly,
the cut still lands on a block boundary.
Everything that depends on cache layout is computed host-side in
set_input_qsa. Blocks are cuts of the position line rather than of the cell
array, so nothing assumes the cache is contiguous.
Measured on the tiny fixture against vLLM, comparing the selected token
indices directly rather than the logits:
below the budget selection identical, and 1024-token logits are
bit-identical to the pre-QSA dense path
above the budget mean jaccard 0.975
The direct index comparison is what made this correct. The reference
rectifies each head's dot product before summing over heads, which an
earlier reading of it had missed; on logits alone the resulting port looked
fine, because on a randomly initialised fixture the known-correct dense
path already disagrees with vLLM by more than the bug did. Comparing the
indices showed 0.794, and fixing the ReLU moved it to 0.975.
* llama: give the qwen4exp indexer cache the attention cache's slots
The indexer cache found its own slots, independently of the attention
cache. Both are the same size and see the same ubatches, so in a
straight-through prefill they agree, which is why every fixture and every
single-shot parity run passed. They drift once the context is being
rewritten between turns, and then the QSA top-k indices, which are applied
against the attention mask, point at the wrong cells.
The seven-turn chat test caught it on the third turn: llama-server aborted
on the assertion that the two caches report the same n_kv.
The cache is a side buffer addressed by the attention cache's cells, so it
now takes that cache's slot layout instead of computing one. Applying that
layout also marks its cells identically, so the two agree cell for cell by
construction rather than by coincidence, and the assertion can no longer
fire.
Inert where the caches already agreed: test-llama-archs green at 126 archs
and 0.00e+00, and the 4096-token tiny fixture is unchanged at max logit
delta 0.0.
* tests: record what the qwen4exp arch-test skip actually observes
The old note guessed that the hyper-connection keys never reach the file.
They do: dumping the gguf_context handed to llama_model_init_from_user
shows both among its 67 KVs, and the loader still reports one missing.
* tests: cover qwen4exp in test-llama-archs
The arch was skipped with a note guessing that the hyper-connection keys
never reached the synthesised file. They did. The suite builds a model, then
saves and reloads it, and llama_model_saver did not re-emit those keys, so
the failure was in the roundtrip leg rather than the first load. Three gaps,
all in shared code and all additive:
- add_kv_from_model wrote no hyper-connection, compress-ratio or PLE keys.
The PLE group only means anything whole, so it is written or omitted
together; the rest follow the file's existing style of writing every key
unconditionally, since an architecture that does not read one is
unaffected by a zero.
- the saver had no uint64 path at all, which the PLE hash constants need.
- add_tensors_from_model enumerates model-level tensors by hand and was
missing per_layer_tok_embd and the three final-mixer tensors.
Two smaller fixes on the qwen4exp side, both found by running the test:
- build_qsa_top_k divided by the compression ratio before asserting it was
non-zero, so a file without the key crashed instead of reporting.
- a layer with no compression ratio now falls back to dense attention,
which is what the model computes below the budget anyway. The test then
has to write a ratio to reach QSA at all, and an indexer key length no
narrower than n_rot, since the indexer ropes with the main attention's
rotary width.
Full suite: 126 archs, qwen4exp at 0.00e+00 with roundtrip OK. The tiny
fixture is unchanged, max logit delta 0.0 against the pre-QSA dense run.
* convert: stream the qwen4exp PLE table instead of concatenating it
The n-gram table arrives as 128 shards that were held in a dict and then
torch.cat-ed, so the peak was the shards plus the concatenation: around
300 GB of RSS on the real checkpoint, which rules out machines that could
otherwise convert this model.
Each shard is now written straight into a memory-mapped file at its final
row offset and dropped, so the resident set is one shard and the rest is
the page cache's problem. The temporary file sits beside the output and is
removed once the write finishes, including on failure.
Shards other than the last must be uniform for direct placement, which is
asserted rather than assumed, and a shard arriving before the stride is
known is held instead of misplaced.
Verified on the tiny fixture: the resulting GGUF is byte-identical to the
one the concatenating path produced (md5 2d274efac91ad1e9a6007efb0687e597).
* quantize: fall back to F16 for 32-block types with an odd ncols
tensor_type_fallback demotes a tensor whose ncols is not a multiple of the
target's block size, but its switch only enumerates the 256-block types. A
target that is already a 32-block type (iq4_nl, q4_0, q5_0, q8_0, ...) falls
into default: and throws, even though the function already knows how to answer
that case: the ncols check right below the switch resolves an unrepresentable
shape to F16.
Route those types into that check instead of throwing. Only paths that abort
today change, so no quantization that currently succeeds is affected.
Found on a 4-wide depthwise conv kernel. llama-quantize reported nothing but
"failed to quantize model from ...", with no tensor name and no exception text,
which made a quant recipe that had simply not pinned the tensor look like a
corrupt model. It now names the tensor and continues.
* quantize: let --tensor-type name per_layer_token_embd
per_layer_token_embd shares the TOKEN_EMBD category with token_embd.weight, so
--token-embedding-type is returned for it before any --tensor-type pattern is
consulted, and there is no way to give it a tier of its own.
That grouping is fine as a default and stays the default. It is a poor fit for
the size, though: on qwen4exp the table is 97.7 GiB of a 337.6 GiB BF16 file and
about 46% of a 4-bit one, roughly eighty times token_embd.weight, and it is
read by ggml_get_rows rather than a matmul so no imatrix ever covers it.
Allow an explicit --tensor-type pattern to name it, and only it. Nothing
changes unless such a pattern is passed, and token_embd.weight keeps the old
precedence in either case.
Measured on Qwen3.8-Flash-Next, Q4_K_M with an imatrix: the table lands at q8_0
(51.9 GiB, 113.5 GiB total) by following --token-embedding-type, and pinning it
q4_1 gives 30.5 GiB for 92.1 GiB total, 19% off the file.
* quantize: size the output buffer exactly instead of nelements * 4
The per-tensor output buffer was sized `nelements * 4`, described as an upper
bound. It is a very loose one: the output is at most 2 bytes per element
(f16/bf16) and usually well under 1.1 (q8_0 and below), so between 2x and 4x of
it is never touched. The exact size is already known here, since it is what the
quantization loop writes, what new_size sums to, and what the GGUF metadata is
asserted against a few lines later.
On a model whose largest tensor is a few GB none of this matters. On
Qwen3.8-Flash-Next it does: per_layer_token_embd is 51.2 G elements, so the
buffer was 205 GB where 54 GB is needed at q8_0 and 32 GB at q4_1.
Measured on that model, VmHWM of a live llama-quantize was 485 GB per process.
Three of them fit in 2 TB and five did not, which is what an OOM-killed quant
ladder looks like. This removes about 150 GB of that.
Byte-identical output, verified against the same binary built at the parent
commit: q4_K, q8_0, q5_K, q6_K and IQ4_XS, over BF16 and F32 sources, with and
without a PLE table present. Six cases, six matching md5s.
* qwen4exp: hash the image placeholder for multimodal batches
The PLE row indices are computed host-side from ubatch->token, and set_input
returned early when that was null. A multimodal ubatch is exactly that case:
the mtmd layer consumes the image placeholder ids and hands llama_decode
embeddings instead. The early return left the I32 index tensor uninitialised,
so ggml_get_rows indexed a 320 M row table with whatever the buffer happened to
contain, and aborted:
GGML_ASSERT(i01 >= 0 && i01 < ne01) failed
ggml_compute_forward_get_rows
mtmd_helper_decode_image_chunk -> llama_decode
Every image request crashed. Nothing caught it because the vision work had only
ever been verified by converting an mmproj, never by running one.
The reference computes the hash over input_ids, where those positions still
hold the image placeholder, so carry that id through as qwen4exp.ple.image_token_id
and hash it. The key is optional: a file converted before it existed falls back
to the PLE EOS token, which is defined and treats the image as a segment
boundary rather than crashing.
Verified end to end with llama-mtmd-cli, a Q4_K_M base and the F16 mmproj, on a
generated image with known content. The model names the red circle, the blue
square, the inverted green triangle and reads "UNSLOTH 42", each with the right
position.
* qwen4exp: support a non-unified KV cache in QSA
set_input_qsa asserted n_stream == 1, so llama-server could not serve this
model with more than one slot unless -kvu was passed. With a non-unified
cache each sequence owns its own cells, and a cell index means a different
token in each stream, so a single shared mapping is wrong.
- cell_blk, blk_cells and bias gain a stream dimension. At n_stream == 1
these collapse to the shapes they had, so the unified path is unchanged.
- Scoring is now batched over streams. ggml_mul_mat matches ne[2] on both
operands, so stream s's queries only ever meet stream s's blocks; without
this sequences would score against each other's context.
- set_input_qsa loops per stream and resolves cells through
v_cells[seq_to_stream[seq_id]], following set_input_kq_mask_impl, instead
of hardcoding v_cells[0].
- llama_kv_cache_context::get_n_stream() is added, mirroring the ns that
get_k and get_v already derive from the slot info.
build_attn_mask_top_k needed no change: it already expects
[n_top_k, n_batch, 1, n_stream], so the top-k result is reshaped to meet it.
set_input_qsa has exactly one caller, so the blast radius is qwen4exp only.
Validation, UD-Q4_K_XL on one B200:
- unified cache unchanged within noise: 1802.9/68.85 -> 1807.2/69.11 t/s at
batch 1, 2262.5/192.43 -> 2270.1/193.75 at batch 4.
- non-unified now runs at npl 1, 4, 16 where it previously aborted, and is
22% faster than the -kvu workaround at batch 16 (1205 vs 984 t/s total),
since per-stream cells avoid the cross-sequence masking a unified cache
pays for.
- no cross-stream contamination: four concurrent sequences each carrying a
distinct secret all recall their own and no other, on both cache modes.
- test-llama-archs green on qwen4exp, deepseek2, gemma3n, qwen3next, llama.
Note on testing: comparing concurrent output against solo output exactly is
not a valid check. It failed 0/4 with no bug present, and the unified-cache
control failed the same way, because batch composition changes the
floating-point reduction order and near-tied tokens flip. The contamination
test above is what the exit code gates on.
* llama: keep the qwen4exp top-k attention mask arch-local
The QSA graph needed a build_attn that attends only to the cells named by a
top_k tensor, and the first version got it by adding a llm_graph_input_attn_kv
overload to llm_graph_context and factoring the mask construction out of the
existing MLA sparse path into a shared build_attn_mask_top_k.
That put a new arch on the shared attention path and made the deepseek32 and
glm-dsa attention build depend on a helper introduced for qwen4exp. Build the
mask in src/models/qwen4exp.cpp instead and leave llama-graph.{h,cpp} exactly as
they were: the MLA path keeps its own copy of the same node sequence.
The nodes emitted are unchanged, so this is bit-identical.
* llama: hold the qwen4exp indexer cache in a new llama_memory_hybrid_idx
The indexer key cache was added by extending llama_memory_hybrid with an
optional third cache, and the host-side cell/block mapping that drives QSA was
added as set_input_qsa on llama_kv_cache. Both are shared classes that every
hybrid and every attention model goes through.
Move both into a new memory type, llama_memory_hybrid_idx, following
llama_kv_cache_msa: the indexer cache and the pos<->cell translation live with
the sparse-attention memory rather than in the classes that serve every other
architecture. llama-kv-cache.{h,cpp} and llama-memory-hybrid.{h,cpp} are
restored to their unmodified state.
init_batch is repeated from llama_memory_hybrid because the indexer cache has to
be handed the attention cache's slot infos, and those are not reachable through
the context the base returns. Allocating them separately lets the two caches
drift, which is what pointed QSA's top-k at the wrong cells before.
The context derives from llama_memory_hybrid_context so build_inp_mem_hybrid
keeps working unchanged, and get_n_stream is computed from the slot infos
exactly as llama_kv_cache_context did.
Behaviour is unchanged: logits over an 8192-token sequence are bit-identical to
the previous implementation, sparse and dense alike.
* llama: save and restore the qwen4exp indexer KV cache
llama_memory_hybrid_idx forwarded clear, seq_rm, seq_cp, seq_keep, seq_add and
seq_div to the indexer cache but not state_write / state_read, so a saved
session dropped the indexer keys and a restored one selected QSA top-k against
an empty cache. The effect is invisible until the context passes
indexer_top_k + compress_ratio - 1 cells, because QSA is exactly dense below
that and the indexer contents cannot change the result.
The indexer section is written last rather than next to the attention cache it
mirrors. As a suffix, a reader that does not expect it stops early and the
trailing bytes are caught by the size check in state_load_file; placed between
the attention and recurrent sections it would instead be parsed as recurrent
state, which can succeed and restore silent garbage. It follows the same
LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY gate as the attention cache, since a partial
checkpoint deliberately skips the token-level attention caches.
The indexer restores its own cells instead of taking the attention cache's
restored slots. The two caches share size, padding and every sequence
operation, and init_batch hands the indexer the attention cache's slot infos,
so both state_read_meta calls run find_slot over identical occupancy and land
on identical cells.
The overrides live on llama_memory_hybrid_idx, the only memory type that owns
an indexer cache, so llama_memory_hybrid and every architecture that uses it
write and read exactly the bytes they did before.
The session and sequence state versions are bumped because the qwen4exp state
layout changed. The session path already rejects a short read via its size
check, but llama_state_seq_load_file accepts one silently, so only the version
check stops a pre-fix blob from being half-restored by a fixed build.
(cherry picked from commit 2721542354f8e158c3217625f4e2e7b83e51e3fe)
* llama: make the qwen4exp PLE n-gram history per context and serialise it
The PLE hash of a token mixes in the ple_ngram_size - 1 tokens before it, which
a decode ubatch does not carry, so they were remembered in a map on
llama_model_qwen4exp. That is the wrong owner twice over.
A llama_model is shared by every context that loads it, and the map was keyed
only by llama_seq_id, so two contexts running the same sequence id - two server
instances on one model, or a draft/target pair - overwrote each other's window.
The next_pos guard turned that into EOS padding instead of a crash, so it
degraded quality silently.
The map was also in no state blob: grep found ple_hist in neither
llama-kv-cache.cpp nor llama-memory-*.cpp nor llama-context.cpp. A restored
context therefore failed the next_pos check on its first ubatch and hashed the
first tokens after the restore against EOS padding. This is why a session blob
round-tripped byte for byte while the restored context computed different
logits: the state was never in the bytes.
It moves to llama_memory_hybrid_idx, which is per context, is the memory type
qwen4exp always builds, and already does the per-sequence bookkeeping this
needs. Every sequence operation now carries the window with it:
seq_rm a rewind (p1 < 0) truncates the window to the surviving prefix and
moves next_pos to p0, so a rollback keeps exact context; a hole
punched in the middle leaves the window non-contiguous, so it is
dropped
seq_cp the destination inherits the source's window, truncated to the
copied position range - a copied sequence continues with the same
n-grams the source would have used
seq_keep every other sequence's window is dropped, like its cells
seq_add a shift that moves the whole window keeps it and moves next_pos with
it, which is the context-shift case; one that cuts through it drops
it
seq_div positions stop being consecutive, so an overlapping window is
dropped
clear everything is dropped
Dropping means next_pos = -1, which set_input turns into full EOS padding: the
same thing a fresh sequence gets, and the same thing this code did before it
followed the sequence operations at all, so no case is worse than before.
The state payload is a self-delimiting list, u32 count then per entry
{ i32 seq_id, i32 next_pos, u32 n_toks, i32 toks[n_toks] }, so a whole-context
save and a single-sequence save share one format and a single-sequence restore
can retarget the window at its destination seq_id. It is written after the
indexer section, last, for the same reason that one is: as a pure suffix an
older reader stops early instead of parsing these bytes as something else.
Unlike the indexer section it is not under LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY.
The window is recurrent state - it is the input the PLE convolution's own
recurrent state is derived from - and the recurrent cache beside it is written
for partial checkpoints too. Gating it would leave the server's speculative
decoding checkpoints restoring the conv state without the window that produced
it.
No further version bump: LLAMA_SESSION_VERSION 10 and LLAMA_STATE_SEQ_VERSION 3
were introduced for the indexer section in the same unreleased series, and both
changes are qwen4exp-only additions to the same blob layout.
Also fixes the padding of a short window. set_input pads a window shorter than
ngram_size - 1 up to that length, but prev() indexes the snapshot with the most
recent token last, and resize() pads at the back, so the filler EOS landed where
the immediately preceding token belongs. It now pads at the front. A window is
short at a sequence start after a one-token prefill, and after a seq_rm rewind,
which the new bookkeeping makes common.
Every architecture other than qwen4exp builds llama_memory_hybrid rather than
llama_memory_hybrid_idx, has no PLE table and never asks for a history, so
nothing about its graph, its sequence operations or its state bytes changes.
(cherry picked from commit de170364c052c68fcf63285cc0028095edb9f23c)
* qwen4exp: tidy comments and simplify image token read
Rewrite the comments this series adds to the AGENTS.md rules: one or two lines,
no prose hard-wrapped mid-sentence, no narrative or history, and no comment that
only restates the code. Net 146 fewer comment lines, no code change.
Correct the PLE image comment: mtmd does not consume the placeholder ids. An
image is decoded as an embeddings-only batch, so ubatch->token is null and the
per-position ids never exist here. gemma3n and gemma4 hit the same case and
stand in row 0 of per_layer_token_embd; qwen4exp stands in the configured image
token id instead.
Read image_token_id straight from self.hparams in the converter. base.py merges
text_config into the root of hparams, and the key sits at the root of
config.json, so the config.json re-read was redundant.
(cherry picked from commit 205840c12169057da3e8d2f65ec4ceec3e18b980)
* qwen4exp: support a quantized KV cache in the QSA attention path
(cherry picked from commit 4c30574f81dc1115d08078c47b6cf8c789c0a842)
* llama: give qwen4exp a large-graph node budget
(cherry picked from commit 37c8c194e6a30e4c46ac29bee3fb264f091596ef)
* qwen4exp: drop an unused variable that breaks -Werror builds
(cherry picked from commit 528d032b51fa3cf935ed3ef6e0fb1c7401df53b5)
* quantize: dequantize and quantize large tensors in row bands
f32_conv_buf held the whole dequantized tensor, which is 204.8 GB for
per_layer_token_embd alone and dies with std::bad_alloc long before the
work buffer is reached. Dequantize and quantize in bands of whole rows
instead, capping the f32 staging at 1 GiB per band.
Rows are independent and the imatrix is indexed by column, so band
boundaries cannot change any output byte. Bands nest inside the existing
per-expert loop so each expert slice keeps its own imatrix, and a band is
kept to at least one quantization chunk per worker thread so the existing
multithreading still has work. F32 sources still stage nothing and are
banded by pointer arithmetic into the tensor.
llama_tensor_dequantize_impl now takes a first element offset; the single
caller is updated.
(cherry picked from commit 658c22549613555dbce57a772be4de8509eba3ee)
* llama: segment the qwen4exp fused QKV for tensor split
qwen4exp was missing from the gated delta net branch of get_split_segments,
so its attn_qkv.weight, shaped {n_embd, 2*key_dim + value_dim}, fell through
to the generic fused QKV rule and tripped
GGML_ASSERT(tensor->ne[axis] == n_embd + 2*n_embd_gqa) while loading with
--split-mode tensor. --split-mode layer was unaffected.
qwen4exp broadcasts K to the V heads by tiling, k_conv is grown with a plain
ggml_repeat_4d over the head axis so that v head j pairs with k head
j % n_k_heads. That is the Qwen 3.5 pattern, not the repeat interleave that
Qwen 3 Next builds explicitly, so qwen4exp takes the else branch and its V is
segmented on the scale of K.
Reported by benklop.
(cherry picked from commit 353d753f595dc81634ae6130188b31f06018f5ae)
* llama: fix the qwen4exp PLE history seq_rm(-1) iterator invalidation and the fatal-warning build
ple_hist_rm recursed over ple_hist with a range-based for and the recursive call
erases the entry it is iterating when the whole sequence is removed (p0 <= 0,
p1 < 0), so the loop then increments an invalidated iterator. It is unreachable
today only because llama_memory_recurrent::seq_rm rejects seq_id < 0 before
llama_memory_hybrid_idx::seq_rm reaches the history, which is a guard in another
class. Advance past the entry before recursing.
Two smaller things in the same area:
- the n_toks sanity bound in ple_hist_state_read was the literal 64, which is
the value of LLAMA_MAX_PLE_HEADS, not of the quantity being checked. The
window is at most ple_ngram_size - 1 tokens, so the bound is
LLAMA_MAX_PLE_NGRAM - 1, eight times tighter.
- build_conv_state_at left mem_size unused, so -DLLAMA_FATAL_WARNINGS=ON does
not compile. Predates this series; drop the line.
(cherry picked from commit 6eba44a89d5f328eb4859b844e1d28fb564cbe3e)
* qwen4exp: include llama-impl.h explicitly for llama_mul_mat_hadamard
(cherry picked from commit b634fd4d250d181ef82bf78bd00c1ae3b96a7af6)
* convert: fix the qwen4exp lint and type-check failures
flake8 flagged an unused MmprojModel import, and ty flagged seven errors in
the PLE streaming path: eos_token_id can be absent, and _ple_map, _ple_path,
_ple_row_dim and _ple_rows_per_shard are all Optional at the declaration but
were dereferenced without narrowing.
The map is opened and the stride fixed before the first shard is written, and
_finish_ple_table only runs once every shard has landed, so the invariants
hold. Assert them so the checker can see it. A missing eos_token_id now raises
with the reason instead of a TypeError from int(None).
* llama: give the qwen4exp indexer cache its own tensor names
The indexer KV cache and the attention KV cache both named their tensors
cache_k_l%d, so the Meta backend matched the indexer cache against the
attention split pattern and aborted in handle_set_rows. Tag the names
instead, and mirror the indexer cache: it has one key head and its
projections are mirrored.
(cherry picked from commit a1cdc8181134659766763a17762545a1f0e5db7b)
* qwen4exp: double the Q split granularity for tensor parallelism
qwen4exp fuses the attention gate into attn_q.weight the same way qwen3next
and qwen 3.5 do, so a device boundary must fall on a whole q+gate pair or the
Q heads stop lining up with the K/V heads and attn_output rows.
(cherry picked from commit 6c9a592f0a425a459ab6efae3b897cf68460e244)
* qwen4exp: keep the indexer cache in step across server slots
The QSA indexer keeps a side cache addressed by the cells of the attention
cache, so cell j has to hold the same token in both: the top-k indices it
produces are applied to the attention KQ mask. init_batch already hands the
indexer the attention cache's slot layout rather than letting it look for its
own, but the restore path did not. state_read called llama_kv_cache::state_read
on the two caches in turn and each ran its own find_slot over its own occupancy.
That agrees only for as long as nothing has already pushed the two caches apart,
which is the property a restore is supposed to re-establish rather than one it
can lean on.
The failure path was the worse half, and it is reachable from the public API
with nothing more than a short buffer. Truncating a good blob at 35 offsets and
feeding it to llama_state_seq_set_data left the two caches disagreeing at 5 of
them, and every one of 23 truncations of a whole-context blob did. Four of those
five land inside the attention section, so the attention cache drops the
sequence and the indexer keeps it; only the cut that lands in the indexer
section gives the opposite direction. llama_kv_cache::state_read cleans up its
own cache and rethrows, so whichever way it falls, nothing is left to bring the
two back together. The server papers over this by clearing the slot when a
prompt cache load fails; a caller of llama_state_seq_set_data that does not is
left with an indexer addressing cells that no longer mean what it thinks.
llama_kv_cache::state_read_sinfo reports the cells a restore landed in, or takes
a copy of them, and state_read_meta uses a supplied layout in place of find_slot
once it has checked that those cells are free here too. The indexer now adopts
the attention cache's restored layout by construction instead of reproducing it
by coincidence, and a layout that does not fit fails the read rather than being
applied over cells that already drifted. The hybrid restore is wrapped so that
any failure drops the sequence, or for a whole-context restore the context, from
all three caches at once, which is a state they do agree on.
* kv-cache: clear the cache once when restoring a whole context
state_read walks the streams of the cache in turn, and for a whole-context restore
each stream went through state_read_meta, which starts by calling clear(). clear()
resets every stream at once, so each stream after the first threw away the streams
already restored, and the K/V buffers with them. A non-unified cache holds one
stream per sequence, so a context saved with N sequences in it came back with only
the sequence in the last stream that carried any cells - the highest sequence id.
A unified cache has one stream and never showed it.
The cache is now emptied once, before the loop, which is what a whole-context
restore means. A blob whose streams are all empty now empties the cache as well,
where before it left the old contents in place.
* kv-cache: check the mirrored slot layout on a whole-context restore too
state_read_meta only looked at the layout it was given on the single-sequence path.
A whole-context restore lays the cells out from 0 in both caches, so they agree as
long as they restore the same number of cells, but nothing checked that they did: an
indexer section belonging to some other context was read over cells the attention
cache had filled from a different one, which is the state the indexer must never be
left in.
* qwen4exp: give the PLE conv history its own mirrored recurrent row
n_embd_r() reserved n_conv + ple_conv_state() so that one cache_r_l row could
carry both the delta-net conv state and the PLE dilated conv history, but the
QWEN4EXP arm of get_split_segments only described n_conv. Under -sm tensor the
segment sum came up short by ple_conv_state() and llama_memory_recurrent
construction aborted in ggml_backend_meta_alloc_ctx_tensors_from_buft.
Widening the segment list is not the fix. The Meta backend propagates a view's
split descriptor from its parent unchanged, so a view of one sub-range of a
split axis is sized as the whole row on every device; declaring the PLE tail as
a second segment merely moves the abort to "shape mismatch for VIEW" at graph
allocation. The two histories also want opposite policies: the delta-net state
is split by head to match wqkv and ssm_conv1d, while per_layer_tok_embd,
ple_conv1d and ple_norm_conv are all mirrored, so every device computes the
whole dilated conv and needs the whole history. One tensor cannot be both, and
the split state has no per-segment mirroring.
Move the PLE history into its own cache_ple_r_l%d row, mark it MIRRORED, and
return n_embd_r() to n_conv. The row is allocated only on layers where is_ple
holds, so mirroring one 92160-element row per device replaces a 92160-element
tail on all 36 recurrent rows: the recurrent R footprint drops rather than
grows. build_conv_state_at now takes its width from the tensor it was handed
and keys its gather on that tensor, which also drops a cont of a strided view.
* no more ple_hist (use master version)
* llama: give the qwen4exp full memory context its indexer cache
graph_reserve() walks a full memory context, and qwen4exp builds its
sparse attention only when the context exposes an indexer cache. the
full-context constructor left ctx_idx null, so the reserved worst case
was the dense fallback: a smaller graph than the one decode executes.
ggml-alloc then had to grow the compute buffer on the first decode,
past the size reported at load.
with -np 4 -c 32768 -fa on -ctk q8_0 -ctv q8_0 on an IQ1_S qwen4exp,
the reserved CUDA0 buffer was 217.00 MiB against 275.71 MiB actually
used, and CUDA_Host 42.31 MiB against 191.14 MiB. reserving the sparse
graph makes both match exactly, in unified and non-unified cache mode.
Co-authored-by: Pascal <admin@serveurperso.com>
Assisted-by: Claude
* qwen4exp: shrink the PLE hparams storage
llama_hparams is held by value inside llm_graph_params and every llm_graph_input_*,
and llm_graph_params is a stack local in graph_reserve and process_ubatch, so its
width is paid on every worker thread stack.
is_ple_impl spent 2048 bytes carrying 512 bits. It is the one per-layer flag that is
not moved through the loader's uint32 array templates, so a bitset costs nothing in
call sites and also removes the uninitialized read that non-qwen4exp archs had, since
nothing filled the array for them.
The PLE head offsets and vocab sizes are token-space indices; the gather that consumes
them already truncates to int32, so 64-bit storage was never reachable. The gguf arrays
stay uint64 for file compatibility and are narrowed on load.
sizeof(llama_hparams) 34440 -> 31944, sizeof(llm_graph_params) 34872 -> 32376.
* llama: opt-in random-access mmap advice for host-resident gather tables
qwen4exp keeps per_layer_token_embd on the host: 26.8 GiB at IQ4_NL, read
by ggml_get_rows as 16 gathers of ~90-170 bytes per token, spread across
16 head regions ~20M rows apart. Measured over 4.75M gathers, no two
consecutive gathers land on the same 4 KiB page, so the readahead the
loader asks for buys nothing here and the whole table ends up cached to
serve about 4% of itself.
llama_mmap applies POSIX_FADV_SEQUENTIAL, MAP_POPULATE and a whole-file
POSIX_MADV_WILLNEED unconditionally. Those are right for streaming the
file once into buffers and wrong for whatever stays mapped afterwards.
Under LLAMA_MMAP_RANDOM the eager pull-in is skipped and the mapping is
advised random once every tensor has been read, so the load itself keeps
its sequential readahead. That alone drops the table to 4.4% resident but
serializes one NVMe latency per gather.
The second half is what pays for it: the PLE input already computes every
row index for the ubatch before the graph runs, so the pages those rows
fall on are handed to the kernel in one batch and the reads overlap.
POSIX_MADV_WILLNEED on POSIX, PrefetchVirtualMemory on Windows, which
takes the discontiguous ranges in a single call.
Off by default and off for every other model: the batched prefetch keys
off "this mapping was advised random", which nothing sets unless the user
opts in.
-c 512 --chunks 60, cold, IQ1_S, mean of 3:
default 35.3 s 26.82 GiB resident (100%)
advice only 104.5 s 1.19 GiB resident (4.4%)
advice + prefetch 34.2 s 1.19 GiB resident (4.4%)
PPL 4.2346 +/- 0.07862 in all three. IQ1_S KLD is unchanged in every
field, including Mean KLD 0.396070 +/- 0.001931 and Same top p 77.325%.
* llama: narrow the random-access mmap advice to the gather table
The advice was applied per mapping: every mapping the model kept got
POSIX_MADV_RANDOM plus a whole-file POSIX_FADV_RANDOM, and the eager
pull-in was skipped for every file. On qwen4exp that also hit
token_embd.weight, which sits 0.33 GiB past the PLE table in the same
shard and is read densely, not by sparse gathers. Measured over
-c 512 --chunks 60 on IQ1_S it fell to 8.45% resident, against 100% with
the feature off.
A model now nominates its gather tables (qwen4exp: per_layer_tok_embd)
and only those byte ranges are advised. The range is rounded out to
whole pages, which on this model takes in 832 bytes before and 192
after. token_embd goes back to 86.55% resident and the PLE table still
drops to 4.44%; smaps shows one VM_RAND_READ VMA of exactly the table
instead of one over all 27.16 GiB that stays mapped.
posix_fadvise is dropped from the narrowed path. POSIX_FADV_RANDOM
ignores its offset and length and marks the whole open file, and the
FMODE_RANDOM it sets is only read by page_cache_sync_ra() on the read()
path, which a fault on a MADV_RANDOM vma never reaches. POSIX_FADV_
DONTNEED does take a range, so the drop mode keeps it.
The eager pull-in is now skipped only for the files holding a nominated
table, and re-issued as WILLNEED over the rest of such a file, so other
shards load exactly as before.
prefetch_rows() keys off the tensor being nominated rather than off a
mapping-level flag, so the batched readahead lands only where the advice
did.
-c 512 --chunks 60, cold, IQ1_S, mean of 3, total wall:
default 32.50 s
whole mapping 30.05 s
narrowed 30.35 s
PPL 4.2061 in all three. IQ1_S KLD is bit-identical with the feature on
and off, including Mean KLD 0.396070 +/- 0.001931 and Same top p
77.325%. tg128 73.65 +/- 0.33 narrowed against 73.49 +/- 0.34 whole.
Assisted-by: Claude
* llama: fold the random-access prefetch into its own feature flag
LLAMA_MMAP_RANDOM_PREFETCH existed to measure the two halves of the feature
apart, and the measurement is done: on a cold cache over the same wikitext
run, MADV_RANDOM without the batched readahead takes 94.4 s against 36.7 s
for an untouched mapping, while the pair together take 34.1 s. Suppressing
the kernel's readahead only pays if we replace it, so the split let a user
select a 2.6x regression through a documented switch.
Keep the accessor, since the call site reads better than a mode comparison,
but derive it from the mode alone.
* FACP (Fewer Acronym Classes Please)
* qwen4exp: bias the QSA selection per block, not per cell
The QSA bias is a graph input, so it is pinned on the host and uploaded every
decode, and at -c 32768 -np 4 its twelve copies were 768 of the 815 MiB of
reserved host compute buffer.
Only one half of it needs a cell: whether the cell sits in the always-visible
tail, and whether its block was pooled. Both are properties of the block. The
other half - empty, other sequence, or in the future - is the plain visible/not
test the attention mask already carries over the same cells, so add that mask
instead of repeating it. The bias then holds one value per block.
A block sits wholly inside or wholly outside the tail because the tail starts on
a block boundary, so one value per block is exact. Cells no block covers keep
their -inf from the mask.
The mask is F16 and the bias F32, and a mixed ggml_add reinterprets the F16
buffer as float rather than converting it, so the cast is required.
reserved host compute buffer at -c 32768 -np 4:
--kv-unified 814.86 -> 238.86 MiB, CUDA0 721.07 -> 421.07 MiB
--no-kv-unified 214.86 -> 70.86 MiB, CUDA0 317.07 -> 265.07 MiB
Selection is unchanged: over 8192 tokens, four times the budget, every QSA
layer returns identical top-k indices and the logprobs are bitwise equal.
Two things a reviewer should know. A cell whose position divides past the last
block is guarded by an assert rather than handled, because no run reached it.
And the mask's same-position M-RoPE rule cannot fire for text and was never
exercised for images, so the 2D case is unverified.
* clean up code comments
* clean up new comments
* revert LLAMA_MMAP_RANDOM
* nits
* replace some changes with #27795
* improve the m-rope image for get_prev_tokens
* LazyChunkedTensor
* fix lint
* add some validations
* reduce input nodes
* trim output tokens
* nits
* some more sanity checks
* fix llm_graph_input_ple reuse
* exclude from webgpu test
---------
Co-authored-by: danielhanchen <danielhanchen@users.noreply.github.com>
Co-authored-by: danielhanchen <unslothshared@gmail.com>
Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
Co-authored-by: Pascal <admin@serveurperso.com>
Co-authored-by: Sigbjørn Skjæret <sigbjorn.skjaeret@huggingface.co>
3221 lines
145 KiB
C++
3221 lines
145 KiB
C++
#include "llama-model.h"
|
|
|
|
#include "llama-arch.h"
|
|
#include "llama-ext.h"
|
|
#include "llama-hparams.h"
|
|
#include "llama-impl.h"
|
|
#include "llama-mmap.h"
|
|
#include "llama-cparams.h"
|
|
#include "llama-model-loader.h"
|
|
|
|
#include "llama-kv-cache.h"
|
|
#include "llama-kv-cache-iswa.h"
|
|
#include "llama-kv-cache-dsa.h"
|
|
#include "llama-kv-cache-dsa-iswa.h"
|
|
#include "llama-kv-cache-msa.h"
|
|
#include "llama-kv-cache-dsv4.h"
|
|
#include "llama-memory-hybrid.h"
|
|
#include "llama-memory-hybrid-iswa.h"
|
|
#include "llama-memory-hybrid-idx.h"
|
|
#include "llama-memory-recurrent.h"
|
|
|
|
#include "llama.h"
|
|
#include "models/models.h"
|
|
|
|
#include "ggml.h"
|
|
#include "ggml-cpp.h"
|
|
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cfloat>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <cmath>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <numeric>
|
|
#include <regex>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
static llama_model * llama_model_mapping(llm_arch arch, const llama_model_params & params) {
|
|
switch (arch) {
|
|
case LLM_ARCH_CLIP:
|
|
return new llama_model_clip(params);
|
|
case LLM_ARCH_LLAMA:
|
|
return new llama_model_llama(params);
|
|
case LLM_ARCH_LLAMA4:
|
|
return new llama_model_llama4(params);
|
|
case LLM_ARCH_LLAMA_EMBED:
|
|
return new llama_model_llama_embed(params);
|
|
case LLM_ARCH_MAINCODER:
|
|
return new llama_model_maincoder(params);
|
|
case LLM_ARCH_TALKIE:
|
|
return new llama_model_talkie(params);
|
|
case LLM_ARCH_DECI:
|
|
return new llama_model_deci(params);
|
|
case LLM_ARCH_BAICHUAN:
|
|
return new llama_model_baichuan(params);
|
|
case LLM_ARCH_FALCON:
|
|
return new llama_model_falcon(params);
|
|
case LLM_ARCH_GROK:
|
|
return new llama_model_grok(params);
|
|
case LLM_ARCH_STARCODER:
|
|
return new llama_model_starcoder(params);
|
|
case LLM_ARCH_REFACT:
|
|
return new llama_model_refact(params);
|
|
case LLM_ARCH_BERT:
|
|
return new llama_model_bert(params);
|
|
case LLM_ARCH_JINA_BERT_V2:
|
|
return new llama_model_jina_bert_v2(params);
|
|
case LLM_ARCH_JINA_BERT_V3:
|
|
return new llama_model_jina_bert_v3(params);
|
|
case LLM_ARCH_NOMIC_BERT:
|
|
return new llama_model_nomic_bert(params);
|
|
case LLM_ARCH_NOMIC_BERT_MOE:
|
|
return new llama_model_nomic_bert_moe(params);
|
|
case LLM_ARCH_MODERN_BERT:
|
|
return new llama_model_modern_bert(params);
|
|
case LLM_ARCH_NEO_BERT:
|
|
return new llama_model_neo_bert(params);
|
|
case LLM_ARCH_EUROBERT:
|
|
return new llama_model_eurobert(params);
|
|
case LLM_ARCH_BLOOM:
|
|
return new llama_model_bloom(params);
|
|
case LLM_ARCH_MPT:
|
|
return new llama_model_mpt(params);
|
|
case LLM_ARCH_STABLELM:
|
|
return new llama_model_stablelm(params);
|
|
case LLM_ARCH_MELLUM:
|
|
return new llama_model_mellum(params);
|
|
case LLM_ARCH_NANBEIGE:
|
|
return new llama_model_nanbeige(params);
|
|
case LLM_ARCH_QWEN:
|
|
return new llama_model_qwen(params);
|
|
case LLM_ARCH_QWEN2:
|
|
return new llama_model_qwen2(params);
|
|
case LLM_ARCH_DREAM:
|
|
return new llama_model_dream(params);
|
|
case LLM_ARCH_LLADA:
|
|
return new llama_model_llada(params);
|
|
case LLM_ARCH_LLADA_MOE:
|
|
return new llama_model_llada_moe(params);
|
|
case LLM_ARCH_RND1:
|
|
return new llama_model_rnd1(params);
|
|
case LLM_ARCH_QWEN2VL:
|
|
return new llama_model_qwen2vl(params);
|
|
case LLM_ARCH_QWEN2MOE:
|
|
return new llama_model_qwen2moe(params);
|
|
case LLM_ARCH_QWEN3:
|
|
return new llama_model_qwen3(params);
|
|
case LLM_ARCH_QWEN3MOE:
|
|
return new llama_model_qwen3moe(params);
|
|
case LLM_ARCH_QWEN3VL:
|
|
return new llama_model_qwen3vl(params);
|
|
case LLM_ARCH_QWEN3VLMOE:
|
|
return new llama_model_qwen3vlmoe(params);
|
|
case LLM_ARCH_QWEN3TTS:
|
|
return new llama_model_qwen3tts(params);
|
|
case LLM_ARCH_POCKETTTS:
|
|
return new llama_model_pockettts(params);
|
|
case LLM_ARCH_PHI2:
|
|
return new llama_model_phi2(params);
|
|
case LLM_ARCH_PHI3:
|
|
return new llama_model_phi3(params);
|
|
case LLM_ARCH_PHIMOE:
|
|
return new llama_model_phimoe(params);
|
|
case LLM_ARCH_PLAMO:
|
|
return new llama_model_plamo(params);
|
|
case LLM_ARCH_PLAMO2:
|
|
return new llama_model_plamo2(params);
|
|
case LLM_ARCH_PLAMO3:
|
|
return new llama_model_plamo3(params);
|
|
case LLM_ARCH_GPT2:
|
|
return new llama_model_gpt2(params);
|
|
case LLM_ARCH_CODESHELL:
|
|
return new llama_model_codeshell(params);
|
|
case LLM_ARCH_ORION:
|
|
return new llama_model_orion(params);
|
|
case LLM_ARCH_INTERNLM2:
|
|
return new llama_model_internlm2(params);
|
|
case LLM_ARCH_MINICPM3:
|
|
return new llama_model_minicpm3(params);
|
|
case LLM_ARCH_GEMMA:
|
|
return new llama_model_gemma(params);
|
|
case LLM_ARCH_GEMMA2:
|
|
return new llama_model_gemma2(params);
|
|
case LLM_ARCH_GEMMA3:
|
|
return new llama_model_gemma3(params);
|
|
case LLM_ARCH_GEMMA3N:
|
|
return new llama_model_gemma3n(params);
|
|
case LLM_ARCH_GEMMA4:
|
|
return new llama_model_gemma4(params);
|
|
case LLM_ARCH_GEMMA4_ASSISTANT:
|
|
return new llama_model_gemma4_assistant(params);
|
|
case LLM_ARCH_GEMMA_EMBEDDING:
|
|
return new llama_model_gemma_embedding(params);
|
|
case LLM_ARCH_STARCODER2:
|
|
return new llama_model_starcoder2(params);
|
|
case LLM_ARCH_MAMBA:
|
|
return new llama_model_mamba(params);
|
|
case LLM_ARCH_MAMBA2:
|
|
return new llama_model_mamba2(params);
|
|
case LLM_ARCH_JAMBA:
|
|
return new llama_model_jamba(params);
|
|
case LLM_ARCH_XVERSE:
|
|
return new llama_model_xverse(params);
|
|
case LLM_ARCH_COMMAND_R:
|
|
return new llama_model_command_r(params);
|
|
case LLM_ARCH_COHERE2:
|
|
return new llama_model_cohere2(params);
|
|
case LLM_ARCH_COHERE2MOE:
|
|
return new llama_model_cohere2moe(params);
|
|
case LLM_ARCH_DBRX:
|
|
return new llama_model_dbrx(params);
|
|
case LLM_ARCH_OLMO:
|
|
return new llama_model_olmo(params);
|
|
case LLM_ARCH_OLMO2:
|
|
return new llama_model_olmo2(params);
|
|
case LLM_ARCH_OLMOE:
|
|
return new llama_model_olmoe(params);
|
|
case LLM_ARCH_MUSE_GLIMMER:
|
|
return new llama_model_muse_glimmer(params);
|
|
case LLM_ARCH_OPENELM:
|
|
return new llama_model_openelm(params);
|
|
case LLM_ARCH_GPTNEOX:
|
|
return new llama_model_gptneox(params);
|
|
case LLM_ARCH_ARCTIC:
|
|
return new llama_model_arctic(params);
|
|
case LLM_ARCH_DEEPSEEK:
|
|
return new llama_model_deepseek(params);
|
|
case LLM_ARCH_DEEPSEEK2:
|
|
return new llama_model_deepseek2(params);
|
|
case LLM_ARCH_DEEPSEEK2OCR:
|
|
return new llama_model_deepseek2ocr(params);
|
|
case LLM_ARCH_DEEPSEEK32:
|
|
return new llama_model_deepseek32(params);
|
|
case LLM_ARCH_DOTS3NOTE:
|
|
return new llama_model_dots3note(params);
|
|
case LLM_ARCH_DEEPSEEK4:
|
|
return new llama_model_deepseek4(params);
|
|
case LLM_ARCH_GLM_DSA:
|
|
return new llama_model_glm_dsa(params);
|
|
case LLM_ARCH_MISTRAL4:
|
|
return new llama_model_mistral4(params);
|
|
case LLM_ARCH_CHATGLM:
|
|
return new llama_model_chatglm(params);
|
|
case LLM_ARCH_GLM4:
|
|
return new llama_model_glm4(params);
|
|
case LLM_ARCH_GLM4_MOE:
|
|
return new llama_model_glm4_moe(params);
|
|
case LLM_ARCH_BITNET:
|
|
return new llama_model_bitnet(params);
|
|
case LLM_ARCH_T5:
|
|
return new llama_model_t5(params);
|
|
case LLM_ARCH_T5ENCODER:
|
|
return new llama_model_t5encoder(params);
|
|
case LLM_ARCH_JAIS:
|
|
return new llama_model_jais(params);
|
|
case LLM_ARCH_JAIS2:
|
|
return new llama_model_jais2(params);
|
|
case LLM_ARCH_NEMOTRON:
|
|
return new llama_model_nemotron(params);
|
|
case LLM_ARCH_NEMOTRON_H:
|
|
return new llama_model_nemotron_h(params);
|
|
case LLM_ARCH_NEMOTRON_H_MOE:
|
|
return new llama_model_nemotron_h_moe(params);
|
|
case LLM_ARCH_EXAONE:
|
|
return new llama_model_exaone(params);
|
|
case LLM_ARCH_EXAONE4:
|
|
return new llama_model_exaone4(params);
|
|
case LLM_ARCH_EXAONE_MOE:
|
|
return new llama_model_exaone_moe(params);
|
|
case LLM_ARCH_RWKV6:
|
|
return new llama_model_rwkv6(params);
|
|
case LLM_ARCH_RWKV6QWEN2:
|
|
return new llama_model_rwkv6qwen2(params);
|
|
case LLM_ARCH_RWKV7:
|
|
return new llama_model_rwkv7(params);
|
|
case LLM_ARCH_ARWKV7:
|
|
return new llama_model_arwkv7(params);
|
|
case LLM_ARCH_GRANITE:
|
|
return new llama_model_granite(params);
|
|
case LLM_ARCH_GRANITE_MOE:
|
|
return new llama_model_granite_moe(params);
|
|
case LLM_ARCH_GRANITE_SWITCH:
|
|
return new llama_model_granite_switch(params);
|
|
case LLM_ARCH_MINICPM:
|
|
return new llama_model_minicpm(params);
|
|
case LLM_ARCH_GRANITE_HYBRID:
|
|
return new llama_model_granite_hybrid(params);
|
|
case LLM_ARCH_GRANITE_SWA:
|
|
return new llama_model_granite_swa(params);
|
|
case LLM_ARCH_CHAMELEON:
|
|
return new llama_model_chameleon(params);
|
|
case LLM_ARCH_WAVTOKENIZER_DEC:
|
|
return new llama_model_wavtokenizer_dec(params);
|
|
case LLM_ARCH_PLM:
|
|
return new llama_model_plm(params);
|
|
case LLM_ARCH_BAILINGMOE:
|
|
return new llama_model_bailingmoe(params);
|
|
case LLM_ARCH_BAILINGMOE2:
|
|
return new llama_model_bailingmoe2(params);
|
|
case LLM_ARCH_BAILINGMOE3:
|
|
return new llama_model_bailingmoe3(params);
|
|
case LLM_ARCH_SEED_OSS:
|
|
return new llama_model_seed_oss(params);
|
|
case LLM_ARCH_DOTS1:
|
|
return new llama_model_dots1(params);
|
|
case LLM_ARCH_ARCEE:
|
|
return new llama_model_arcee(params);
|
|
case LLM_ARCH_AFMOE:
|
|
return new llama_model_afmoe(params);
|
|
case LLM_ARCH_LAGUNA:
|
|
return new llama_model_laguna(params);
|
|
case LLM_ARCH_ERNIE4_5:
|
|
return new llama_model_ernie4_5(params);
|
|
case LLM_ARCH_ERNIE4_5_MOE:
|
|
return new llama_model_ernie4_5_moe(params);
|
|
case LLM_ARCH_PADDLEOCR:
|
|
return new llama_model_paddleocr(params);
|
|
case LLM_ARCH_HUNYUAN_MOE:
|
|
return new llama_model_hunyuan_moe(params);
|
|
case LLM_ARCH_HUNYUAN_VL:
|
|
return new llama_model_hunyuan_vl(params);
|
|
case LLM_ARCH_HUNYUAN_DENSE:
|
|
return new llama_model_hunyuan_dense(params);
|
|
case LLM_ARCH_HY_V3:
|
|
return new llama_model_hy_v3(params);
|
|
case LLM_ARCH_SMOLLM3:
|
|
return new llama_model_smollm3(params);
|
|
case LLM_ARCH_OPENAI_MOE:
|
|
return new llama_model_openai_moe(params);
|
|
case LLM_ARCH_FALCON_H1:
|
|
return new llama_model_falcon_h1(params);
|
|
case LLM_ARCH_LFM2:
|
|
return new llama_model_lfm2(params);
|
|
case LLM_ARCH_LFM2MOE:
|
|
return new llama_model_lfm2moe(params);
|
|
case LLM_ARCH_SMALLTHINKER:
|
|
return new llama_model_smallthinker(params);
|
|
case LLM_ARCH_GROVEMOE:
|
|
return new llama_model_grovemoe(params);
|
|
case LLM_ARCH_APERTUS:
|
|
return new llama_model_apertus(params);
|
|
case LLM_ARCH_MINIMAX_01:
|
|
return new llama_model_minimax_01(params);
|
|
case LLM_ARCH_MINIMAX_M2:
|
|
return new llama_model_minimax_m2(params);
|
|
case LLM_ARCH_MINIMAX_M3:
|
|
return new llama_model_minimax_m3(params);
|
|
case LLM_ARCH_COGVLM:
|
|
return new llama_model_cogvlm(params);
|
|
case LLM_ARCH_PANGU_EMBED:
|
|
return new llama_model_pangu_embed(params);
|
|
case LLM_ARCH_QWEN3NEXT:
|
|
return new llama_model_qwen3next(params);
|
|
case LLM_ARCH_QWEN35:
|
|
return new llama_model_qwen35(params);
|
|
case LLM_ARCH_QWEN35MOE:
|
|
return new llama_model_qwen35moe(params);
|
|
case LLM_ARCH_QWEN4EXP:
|
|
return new llama_model_qwen4exp(params);
|
|
case LLM_ARCH_MISTRAL3:
|
|
return new llama_model_mistral3(params);
|
|
case LLM_ARCH_EAGLE3:
|
|
return new llama_model_eagle3(params);
|
|
case LLM_ARCH_DFLASH:
|
|
return new llama_model_dflash(params);
|
|
case LLM_ARCH_MIMO2:
|
|
return new llama_model_mimo2(params);
|
|
case LLM_ARCH_KIMI_LINEAR:
|
|
return new llama_model_kimi_linear(params);
|
|
case LLM_ARCH_KIMI_K3:
|
|
return new llama_model_kimi_k3(params);
|
|
case LLM_ARCH_STEP35:
|
|
return new llama_model_step35(params);
|
|
default:
|
|
throw std::runtime_error(std::string("unsupported model architecture: '") + llm_arch_name(arch) + "'");
|
|
}
|
|
|
|
}
|
|
|
|
llama_model * llama_model_create(llm_arch arch, const llama_model_params & params) {
|
|
llama_model * model = llama_model_mapping(arch, params);
|
|
|
|
if (model != nullptr) {
|
|
model->arch = arch;
|
|
if (params.split_mode == LLAMA_SPLIT_MODE_TENSOR && !llm_arch_supports_sm_tensor(arch)) {
|
|
throw std::runtime_error(std::string("LLAMA_SPLIT_MODE_TENSOR not implemented for architecture '") + llm_arch_name(arch) + "'");
|
|
}
|
|
}
|
|
|
|
return model;
|
|
}
|
|
|
|
llama_model * llama_model_create(llama_model_loader & ml, const llama_model_params & params) {
|
|
llm_arch arch = ml.get_arch();
|
|
if (arch == LLM_ARCH_UNKNOWN) {
|
|
throw std::runtime_error("unknown model architecture: '" + ml.get_arch_name() + "'");
|
|
}
|
|
|
|
return llama_model_create(arch, params);
|
|
}
|
|
|
|
struct ggml_backend_meta_split_state llama_meta_device_get_split_state(const struct ggml_tensor * tensor, void * userdata) {
|
|
const llama_meta_device_get_split_state_userdata * ud = (const llama_meta_device_get_split_state_userdata *) userdata;
|
|
const llama_hparams & hparams = ud->model->hparams;
|
|
const std::string tensor_name = tensor->name;
|
|
const bool is_dsv4 = ud->model->arch == LLM_ARCH_DEEPSEEK4 ||
|
|
(ud->model->arch == LLM_ARCH_DFLASH && hparams.dsv4_hc_mult > 0);
|
|
|
|
static const std::regex pattern_q_weight ("blk\\.\\d*\\.attn_q.weight");
|
|
static const std::regex pattern_kv_weight ("blk\\.\\d*\\.attn_(k|v).weight");
|
|
static const std::regex pattern_qkv_weight ("blk\\.\\d*\\.attn_qkv.weight");
|
|
static const std::regex pattern_q_bias ("blk\\.\\d*\\.attn_q\\.bias");
|
|
static const std::regex pattern_kv_bias ("blk\\.\\d*\\.attn_(k|v)\\.bias");
|
|
static const std::regex pattern_qkv_bias ("blk\\.\\d*\\.attn_qkv.bias");
|
|
static const std::regex pattern_qk_norm ("blk\\.\\d*\\.attn_(q|k)_norm\\.weight");
|
|
static const std::regex pattern_kv_cache ("cache_(k|v)_l\\d*");
|
|
static const std::regex pattern_idx_cache ("cache_idx_(k|v)_l\\d*");
|
|
static const std::regex pattern_dsv4_state ("dsv4_(csa|hca|lid)_state_(kv|score)_l\\d*");
|
|
static const std::regex pattern_attn_sinks ("blk\\.\\d*\\.attn_sinks.weight");
|
|
static const std::regex pattern_attn_out_weight ("blk\\.\\d*\\.attn_output.weight");
|
|
static const std::regex pattern_attn_out_bias ("blk\\.\\d*\\.attn_output.bias");
|
|
static const std::regex pattern_attn_out_a_weight("blk\\.\\d*\\.attn_output_a\\.weight");
|
|
static const std::regex pattern_attn_out_b_weight("blk\\.\\d*\\.attn_output_b\\.weight");
|
|
static const std::regex pattern_attn_q_b_weight ("blk\\.\\d*\\.attn_q_b\\.weight");
|
|
static const std::regex pattern_attn_gate_weight("blk\\.\\d*\\.attn_gate.weight");
|
|
|
|
static const std::regex pattern_ssm_dt ("blk\\.\\d*\\.ssm_dt.bias");
|
|
static const std::regex pattern_ssm_a ("blk\\.\\d*\\.ssm_a");
|
|
static const std::regex pattern_ssm_alpha ("blk\\.\\d*\\.ssm_alpha.weight");
|
|
static const std::regex pattern_ssm_beta ("blk\\.\\d*\\.ssm_beta.weight");
|
|
static const std::regex pattern_ssm_beta_alpha ("blk\\.\\d*\\.ssm_ba.weight");
|
|
static const std::regex pattern_r_cache ("cache_r_l\\d*");
|
|
static const std::regex pattern_ple_r_cache ("cache_ple_r_l\\d*");
|
|
static const std::regex pattern_s_cache ("cache_s_l\\d*");
|
|
static const std::regex pattern_ssm_conv1d ("blk\\.\\d*\\.ssm_conv1d.weight");
|
|
static const std::regex pattern_ssm_out_weight ("blk\\.\\d*\\.ssm_out.weight");
|
|
|
|
static const std::regex pattern_ffn_up_weight ("blk\\.\\d*\\.ffn_up(_exps)?.weight");
|
|
static const std::regex pattern_ffn_up_bias ("blk\\.\\d*\\.ffn_up(_exps)?.bias");
|
|
static const std::regex pattern_ffn_gate_weight ("blk\\.\\d*\\.ffn_gate(_exps)?.weight");
|
|
static const std::regex pattern_ffn_gate_bias ("blk\\.\\d*\\.ffn_gate(_exps)?.bias");
|
|
static const std::regex pattern_ffn_gate_up_weight("blk\\.\\d*\\.ffn_gate_up(_exps)?.weight");
|
|
static const std::regex pattern_ffn_down_weight ("blk\\.\\d*\\.ffn_down(_exps)?.weight");
|
|
static const std::regex pattern_ffn_down_bias ("blk\\.\\d*\\.ffn_down.bias");
|
|
static const std::regex pattern_ffn_down_exps_bias ("blk\\.\\d*\\.ffn_down_exps.bias");
|
|
static const std::regex pattern_ffn_up_shexp_weight ("blk\\.\\d*\\.ffn_up_shexp.weight");
|
|
static const std::regex pattern_ffn_gate_shexp_weight ("blk\\.\\d*\\.ffn_gate_shexp.weight");
|
|
static const std::regex pattern_ffn_down_shexp_weight ("blk\\.\\d*\\.ffn_down_shexp.weight");
|
|
|
|
static const std::regex pattern_output_weight("output\\.weight");
|
|
static const std::regex pattern_output_bias ("output\\.bias");
|
|
|
|
struct tensor_config {
|
|
ggml_backend_meta_split_axis axis;
|
|
|
|
const ggml_tensor * tensor_axis_0;
|
|
|
|
uint32_t il;
|
|
size_t rotation; // when assigning tensor slices, rotate how the rounding is done for more even allocation
|
|
};
|
|
|
|
auto get_tensor_config_impl = [&](
|
|
const ggml_backend_meta_split_axis axis, const std::string & suffix = "", const std::string & suffix_fallback = "") -> tensor_config {
|
|
// the layers in a tensor can be inhomogeneous, if the pattern is cleanly divided by the number of GPUs there can be aliasing effects,
|
|
// count only the same type of previous layers to avoid this
|
|
auto get_il_eff = [&](const size_t il){
|
|
size_t ret = 0;
|
|
const bool il_is_recr = hparams.is_recr(il);
|
|
const bool il_is_swa = hparams.is_swa(il);
|
|
for (size_t il_prev = 0; il_prev < il; il_prev++) {
|
|
ret += hparams.is_recr(il_prev) == il_is_recr && hparams.is_swa(il_prev) == il_is_swa;
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
uint32_t il;
|
|
std::string prefix;
|
|
size_t rotation;
|
|
if (tensor_name.substr(0, 4) == "blk.") {
|
|
const size_t length_prefix = tensor_name.find('.', 4);
|
|
GGML_ASSERT(length_prefix != std::string::npos);
|
|
prefix = tensor_name.substr(0, length_prefix + 1);
|
|
il = std::stoull(tensor_name.substr(4, length_prefix));
|
|
rotation = get_il_eff(il) % ud->n_devices;
|
|
} else if (tensor_name.substr(0, 6) == "cache_") {
|
|
const size_t layer_index_start = tensor_name.find("_l", 6);
|
|
GGML_ASSERT(layer_index_start != std::string::npos);
|
|
il = std::stoull(tensor_name.substr(layer_index_start + 2));
|
|
prefix = "blk." + std::to_string(il) + ".";
|
|
rotation = get_il_eff(il) % ud->n_devices;
|
|
} else {
|
|
il = 0;
|
|
rotation = hparams.n_layer() % ud->n_devices;
|
|
}
|
|
const ggml_tensor * tensor_axis_0 = suffix.empty() ? tensor : ud->model->get_tensor((prefix + suffix).c_str());
|
|
if (tensor_axis_0 == nullptr) {
|
|
GGML_ASSERT(!suffix_fallback.empty());
|
|
tensor_axis_0 = ud->model->get_tensor((prefix + suffix_fallback).c_str());
|
|
}
|
|
GGML_ASSERT(tensor_axis_0 != nullptr);
|
|
return {axis, tensor_axis_0, il, rotation};
|
|
};
|
|
|
|
auto get_tensor_config = [&]() -> tensor_config {
|
|
if (is_dsv4) {
|
|
if (std::regex_match(tensor_name, pattern_kv_cache) ||
|
|
std::regex_match(tensor_name, pattern_dsv4_state)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED);
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_attn_sinks)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "attn_output_a.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_attn_q_b_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "attn_output_a.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_attn_out_a_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_2);
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_attn_out_b_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0);
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ffn_up_shexp_weight) ||
|
|
std::regex_match(tensor_name, pattern_ffn_gate_shexp_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "ffn_down_shexp.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ffn_down_shexp_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "ffn_down_shexp.weight");
|
|
}
|
|
}
|
|
|
|
// the qsa indexer has one key head and its projections are mirrored, so its cache cannot be split
|
|
if (std::regex_match(tensor_name, pattern_idx_cache)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED);
|
|
}
|
|
|
|
// the PLE table is model-level and its conv is mirrored, so every device runs the whole conv and needs the whole history
|
|
if (std::regex_match(tensor_name, pattern_ple_r_cache)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED);
|
|
}
|
|
|
|
// standard attention
|
|
if (std::regex_match(tensor_name, pattern_q_weight) || std::regex_match(tensor_name, pattern_kv_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "attn_output.weight", "ssm_out.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_q_bias) || std::regex_match(tensor_name, pattern_kv_bias)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "attn_output.weight", "ssm_out.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_qkv_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "attn_output.weight", "ssm_out.weight");
|
|
}
|
|
if ( std::regex_match(tensor_name, pattern_qkv_bias)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "attn_output.weight", "ssm_out.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_qk_norm)) {
|
|
return get_tensor_config_impl(tensor->ne[1] == 1 ? GGML_BACKEND_SPLIT_AXIS_MIRRORED : GGML_BACKEND_SPLIT_AXIS_1, "attn_output.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_kv_cache) || std::regex_match(tensor_name, pattern_attn_sinks)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "attn_output.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_attn_out_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0);
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_attn_out_bias)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED);
|
|
}
|
|
|
|
if (std::regex_match(tensor_name, pattern_attn_gate_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "attn_output.weight", "ssm_out.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ssm_dt) || std::regex_match(tensor_name, pattern_ssm_a)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "ssm_out.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ssm_alpha) || std::regex_match(tensor_name, pattern_ssm_beta) ||
|
|
std::regex_match(tensor_name, pattern_ssm_beta_alpha)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "ssm_out.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_r_cache) || std::regex_match(tensor_name, pattern_s_cache)) {
|
|
if (ud->model->arch == LLM_ARCH_LFM2 || ud->model->arch == LLM_ARCH_LFM2MOE) {
|
|
// the LFM2 shortconv block runs fully mirrored, so its conv state must be mirrored too
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED, "");
|
|
}
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "ssm_out.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ssm_conv1d)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "ssm_out.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ssm_out_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0);
|
|
}
|
|
|
|
// FFN
|
|
if (std::regex_match(tensor_name, pattern_ffn_up_weight) || std::regex_match(tensor_name, pattern_ffn_gate_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "ffn_down.weight", "ffn_down_exps.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ffn_up_bias) || std::regex_match(tensor_name, pattern_ffn_gate_bias)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "ffn_down.weight", "ffn_down_exps.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ffn_gate_up_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1, "ffn_down.weight", "ffn_down_exps.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ffn_down_weight)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0, "ffn_down.weight", "ffn_down_exps.weight");
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ffn_down_bias)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED);
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ffn_down_exps_bias)) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_PARTIAL, "ffn_down_exps.weight");
|
|
}
|
|
|
|
// output
|
|
if (std::regex_match(tensor_name, pattern_output_weight)) {
|
|
if (is_dsv4) {
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED);
|
|
}
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_1);
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_output_bias)) {
|
|
const ggml_tensor * output_weight = ud->model->get_tensor("output.weight");
|
|
GGML_ASSERT(output_weight != nullptr);
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_0);
|
|
}
|
|
|
|
// everything else
|
|
return get_tensor_config_impl(GGML_BACKEND_SPLIT_AXIS_MIRRORED);
|
|
};
|
|
|
|
auto get_split_segments = [&](int axis, uint32_t il) -> std::vector<std::pair<int64_t, uint32_t>> {
|
|
if (ud->model->arch == LLM_ARCH_QWEN3NEXT || ud->model->arch == LLM_ARCH_QWEN35 || ud->model->arch == LLM_ARCH_QWEN35MOE ||
|
|
ud->model->arch == LLM_ARCH_QWEN4EXP) {
|
|
const int64_t head_k_dim = hparams.ssm_d_state;
|
|
const int64_t head_v_dim = hparams.ssm_d_state;
|
|
const int64_t n_k_heads = hparams.ssm_n_group;
|
|
const int64_t n_v_heads = hparams.ssm_dt_rank;
|
|
const int64_t key_dim = head_k_dim * n_k_heads;
|
|
const int64_t value_dim = head_v_dim * n_v_heads;
|
|
|
|
// both Qwen 3 Next and Qwen 3.5 support n_v_heads > n_k_heads but the broadcasting pattern is different:
|
|
// - Qwen 3 Next: [k0_v0, k0_v1, k1_v2, k1_v3] (this is the default split pattern)
|
|
// - Qwen 3.5: [k0_v0, k1_v1, k0_v2, k1_v3] (needs segmenting of V on the scale of K to get the correct pattern)
|
|
if (ud->model->arch == LLM_ARCH_QWEN3NEXT) {
|
|
if (std::regex_match(tensor_name, pattern_qkv_weight) || std::regex_match(tensor_name, pattern_ssm_conv1d)) {
|
|
GGML_ASSERT(tensor->ne[axis] == 2*key_dim + value_dim);
|
|
return {{key_dim, 2}, {value_dim, 1}};
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_r_cache)) {
|
|
return {{key_dim * (hparams.ssm_d_conv - 1), 2}, {value_dim * (hparams.ssm_d_conv - 1), 1}};
|
|
}
|
|
} else {
|
|
const int64_t head_ratio = n_v_heads / n_k_heads;
|
|
if (std::regex_match(tensor_name, pattern_qkv_weight) || std::regex_match(tensor_name, pattern_ssm_conv1d)) {
|
|
GGML_ASSERT(tensor->ne[axis] == 2*key_dim + value_dim);
|
|
return {{key_dim, 2 + head_ratio}};
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_attn_gate_weight) || std::regex_match(tensor_name, pattern_ssm_out_weight)) {
|
|
return {{key_dim, head_ratio}};
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ssm_dt) || std::regex_match(tensor_name, pattern_ssm_a) ||
|
|
std::regex_match(tensor_name, pattern_ssm_alpha) || std::regex_match(tensor_name, pattern_ssm_beta)) {
|
|
return {{n_k_heads, head_ratio}};
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_r_cache)) {
|
|
return {{key_dim * (hparams.ssm_d_conv - 1), 2 + head_ratio}};
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_s_cache)) {
|
|
return {{n_k_heads * head_v_dim * head_v_dim, head_ratio}};
|
|
}
|
|
}
|
|
|
|
// the FFN is the same for Qwen 3 Next and Qwen 3.5:
|
|
if (std::regex_match(tensor_name, pattern_ffn_gate_up_weight)) {
|
|
const int64_t n_ff_exp = hparams.n_ff_exp;
|
|
GGML_ASSERT(tensor->ne[axis] == 2*n_ff_exp);
|
|
return {{n_ff_exp, 2}};
|
|
}
|
|
return {{tensor->ne[axis], 1}};
|
|
}
|
|
|
|
if (std::regex_match(tensor_name, pattern_qkv_weight) || std::regex_match(tensor_name, pattern_qkv_bias)) {
|
|
const int64_t n_embd = hparams.n_embd;
|
|
const int64_t n_embd_gqa = hparams.n_embd_v_gqa(il);
|
|
GGML_ASSERT(hparams.n_embd_k_gqa() == n_embd_gqa);
|
|
GGML_ASSERT(tensor->ne[axis] == n_embd + 2*n_embd_gqa);
|
|
return {{n_embd, 1}, {n_embd_gqa, 2}};
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ffn_up_weight) || std::regex_match(tensor_name, pattern_ffn_up_bias)) {
|
|
const int64_t n_ff = hparams.n_ff(il);
|
|
// some models such as Phi 3 have fused up + gate tensors named "up" tensors, which need to be segmented
|
|
if (tensor->ne[axis] == 2*n_ff) {
|
|
return {{n_ff, 2}};
|
|
}
|
|
return {{tensor->ne[axis], 1}};
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ffn_gate_up_weight)) {
|
|
const int64_t n_ff_exp = hparams.n_ff_exp;
|
|
GGML_ASSERT(tensor->ne[axis] == 2*n_ff_exp);
|
|
return {{n_ff_exp, 2}};
|
|
}
|
|
return {{tensor->ne[axis], 1}};
|
|
};
|
|
|
|
auto get_split_granularity = [&](int64_t blck_size, uint32_t il, const std::vector<std::pair<int64_t, uint32_t>> & segments) -> std::vector<int64_t> {
|
|
// for better performance it may make sense to round up blck_size to a higher power of 2 so that more efficient kernels can be used
|
|
if (hparams.is_recr(il)) {
|
|
// linear attention
|
|
const int64_t head_dim = hparams.ssm_d_state;
|
|
const int64_t blck_size_perf = std::lcm(blck_size, 128);
|
|
const int64_t granularity_qkv = std::lcm(blck_size_perf, head_dim);
|
|
if (std::regex_match(tensor_name, pattern_qkv_weight) || std::regex_match(tensor_name, pattern_attn_gate_weight) ||
|
|
std::regex_match(tensor_name, pattern_ssm_conv1d) || std::regex_match(tensor_name, pattern_ssm_out_weight)) {
|
|
return std::vector<int64_t>(segments.size(), granularity_qkv);
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ssm_dt) || std::regex_match(tensor_name, pattern_ssm_a) ||
|
|
std::regex_match(tensor_name, pattern_ssm_alpha) || std::regex_match(tensor_name, pattern_ssm_beta)) {
|
|
return std::vector<int64_t>(segments.size(), granularity_qkv / head_dim);
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_ssm_beta_alpha)) {
|
|
return std::vector<int64_t>(segments.size(), 2 * (granularity_qkv / head_dim));
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_r_cache)) {
|
|
return std::vector<int64_t>(segments.size(), granularity_qkv * (hparams.ssm_d_conv - 1));
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_s_cache)) {
|
|
return std::vector<int64_t>(segments.size(), granularity_qkv * head_dim);
|
|
}
|
|
} else {
|
|
// regular attention
|
|
const uint32_t n_gqa = hparams.n_gqa(il);
|
|
const uint32_t n_embd_q = n_gqa * hparams.n_embd_head_k(il);
|
|
|
|
// to handle head sizes like 80, only increase granularity while it doesn't cause underutilization
|
|
int64_t blck_size_perf = blck_size;
|
|
while (blck_size_perf < 128 && blck_size_perf*ud->n_devices < n_embd_q) {
|
|
blck_size_perf *= 2;
|
|
}
|
|
|
|
const int64_t granularity_q = std::lcm(n_embd_q, blck_size_perf);
|
|
const int64_t granularity_head = granularity_q / hparams.n_embd_head_k(il); // for tensors with one value per head
|
|
if (std::regex_match(tensor_name, pattern_attn_sinks)) {
|
|
GGML_ASSERT(segments.size() == 1);
|
|
if (is_dsv4) {
|
|
return {hparams.n_head(il) / hparams.dsv4_o_group_count};
|
|
}
|
|
return {granularity_head};
|
|
}
|
|
|
|
if (is_dsv4) {
|
|
if (std::regex_match(tensor_name, pattern_attn_q_b_weight)) {
|
|
GGML_ASSERT(segments.size() == 1);
|
|
// the grouped output projection requires each device to hold whole groups of heads
|
|
const int64_t n_head_group = hparams.n_head(il) / hparams.dsv4_o_group_count;
|
|
return {n_head_group * hparams.n_embd_head_k(il)};
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_attn_out_a_weight)) {
|
|
GGML_ASSERT(segments.size() == 1);
|
|
return {1};
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_attn_out_b_weight)) {
|
|
GGML_ASSERT(segments.size() == 1);
|
|
// the boundaries must align with wo_a's per-group split, so quant blocks must not straddle groups
|
|
GGML_ASSERT(hparams.dsv4_o_lora_rank % blck_size == 0);
|
|
return {hparams.dsv4_o_lora_rank};
|
|
}
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_q_weight) || std::regex_match(tensor_name, pattern_q_bias)) {
|
|
GGML_ASSERT(segments.size() == 1);
|
|
// some models have Q gate tensors, for those cases the granularity needs to be doubled:
|
|
if (ud->model->arch == LLM_ARCH_QWEN3NEXT || ud->model->arch == LLM_ARCH_QWEN35 || ud->model->arch == LLM_ARCH_QWEN35MOE ||
|
|
ud->model->arch == LLM_ARCH_QWEN4EXP) {
|
|
return {std::lcm(2*n_embd_q, blck_size_perf)};
|
|
}
|
|
return {granularity_q};
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_attn_out_weight)) {
|
|
GGML_ASSERT(segments.size() == 1);
|
|
return {granularity_q};
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_attn_gate_weight)) {
|
|
GGML_ASSERT(segments.size() == 1);
|
|
if (tensor->ne[1] == hparams.n_head(il)) {
|
|
return {granularity_head};
|
|
}
|
|
return {granularity_q};
|
|
}
|
|
|
|
const int64_t granularity_kv = granularity_q / n_gqa;
|
|
if (std::regex_match(tensor_name, pattern_kv_weight) ||
|
|
std::regex_match(tensor_name, pattern_kv_bias) ||
|
|
std::regex_match(tensor_name, pattern_kv_cache)) {
|
|
GGML_ASSERT(segments.size() == 1);
|
|
return {granularity_kv};
|
|
}
|
|
if (std::regex_match(tensor_name, pattern_qkv_weight) || std::regex_match(tensor_name, pattern_qkv_bias)) {
|
|
GGML_ASSERT(segments.size() == 2);
|
|
return {granularity_q, granularity_kv};
|
|
}
|
|
}
|
|
|
|
// FFN
|
|
if (std::regex_match(tensor_name, pattern_ffn_up_weight) || std::regex_match(tensor_name, pattern_ffn_up_bias) ||
|
|
std::regex_match(tensor_name, pattern_ffn_gate_weight) || std::regex_match(tensor_name, pattern_ffn_gate_bias) ||
|
|
std::regex_match(tensor_name, pattern_ffn_gate_up_weight) ||
|
|
std::regex_match(tensor_name, pattern_ffn_down_weight) ||
|
|
std::regex_match(tensor_name, pattern_ffn_up_shexp_weight) ||
|
|
std::regex_match(tensor_name, pattern_ffn_gate_shexp_weight) ||
|
|
std::regex_match(tensor_name, pattern_ffn_down_shexp_weight)) {
|
|
const int64_t blck_size_perf = std::lcm(blck_size, 128);
|
|
GGML_ASSERT(segments.size() == 1);
|
|
return {blck_size_perf};
|
|
}
|
|
|
|
// everything else
|
|
GGML_ASSERT(segments.size() == 1);
|
|
return {1};
|
|
};
|
|
|
|
ggml_backend_meta_split_state split_state;
|
|
memset(&split_state, 0, sizeof(split_state));
|
|
tensor_config tc = get_tensor_config();
|
|
split_state.axis = tc.axis;
|
|
if (split_state.axis >= 0 && split_state.axis < GGML_MAX_DIMS) {
|
|
const int64_t blck_size = ggml_blck_size(tc.tensor_axis_0->type);
|
|
const float * tensor_split = ud->model->tensor_split();
|
|
std::vector<float> tensor_split_scan;
|
|
tensor_split_scan.reserve(ud->n_devices);
|
|
for (size_t j = 0; j < ud->n_devices; j++) {
|
|
tensor_split_scan.push_back(tensor_split == nullptr ? 0.0f : tensor_split[(j + tc.rotation) % ud->n_devices]);
|
|
if (j > 0) {
|
|
tensor_split_scan[j] += tensor_split_scan[j - 1];
|
|
}
|
|
}
|
|
const std::vector<std::pair<int64_t, uint32_t>> segments = get_split_segments(split_state.axis, tc.il);
|
|
const std::vector<int64_t> granularity = get_split_granularity(blck_size, tc.il, segments);
|
|
for (size_t is = 0; is < segments.size(); is++) {
|
|
const int64_t ne_s = segments[is].first;
|
|
const uint32_t nr_s = segments[is].second;
|
|
const int64_t g_s = granularity[is];
|
|
int64_t low = 0;
|
|
size_t j = 0;
|
|
for (; j < ud->n_devices - 1; j++) {
|
|
int64_t high = tensor_split_scan.back() == 0.0f ?
|
|
ne_s * (j+1)/ud->n_devices : ne_s * tensor_split_scan[j]/tensor_split_scan.back();
|
|
if (high % g_s != 0) {
|
|
high -= high % g_s;
|
|
}
|
|
split_state.ne[is*ud->n_devices + (j + tc.rotation) % ud->n_devices] = high - low;
|
|
low = high;
|
|
}
|
|
split_state.ne[is*ud->n_devices + (j + tc.rotation) % ud->n_devices] = ne_s - low;
|
|
split_state.nr[is] = nr_s;
|
|
}
|
|
split_state.n_segments = segments.size();
|
|
} else {
|
|
memset(split_state.ne, 0, sizeof(split_state.ne));
|
|
split_state.nr[0] = 1;
|
|
split_state.n_segments = 1;
|
|
if (split_state.axis == GGML_BACKEND_SPLIT_AXIS_PARTIAL) {
|
|
GGML_ASSERT(tc.tensor_axis_0 != tensor);
|
|
const ggml_backend_meta_split_state source_split_state = llama_meta_device_get_split_state(tc.tensor_axis_0, userdata);
|
|
GGML_ASSERT(source_split_state.axis >= 0 && source_split_state.axis < GGML_MAX_DIMS);
|
|
for (size_t j = 0; j < ud->n_devices; j++) {
|
|
for (size_t is = 0; is < source_split_state.n_segments; is++) {
|
|
split_state.ne[j] += source_split_state.ne[is*ud->n_devices + j] * source_split_state.nr[is];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return split_state;
|
|
GGML_UNUSED(userdata);
|
|
}
|
|
|
|
const char * llm_type_name(llm_type type) {
|
|
switch (type) {
|
|
case LLM_TYPE_14M: return "14M";
|
|
case LLM_TYPE_17M: return "17M";
|
|
case LLM_TYPE_22M: return "22M";
|
|
case LLM_TYPE_33M: return "33M";
|
|
case LLM_TYPE_47M: return "47M";
|
|
case LLM_TYPE_60M: return "60M";
|
|
case LLM_TYPE_70M: return "70M";
|
|
case LLM_TYPE_80M: return "80M";
|
|
case LLM_TYPE_109M: return "109M";
|
|
case LLM_TYPE_137M: return "137M";
|
|
case LLM_TYPE_140M: return "140M";
|
|
case LLM_TYPE_149M: return "149M";
|
|
case LLM_TYPE_160M: return "160M";
|
|
case LLM_TYPE_190M: return "190M";
|
|
case LLM_TYPE_220M: return "220M";
|
|
case LLM_TYPE_230M: return "230M";
|
|
case LLM_TYPE_250M: return "250M";
|
|
case LLM_TYPE_256M: return "256M";
|
|
case LLM_TYPE_270M: return "270M";
|
|
case LLM_TYPE_335M: return "335M";
|
|
case LLM_TYPE_350M: return "350M";
|
|
case LLM_TYPE_360M: return "360M";
|
|
case LLM_TYPE_395M: return "395M";
|
|
case LLM_TYPE_410M: return "410M";
|
|
case LLM_TYPE_450M: return "450M";
|
|
case LLM_TYPE_475M: return "475M";
|
|
case LLM_TYPE_558M: return "558M";
|
|
case LLM_TYPE_700M: return "700M";
|
|
case LLM_TYPE_770M: return "770M";
|
|
case LLM_TYPE_780M: return "780M";
|
|
case LLM_TYPE_950M: return "950M";
|
|
case LLM_TYPE_0_3B: return "0.3B";
|
|
case LLM_TYPE_0_5B: return "0.5B";
|
|
case LLM_TYPE_0_6B: return "0.6B";
|
|
case LLM_TYPE_0_8B: return "0.8B";
|
|
case LLM_TYPE_1B: return "1B";
|
|
case LLM_TYPE_1_2B: return "1.2B";
|
|
case LLM_TYPE_1_3B: return "1.3B";
|
|
case LLM_TYPE_1_4B: return "1.4B";
|
|
case LLM_TYPE_1_5B: return "1.5B";
|
|
case LLM_TYPE_1_6B: return "1.6B";
|
|
case LLM_TYPE_1_7B: return "1.7B";
|
|
case LLM_TYPE_1_8B: return "1.8B";
|
|
case LLM_TYPE_2B: return "2B";
|
|
case LLM_TYPE_2_6B: return "2.6B";
|
|
case LLM_TYPE_2_8B: return "2.8B";
|
|
case LLM_TYPE_2_9B: return "2.9B";
|
|
case LLM_TYPE_3B: return "3B";
|
|
case LLM_TYPE_4B: return "4B";
|
|
case LLM_TYPE_6B: return "6B";
|
|
case LLM_TYPE_6_9B: return "6.9B";
|
|
case LLM_TYPE_7B: return "7B";
|
|
case LLM_TYPE_8B: return "8B";
|
|
case LLM_TYPE_9B: return "9B";
|
|
case LLM_TYPE_11B: return "11B";
|
|
case LLM_TYPE_12B: return "12B";
|
|
case LLM_TYPE_13B: return "13B";
|
|
case LLM_TYPE_14B: return "14B";
|
|
case LLM_TYPE_15B: return "15B";
|
|
case LLM_TYPE_16B: return "16B";
|
|
case LLM_TYPE_20B: return "20B";
|
|
case LLM_TYPE_26B: return "26B";
|
|
case LLM_TYPE_27B: return "27B";
|
|
case LLM_TYPE_30B: return "30B";
|
|
case LLM_TYPE_31B: return "31B";
|
|
case LLM_TYPE_32B: return "32B";
|
|
case LLM_TYPE_34B: return "34B";
|
|
case LLM_TYPE_35B: return "35B";
|
|
case LLM_TYPE_36B: return "36B";
|
|
case LLM_TYPE_40B: return "40B";
|
|
case LLM_TYPE_65B: return "65B";
|
|
case LLM_TYPE_70B: return "70B";
|
|
case LLM_TYPE_120B: return "120B";
|
|
case LLM_TYPE_142B: return "142B";
|
|
case LLM_TYPE_236B: return "236B";
|
|
case LLM_TYPE_290B: return "290B";
|
|
case LLM_TYPE_314B: return "314B";
|
|
case LLM_TYPE_405B: return "405B";
|
|
case LLM_TYPE_456B: return "456B";
|
|
case LLM_TYPE_671B: return "671B";
|
|
case LLM_TYPE_SMALL: return "0.1B";
|
|
case LLM_TYPE_MEDIUM: return "0.4B";
|
|
case LLM_TYPE_LARGE: return "0.8B";
|
|
case LLM_TYPE_XL: return "1.5B";
|
|
case LLM_TYPE_A1_7B: return "A1.7B";
|
|
case LLM_TYPE_A2_7B: return "A2.7B";
|
|
case LLM_TYPE_8x7B: return "8x7B";
|
|
case LLM_TYPE_8x22B: return "8x22B";
|
|
case LLM_TYPE_16x12B: return "16x12B";
|
|
case LLM_TYPE_16x3_8B: return "16x3.8B";
|
|
case LLM_TYPE_10B_128x3_66B: return "10B+128x3.66B";
|
|
case LLM_TYPE_57B_A14B: return "57B.A14B";
|
|
case LLM_TYPE_17B_16E: return "17Bx16E (Scout)";
|
|
case LLM_TYPE_17B_128E: return "17Bx128E (Maverick)";
|
|
case LLM_TYPE_A13B: return "A13B";
|
|
case LLM_TYPE_7B_A1B: return "7B.A1B";
|
|
case LLM_TYPE_8B_A1B: return "8B.A1B";
|
|
case LLM_TYPE_7_9B_A1_3B: return "7.9B.A1.3B";
|
|
case LLM_TYPE_12B_A2_5B: return "12B.A2.5B";
|
|
case LLM_TYPE_16B_A1B: return "16B.A1B";
|
|
case LLM_TYPE_21B_A3B: return "21B.A3B";
|
|
case LLM_TYPE_24B_A2B: return "24B.A2B";
|
|
case LLM_TYPE_26B_A4B: return "26B.A4B";
|
|
case LLM_TYPE_30B_A3B: return "30B.A3B";
|
|
case LLM_TYPE_31B_A3_5B: return "31B.A3.5B";
|
|
case LLM_TYPE_35B_A3B: return "35B.A3B";
|
|
case LLM_TYPE_48B_A3B: return "48B.A3B";
|
|
case LLM_TYPE_80B_A3B: return "80B.A3B";
|
|
case LLM_TYPE_A3B: return "A3B";
|
|
case LLM_TYPE_100B_A6B: return "100B.A6B";
|
|
case LLM_TYPE_102B_A12B: return "102B.A12B";
|
|
case LLM_TYPE_106B_A12B: return "106B.A12B";
|
|
case LLM_TYPE_118B_A8B: return "118B.A8B";
|
|
case LLM_TYPE_120B_A12B: return "120B.A12B";
|
|
case LLM_TYPE_122B_A10B: return "122B.A10B";
|
|
case LLM_TYPE_124B_A5_1B: return "124B.A5.1B";
|
|
case LLM_TYPE_196B_A11B: return "196B.A11B";
|
|
case LLM_TYPE_230B_A10B: return "230B.A10B";
|
|
case LLM_TYPE_428B_A23B: return "428B.A23B";
|
|
case LLM_TYPE_235B_A22B: return "235B.A22B";
|
|
case LLM_TYPE_288B_A19B: return "288B.A19B";
|
|
case LLM_TYPE_300B_A47B: return "300B.A47B";
|
|
case LLM_TYPE_310B_A15B: return "310B.A15B";
|
|
case LLM_TYPE_355B_A32B: return "355B.A32B";
|
|
case LLM_TYPE_397B_A17B: return "397B.A17B";
|
|
case LLM_TYPE_685B_A37B: return "685B.A37B";
|
|
case LLM_TYPE_744B_A40B: return "744B.A40B";
|
|
case LLM_TYPE_2_8T_A50B: return "2.8T.A50B";
|
|
case LLM_TYPE_E2B: return "E2B";
|
|
case LLM_TYPE_E4B: return "E4B";
|
|
default: return "?B";
|
|
}
|
|
}
|
|
|
|
static const char * llama_expert_gating_func_name(llama_expert_gating_func_type type) {
|
|
switch (type) {
|
|
case LLAMA_EXPERT_GATING_FUNC_TYPE_SOFTMAX: return "softmax";
|
|
case LLAMA_EXPERT_GATING_FUNC_TYPE_SIGMOID: return "sigmoid";
|
|
case LLAMA_EXPERT_GATING_FUNC_TYPE_SQRT_SOFTPLUS: return "sqrtsoftplus";
|
|
default: return "unknown";
|
|
}
|
|
}
|
|
|
|
static const std::map<llama_rope_scaling_type, const char *> LLAMA_ROPE_SCALING_TYPES = {
|
|
{ LLAMA_ROPE_SCALING_TYPE_NONE, "none" },
|
|
{ LLAMA_ROPE_SCALING_TYPE_LINEAR, "linear" },
|
|
{ LLAMA_ROPE_SCALING_TYPE_YARN, "yarn" },
|
|
{ LLAMA_ROPE_SCALING_TYPE_LONGROPE, "longrope" },
|
|
};
|
|
|
|
std::string llama_rope_scaling_type_name(llama_rope_scaling_type rope_scaling_type) {
|
|
return LLAMA_ROPE_SCALING_TYPES.at(rope_scaling_type);
|
|
}
|
|
|
|
static llama_rope_scaling_type llama_rope_scaling_type_from_string(const std::string & name) {
|
|
for (const auto & kv : LLAMA_ROPE_SCALING_TYPES) {
|
|
if (kv.second == name) {
|
|
return (llama_rope_scaling_type) kv.first;
|
|
}
|
|
}
|
|
|
|
return LLAMA_ROPE_SCALING_TYPE_UNSPECIFIED;
|
|
}
|
|
|
|
// Maps the GGUF `<arch>.hidden_activation` string to the FFN op type used by the
|
|
// graph builders. Only gated activations that map cleanly to llm_ffn_op_type are
|
|
// listed; unrecognized values fall back to GeGLU, which matches the historical
|
|
// default for ModernBert-style architectures.
|
|
static const std::map<std::string, llm_ffn_op_type> LLM_FFN_OP_TYPES_FROM_STRING = {
|
|
{ "gelu", LLM_FFN_GEGLU },
|
|
{ "geglu", LLM_FFN_GEGLU },
|
|
{ "silu", LLM_FFN_SWIGLU },
|
|
{ "swish", LLM_FFN_SWIGLU },
|
|
{ "swiglu", LLM_FFN_SWIGLU },
|
|
{ "relu", LLM_FFN_RELU },
|
|
{ "reglu", LLM_FFN_REGLU },
|
|
};
|
|
|
|
llm_ffn_op_type llm_ffn_op_type_from_string(const std::string & name, llm_ffn_op_type fallback) {
|
|
const auto it = LLM_FFN_OP_TYPES_FROM_STRING.find(name);
|
|
if (it != LLM_FFN_OP_TYPES_FROM_STRING.end()) {
|
|
return it->second;
|
|
}
|
|
return fallback;
|
|
}
|
|
|
|
// CPU: ACCEL -> GPU host -> CPU extra -> CPU
|
|
static buft_list_t make_cpu_buft_list(const std::vector<llama_device> & devices, bool use_extra_bufts, bool no_host) {
|
|
buft_list_t buft_list;
|
|
|
|
// add ACCEL buffer types
|
|
for (size_t i = 0; i < ggml_backend_dev_count(); ++i) {
|
|
ggml_backend_dev_t dev = ggml_backend_dev_get(i);
|
|
if (ggml_backend_dev_type(dev) == GGML_BACKEND_DEVICE_TYPE_ACCEL) {
|
|
auto * buft = ggml_backend_dev_buffer_type(dev);
|
|
// skip
|
|
if (buft != ggml_backend_cpu_buffer_type()) {
|
|
buft_list.emplace_back(dev, buft);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add a host buffer type
|
|
// storing the tensors in a host buffer is useful when the processing of large batches
|
|
// is offloaded to a GPU device, since it reduces the time spent on data transfers
|
|
// generally, this will be done using the first device in the list
|
|
// a better approach would be to handle this on a weight-by-weight basis using the offload_op
|
|
// function of the device to determine if it would benefit from being stored in a host buffer
|
|
if (!no_host) {
|
|
for (const auto & dev : devices) {
|
|
ggml_backend_buffer_type_t buft = ggml_backend_dev_host_buffer_type(dev.dev);
|
|
if (buft) {
|
|
buft_list.emplace_back(dev.dev, buft);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add extra buffer types
|
|
if (use_extra_bufts) {
|
|
auto * cpu_dev = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU);
|
|
if (cpu_dev == nullptr) {
|
|
throw std::runtime_error(format("%s: no CPU backend found", __func__));
|
|
}
|
|
|
|
auto * cpu_reg = ggml_backend_dev_backend_reg(cpu_dev);
|
|
auto ggml_backend_dev_get_extra_bufts_fn = (ggml_backend_dev_get_extra_bufts_t)
|
|
ggml_backend_reg_get_proc_address(cpu_reg, "ggml_backend_dev_get_extra_bufts");
|
|
if (ggml_backend_dev_get_extra_bufts_fn) {
|
|
ggml_backend_buffer_type_t * extra_bufts = ggml_backend_dev_get_extra_bufts_fn(cpu_dev);
|
|
while (extra_bufts && *extra_bufts) {
|
|
buft_list.emplace_back(cpu_dev, *extra_bufts);
|
|
++extra_bufts;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add the CPU buffer type
|
|
for (size_t i = 0; i < ggml_backend_dev_count(); ++i) {
|
|
ggml_backend_dev_t dev = ggml_backend_dev_get(i);
|
|
if (ggml_backend_dev_type(dev) == GGML_BACKEND_DEVICE_TYPE_CPU) {
|
|
buft_list.emplace_back(dev, ggml_backend_dev_buffer_type(dev));
|
|
}
|
|
}
|
|
|
|
return buft_list;
|
|
}
|
|
|
|
// GPU: split if LLAMA_SPLIT_MODE_ROW -> GPU
|
|
static buft_list_t make_gpu_buft_list(ggml_backend_dev_t dev, llama_split_mode split_mode, const float * tensor_split) {
|
|
buft_list_t buft_list;
|
|
|
|
// add the device split buffer type if requested and available
|
|
if (split_mode == LLAMA_SPLIT_MODE_ROW) {
|
|
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(dev);
|
|
auto ggml_backend_split_buffer_type_fn = (ggml_backend_split_buffer_type_t)
|
|
ggml_backend_reg_get_proc_address(reg, "ggml_backend_split_buffer_type");
|
|
if (ggml_backend_split_buffer_type_fn) {
|
|
size_t dev_index = [&]() {
|
|
auto * reg = ggml_backend_dev_backend_reg(dev);
|
|
for (size_t i = 0; i < ggml_backend_reg_dev_count(reg); ++i) {
|
|
if (ggml_backend_reg_dev_get(reg, i) == dev) {
|
|
return i;
|
|
}
|
|
}
|
|
throw std::runtime_error(format("device %s not found in its backend reg", ggml_backend_dev_name(dev)));
|
|
}();
|
|
auto * buft = ggml_backend_split_buffer_type_fn(dev_index, tensor_split);
|
|
if (buft != nullptr) {
|
|
buft_list.emplace_back(dev, buft);
|
|
}
|
|
} else {
|
|
throw std::runtime_error(format("device %s does not support split buffers", ggml_backend_dev_name(dev)));
|
|
}
|
|
}
|
|
|
|
// add the device default buffer type
|
|
buft_list.emplace_back(dev, ggml_backend_dev_buffer_type(dev));
|
|
|
|
// add the device extra buffer type (if any)
|
|
ggml_backend_reg_t reg = ggml_backend_dev_backend_reg(dev);
|
|
if (reg) {
|
|
auto ggml_backend_dev_get_extra_bufts_fn = (ggml_backend_dev_get_extra_bufts_t)
|
|
ggml_backend_reg_get_proc_address(reg, "ggml_backend_dev_get_extra_bufts");
|
|
|
|
if (ggml_backend_dev_get_extra_bufts_fn) {
|
|
ggml_backend_buffer_type_t * extra_bufts = ggml_backend_dev_get_extra_bufts_fn(dev);
|
|
while (extra_bufts && *extra_bufts) {
|
|
buft_list.emplace_back(dev, *extra_bufts);
|
|
++extra_bufts;
|
|
}
|
|
}
|
|
}
|
|
|
|
return buft_list;
|
|
}
|
|
|
|
struct llama_model::impl {
|
|
impl() = default;
|
|
~impl() = default;
|
|
|
|
uint64_t n_elements = 0;
|
|
|
|
size_t n_bytes = 0;
|
|
|
|
std::string desc_str;
|
|
|
|
llama_ftype ftype = LLAMA_FTYPE_ALL_F32;
|
|
|
|
// model memory mapped files
|
|
llama_mmaps mappings;
|
|
|
|
// objects representing data potentially being locked in memory
|
|
llama_mlocks mlock_bufs;
|
|
llama_mlocks mlock_mmaps;
|
|
|
|
// contexts where the model tensors metadata is stored as well as the corresponding buffers:
|
|
std::vector<std::pair<ggml_context_ptr, std::vector<ggml_backend_buffer_ptr>>> ctxs_bufs;
|
|
|
|
buft_list_t cpu_buft_list;
|
|
std::map<ggml_backend_dev_t, buft_list_t> gpu_buft_list;
|
|
|
|
struct layer_dev {
|
|
ggml_backend_dev_t dev;
|
|
buft_list_t * buft_list;
|
|
};
|
|
|
|
layer_dev dev_input = {};
|
|
layer_dev dev_output = {};
|
|
std::vector<layer_dev> dev_layer;
|
|
|
|
bool has_tensor_overrides;
|
|
|
|
std::vector<float> tensor_split_owned;
|
|
};
|
|
|
|
llama_model::llama_model(const llama_model_params & params) : params(params), pimpl(std::make_unique<impl>()) {
|
|
if (params.tensor_split != nullptr) {
|
|
// llama_model_params stores tensor_split as a borrowed pointer, but the model
|
|
// may need it later for tensor-parallel KV-cache split metadata.
|
|
pimpl->tensor_split_owned.assign(params.tensor_split, params.tensor_split + llama_max_devices());
|
|
this->params.tensor_split = pimpl->tensor_split_owned.data();
|
|
}
|
|
pimpl->has_tensor_overrides = params.tensor_buft_overrides && params.tensor_buft_overrides[0].pattern;
|
|
}
|
|
|
|
llama_model::~llama_model() {
|
|
for (auto * lora : loras) {
|
|
delete lora;
|
|
}
|
|
}
|
|
|
|
void llama_model_base::load_stats(llama_model_loader & ml) {
|
|
pimpl->n_elements = ml.n_elements;
|
|
pimpl->n_bytes = ml.n_bytes;
|
|
}
|
|
|
|
void llama_model_base::load_hparams(llama_model_loader & ml) {
|
|
const gguf_context * ctx = ml.metadata;
|
|
|
|
// get metadata as string
|
|
for (int i = 0; i < gguf_get_n_kv(ctx); i++) {
|
|
gguf_type type = gguf_get_kv_type(ctx, i);
|
|
if (type == GGUF_TYPE_ARRAY) {
|
|
continue;
|
|
}
|
|
const char * name = gguf_get_key(ctx, i);
|
|
const std::string value = gguf_kv_to_str(ctx, i);
|
|
gguf_kv.emplace(name, value);
|
|
}
|
|
|
|
// get general kv
|
|
ml.get_key(LLM_KV_GENERAL_NAME, name, false);
|
|
|
|
// everything past this point is not vocab-related
|
|
// for CLIP models, we only need to load tensors, no hparams
|
|
if (hparams.vocab_only || ml.get_arch() == LLM_ARCH_CLIP) {
|
|
return;
|
|
}
|
|
|
|
ml.get_key(LLM_KV_CONTEXT_LENGTH, hparams.n_ctx_train);
|
|
ml.get_key(LLM_KV_EMBEDDING_LENGTH, hparams.n_embd);
|
|
ml.get_key(LLM_KV_EMBEDDING_LENGTH_OUT, hparams.n_embd_out_impl, false);
|
|
ml.get_key(LLM_KV_ATTENTION_CAUSAL, hparams.causal_attn, false);
|
|
ml.get_key(LLM_KV_POOLING_TYPE, hparams.pooling_type, false);
|
|
ml.get_key(LLM_KV_BLOCK_COUNT, hparams.n_layer_all);
|
|
GGML_ASSERT(hparams.n_layer_all > 0 && hparams.n_layer_all <= LLAMA_MAX_LAYERS);
|
|
ml.get_key(LLM_KV_EXPERT_COUNT, hparams.n_expert, false);
|
|
ml.get_key(LLM_KV_EXPERT_USED_COUNT, hparams.n_expert_used, false);
|
|
ml.get_key(LLM_KV_EXPERT_GROUP_COUNT, hparams.n_expert_groups, false);
|
|
ml.get_key(LLM_KV_EXPERT_GROUP_USED_COUNT, hparams.n_group_used, false);
|
|
|
|
if (arch == LLM_ARCH_HUNYUAN_VL || arch == LLM_ARCH_HUNYUAN_DENSE) {
|
|
if (hparams.n_expert <= 1) {
|
|
hparams.n_expert = 0;
|
|
hparams.n_expert_used = 0;
|
|
}
|
|
}
|
|
|
|
if (arch == LLM_ARCH_WAVTOKENIZER_DEC) {
|
|
ml.get_key(LLM_KV_FEATURES_LENGTH, hparams.n_embd);
|
|
ml.get_key(LLM_KV_EMBEDDING_LENGTH, hparams.n_embd_out_impl);
|
|
|
|
ml.get_key(LLM_KV_POSNET_EMBEDDING_LENGTH, hparams.posnet.n_embd);
|
|
ml.get_key(LLM_KV_POSNET_BLOCK_COUNT, hparams.posnet.n_layer);
|
|
|
|
ml.get_key(LLM_KV_CONVNEXT_EMBEDDING_LENGTH, hparams.convnext.n_embd);
|
|
ml.get_key(LLM_KV_CONVNEXT_BLOCK_COUNT, hparams.convnext.n_layer);
|
|
|
|
GGML_ASSERT(hparams.posnet.n_layer <= hparams.n_layer_all);
|
|
GGML_ASSERT(hparams.convnext.n_layer <= hparams.n_layer_all);
|
|
}
|
|
|
|
GGML_ASSERT(hparams.n_expert <= LLAMA_MAX_EXPERTS);
|
|
GGML_ASSERT(hparams.n_expert_used <= hparams.n_expert);
|
|
if (hparams.n_expert > 0) {
|
|
GGML_ASSERT(hparams.n_expert_used > 0);
|
|
GGML_ASSERT(hparams.n_expert_groups < hparams.n_expert);
|
|
if (hparams.n_expert_groups > 1) {
|
|
GGML_ASSERT(hparams.n_expert % hparams.n_expert_groups == 0);
|
|
GGML_ASSERT(hparams.n_group_used > 0);
|
|
GGML_ASSERT(hparams.n_group_used < hparams.n_expert_groups);
|
|
}
|
|
} else {
|
|
GGML_ASSERT(hparams.n_expert_used == 0);
|
|
GGML_ASSERT(hparams.n_expert_groups == 0);
|
|
}
|
|
|
|
std::fill(hparams.n_head_arr.begin(), hparams.n_head_arr.end(), 0);
|
|
std::fill(hparams.n_head_kv_arr.begin(), hparams.n_head_kv_arr.end(), 0);
|
|
std::fill(hparams.n_ff_arr.begin(), hparams.n_ff_arr.end(), 0);
|
|
|
|
std::fill(hparams.rope_sections.begin(), hparams.rope_sections.end(), 0);
|
|
std::fill(hparams.rope_pattern.begin(), hparams.rope_pattern.end(), 1);
|
|
std::fill(hparams.is_swa_impl.begin(), hparams.is_swa_impl.end(), 0);
|
|
std::fill(hparams.is_recr_impl.begin(), hparams.is_recr_impl.end(), llm_arch_is_recurrent(ml.get_arch()) ? 1 : 0);
|
|
std::fill(hparams.is_indexer_full_impl.begin(), hparams.is_indexer_full_impl.end(), 0);
|
|
|
|
std::fill(hparams.xielu_alpha_n.begin(), hparams.xielu_alpha_n.end(), 0.0f);
|
|
std::fill(hparams.xielu_alpha_p.begin(), hparams.xielu_alpha_p.end(), 0.0f);
|
|
std::fill(hparams.xielu_beta.begin(), hparams.xielu_beta.end(), 0.0f);
|
|
std::fill(hparams.xielu_eps.begin(), hparams.xielu_eps.end(), 0.0f);
|
|
|
|
std::fill(hparams.swiglu_clamp_exp.begin(), hparams.swiglu_clamp_exp.end(), 0.0f);
|
|
std::fill(hparams.swiglu_clamp_shexp.begin(), hparams.swiglu_clamp_shexp.end(), 0.0f);
|
|
|
|
ml.get_key_or_arr(LLM_KV_FEED_FORWARD_LENGTH, hparams.n_ff_arr, hparams.n_layer(), false);
|
|
ml.get_key_or_arr(LLM_KV_ATTENTION_HEAD_COUNT, hparams.n_head_arr, hparams.n_layer(), false);
|
|
|
|
// Populate deepstack_mapping_arr - initialized to -1 (no deepstack)
|
|
std::fill(hparams.deepstack_mapping_arr.begin(), hparams.deepstack_mapping_arr.end(), -1);
|
|
|
|
// n_head_kv is optional, default to n_head
|
|
hparams.n_head_kv_arr = hparams.n_head_arr;
|
|
|
|
ml.get_key_or_arr(LLM_KV_ATTENTION_HEAD_COUNT_KV, hparams.n_head_kv_arr, hparams.n_layer(), false);
|
|
|
|
bool rope_finetuned = false;
|
|
ml.get_key(LLM_KV_ROPE_SCALING_FINETUNED, rope_finetuned, false);
|
|
hparams.rope_finetuned = rope_finetuned;
|
|
|
|
hparams.n_ctx_orig_yarn = hparams.n_ctx_train;
|
|
ml.get_key(LLM_KV_ROPE_SCALING_ORIG_CTX_LEN, hparams.n_ctx_orig_yarn, false);
|
|
|
|
// rope_freq_base (optional)
|
|
hparams.rope_freq_base_train = 10000.0f;
|
|
ml.get_key(LLM_KV_ROPE_FREQ_BASE, hparams.rope_freq_base_train, false);
|
|
|
|
std::string rope_scaling("linear");
|
|
ml.get_key(LLM_KV_ROPE_SCALING_TYPE, rope_scaling, false);
|
|
hparams.rope_scaling_type_train = llama_rope_scaling_type_from_string(rope_scaling);
|
|
GGML_ASSERT(hparams.rope_scaling_type_train != LLAMA_ROPE_SCALING_TYPE_UNSPECIFIED);
|
|
|
|
// TODO: Handle SWA metadata similarly when models start implementing it
|
|
// rope_freq_scale (inverse of the kv) is optional
|
|
float ropescale = 0.0f;
|
|
if (!ml.get_key(LLM_KV_ROPE_SCALING_FACTOR, ropescale, false)) {
|
|
// try the old key name
|
|
ml.get_key(LLM_KV_ROPE_SCALE_LINEAR, ropescale, false);
|
|
}
|
|
hparams.rope_freq_scale_train = ropescale == 0.0f ? 1.0f : 1.0f/ropescale;
|
|
|
|
ml.get_key(LLM_KV_ROPE_SCALING_ATTN_FACTOR, hparams.rope_attn_factor, false);
|
|
ml.get_key(LLM_KV_ROPE_SCALING_ALPHA, hparams.rope_scaling_alpha, false);
|
|
|
|
// non-transformer models do not have attention heads
|
|
if (hparams.n_head() > 0) {
|
|
// gpt-neox n_rot = rotary_pct * (n_embd / n_head)
|
|
// gpt-j n_rot = rotary_dim
|
|
|
|
hparams.n_embd_head_k_full = hparams.n_embd / hparams.n_head();
|
|
ml.get_key(LLM_KV_ATTENTION_KEY_LENGTH, hparams.n_embd_head_k_full, false);
|
|
|
|
hparams.n_embd_head_v_full = hparams.n_embd / hparams.n_head();
|
|
ml.get_key(LLM_KV_ATTENTION_VALUE_LENGTH, hparams.n_embd_head_v_full, false);
|
|
|
|
// sanity check for n_rot (optional)
|
|
hparams.n_rot_full = hparams.n_embd_head_k_full;
|
|
|
|
ml.get_key(LLM_KV_ROPE_DIMENSION_COUNT, hparams.n_rot_full, false);
|
|
|
|
if (arch == LLM_ARCH_LLAMA || arch == LLM_ARCH_DECI || arch == LLM_ARCH_FALCON || arch == LLM_ARCH_LLAMA_EMBED) {
|
|
if (hparams.n_rot_full != hparams.n_embd_head_k_full) {
|
|
throw std::runtime_error(format("invalid n_rot: %u, expected %u", hparams.n_rot_full, hparams.n_embd_head_k_full));
|
|
}
|
|
}
|
|
} else {
|
|
hparams.n_rot_full = 0;
|
|
hparams.n_embd_head_k_full = 0;
|
|
hparams.n_embd_head_v_full = 0;
|
|
}
|
|
|
|
// head size and n_rot for SWA layers
|
|
{
|
|
hparams.n_embd_head_k_swa = hparams.n_embd_head_k_full;
|
|
hparams.n_embd_head_v_swa = hparams.n_embd_head_v_full;
|
|
ml.get_key(LLM_KV_ATTENTION_KEY_LENGTH_SWA, hparams.n_embd_head_k_swa, false);
|
|
ml.get_key(LLM_KV_ATTENTION_VALUE_LENGTH_SWA, hparams.n_embd_head_v_swa, false);
|
|
|
|
hparams.n_rot_swa = hparams.n_rot_full;
|
|
ml.get_key(LLM_KV_ROPE_DIMENSION_COUNT_SWA, hparams.n_rot_swa, false);
|
|
}
|
|
|
|
// for classifier models
|
|
ml.get_arr(LLM_KV_CLASSIFIER_OUTPUT_LABELS, classifier_labels, false);
|
|
if (!classifier_labels.empty()) {
|
|
hparams.n_cls_out = classifier_labels.size();
|
|
}
|
|
|
|
// per-arch hparams
|
|
load_arch_hparams(ml);
|
|
|
|
pimpl->n_bytes = ml.n_bytes;
|
|
|
|
pimpl->desc_str = arch_name() + " " + type_name() + " " + ml.ftype_name();
|
|
|
|
pimpl->ftype = ml.ftype;
|
|
|
|
if (hparams.f_max_alibi_bias > 0.0f) {
|
|
hparams.use_alibi = true;
|
|
}
|
|
|
|
hparams.rope_type = llama_model_rope_type(this);
|
|
}
|
|
|
|
void llama_model_base::load_vocab(llama_model_loader & ml) {
|
|
const auto kv = LLM_KV(arch);
|
|
|
|
vocab.load(ml, kv);
|
|
}
|
|
|
|
bool llama_model_base::load_tensors(llama_model_loader & ml) {
|
|
const auto & split_mode = params.split_mode;
|
|
const bool use_mlock = params.load_mode == LLAMA_LOAD_MODE_MLOCK || params.load_mode == LLAMA_LOAD_MODE_MMAP_MLOCK;
|
|
const auto & tensor_split = params.tensor_split;
|
|
|
|
const int n_layer_all = hparams.n_layer_all;
|
|
const int n_gpu_layers = this->n_gpu_layers();
|
|
|
|
const bool use_mmap_buffer = true;
|
|
|
|
this->ml = &ml; // to be used by create_tensor() and load_arch_tensors()
|
|
|
|
if (ml.use_mmap && params.load_mode == LLAMA_LOAD_MODE_AUTO) {
|
|
for (const auto & dev : devices) {
|
|
ggml_backend_dev_props props;
|
|
ggml_backend_dev_get_props(dev.dev, &props);
|
|
if (!props.caps.mmap_support) {
|
|
ml.use_mmap = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const char * load_mode_name = params.load_mode == LLAMA_LOAD_MODE_AUTO
|
|
? llama_load_mode_name(ml.use_mmap ? LLAMA_LOAD_MODE_MMAP : LLAMA_LOAD_MODE_NONE)
|
|
: llama_load_mode_name(params.load_mode);
|
|
|
|
LLAMA_LOG_INFO("%s: loading model tensors, this can take a while... (load_mode = %s)\n",
|
|
__func__, load_mode_name);
|
|
|
|
// build a list of buffer types for the CPU and GPU devices
|
|
pimpl->cpu_buft_list = make_cpu_buft_list(devices, params.use_extra_bufts, params.no_host);
|
|
for (const auto & dev : devices) {
|
|
buft_list_t buft_list = make_gpu_buft_list(dev.dev, split_mode, tensor_split);
|
|
// add CPU buffer types as a fallback
|
|
buft_list.insert(buft_list.end(), pimpl->cpu_buft_list.begin(), pimpl->cpu_buft_list.end());
|
|
pimpl->gpu_buft_list.emplace(dev.dev, std::move(buft_list));
|
|
}
|
|
|
|
ggml_backend_dev_t cpu_dev = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU);
|
|
if (cpu_dev == nullptr) {
|
|
throw std::runtime_error(format("%s: no CPU backend found", __func__));
|
|
}
|
|
|
|
// calculate the split points
|
|
bool all_zero = tensor_split == nullptr || std::all_of(tensor_split, tensor_split + n_devices(), [](float x) { return x == 0.0f; });
|
|
std::vector<float> splits(n_devices());
|
|
if (all_zero) {
|
|
// default split, by free memory
|
|
for (size_t i = 0; i < n_devices(); ++i) {
|
|
ggml_backend_dev_t dev = devices[i].dev;
|
|
size_t total;
|
|
size_t free;
|
|
ggml_backend_dev_memory(dev, &free, &total);
|
|
|
|
// devices can return 0 bytes for free and total memory if they do not
|
|
// have any to report. in this case, we will use the host memory as a fallback
|
|
// fixes: https://github.com/ggml-org/llama.cpp/issues/18577
|
|
if (free == 0 && total == 0) {
|
|
ggml_backend_dev_memory(cpu_dev, &free, &total);
|
|
}
|
|
splits[i] = free;
|
|
}
|
|
} else {
|
|
std::copy(tensor_split, tensor_split + n_devices(), splits.begin());
|
|
}
|
|
|
|
// sum and normalize the splits to get the split points
|
|
float split_sum = 0.0f;
|
|
for (size_t i = 0; i < n_devices(); ++i) {
|
|
split_sum += splits[i];
|
|
splits[i] = split_sum;
|
|
}
|
|
for (size_t i = 0; i < n_devices(); ++i) {
|
|
splits[i] /= split_sum;
|
|
}
|
|
|
|
const int i_gpu_start = std::max(n_layer_all + 1 - n_gpu_layers, 0);
|
|
const int act_gpu_layers = devices.empty() ? 0 : std::min(n_gpu_layers, n_layer_all + 1);
|
|
auto get_layer_buft_list = [&](int il) -> llama_model::impl::layer_dev {
|
|
const bool is_swa = il < n_layer_all && hparams.is_swa(il);
|
|
if (il < i_gpu_start || (il - i_gpu_start) >= act_gpu_layers) {
|
|
LLAMA_LOG_DEBUG("load_tensors: layer %3d assigned to device %s, is_swa = %d\n", il, ggml_backend_dev_name(cpu_dev), is_swa);
|
|
return {cpu_dev, &pimpl->cpu_buft_list};
|
|
}
|
|
const int layer_gpu = std::upper_bound(splits.begin(), splits.begin() + n_devices(), float(il - i_gpu_start)/act_gpu_layers) - splits.begin();
|
|
auto * dev = devices.at(layer_gpu).dev;
|
|
LLAMA_LOG_DEBUG("load_tensors: layer %3d assigned to device %s, is_swa = %d\n", il, ggml_backend_dev_name(dev), is_swa);
|
|
return {dev, &pimpl->gpu_buft_list.at(dev)};
|
|
};
|
|
|
|
// assign the input layer
|
|
// there is very little benefit to offloading the input layer, so always keep it on the CPU
|
|
pimpl->dev_input = { cpu_dev, &pimpl->cpu_buft_list };
|
|
|
|
// assign the repeating layers to the devices according to the splits
|
|
pimpl->dev_layer.resize(n_layer_all);
|
|
for (int il = 0; il < n_layer_all; ++il) {
|
|
pimpl->dev_layer[il] = get_layer_buft_list(il);
|
|
}
|
|
|
|
// assign the output layer
|
|
pimpl->dev_output = get_layer_buft_list(n_layer_all);
|
|
|
|
const auto TENSOR_NOT_REQUIRED = llama_model_loader::TENSOR_NOT_REQUIRED;
|
|
|
|
// create tensors for the weights
|
|
{
|
|
// TODO: move to a separate function
|
|
const auto tn = LLM_TN(arch);
|
|
|
|
const int64_t n_expert = hparams.n_expert;
|
|
const int64_t n_expert_used = hparams.n_expert_used;
|
|
|
|
if (n_expert > 0 && n_expert_used == 0) {
|
|
throw std::runtime_error("model has expert layers but no expert layers are used");
|
|
}
|
|
|
|
layers.resize(n_layer_all);
|
|
|
|
// call the per-model loading function
|
|
load_arch_tensors(ml);
|
|
|
|
// generic pass: load optional per-tensor/per-expert ".scale" tensors (e.g. NVFP4 scale2)
|
|
// this avoids having to add scale loading to every architecture
|
|
for (int i = 0; i < n_layer_all; ++i) {
|
|
auto & layer = layers[i];
|
|
|
|
// attention weight scales (per-tensor, shape {1})
|
|
if (!layer.wq_s && layer.wq) {
|
|
layer.wq_s = create_tensor(tn(LLM_TENSOR_ATTN_Q, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.wk_s && layer.wk) {
|
|
layer.wk_s = create_tensor(tn(LLM_TENSOR_ATTN_K, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.wv_s && layer.wv) {
|
|
layer.wv_s = create_tensor(tn(LLM_TENSOR_ATTN_V, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.wo_s && layer.wo) {
|
|
layer.wo_s = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.wqkv_s && layer.wqkv) {
|
|
layer.wqkv_s = create_tensor(tn(LLM_TENSOR_ATTN_QKV, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.wqkv_gate_s && layer.wqkv_gate) {
|
|
layer.wqkv_gate_s = create_tensor(tn(LLM_TENSOR_ATTN_GATE, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
|
|
// dense FFN weight scales (per-tensor, shape {1})
|
|
if (!layer.ffn_gate_s && layer.ffn_gate) {
|
|
layer.ffn_gate_s = create_tensor(tn(LLM_TENSOR_FFN_GATE, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_down_s && layer.ffn_down) {
|
|
layer.ffn_down_s = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_up_s && layer.ffn_up) {
|
|
layer.ffn_up_s = create_tensor(tn(LLM_TENSOR_FFN_UP, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_gate_shexp_s && layer.ffn_gate_shexp) {
|
|
layer.ffn_gate_shexp_s = create_tensor(tn(LLM_TENSOR_FFN_GATE_SHEXP, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_down_shexp_s && layer.ffn_down_shexp) {
|
|
layer.ffn_down_shexp_s = create_tensor(tn(LLM_TENSOR_FFN_DOWN_SHEXP, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_up_shexp_s && layer.ffn_up_shexp) {
|
|
layer.ffn_up_shexp_s = create_tensor(tn(LLM_TENSOR_FFN_UP_SHEXP, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
|
|
// MoE expert weight scales (per-expert, shape {n_expert})
|
|
if (!layer.ffn_gate_exps_s && layer.ffn_gate_exps) {
|
|
layer.ffn_gate_exps_s = create_tensor(tn(LLM_TENSOR_FFN_GATE_EXPS, "scale", i), {n_expert}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_down_exps_s && layer.ffn_down_exps) {
|
|
layer.ffn_down_exps_s = create_tensor(tn(LLM_TENSOR_FFN_DOWN_EXPS, "scale", i), {n_expert}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_up_exps_s && layer.ffn_up_exps) {
|
|
layer.ffn_up_exps_s = create_tensor(tn(LLM_TENSOR_FFN_UP_EXPS, "scale", i), {n_expert}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
|
|
// recurrent / linear-attention weight scales (per-tensor, shape {1})
|
|
if (!layer.ssm_in_s && layer.ssm_in) {
|
|
layer.ssm_in_s = create_tensor(tn(LLM_TENSOR_SSM_IN, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ssm_out_s && layer.ssm_out) {
|
|
layer.ssm_out_s = create_tensor(tn(LLM_TENSOR_SSM_OUT, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ssm_alpha_s && layer.ssm_alpha) {
|
|
layer.ssm_alpha_s = create_tensor(tn(LLM_TENSOR_SSM_ALPHA, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ssm_beta_s && layer.ssm_beta) {
|
|
layer.ssm_beta_s = create_tensor(tn(LLM_TENSOR_SSM_BETA, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.nextn.eh_proj_s && layer.nextn.eh_proj) {
|
|
layer.nextn.eh_proj_s = create_tensor(tn(LLM_TENSOR_NEXTN_EH_PROJ, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.nextn.shared_head_head_s && layer.nextn.shared_head_head) {
|
|
layer.nextn.shared_head_head_s = create_tensor(tn(LLM_TENSOR_NEXTN_SHARED_HEAD_HEAD, "scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
|
|
// input scales
|
|
if (!layer.wq_in_s && layer.wq) {
|
|
layer.wq_in_s = create_tensor(tn(LLM_TENSOR_ATTN_Q, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.wk_in_s && layer.wk) {
|
|
layer.wk_in_s = create_tensor(tn(LLM_TENSOR_ATTN_K, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.wv_in_s && layer.wv) {
|
|
layer.wv_in_s = create_tensor(tn(LLM_TENSOR_ATTN_V, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.wo_in_s && layer.wo) {
|
|
layer.wo_in_s = create_tensor(tn(LLM_TENSOR_ATTN_OUT, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.wqkv_in_s && layer.wqkv) {
|
|
layer.wqkv_in_s = create_tensor(tn(LLM_TENSOR_ATTN_QKV, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.wqkv_gate_in_s && layer.wqkv_gate) {
|
|
layer.wqkv_gate_in_s = create_tensor(tn(LLM_TENSOR_ATTN_GATE, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_gate_in_s && layer.ffn_gate) {
|
|
layer.ffn_gate_in_s = create_tensor(tn(LLM_TENSOR_FFN_GATE, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_down_in_s && layer.ffn_down) {
|
|
layer.ffn_down_in_s = create_tensor(tn(LLM_TENSOR_FFN_DOWN, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_up_in_s && layer.ffn_up) {
|
|
layer.ffn_up_in_s = create_tensor(tn(LLM_TENSOR_FFN_UP, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_gate_exps_in_s && layer.ffn_gate_exps) {
|
|
layer.ffn_gate_exps_in_s = create_tensor(tn(LLM_TENSOR_FFN_GATE_EXPS, "input_scale", i), {n_expert}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_down_exps_in_s && layer.ffn_down_exps) {
|
|
layer.ffn_down_exps_in_s = create_tensor(tn(LLM_TENSOR_FFN_DOWN_EXPS, "input_scale", i), {n_expert}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_up_exps_in_s && layer.ffn_up_exps) {
|
|
layer.ffn_up_exps_in_s = create_tensor(tn(LLM_TENSOR_FFN_UP_EXPS, "input_scale", i), {n_expert}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_gate_shexp_in_s && layer.ffn_gate_shexp) {
|
|
layer.ffn_gate_shexp_in_s = create_tensor(tn(LLM_TENSOR_FFN_GATE_SHEXP, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_down_shexp_in_s && layer.ffn_down_shexp) {
|
|
layer.ffn_down_shexp_in_s = create_tensor(tn(LLM_TENSOR_FFN_DOWN_SHEXP, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ffn_up_shexp_in_s && layer.ffn_up_shexp) {
|
|
layer.ffn_up_shexp_in_s = create_tensor(tn(LLM_TENSOR_FFN_UP_SHEXP, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ssm_in_in_s && layer.ssm_in) {
|
|
layer.ssm_in_in_s = create_tensor(tn(LLM_TENSOR_SSM_IN, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ssm_out_in_s && layer.ssm_out) {
|
|
layer.ssm_out_in_s = create_tensor(tn(LLM_TENSOR_SSM_OUT, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ssm_alpha_in_s && layer.ssm_alpha) {
|
|
layer.ssm_alpha_in_s = create_tensor(tn(LLM_TENSOR_SSM_ALPHA, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.ssm_beta_in_s && layer.ssm_beta) {
|
|
layer.ssm_beta_in_s = create_tensor(tn(LLM_TENSOR_SSM_BETA, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.nextn.eh_proj_in_s && layer.nextn.eh_proj) {
|
|
layer.nextn.eh_proj_in_s = create_tensor(tn(LLM_TENSOR_NEXTN_EH_PROJ, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
if (!layer.nextn.shared_head_head_in_s && layer.nextn.shared_head_head) {
|
|
layer.nextn.shared_head_head_in_s = create_tensor(tn(LLM_TENSOR_NEXTN_SHARED_HEAD_HEAD, "input_scale", i), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
}
|
|
// output scales
|
|
if (output && output->type == GGML_TYPE_NVFP4) {
|
|
// weight scale
|
|
if (!output_s) {
|
|
output_s = create_tensor(tn(LLM_TENSOR_OUTPUT, "scale"), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
// input scale
|
|
if (!output_in_s) {
|
|
output_in_s = create_tensor(tn(LLM_TENSOR_OUTPUT, "input_scale"), {1}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
}
|
|
}
|
|
ml.done_getting_tensors();
|
|
|
|
// Tied NVFP4 output is valid when no separate LM-head scale tensors are present.
|
|
// If sidecar scales exist, the output weight must be an actual output tensor.
|
|
GGML_ASSERT(!(output && tok_embd &&
|
|
strcmp(output->name, tok_embd->name) == 0 &&
|
|
output->type == GGML_TYPE_NVFP4 &&
|
|
(output_s || output_in_s)));
|
|
// populate tensors_by_name
|
|
for (auto & [_, ctx_ptr] : ml.ctx_map) {
|
|
for (auto * cur = ggml_get_first_tensor(ctx_ptr.get()); cur != NULL; cur = ggml_get_next_tensor(ctx_ptr.get(), cur)) {
|
|
tensors_by_name.emplace_back(ggml_get_name(cur), cur);
|
|
}
|
|
}
|
|
|
|
ml.init_mappings(true, use_mlock ? &pimpl->mlock_mmaps : nullptr);
|
|
pimpl->mappings.reserve(ml.mappings.size());
|
|
|
|
// create the backend buffers
|
|
std::vector<std::pair<ggml_context *, llama_buf_map>> ctx_buf_maps;
|
|
ctx_buf_maps.reserve(ml.ctx_map.size());
|
|
|
|
// Ensure we have enough capacity for the maximum backend buffer we will potentially create
|
|
const size_t n_max_backend_buffer = ml.ctx_map.size() * ml.files.size();
|
|
pimpl->ctxs_bufs.reserve(n_max_backend_buffer);
|
|
|
|
for (auto & [buft, ctx_ptr] : ml.ctx_map) {
|
|
ggml_context * ctx = ctx_ptr.get();
|
|
|
|
// skip contexts without tensors
|
|
if (ggml_get_first_tensor(ctx) == nullptr) {
|
|
continue;
|
|
}
|
|
|
|
llama_buf_map buf_map;
|
|
buf_map.reserve(n_max_backend_buffer);
|
|
|
|
// check if it is possible to use buffer_from_host_ptr with this buffer type
|
|
ggml_backend_dev_t dev = ggml_backend_buft_get_device(buft);
|
|
if (!dev) {
|
|
// FIXME: workaround for CPU backend buft having a NULL device
|
|
dev = ggml_backend_dev_by_type(GGML_BACKEND_DEVICE_TYPE_CPU);
|
|
if (!dev) {
|
|
throw std::runtime_error(format("%s: no CPU backend found", __func__));
|
|
}
|
|
}
|
|
ggml_backend_dev_props props;
|
|
ggml_backend_dev_get_props(dev, &props);
|
|
bool buffer_from_host_ptr_supported = props.caps.buffer_from_host_ptr;
|
|
bool is_default_buft = buft == ggml_backend_dev_buffer_type(dev);
|
|
|
|
std::vector<ggml_backend_buffer_ptr> bufs;
|
|
if (ml.use_mmap && use_mmap_buffer && buffer_from_host_ptr_supported && is_default_buft) {
|
|
GGML_ASSERT(!ml.no_alloc);
|
|
for (uint32_t idx = 0; idx < ml.files.size(); idx++) {
|
|
// only the mmap region containing the tensors in the model is mapped to the backend buffer
|
|
// this is important for metal with apple silicon: if the entire model could be mapped to a metal buffer,
|
|
// then we could just use metal for all layers
|
|
// this allows using partial offloading when the model size exceeds the metal buffer size, but not the RAM size
|
|
void * addr = nullptr;
|
|
size_t first, last; // NOLINT
|
|
ml.get_mapping_range(&first, &last, &addr, idx, ctx);
|
|
if (first >= last) {
|
|
continue;
|
|
}
|
|
const size_t max_size = ggml_get_max_tensor_size(ctx);
|
|
ggml_backend_buffer_t buf = ggml_backend_dev_buffer_from_host_ptr(dev, (char *) addr + first, last - first, max_size);
|
|
if (buf == nullptr) {
|
|
throw std::runtime_error(format("unable to allocate %s buffer", ggml_backend_buft_name(buft)));
|
|
}
|
|
bufs.emplace_back(buf);
|
|
buf_map.emplace(idx, buf);
|
|
}
|
|
} else {
|
|
ggml_backend_buffer_t buf;
|
|
if (ml.no_alloc) {
|
|
buf = ggml_backend_buft_alloc_buffer(buft, /*size =*/ 0); // dummy buffer
|
|
for (ggml_tensor * t = ggml_get_first_tensor(ctx); t != nullptr; t = ggml_get_next_tensor(ctx, t)) {
|
|
t->buffer = buf; // set dummy buffer for weights so that the backend scheduler won't try to allocate them
|
|
}
|
|
} else {
|
|
buf = ggml_backend_alloc_ctx_tensors_from_buft(ctx, buft); // real buffer
|
|
}
|
|
if (buf == nullptr) {
|
|
throw std::runtime_error(format("unable to allocate %s buffer", ggml_backend_buft_name(buft)));
|
|
}
|
|
if (use_mlock && ggml_backend_buffer_is_host(buf)) {
|
|
pimpl->mlock_bufs.emplace_back(new llama_mlock);
|
|
auto & mlock_buf = pimpl->mlock_bufs.back();
|
|
mlock_buf->init (ggml_backend_buffer_get_base(buf));
|
|
mlock_buf->grow_to(ggml_backend_buffer_get_size(buf));
|
|
}
|
|
bufs.emplace_back(buf);
|
|
for (uint32_t idx = 0; idx < ml.files.size(); idx++) {
|
|
buf_map.emplace(idx, buf);
|
|
}
|
|
}
|
|
|
|
for (auto & buf : bufs) {
|
|
// indicate that this buffer contains weights
|
|
// this is used by ggml_backend_sched to improve op scheduling: ops that use a weight are preferably scheduled to the backend that contains the weight
|
|
ggml_backend_buffer_set_usage(buf.get(), GGML_BACKEND_BUFFER_USAGE_WEIGHTS);
|
|
}
|
|
|
|
pimpl->ctxs_bufs.emplace_back(std::move(ctx_ptr), std::move(bufs));
|
|
|
|
ctx_buf_maps.emplace_back(ctx, buf_map);
|
|
}
|
|
|
|
if (llama_supports_gpu_offload()) {
|
|
const int n_gpu = std::min(n_gpu_layers, n_layer_all);
|
|
|
|
int n_repeating = n_gpu;
|
|
if (n_repeating > 0) {
|
|
LLAMA_LOG_INFO("%s: offloading output layer to GPU\n", __func__);
|
|
n_repeating--;
|
|
}
|
|
LLAMA_LOG_INFO("%s: offloading %d repeating layers to GPU\n", __func__, n_repeating);
|
|
|
|
const int max_backend_supported_layers = n_layer_all + 1;
|
|
const int max_offloadable_layers = n_layer_all + 1;
|
|
|
|
LLAMA_LOG_INFO("%s: offloaded %d/%d layers to GPU\n", __func__, std::min(n_gpu_layers, max_offloadable_layers), max_backend_supported_layers);
|
|
}
|
|
|
|
// print memory requirements per buffer type
|
|
for (auto & [_, bufs] : pimpl->ctxs_bufs) {
|
|
for (auto & buf: bufs) {
|
|
LLAMA_LOG_INFO("%s: %12s model buffer size = %8.2f MiB\n",
|
|
__func__, ggml_backend_buffer_name(buf.get()), ggml_backend_buffer_get_size(buf.get()) / 1024.0 / 1024.0);
|
|
}
|
|
}
|
|
|
|
if (ml.no_alloc) {
|
|
return true;
|
|
}
|
|
|
|
// load tensor data
|
|
for (auto & [ctx, buf_map] : ctx_buf_maps) {
|
|
if (!ml.load_all_data(ctx, buf_map, use_mlock ? &pimpl->mlock_mmaps : NULL, params.progress_callback, params.progress_callback_user_data)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (use_mmap_buffer) {
|
|
for (auto & mapping : ml.mappings) {
|
|
pimpl->mappings.emplace_back(std::move(mapping));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ggml_tensor * llama_model_base::create_tensor(llama_model_loader & ml, const LLM_TN_IMPL & tn, const std::initializer_list<int64_t> & ne, int flags) {
|
|
const buft_list_t * buft_list_layer = tn.bid == -1 ? nullptr : pimpl->dev_layer.at(tn.bid).buft_list;
|
|
return ml.create_tensor(
|
|
hparams, &pimpl->cpu_buft_list, pimpl->dev_input.buft_list, pimpl->dev_output.buft_list, buft_list_layer,
|
|
tn, ne, flags);
|
|
}
|
|
|
|
std::string llama_model::arch_name() const {
|
|
return llm_arch_name(arch);
|
|
}
|
|
|
|
std::string llama_model::type_name() const {
|
|
return llm_type_name(type);
|
|
}
|
|
|
|
std::string llama_model::desc() const {
|
|
return pimpl->desc_str;
|
|
}
|
|
|
|
llama_ftype llama_model::ftype() const {
|
|
return pimpl->ftype;
|
|
}
|
|
|
|
size_t llama_model::size() const {
|
|
return pimpl->n_bytes;
|
|
}
|
|
|
|
size_t llama_model::n_tensors() const {
|
|
return tensors_by_name.size();
|
|
}
|
|
|
|
size_t llama_model::n_devices() const {
|
|
return devices.size();
|
|
}
|
|
|
|
const float * llama_model::tensor_split() const {
|
|
return params.tensor_split;
|
|
}
|
|
|
|
uint32_t llama_model::n_gpu_layers() const {
|
|
// note: plus 1 for the "output" layer
|
|
return params.n_gpu_layers >= 0 ? params.n_gpu_layers : hparams.n_layer_all + 1;
|
|
}
|
|
|
|
llama_split_mode llama_model::split_mode() const {
|
|
return params.split_mode;
|
|
}
|
|
|
|
std::map<ggml_backend_buffer_type_t, size_t> llama_model::memory_breakdown() const {
|
|
std::map<ggml_backend_buffer_type_t, size_t> ret;
|
|
for (const auto & [ctx, bufs] : pimpl->ctxs_bufs) {
|
|
if (hparams.no_alloc) {
|
|
GGML_ASSERT(bufs.size() == 1);
|
|
ggml_backend_buffer_t buf = bufs[0].get();
|
|
GGML_ASSERT(ggml_backend_buffer_get_base(buf) == nullptr);
|
|
ggml_backend_buffer_type_t buft = ggml_backend_buffer_get_type(buf);
|
|
ret[buft] += ggml_backend_alloc_ctx_tensors_from_buft_size(ctx.get(), buft);
|
|
} else {
|
|
for (const auto & buf : bufs) {
|
|
// GGML_ASSERT(ggml_backend_buffer_get_base(buf.get()) != nullptr); // multi_buffer does not have a defined base
|
|
ret[ggml_backend_buffer_get_type(buf.get())] += ggml_backend_buffer_get_size(buf.get());
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
uint64_t llama_model::n_elements() const {
|
|
return pimpl->n_elements;
|
|
}
|
|
|
|
void llama_model::print_info() const {
|
|
const std::string rope_scaling_type = llama_rope_scaling_type_name(hparams.rope_scaling_type_train);
|
|
|
|
auto print_f = [](const std::function<int32_t(uint32_t)> & f, uint32_t n) {
|
|
bool is_var = false;
|
|
|
|
std::vector<int32_t> v;
|
|
for (uint32_t i = 0; i < n; ++i) {
|
|
v.push_back(f(i));
|
|
if (v[i] != v[0]) {
|
|
is_var = true;
|
|
}
|
|
}
|
|
|
|
std::stringstream ss;
|
|
|
|
if (is_var) {
|
|
ss << "[";
|
|
for (uint32_t i = 0; i < n; ++i) {
|
|
ss << v[i];
|
|
if (i < n - 1) {
|
|
ss << ", ";
|
|
}
|
|
}
|
|
ss << "]";
|
|
} else {
|
|
ss << v[0];
|
|
}
|
|
|
|
return ss.str();
|
|
};
|
|
|
|
// hparams
|
|
LLAMA_LOG_INFO("%s: arch = %s\n", __func__, arch_name().c_str());
|
|
LLAMA_LOG_INFO("%s: vocab_only = %d\n", __func__, hparams.vocab_only);
|
|
LLAMA_LOG_INFO("%s: no_alloc = %d\n", __func__, hparams.no_alloc);
|
|
|
|
if (!hparams.vocab_only) {
|
|
LLAMA_LOG_INFO("%s: n_ctx_train = %u\n", __func__, hparams.n_ctx_train);
|
|
LLAMA_LOG_INFO("%s: n_embd_inp = %u\n", __func__, hparams.n_embd_inp());
|
|
LLAMA_LOG_INFO("%s: n_embd = %u\n", __func__, hparams.n_embd);
|
|
LLAMA_LOG_INFO("%s: n_embd_out = %u\n", __func__, hparams.n_embd_out());
|
|
LLAMA_LOG_INFO("%s: n_layer = %u\n", __func__, hparams.n_layer());
|
|
LLAMA_LOG_INFO("%s: n_layer_all = %u\n", __func__, hparams.n_layer_all);
|
|
LLAMA_LOG_INFO("%s: n_head = %s\n", __func__, print_f([&](uint32_t il) { return hparams.n_head(il); }, hparams.n_layer_all).c_str());
|
|
LLAMA_LOG_INFO("%s: n_head_kv = %s\n", __func__, print_f([&](uint32_t il) { return hparams.n_head_kv(il); }, hparams.n_layer_all).c_str());
|
|
LLAMA_LOG_INFO("%s: n_rot = %u\n", __func__, hparams.n_rot_full);
|
|
LLAMA_LOG_INFO("%s: n_swa = %u\n", __func__, hparams.n_swa);
|
|
LLAMA_LOG_INFO("%s: is_swa_any = %u\n", __func__, hparams.is_swa_any());
|
|
LLAMA_LOG_INFO("%s: n_embd_head_k = %u\n", __func__, hparams.n_embd_head_k_full);
|
|
LLAMA_LOG_INFO("%s: n_embd_head_v = %u\n", __func__, hparams.n_embd_head_v_full);
|
|
LLAMA_LOG_INFO("%s: n_gqa = %s\n", __func__, print_f([&](uint32_t il) { return hparams.n_gqa(il); }, hparams.n_layer_all).c_str());
|
|
LLAMA_LOG_INFO("%s: n_embd_k_gqa = %s\n", __func__, print_f([&](uint32_t il) { return hparams.n_embd_k_gqa(il); }, hparams.n_layer_all).c_str());
|
|
LLAMA_LOG_INFO("%s: n_embd_v_gqa = %s\n", __func__, print_f([&](uint32_t il) { return hparams.n_embd_v_gqa(il); }, hparams.n_layer_all).c_str());
|
|
LLAMA_LOG_INFO("%s: f_norm_eps = %.1e\n", __func__, hparams.f_norm_eps);
|
|
LLAMA_LOG_INFO("%s: f_norm_rms_eps = %.1e\n", __func__, hparams.f_norm_rms_eps);
|
|
LLAMA_LOG_INFO("%s: f_clamp_kqv = %.1e\n", __func__, hparams.f_clamp_kqv);
|
|
LLAMA_LOG_INFO("%s: f_max_alibi_bias = %.1e\n", __func__, hparams.f_max_alibi_bias);
|
|
LLAMA_LOG_INFO("%s: f_logit_scale = %.1e\n", __func__, hparams.f_logit_scale);
|
|
LLAMA_LOG_INFO("%s: f_attn_scale = %.1e\n", __func__, hparams.f_attention_scale);
|
|
LLAMA_LOG_INFO("%s: f_attn_value_scale = %.4f\n", __func__, hparams.f_attn_value_scale);
|
|
LLAMA_LOG_INFO("%s: n_ff = %s\n", __func__, print_f([&](uint32_t il) { return hparams.n_ff(il); }, hparams.n_layer_all).c_str());
|
|
LLAMA_LOG_INFO("%s: n_expert = %u\n", __func__, hparams.n_expert);
|
|
LLAMA_LOG_INFO("%s: n_expert_used = %u\n", __func__, hparams.n_expert_used);
|
|
LLAMA_LOG_INFO("%s: n_expert_groups = %d\n", __func__, hparams.n_expert_groups);
|
|
LLAMA_LOG_INFO("%s: n_group_used = %d\n", __func__, hparams.n_group_used);
|
|
LLAMA_LOG_INFO("%s: causal attn = %d\n", __func__, hparams.causal_attn);
|
|
LLAMA_LOG_INFO("%s: pooling type = %d\n", __func__, hparams.pooling_type);
|
|
LLAMA_LOG_INFO("%s: rope type = %d\n", __func__, hparams.rope_type);
|
|
LLAMA_LOG_INFO("%s: rope scaling = %s\n", __func__, rope_scaling_type.c_str());
|
|
LLAMA_LOG_INFO("%s: freq_base_train = %.1f\n", __func__, hparams.rope_freq_base_train);
|
|
LLAMA_LOG_INFO("%s: freq_scale_train = %g\n", __func__, hparams.rope_freq_scale_train);
|
|
if (hparams.swa_type != LLAMA_SWA_TYPE_NONE) {
|
|
LLAMA_LOG_INFO("%s: freq_base_swa = %.1f\n", __func__, hparams.rope_freq_base_train_swa);
|
|
LLAMA_LOG_INFO("%s: freq_scale_swa = %g\n", __func__, hparams.rope_freq_scale_train_swa);
|
|
LLAMA_LOG_INFO("%s: n_embd_head_k_swa = %u\n", __func__, hparams.n_embd_head_k_swa);
|
|
LLAMA_LOG_INFO("%s: n_embd_head_v_swa = %u\n", __func__, hparams.n_embd_head_v_swa);
|
|
LLAMA_LOG_INFO("%s: n_rot_swa = %u\n", __func__, hparams.n_rot_swa);
|
|
}
|
|
LLAMA_LOG_INFO("%s: n_ctx_orig_yarn = %u\n", __func__, hparams.n_ctx_orig_yarn);
|
|
LLAMA_LOG_INFO("%s: rope_yarn_log_mul = %.4f\n", __func__, hparams.rope_yarn_log_mul);
|
|
LLAMA_LOG_INFO("%s: rope_finetuned = %s\n", __func__, hparams.rope_finetuned ? "yes" : "unknown");
|
|
if (arch == LLM_ARCH_GRANITE &&
|
|
std::any_of(hparams.deepstack_mapping_arr.begin(),
|
|
hparams.deepstack_mapping_arr.end(),
|
|
[](const auto & entry) { return entry >= 0; })) {
|
|
LLAMA_LOG_INFO("%s: deepstack_mapping_arr = %s\n", __func__,
|
|
print_f([&](uint32_t il) { return hparams.deepstack_mapping_arr[il]; },
|
|
hparams.n_layer_all).c_str());
|
|
}
|
|
// MRoPE (Multi-axis Rotary Position Embedding) sections
|
|
if (const auto & s = hparams.rope_sections; s[0] || s[1] || s[2] || s[3]) {
|
|
LLAMA_LOG_INFO("%s: mrope sections = [%d, %d, %d, %d]\n", __func__, s[0], s[1], s[2], s[3]);
|
|
}
|
|
if (!classifier_labels.empty()) {
|
|
LLAMA_LOG_INFO("%s: n_cls_out = %u\n", __func__, hparams.n_cls_out);
|
|
|
|
size_t i = 0;
|
|
for (const auto & label : classifier_labels) {
|
|
LLAMA_LOG_INFO("%s: cls_label[%2zu] = %s\n", __func__, i++, label.c_str());
|
|
}
|
|
}
|
|
|
|
if (arch == LLM_ARCH_MAMBA ||
|
|
arch == LLM_ARCH_MAMBA2 ||
|
|
arch == LLM_ARCH_JAMBA ||
|
|
arch == LLM_ARCH_FALCON_H1 ||
|
|
arch == LLM_ARCH_PLAMO2 ||
|
|
arch == LLM_ARCH_GRANITE_HYBRID ||
|
|
arch == LLM_ARCH_QWEN3NEXT ||
|
|
arch == LLM_ARCH_QWEN35 ||
|
|
arch == LLM_ARCH_QWEN35MOE ||
|
|
arch == LLM_ARCH_NEMOTRON_H ||
|
|
arch == LLM_ARCH_NEMOTRON_H_MOE) {
|
|
LLAMA_LOG_INFO("%s: ssm_d_conv = %u\n", __func__, hparams.ssm_d_conv);
|
|
LLAMA_LOG_INFO("%s: ssm_d_inner = %u\n", __func__, hparams.ssm_d_inner);
|
|
LLAMA_LOG_INFO("%s: ssm_d_state = %u\n", __func__, hparams.ssm_d_state);
|
|
LLAMA_LOG_INFO("%s: ssm_dt_rank = %u\n", __func__, hparams.ssm_dt_rank);
|
|
LLAMA_LOG_INFO("%s: ssm_n_group = %u\n", __func__, hparams.ssm_n_group);
|
|
LLAMA_LOG_INFO("%s: ssm_dt_b_c_rms = %d\n", __func__, hparams.ssm_dt_b_c_rms);
|
|
}
|
|
|
|
LLAMA_LOG_INFO("%s: model type = %s\n", __func__, type_name().c_str());
|
|
if (pimpl->n_elements >= 1e12) {
|
|
LLAMA_LOG_INFO("%s: model params = %.2f T\n", __func__, pimpl->n_elements*1e-12);
|
|
} else if (pimpl->n_elements >= 1e9) {
|
|
LLAMA_LOG_INFO("%s: model params = %.2f B\n", __func__, pimpl->n_elements*1e-9);
|
|
} else if (pimpl->n_elements >= 1e6) {
|
|
LLAMA_LOG_INFO("%s: model params = %.2f M\n", __func__, pimpl->n_elements*1e-6);
|
|
} else {
|
|
LLAMA_LOG_INFO("%s: model params = %.2f K\n", __func__, pimpl->n_elements*1e-3);
|
|
}
|
|
|
|
// general kv
|
|
LLAMA_LOG_INFO("%s: general.name = %s\n", __func__, name.c_str());
|
|
|
|
if (arch == LLM_ARCH_DEEPSEEK) {
|
|
LLAMA_LOG_INFO("%s: n_layer_dense_lead = %d\n", __func__, hparams.n_layer_dense_lead);
|
|
LLAMA_LOG_INFO("%s: n_ff_exp = %d\n", __func__, hparams.n_ff_exp);
|
|
LLAMA_LOG_INFO("%s: n_expert_shared = %d\n", __func__, hparams.n_expert_shared);
|
|
LLAMA_LOG_INFO("%s: expert_weights_scale = %.1f\n", __func__, hparams.expert_weights_scale);
|
|
}
|
|
|
|
if (arch == LLM_ARCH_DEEPSEEK2 || arch == LLM_ARCH_DEEPSEEK2OCR ||
|
|
arch == LLM_ARCH_DEEPSEEK32 || arch == LLM_ARCH_GLM_DSA ||
|
|
arch == LLM_ARCH_DOTS3NOTE || arch == LLM_ARCH_MISTRAL4) {
|
|
LLAMA_LOG_INFO("%s: n_layer_dense_lead = %d\n", __func__, hparams.n_layer_dense_lead);
|
|
LLAMA_LOG_INFO("%s: n_lora_q = %d\n", __func__, hparams.n_lora_q);
|
|
LLAMA_LOG_INFO("%s: n_lora_kv = %d\n", __func__, hparams.n_lora_kv);
|
|
LLAMA_LOG_INFO("%s: n_embd_head_k_mla = %d\n", __func__, hparams.n_embd_head_k_mla());
|
|
LLAMA_LOG_INFO("%s: n_embd_head_v_mla = %d\n", __func__, hparams.n_embd_head_v_mla());
|
|
LLAMA_LOG_INFO("%s: n_ff_exp = %d\n", __func__, hparams.n_ff_exp);
|
|
LLAMA_LOG_INFO("%s: n_expert_shared = %d\n", __func__, hparams.n_expert_shared);
|
|
LLAMA_LOG_INFO("%s: expert_weights_scale = %.1f\n", __func__, hparams.expert_weights_scale);
|
|
LLAMA_LOG_INFO("%s: expert_weights_norm = %d\n", __func__, hparams.expert_weights_norm);
|
|
LLAMA_LOG_INFO("%s: expert_gating_func = %s\n", __func__, llama_expert_gating_func_name((llama_expert_gating_func_type) hparams.expert_gating_func));
|
|
}
|
|
|
|
if (arch == LLM_ARCH_QWEN2MOE) {
|
|
LLAMA_LOG_INFO("%s: n_ff_exp = %d\n", __func__, hparams.n_ff_exp);
|
|
LLAMA_LOG_INFO("%s: n_ff_shexp = %d\n", __func__, hparams.n_ff_shexp);
|
|
}
|
|
|
|
if (arch == LLM_ARCH_MELLUM ||
|
|
arch == LLM_ARCH_COHERE2MOE ||
|
|
arch == LLM_ARCH_QWEN3MOE ||
|
|
arch == LLM_ARCH_OPENAI_MOE ||
|
|
arch == LLM_ARCH_QWEN3VLMOE ||
|
|
arch == LLM_ARCH_RND1) {
|
|
LLAMA_LOG_INFO("%s: n_ff_exp = %d\n", __func__, hparams.n_ff_exp);
|
|
}
|
|
|
|
if (arch == LLM_ARCH_MINICPM ||
|
|
arch == LLM_ARCH_GRANITE ||
|
|
arch == LLM_ARCH_GRANITE_MOE ||
|
|
arch == LLM_ARCH_GRANITE_HYBRID ||
|
|
arch == LLM_ARCH_GRANITE_SWITCH ||
|
|
arch == LLM_ARCH_NEMOTRON_H_MOE) {
|
|
LLAMA_LOG_INFO("%s: f_embedding_scale = %f\n", __func__, hparams.f_embedding_scale);
|
|
LLAMA_LOG_INFO("%s: f_residual_scale = %f\n", __func__, hparams.f_residual_scale);
|
|
LLAMA_LOG_INFO("%s: f_attention_scale = %f\n", __func__, hparams.f_attention_scale);
|
|
LLAMA_LOG_INFO("%s: n_ff_shexp = %d\n", __func__, hparams.n_ff_shexp);
|
|
}
|
|
|
|
if (arch == LLM_ARCH_BAILINGMOE) {
|
|
LLAMA_LOG_INFO("%s: n_layer_dense_lead = %d\n", __func__, hparams.n_layer_dense_lead);
|
|
LLAMA_LOG_INFO("%s: n_ff_exp = %d\n", __func__, hparams.n_ff_exp);
|
|
LLAMA_LOG_INFO("%s: n_expert_shared = %d\n", __func__, hparams.n_expert_shared);
|
|
LLAMA_LOG_INFO("%s: expert_weights_scale = %.1f\n", __func__, hparams.expert_weights_scale);
|
|
LLAMA_LOG_INFO("%s: expert_weights_norm = %d\n", __func__, hparams.expert_weights_norm);
|
|
}
|
|
|
|
if (arch == LLM_ARCH_BAILINGMOE2 || arch == LLM_ARCH_BAILINGMOE3) {
|
|
LLAMA_LOG_INFO("%s: n_layer_dense_lead = %d\n", __func__, hparams.n_layer_dense_lead);
|
|
LLAMA_LOG_INFO("%s: n_ff_exp = %d\n", __func__, hparams.n_ff_exp);
|
|
LLAMA_LOG_INFO("%s: n_ff_shexp = %d\n", __func__, hparams.n_ff_shexp);
|
|
LLAMA_LOG_INFO("%s: n_expert_shared = %d\n", __func__, hparams.n_expert_shared);
|
|
LLAMA_LOG_INFO("%s: expert_weights_scale = %.1f\n", __func__, hparams.expert_weights_scale);
|
|
LLAMA_LOG_INFO("%s: expert_weights_norm = %d\n", __func__, hparams.expert_weights_norm);
|
|
LLAMA_LOG_INFO("%s: expert_gating_func = %s\n", __func__, llama_expert_gating_func_name((llama_expert_gating_func_type) hparams.expert_gating_func));
|
|
LLAMA_LOG_INFO("%s: n_layer_nextn = %d\n", __func__, hparams.n_layer_nextn);
|
|
}
|
|
|
|
if (arch == LLM_ARCH_SMALLTHINKER || arch == LLM_ARCH_LFM2MOE) {
|
|
LLAMA_LOG_INFO("%s: n_ff_exp = %d\n", __func__, hparams.n_ff_exp);
|
|
LLAMA_LOG_INFO("%s: expert_gating_func = %s\n", __func__, llama_expert_gating_func_name((llama_expert_gating_func_type) hparams.expert_gating_func));
|
|
}
|
|
|
|
if (arch == LLM_ARCH_GROVEMOE) {
|
|
LLAMA_LOG_INFO("%s: n_ff_exp = %d\n", __func__, hparams.n_ff_exp);
|
|
LLAMA_LOG_INFO("%s: n_ff_chexp = %d\n", __func__, hparams.n_ff_chexp);
|
|
LLAMA_LOG_INFO("%s: n_group_experts = %d\n", __func__, hparams.n_group_experts);
|
|
LLAMA_LOG_INFO("%s: expert_group_scale = %.2f\n", __func__, hparams.expert_group_scale);
|
|
}
|
|
}
|
|
|
|
vocab.print_info();
|
|
}
|
|
|
|
ggml_backend_dev_t llama_model::dev_layer(int il) const {
|
|
return pimpl->dev_layer.at(il).dev;
|
|
}
|
|
|
|
ggml_backend_dev_t llama_model::dev_output() const {
|
|
return pimpl->dev_output.dev;
|
|
}
|
|
|
|
template<typename F>
|
|
static bool buft_supported(ggml_backend_buffer_type_t buft, ggml_backend_dev_t dev, F & fn) {
|
|
ggml_init_params params = {
|
|
/*.mem_size =*/ ggml_tensor_overhead()*8,
|
|
/*.mem_buffer =*/ NULL,
|
|
/*.no_alloc =*/ true,
|
|
};
|
|
|
|
ggml_context_ptr ctx { ggml_init(params) };
|
|
if (!ctx) {
|
|
throw std::runtime_error(format("failed to create ggml context"));
|
|
}
|
|
|
|
ggml_backend_buffer_ptr buf { ggml_backend_buft_alloc_buffer(buft, 0) };
|
|
ggml_tensor * op_tensor = fn(ctx.get());
|
|
for (int i = 0; i < GGML_MAX_SRC; i++) {
|
|
if (op_tensor->src[i] != nullptr) {
|
|
assert(op_tensor->src[i]->buffer == nullptr);
|
|
op_tensor->src[i]->buffer = buf.get();
|
|
}
|
|
}
|
|
|
|
bool op_supported = ggml_backend_dev_supports_op(dev, op_tensor);
|
|
|
|
return op_supported;
|
|
}
|
|
|
|
template<typename F>
|
|
static ggml_backend_buffer_type_t select_buft(const buft_list_t & buft_list, const F & fn) {
|
|
for (const auto & cur : buft_list) {
|
|
ggml_backend_dev_t cur_dev = cur.first;
|
|
ggml_backend_buffer_type_t cur_buft = cur.second;
|
|
if (buft_supported(cur_buft, cur_dev, fn)) {
|
|
return cur_buft;
|
|
}
|
|
}
|
|
|
|
throw std::runtime_error(format("no suitable buffer type found"));
|
|
}
|
|
|
|
ggml_backend_buffer_type_t llama_model::select_buft(int il) const {
|
|
return ::select_buft(
|
|
*pimpl->dev_layer.at(il).buft_list,
|
|
[&](ggml_context * ctx) {
|
|
ggml_tensor * cur = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, hparams.n_embd);
|
|
ggml_tensor * layer_dir = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, hparams.n_embd);
|
|
return ggml_add(ctx, cur, layer_dir);
|
|
});
|
|
}
|
|
|
|
bool llama_model::has_tensor_overrides() const {
|
|
return pimpl->has_tensor_overrides;
|
|
}
|
|
|
|
const ggml_tensor * llama_model::get_tensor(const char * name) const {
|
|
auto it = std::find_if(tensors_by_name.begin(), tensors_by_name.end(),
|
|
[name](const std::pair<std::string, ggml_tensor *> & it) {
|
|
return it.first == name;
|
|
});
|
|
if (it == tensors_by_name.end()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return it->second;
|
|
}
|
|
|
|
float llama_model::get_rope_freq_base (const llama_cparams & cparams, int il) const {
|
|
return hparams.is_swa(il) ? hparams.rope_freq_base_train_swa : cparams.rope_freq_base;
|
|
}
|
|
|
|
float llama_model::get_rope_freq_scale(const llama_cparams & cparams, int il) const {
|
|
return hparams.is_swa(il) ? hparams.rope_freq_scale_train_swa : cparams.rope_freq_scale;
|
|
}
|
|
|
|
ggml_tensor * llama_model::get_rope_factors(const llama_cparams & cparams, int il) const {
|
|
const uint32_t n_ctx_seq = cparams.n_ctx_seq;
|
|
|
|
// choose long/short freq factors based on the context size
|
|
if (layers[il].rope_freqs != nullptr) {
|
|
return layers[il].rope_freqs;
|
|
}
|
|
|
|
if (n_ctx_seq > hparams.n_ctx_orig_yarn) {
|
|
return layers[il].rope_long;
|
|
}
|
|
|
|
return layers[il].rope_short;
|
|
}
|
|
|
|
llama_memory_i * llama_model::create_memory(const llama_memory_params & params, const llama_cparams & cparams) const {
|
|
llama_memory_i * res;
|
|
|
|
switch (arch) {
|
|
// Models that need specific instantiation should be handled in the
|
|
// switch statement
|
|
case LLM_ARCH_BERT:
|
|
case LLM_ARCH_JINA_BERT_V2:
|
|
case LLM_ARCH_JINA_BERT_V3:
|
|
case LLM_ARCH_NOMIC_BERT:
|
|
case LLM_ARCH_NOMIC_BERT_MOE:
|
|
case LLM_ARCH_NEO_BERT:
|
|
case LLM_ARCH_EUROBERT:
|
|
case LLM_ARCH_WAVTOKENIZER_DEC:
|
|
case LLM_ARCH_MODERN_BERT:
|
|
case LLM_ARCH_GEMMA_EMBEDDING:
|
|
case LLM_ARCH_DREAM:
|
|
case LLM_ARCH_LLADA:
|
|
case LLM_ARCH_LLADA_MOE:
|
|
case LLM_ARCH_RND1:
|
|
{
|
|
res = nullptr;
|
|
} break;
|
|
case LLM_ARCH_MINIMAX_M3:
|
|
{
|
|
// sparse (MSA) layers carry an indexer key cache, but leading dense layers do not
|
|
llama_kv_cache::layer_filter_cb filter_idx =
|
|
[&](int32_t il) { return (uint32_t) il >= hparams.n_layer_dense_lead; };
|
|
|
|
res = new llama_kv_cache_msa(
|
|
*this,
|
|
params.type_k,
|
|
params.type_v,
|
|
!cparams.flash_attn,
|
|
cparams.offload_kqv,
|
|
cparams.kv_unified,
|
|
cparams.n_ctx_seq,
|
|
cparams.n_seq_max,
|
|
1,
|
|
hparams.n_swa,
|
|
hparams.swa_type,
|
|
nullptr,
|
|
filter_idx,
|
|
nullptr);
|
|
} break;
|
|
case LLM_ARCH_GLM_DSA:
|
|
case LLM_ARCH_DEEPSEEK32:
|
|
{
|
|
if (params.ctx_type == LLAMA_CONTEXT_TYPE_MTP && hparams.n_layer_nextn > 0) {
|
|
// The NextN/MTP draft head runs dense MLA (no DSA indexer), so the
|
|
// MTP context uses a plain attention KV cache holding only the
|
|
// nextn layer(s) - same pattern as the hybrid Qwen3.5 MTP context.
|
|
llama_kv_cache::layer_filter_cb filter =
|
|
[&](uint32_t il) { return il >= hparams.n_layer(); };
|
|
|
|
res = new llama_kv_cache(
|
|
*this,
|
|
hparams,
|
|
params.type_k,
|
|
params.type_v,
|
|
!cparams.flash_attn,
|
|
cparams.offload_kqv,
|
|
cparams.kv_unified,
|
|
cparams.n_ctx_seq,
|
|
cparams.n_seq_max,
|
|
1,
|
|
hparams.n_swa,
|
|
hparams.swa_type,
|
|
nullptr,
|
|
filter,
|
|
nullptr,
|
|
nullptr);
|
|
} else {
|
|
// Main context: DSA cache for the trunk layers only - the nextn
|
|
// layer(s) are never attended by the trunk graph.
|
|
llama_kv_cache::layer_filter_cb filter_mla = nullptr;
|
|
if (hparams.n_layer_nextn > 0) {
|
|
filter_mla = [&](uint32_t il) { return il < hparams.n_layer(); };
|
|
}
|
|
llama_kv_cache::layer_filter_cb filter_lid = [&](uint32_t il) { return il < hparams.n_layer() && (arch != LLM_ARCH_GLM_DSA || hparams.is_indexer_full(il)); };
|
|
|
|
res = new llama_kv_cache_dsa(
|
|
*this,
|
|
params.type_k,
|
|
params.type_v,
|
|
!cparams.flash_attn,
|
|
cparams.offload_kqv,
|
|
cparams.kv_unified,
|
|
cparams.n_ctx_seq,
|
|
cparams.n_seq_max,
|
|
1,
|
|
hparams.n_swa,
|
|
hparams.swa_type,
|
|
filter_mla,
|
|
filter_lid,
|
|
nullptr);
|
|
}
|
|
} break;
|
|
case LLM_ARCH_DOTS3NOTE:
|
|
{
|
|
GGML_ASSERT(hparams.swa_type != LLAMA_SWA_TYPE_NONE);
|
|
|
|
if (params.ctx_type == LLAMA_CONTEXT_TYPE_MTP && hparams.n_layer_nextn > 0) {
|
|
// MTP draft context: plain attention KV cache holding only the nextn layer
|
|
llama_kv_cache::layer_filter_cb filter =
|
|
[&](uint32_t il) { return il >= hparams.n_layer(); };
|
|
|
|
res = new llama_kv_cache(
|
|
*this,
|
|
hparams,
|
|
params.type_k,
|
|
params.type_v,
|
|
!cparams.flash_attn,
|
|
cparams.offload_kqv,
|
|
cparams.kv_unified,
|
|
cparams.n_ctx_seq,
|
|
cparams.n_seq_max,
|
|
1,
|
|
hparams.n_swa,
|
|
hparams.swa_type,
|
|
nullptr,
|
|
filter,
|
|
nullptr,
|
|
nullptr);
|
|
} else {
|
|
// main context: DSA cache for the trunk full-attention layers plus a window-sized SWA cache
|
|
llama_kv_cache::layer_filter_cb filter_mla = nullptr;
|
|
if (hparams.n_layer_nextn > 0) {
|
|
filter_mla = [&](uint32_t il) { return il < hparams.n_layer(); };
|
|
}
|
|
llama_kv_cache::layer_filter_cb filter_lid = [&](uint32_t il) { return il < hparams.n_layer() && hparams.is_indexer_full(il); };
|
|
|
|
res = new llama_kv_cache_dsa_iswa(
|
|
*this,
|
|
params.type_k,
|
|
params.type_v,
|
|
!cparams.flash_attn,
|
|
cparams.offload_kqv,
|
|
params.swa_full,
|
|
cparams.kv_unified,
|
|
cparams.n_ctx_seq,
|
|
cparams.n_seq_max,
|
|
cparams.n_ubatch,
|
|
1,
|
|
filter_mla,
|
|
filter_lid,
|
|
nullptr);
|
|
}
|
|
} break;
|
|
case LLM_ARCH_DEEPSEEK4:
|
|
{
|
|
GGML_ASSERT(hparams.swa_type != LLAMA_SWA_TYPE_NONE);
|
|
|
|
if (params.ctx_type == LLAMA_CONTEXT_TYPE_MTP) {
|
|
const llama_memory_i::layer_filter_cb filter_mtp = [&](int32_t il) {
|
|
return il >= (int32_t) hparams.n_layer();
|
|
};
|
|
|
|
res = new llama_kv_cache_iswa(
|
|
*this,
|
|
params.type_k,
|
|
params.type_v,
|
|
!cparams.flash_attn,
|
|
cparams.offload_kqv,
|
|
params.swa_full,
|
|
cparams.kv_unified,
|
|
cparams.n_ctx_seq,
|
|
cparams.n_seq_max,
|
|
cparams.n_ubatch,
|
|
1,
|
|
nullptr,
|
|
filter_mtp,
|
|
nullptr,
|
|
nullptr);
|
|
} else {
|
|
res = new llama_kv_cache_dsv4(
|
|
*this,
|
|
params.type_k,
|
|
params.type_v,
|
|
!cparams.flash_attn,
|
|
cparams.offload_kqv,
|
|
params.swa_full,
|
|
cparams.kv_unified,
|
|
cparams.n_ctx_seq,
|
|
cparams.n_seq_max,
|
|
cparams.n_ubatch,
|
|
1,
|
|
cparams.n_rs_seq,
|
|
nullptr,
|
|
nullptr);
|
|
}
|
|
} break;
|
|
case LLM_ARCH_DFLASH:
|
|
{
|
|
// DSV4 DSpark stages store a single MLA-style K per position (window = the draft ring)
|
|
if (hparams.dsv4_hc_mult > 0) {
|
|
GGML_ASSERT(hparams.swa_type != LLAMA_SWA_TYPE_NONE);
|
|
|
|
res = new llama_kv_cache_iswa(
|
|
*this,
|
|
params.type_k,
|
|
params.type_v,
|
|
!cparams.flash_attn,
|
|
cparams.offload_kqv,
|
|
params.swa_full,
|
|
cparams.kv_unified,
|
|
cparams.n_ctx_seq,
|
|
cparams.n_seq_max,
|
|
cparams.n_ubatch,
|
|
1,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr,
|
|
nullptr);
|
|
break;
|
|
}
|
|
}
|
|
[[fallthrough]];
|
|
// Models that need standard caching should rely on recurrent/hybrid
|
|
// checks
|
|
default:
|
|
{
|
|
// Dense MTP heads use a plain attention KV cache instead of the hybrid wrapper.
|
|
const bool mtp_on_hybrid_qwen =
|
|
params.ctx_type == LLAMA_CONTEXT_TYPE_MTP &&
|
|
(arch == LLM_ARCH_QWEN3NEXT || arch == LLM_ARCH_QWEN35 || arch == LLM_ARCH_QWEN35MOE ||
|
|
arch == LLM_ARCH_BAILINGMOE3);
|
|
|
|
const bool mtp_on_hybrid_nemotron =
|
|
params.ctx_type == LLAMA_CONTEXT_TYPE_MTP && arch == LLM_ARCH_NEMOTRON_H_MOE;
|
|
|
|
if (llm_arch_is_recurrent(arch)) {
|
|
res = new llama_memory_recurrent(
|
|
*this,
|
|
GGML_TYPE_F32,
|
|
GGML_TYPE_F32,
|
|
cparams.offload_kqv,
|
|
std::max((uint32_t) 1, cparams.n_seq_max),
|
|
cparams.n_seq_max,
|
|
cparams.n_rs_seq,
|
|
nullptr);
|
|
} else if (llm_arch_is_hybrid(arch) && !mtp_on_hybrid_qwen && !mtp_on_hybrid_nemotron) {
|
|
// The main difference between hybrid architectures is the
|
|
// layer filters, so pick the right one here
|
|
llama_memory_hybrid::layer_filter_cb filter_attn = nullptr;
|
|
llama_memory_hybrid::layer_filter_cb filter_recr = nullptr;
|
|
// only the sparse-attention architectures use llama_memory_hybrid_idx
|
|
// a null filter_idx means the GGUF has no indexer tensors
|
|
llama_memory_hybrid::layer_filter_cb filter_idx = nullptr;
|
|
const bool needs_mem_idx = (arch == LLM_ARCH_QWEN4EXP);
|
|
if (arch == LLM_ARCH_FALCON_H1) {
|
|
filter_attn = [&](uint32_t) { return true; };
|
|
filter_recr = [&](uint32_t) { return true; };
|
|
} else if (arch == LLM_ARCH_NEMOTRON_H || arch == LLM_ARCH_NEMOTRON_H_MOE) {
|
|
filter_attn = [&](uint32_t il) {
|
|
return !hparams.is_recr(il) && hparams.n_ff(il) == 0;
|
|
};
|
|
filter_recr = [&](uint32_t il) {
|
|
return hparams.is_recr(il) && hparams.n_ff(il) == 0;
|
|
};
|
|
} else if (arch == LLM_ARCH_QWEN3NEXT || arch == LLM_ARCH_QWEN35 || arch == LLM_ARCH_QWEN35MOE || arch == LLM_ARCH_QWEN4EXP || arch == LLM_ARCH_MINIMAX_01) {
|
|
filter_attn = [&](uint32_t il) {
|
|
return il < hparams.n_layer() && !hparams.is_recr(il);
|
|
};
|
|
filter_recr = [&](uint32_t il) {
|
|
return il < hparams.n_layer() && hparams.is_recr(il);
|
|
};
|
|
|
|
if (arch == LLM_ARCH_QWEN4EXP && hparams.indexer_head_size > 0) {
|
|
// QSA runs on the dense-attention layers only
|
|
filter_idx = [&](uint32_t il) {
|
|
return il < hparams.n_layer() && !hparams.is_recr(il);
|
|
};
|
|
}
|
|
}
|
|
|
|
if (hparams.swa_type != LLAMA_SWA_TYPE_NONE) {
|
|
// Use hybrid-iswa for hybrid models with SWA
|
|
res = new llama_memory_hybrid_iswa(
|
|
/* model */ *this,
|
|
/* attn_type_k */ params.type_k,
|
|
/* attn_type_v */ params.type_v,
|
|
/* attn_v_trans */ !cparams.flash_attn,
|
|
/* attn_swa_full */ params.swa_full,
|
|
/* attn_kv_size */ cparams.n_ctx_seq,
|
|
/* attn_n_ubatch */ cparams.n_ubatch,
|
|
/* attn_n_pad */ 1,
|
|
/* recurrent_type_r */ GGML_TYPE_F32,
|
|
/* recurrent_type_s */ GGML_TYPE_F32,
|
|
/* recurrent_rs_size */ std::max((uint32_t) 1, cparams.n_seq_max),
|
|
/* n_seq_max */ cparams.n_seq_max,
|
|
/* n_rs_seq */ cparams.n_rs_seq,
|
|
/* offload */ cparams.offload_kqv,
|
|
/* unified */ cparams.kv_unified,
|
|
/* filter_attn */ std::move(filter_attn),
|
|
/* filter_recr */ std::move(filter_recr));
|
|
} else if (needs_mem_idx) {
|
|
// sparse attention over a per-token indexer cache, in its own memory type
|
|
res = new llama_memory_hybrid_idx(
|
|
/* model */ *this,
|
|
/* attn_type_k */ params.type_k,
|
|
/* attn_type_v */ params.type_v,
|
|
/* attn_v_trans */ !cparams.flash_attn,
|
|
/* attn_kv_size */ cparams.n_ctx_seq,
|
|
/* attn_n_pad */ 1,
|
|
/* attn_n_swa */ hparams.n_swa,
|
|
/* attn_swa_type */ hparams.swa_type,
|
|
/* recurrent_type_k */ GGML_TYPE_F32,
|
|
/* recurrent_type_v */ GGML_TYPE_F32,
|
|
/* recurrent_kv_size */ std::max((uint32_t) 1, cparams.n_seq_max),
|
|
/* n_seq_max */ cparams.n_seq_max,
|
|
/* n_rs_seq */ cparams.n_rs_seq,
|
|
/* offload */ cparams.offload_kqv,
|
|
/* unified */ cparams.kv_unified,
|
|
/* filter_attn */ std::move(filter_attn),
|
|
/* filter_recr */ std::move(filter_recr),
|
|
/* filter_idx */ std::move(filter_idx));
|
|
} else {
|
|
res = new llama_memory_hybrid(
|
|
/* model */ *this,
|
|
/* attn_type_k */ params.type_k,
|
|
/* attn_type_v */ params.type_v,
|
|
/* attn_v_trans */ !cparams.flash_attn,
|
|
/* attn_kv_size */ cparams.n_ctx_seq,
|
|
/* attn_n_pad */ 1,
|
|
/* attn_n_swa */ hparams.n_swa,
|
|
/* attn_swa_type */ hparams.swa_type,
|
|
/* recurrent_type_k */ GGML_TYPE_F32,
|
|
/* recurrent_type_v */ GGML_TYPE_F32,
|
|
/* recurrent_kv_size */ std::max((uint32_t) 1, cparams.n_seq_max),
|
|
/* n_seq_max */ cparams.n_seq_max,
|
|
/* n_rs_seq */ cparams.n_rs_seq,
|
|
/* offload */ cparams.offload_kqv,
|
|
/* unified */ cparams.kv_unified,
|
|
/* filter_attn */ std::move(filter_attn),
|
|
/* filter_recr */ std::move(filter_recr));
|
|
}
|
|
} else {
|
|
llama_kv_cache::layer_filter_cb filter = nullptr;
|
|
llama_memory_i::layer_reuse_cb reuse = nullptr;
|
|
llama_kv_cache::layer_share_cb share = nullptr;
|
|
|
|
if (arch == LLM_ARCH_GEMMA3N || arch == LLM_ARCH_GEMMA4) {
|
|
reuse = [&](uint32_t il) {
|
|
GGML_ASSERT(hparams.n_layer_kv_from_start >= 2);
|
|
|
|
if (il >= (uint32_t)hparams.n_layer_kv_from_start) {
|
|
return hparams.n_layer_kv_from_start - (hparams.is_swa(il) ? 2 : 1);
|
|
}
|
|
|
|
return -1;
|
|
};
|
|
}
|
|
|
|
if (mtp_on_hybrid_qwen || mtp_on_hybrid_nemotron) {
|
|
filter = [&](uint32_t il) { return il >= hparams.n_layer(); };
|
|
}
|
|
|
|
if ((arch == LLM_ARCH_STEP35 || arch == LLM_ARCH_HY_V3 || arch == LLM_ARCH_GLM_DSA ||
|
|
arch == LLM_ARCH_MIMO2 || arch == LLM_ARCH_DEEPSEEK32) &&
|
|
hparams.n_layer_nextn > 0) {
|
|
if (params.ctx_type == LLAMA_CONTEXT_TYPE_MTP) {
|
|
filter = [&](uint32_t il) { return il >= hparams.n_layer(); };
|
|
} else {
|
|
filter = [&](uint32_t il) { return il < hparams.n_layer(); };
|
|
}
|
|
}
|
|
|
|
if (hparams.swa_type != LLAMA_SWA_TYPE_NONE) {
|
|
GGML_ASSERT(hparams.is_swa_any());
|
|
|
|
if (arch == LLM_ARCH_GEMMA4_ASSISTANT) {
|
|
llama_memory_t mem_other = llama_get_memory(cparams.ctx_other);
|
|
|
|
share = [&](int32_t il) {
|
|
const llama_model * model_other = llama_get_model(cparams.ctx_other);
|
|
|
|
if (hparams.is_swa(il)) {
|
|
return llama_model_n_layer(model_other) - 2;
|
|
}
|
|
|
|
return llama_model_n_layer(model_other) - 1;
|
|
};
|
|
|
|
res = new llama_kv_cache_iswa(
|
|
*this,
|
|
params.type_k,
|
|
params.type_v,
|
|
!cparams.flash_attn,
|
|
cparams.offload_kqv,
|
|
params.swa_full,
|
|
cparams.kv_unified,
|
|
cparams.n_ctx_seq,
|
|
cparams.n_seq_max,
|
|
cparams.n_ubatch,
|
|
1,
|
|
mem_other,
|
|
filter,
|
|
reuse,
|
|
share);
|
|
} else {
|
|
res = new llama_kv_cache_iswa(
|
|
*this,
|
|
params.type_k,
|
|
params.type_v,
|
|
!cparams.flash_attn,
|
|
cparams.offload_kqv,
|
|
params.swa_full,
|
|
cparams.kv_unified,
|
|
cparams.n_ctx_seq,
|
|
cparams.n_seq_max,
|
|
cparams.n_ubatch,
|
|
1,
|
|
nullptr,
|
|
filter,
|
|
reuse,
|
|
share);
|
|
}
|
|
} else {
|
|
GGML_ASSERT(!hparams.is_swa_any());
|
|
|
|
res = new llama_kv_cache(
|
|
*this,
|
|
hparams,
|
|
params.type_k,
|
|
params.type_v,
|
|
!cparams.flash_attn,
|
|
cparams.offload_kqv,
|
|
cparams.kv_unified,
|
|
cparams.n_ctx_seq,
|
|
cparams.n_seq_max,
|
|
1,
|
|
hparams.n_swa,
|
|
hparams.swa_type,
|
|
nullptr,
|
|
filter,
|
|
nullptr,
|
|
nullptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
ggml_cgraph * llama_model::build_graph(const llm_graph_params & params) const {
|
|
std::unique_ptr<llm_graph_context> llm = build_arch_graph(params);
|
|
|
|
// add on pooling layer
|
|
llm->build_pooling(cls, cls_b, cls_out, cls_out_b, cls_norm);
|
|
|
|
// add backend sampling layers (if any)
|
|
llm->build_sampling();
|
|
|
|
// if the gguf model was converted with --sentence-transformers-dense-modules
|
|
// there will be two additional dense projection layers
|
|
// dense linear projections are applied after pooling
|
|
// TODO: move reranking logic here and generalize
|
|
llm->build_dense_out(dense_2_out_layers, dense_2_out_layers_b, dense_3_out_layers);
|
|
|
|
llm->res->set_outputs(params);
|
|
|
|
return llm->res->get_gf();
|
|
}
|
|
|
|
|
|
//
|
|
// interface implementation
|
|
//
|
|
|
|
llama_model_params llama_model_default_params() {
|
|
llama_model_params result = {
|
|
/*.devices =*/ nullptr,
|
|
/*.tensor_buft_overrides =*/ nullptr,
|
|
/*.n_gpu_layers =*/ -1,
|
|
/*.split_mode =*/ LLAMA_SPLIT_MODE_LAYER,
|
|
/*.load_mode =*/ LLAMA_LOAD_MODE_AUTO,
|
|
/*.tensor_read_lazy =*/ LLAMA_TENSOR_READ_LAZY_AUTO,
|
|
/*.main_gpu =*/ 0,
|
|
/*.tensor_split =*/ nullptr,
|
|
/*.progress_callback =*/ nullptr,
|
|
/*.progress_callback_user_data =*/ nullptr,
|
|
/*.kv_overrides =*/ nullptr,
|
|
/*.vocab_only =*/ false,
|
|
/*.check_tensors =*/ false,
|
|
/*.use_extra_bufts =*/ true,
|
|
/*.no_host =*/ false,
|
|
/*.no_alloc =*/ false,
|
|
/*.load_mtp =*/ false,
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
const llama_vocab * llama_model_get_vocab(const llama_model * model) {
|
|
return &model->vocab;
|
|
}
|
|
|
|
void llama_free_model(llama_model * model) {
|
|
llama_model_free(model);
|
|
}
|
|
|
|
void llama_model_free(llama_model * model) {
|
|
delete model;
|
|
}
|
|
|
|
int32_t llama_model_n_ctx_train(const llama_model * model) {
|
|
return model->hparams.n_ctx_train;
|
|
}
|
|
|
|
int32_t llama_model_n_embd(const llama_model * model) {
|
|
return model->hparams.n_embd;
|
|
}
|
|
|
|
int32_t llama_model_n_embd_inp(const llama_model * model) {
|
|
return model->hparams.n_embd_inp();
|
|
}
|
|
|
|
int32_t llama_model_n_embd_out(const llama_model * model) {
|
|
return model->hparams.n_embd_out();
|
|
}
|
|
|
|
int32_t llama_model_n_layer(const llama_model * model) {
|
|
return model->hparams.n_layer();
|
|
}
|
|
|
|
int32_t llama_model_n_layer_nextn(const llama_model * model) {
|
|
return model->hparams.n_layer_nextn;
|
|
}
|
|
|
|
int32_t llama_model_dflash_selector_top_k(const llama_model * model) {
|
|
return model->hparams.dflash_selector_top_k;
|
|
}
|
|
|
|
int32_t llama_model_n_head(const llama_model * model) {
|
|
return model->hparams.n_head();
|
|
}
|
|
|
|
int32_t llama_model_n_head_kv(const llama_model * model) {
|
|
return model->hparams.n_head_kv();
|
|
}
|
|
|
|
int32_t llama_model_n_swa(const llama_model * model) {
|
|
// dsv4 kv-cache has SWA but it cannot be used as a rollback because of
|
|
// other compression ratios, so we return 0 here
|
|
if (model->arch == LLM_ARCH_DEEPSEEK4) {
|
|
return 0;
|
|
}
|
|
return model->hparams.n_swa;
|
|
}
|
|
|
|
|
|
uint32_t llama_model_n_cls_out(const struct llama_model * model) {
|
|
return model->hparams.n_cls_out;
|
|
}
|
|
|
|
const char * llama_model_cls_label(const struct llama_model * model, uint32_t i) {
|
|
if (i < model->classifier_labels.size()) {
|
|
return model->classifier_labels[i].c_str();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// deprecated
|
|
int32_t llama_n_ctx_train(const llama_model * model) {
|
|
return llama_model_n_ctx_train(model);
|
|
}
|
|
|
|
// deprecated
|
|
int32_t llama_n_embd(const llama_model * model) {
|
|
return llama_model_n_embd(model);
|
|
}
|
|
|
|
// deprecated
|
|
int32_t llama_n_layer(const llama_model * model) {
|
|
return llama_model_n_layer(model);
|
|
}
|
|
|
|
// deprecated
|
|
int32_t llama_n_head(const llama_model * model) {
|
|
return llama_model_n_head(model);
|
|
}
|
|
|
|
llama_rope_type llama_model_rope_type(const llama_model * model) {
|
|
switch (model->arch) {
|
|
// these models do not use RoPE
|
|
case LLM_ARCH_CLIP:
|
|
case LLM_ARCH_GPT2:
|
|
case LLM_ARCH_GPTJ:
|
|
case LLM_ARCH_MPT:
|
|
case LLM_ARCH_REFACT:
|
|
case LLM_ARCH_BLOOM:
|
|
case LLM_ARCH_MAMBA:
|
|
case LLM_ARCH_MAMBA2:
|
|
case LLM_ARCH_JAMBA:
|
|
case LLM_ARCH_JINA_BERT_V2:
|
|
case LLM_ARCH_T5:
|
|
case LLM_ARCH_T5ENCODER:
|
|
case LLM_ARCH_JAIS:
|
|
case LLM_ARCH_RWKV6:
|
|
case LLM_ARCH_RWKV6QWEN2:
|
|
case LLM_ARCH_RWKV7:
|
|
case LLM_ARCH_ARWKV7:
|
|
case LLM_ARCH_WAVTOKENIZER_DEC:
|
|
case LLM_ARCH_NEMOTRON_H:
|
|
case LLM_ARCH_NEMOTRON_H_MOE:
|
|
case LLM_ARCH_KIMI_LINEAR:
|
|
case LLM_ARCH_KIMI_K3:
|
|
return LLAMA_ROPE_TYPE_NONE;
|
|
|
|
// use what we call a normal RoPE, operating on pairs of consecutive head values
|
|
case LLM_ARCH_LLAMA:
|
|
case LLM_ARCH_LLADA:
|
|
case LLM_ARCH_LLAMA4:
|
|
case LLM_ARCH_DECI:
|
|
case LLM_ARCH_BAICHUAN:
|
|
case LLM_ARCH_STARCODER:
|
|
case LLM_ARCH_INTERNLM2:
|
|
case LLM_ARCH_MINICPM:
|
|
case LLM_ARCH_XVERSE:
|
|
case LLM_ARCH_COMMAND_R:
|
|
case LLM_ARCH_COHERE2:
|
|
case LLM_ARCH_COHERE2MOE:
|
|
case LLM_ARCH_OLMO:
|
|
case LLM_ARCH_ARCTIC:
|
|
case LLM_ARCH_DEEPSEEK:
|
|
case LLM_ARCH_DEEPSEEK2:
|
|
case LLM_ARCH_DEEPSEEK2OCR:
|
|
case LLM_ARCH_DEEPSEEK32:
|
|
case LLM_ARCH_DEEPSEEK4:
|
|
case LLM_ARCH_MUSE_GLIMMER:
|
|
case LLM_ARCH_PLM:
|
|
case LLM_ARCH_CHATGLM:
|
|
case LLM_ARCH_GRANITE:
|
|
case LLM_ARCH_GRANITE_MOE:
|
|
case LLM_ARCH_GRANITE_HYBRID:
|
|
case LLM_ARCH_GRANITE_SWITCH:
|
|
case LLM_ARCH_GRANITE_SWA:
|
|
case LLM_ARCH_CHAMELEON:
|
|
case LLM_ARCH_BAILINGMOE:
|
|
case LLM_ARCH_BAILINGMOE3:
|
|
case LLM_ARCH_NEO_BERT:
|
|
case LLM_ARCH_SMOLLM3:
|
|
case LLM_ARCH_ARCEE:
|
|
case LLM_ARCH_ERNIE4_5:
|
|
case LLM_ARCH_ERNIE4_5_MOE:
|
|
case LLM_ARCH_MISTRAL3:
|
|
case LLM_ARCH_EAGLE3:
|
|
case LLM_ARCH_MISTRAL4:
|
|
case LLM_ARCH_LLAMA_EMBED:
|
|
case LLM_ARCH_MAINCODER:
|
|
case LLM_ARCH_GLM_DSA:
|
|
case LLM_ARCH_DOTS3NOTE:
|
|
case LLM_ARCH_NANBEIGE:
|
|
case LLM_ARCH_POCKETTTS:
|
|
return LLAMA_ROPE_TYPE_NORM;
|
|
|
|
// the pairs of head values are offset by n_rot/2
|
|
case LLM_ARCH_FALCON:
|
|
case LLM_ARCH_FALCON_H1:
|
|
case LLM_ARCH_GROK:
|
|
case LLM_ARCH_DBRX:
|
|
case LLM_ARCH_BERT:
|
|
case LLM_ARCH_JINA_BERT_V3:
|
|
case LLM_ARCH_MODERN_BERT:
|
|
case LLM_ARCH_NOMIC_BERT:
|
|
case LLM_ARCH_NOMIC_BERT_MOE:
|
|
case LLM_ARCH_EUROBERT:
|
|
case LLM_ARCH_STABLELM:
|
|
case LLM_ARCH_BITNET:
|
|
case LLM_ARCH_QWEN:
|
|
case LLM_ARCH_QWEN2:
|
|
case LLM_ARCH_DREAM:
|
|
case LLM_ARCH_QWEN2MOE:
|
|
case LLM_ARCH_QWEN3:
|
|
case LLM_ARCH_QWEN3MOE:
|
|
case LLM_ARCH_LLADA_MOE:
|
|
case LLM_ARCH_RND1:
|
|
case LLM_ARCH_OLMO2:
|
|
case LLM_ARCH_OLMOE:
|
|
case LLM_ARCH_PHI2:
|
|
case LLM_ARCH_PHI3:
|
|
case LLM_ARCH_PHIMOE:
|
|
case LLM_ARCH_PLAMO:
|
|
case LLM_ARCH_PLAMO2:
|
|
case LLM_ARCH_PLAMO3:
|
|
case LLM_ARCH_GEMMA:
|
|
case LLM_ARCH_GEMMA2:
|
|
case LLM_ARCH_GEMMA3:
|
|
case LLM_ARCH_GEMMA3N:
|
|
case LLM_ARCH_GEMMA4:
|
|
case LLM_ARCH_GEMMA4_ASSISTANT:
|
|
case LLM_ARCH_GEMMA_EMBEDDING:
|
|
case LLM_ARCH_STARCODER2:
|
|
case LLM_ARCH_OPENELM:
|
|
case LLM_ARCH_GPTNEOX:
|
|
case LLM_ARCH_CODESHELL:
|
|
case LLM_ARCH_ORION:
|
|
case LLM_ARCH_NEMOTRON:
|
|
case LLM_ARCH_EXAONE:
|
|
case LLM_ARCH_EXAONE4:
|
|
case LLM_ARCH_EXAONE_MOE:
|
|
case LLM_ARCH_MINICPM3:
|
|
case LLM_ARCH_BAILINGMOE2:
|
|
case LLM_ARCH_DOTS1:
|
|
case LLM_ARCH_HUNYUAN_MOE:
|
|
case LLM_ARCH_JAIS2:
|
|
case LLM_ARCH_OPENAI_MOE:
|
|
case LLM_ARCH_HUNYUAN_DENSE:
|
|
case LLM_ARCH_HY_V3:
|
|
case LLM_ARCH_LFM2:
|
|
case LLM_ARCH_LFM2MOE:
|
|
case LLM_ARCH_SMALLTHINKER:
|
|
case LLM_ARCH_SEED_OSS:
|
|
case LLM_ARCH_GROVEMOE:
|
|
case LLM_ARCH_APERTUS:
|
|
case LLM_ARCH_MINIMAX_01:
|
|
case LLM_ARCH_MINIMAX_M2:
|
|
case LLM_ARCH_MINIMAX_M3:
|
|
case LLM_ARCH_COGVLM:
|
|
case LLM_ARCH_PANGU_EMBED:
|
|
case LLM_ARCH_AFMOE:
|
|
case LLM_ARCH_LAGUNA:
|
|
case LLM_ARCH_QWEN3NEXT:
|
|
case LLM_ARCH_MIMO2:
|
|
case LLM_ARCH_STEP35:
|
|
case LLM_ARCH_TALKIE:
|
|
case LLM_ARCH_MELLUM:
|
|
return LLAMA_ROPE_TYPE_NEOX;
|
|
|
|
case LLM_ARCH_DFLASH:
|
|
// drafts for M-RoPE targets carry rope sections and follow the target's temporal dim
|
|
if (const auto & s = model->hparams.rope_sections; s[0] || s[1] || s[2] || s[3]) {
|
|
return LLAMA_ROPE_TYPE_MROPE;
|
|
}
|
|
// DSV4 DSpark drafters use DeepSeek-V4's normal RoPE; legacy DFlash backbones are NeoX
|
|
return model->hparams.dsv4_hc_mult > 0 ? LLAMA_ROPE_TYPE_NORM : LLAMA_ROPE_TYPE_NEOX;
|
|
|
|
case LLM_ARCH_QWEN2VL:
|
|
case LLM_ARCH_PADDLEOCR:
|
|
return LLAMA_ROPE_TYPE_MROPE;
|
|
case LLM_ARCH_QWEN3VL:
|
|
case LLM_ARCH_QWEN3VLMOE:
|
|
case LLM_ARCH_QWEN35:
|
|
case LLM_ARCH_QWEN35MOE:
|
|
case LLM_ARCH_QWEN4EXP:
|
|
case LLM_ARCH_QWEN3TTS:
|
|
return LLAMA_ROPE_TYPE_IMROPE;
|
|
|
|
case LLM_ARCH_GLM4:
|
|
return model->hparams.use_mrope() ? LLAMA_ROPE_TYPE_MROPE : LLAMA_ROPE_TYPE_NORM;
|
|
case LLM_ARCH_GLM4_MOE:
|
|
return model->hparams.use_mrope() ? LLAMA_ROPE_TYPE_MROPE : LLAMA_ROPE_TYPE_NEOX;
|
|
|
|
case LLM_ARCH_HUNYUAN_VL:
|
|
return model->hparams.use_mrope() ? LLAMA_ROPE_TYPE_MROPE : LLAMA_ROPE_TYPE_NEOX;
|
|
|
|
// all model arches should be listed explicitly here
|
|
case LLM_ARCH_UNKNOWN:
|
|
GGML_ABORT("unknown architecture");
|
|
}
|
|
|
|
return LLAMA_ROPE_TYPE_NONE;
|
|
}
|
|
|
|
float llama_model_rope_freq_scale_train(const llama_model * model) {
|
|
return model->hparams.rope_freq_scale_train;
|
|
}
|
|
|
|
int32_t llama_model_meta_val_str(const llama_model * model, const char * key, char * buf, size_t buf_size) {
|
|
const auto & it = model->gguf_kv.find(key);
|
|
if (it == model->gguf_kv.end()) {
|
|
if (buf_size > 0) {
|
|
buf[0] = '\0';
|
|
}
|
|
return -1;
|
|
}
|
|
return snprintf(buf, buf_size, "%s", it->second.c_str());
|
|
}
|
|
|
|
int32_t llama_model_meta_count(const llama_model * model) {
|
|
return (int)model->gguf_kv.size();
|
|
}
|
|
|
|
const char * llama_model_meta_key_str(llama_model_meta_key key) {
|
|
switch (key) {
|
|
case LLAMA_MODEL_META_KEY_SAMPLING_SEQUENCE: return "general.sampling.sequence";
|
|
case LLAMA_MODEL_META_KEY_SAMPLING_TOP_K: return "general.sampling.top_k";
|
|
case LLAMA_MODEL_META_KEY_SAMPLING_TOP_P: return "general.sampling.top_p";
|
|
case LLAMA_MODEL_META_KEY_SAMPLING_MIN_P: return "general.sampling.min_p";
|
|
case LLAMA_MODEL_META_KEY_SAMPLING_XTC_PROBABILITY: return "general.sampling.xtc_probability";
|
|
case LLAMA_MODEL_META_KEY_SAMPLING_XTC_THRESHOLD: return "general.sampling.xtc_threshold";
|
|
case LLAMA_MODEL_META_KEY_SAMPLING_TEMP: return "general.sampling.temp";
|
|
case LLAMA_MODEL_META_KEY_SAMPLING_PENALTY_LAST_N: return "general.sampling.penalty_last_n";
|
|
case LLAMA_MODEL_META_KEY_SAMPLING_PENALTY_REPEAT: return "general.sampling.penalty_repeat";
|
|
case LLAMA_MODEL_META_KEY_SAMPLING_MIROSTAT: return "general.sampling.mirostat";
|
|
case LLAMA_MODEL_META_KEY_SAMPLING_MIROSTAT_TAU: return "general.sampling.mirostat_tau";
|
|
case LLAMA_MODEL_META_KEY_SAMPLING_MIROSTAT_ETA: return "general.sampling.mirostat_eta";
|
|
default: return nullptr;
|
|
}
|
|
}
|
|
|
|
int32_t llama_model_meta_key_by_index(const llama_model * model, int i, char * buf, size_t buf_size) {
|
|
if (i < 0 || i >= (int)model->gguf_kv.size()) {
|
|
if (buf_size > 0) {
|
|
buf[0] = '\0';
|
|
}
|
|
return -1;
|
|
}
|
|
auto it = model->gguf_kv.begin();
|
|
std::advance(it, i);
|
|
return snprintf(buf, buf_size, "%s", it->first.c_str());
|
|
}
|
|
|
|
int32_t llama_model_meta_val_str_by_index(const llama_model * model, int32_t i, char * buf, size_t buf_size) {
|
|
if (i < 0 || i >= (int)model->gguf_kv.size()) {
|
|
if (buf_size > 0) {
|
|
buf[0] = '\0';
|
|
}
|
|
return -1;
|
|
}
|
|
auto it = model->gguf_kv.begin();
|
|
std::advance(it, i);
|
|
return snprintf(buf, buf_size, "%s", it->second.c_str());
|
|
}
|
|
|
|
int32_t llama_model_desc(const llama_model * model, char * buf, size_t buf_size) {
|
|
return snprintf(buf, buf_size, "%s", model->desc().c_str());
|
|
}
|
|
|
|
llama_ftype llama_model_ftype(const llama_model * model) {
|
|
return model->ftype();
|
|
}
|
|
|
|
uint64_t llama_model_size(const llama_model * model) {
|
|
return model->size();
|
|
}
|
|
|
|
const char * llama_model_chat_template(const llama_model * model, const char * name) {
|
|
const auto key = name ? LLM_KV(model->arch, name)(LLM_KV_TOKENIZER_CHAT_TEMPLATE)
|
|
: LLM_KV(model->arch)(LLM_KV_TOKENIZER_CHAT_TEMPLATE);
|
|
const auto & it = model->gguf_kv.find(key);
|
|
if (it == model->gguf_kv.end()) {
|
|
// one-off fix for very popular models (so we are not flooded with issues)
|
|
// do not extend this list unless absolutely necessary
|
|
// Mistral-Small-2503 does not have built-in chat template
|
|
llama_vocab_pre_type pre_type = model->vocab.get_pre_type();
|
|
if (!name && pre_type == LLAMA_VOCAB_PRE_TYPE_TEKKEN && model->layers.size() == 40) {
|
|
return "mistral-v7-tekken";
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
return it->second.c_str();
|
|
}
|
|
|
|
uint64_t llama_model_n_params(const llama_model * model) {
|
|
return model->n_elements();
|
|
}
|
|
|
|
bool llama_model_has_encoder(const llama_model * model) {
|
|
switch (model->arch) {
|
|
case LLM_ARCH_T5:
|
|
case LLM_ARCH_T5ENCODER:
|
|
case LLM_ARCH_EAGLE3:
|
|
case LLM_ARCH_DFLASH: return true;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
bool llama_model_has_decoder(const llama_model * model) {
|
|
switch (model->arch) {
|
|
case LLM_ARCH_T5ENCODER: return false;
|
|
default: return true;
|
|
}
|
|
}
|
|
|
|
llama_token llama_model_decoder_start_token(const llama_model * model) {
|
|
return model->hparams.dec_start_token_id;
|
|
}
|
|
|
|
bool llama_model_is_recurrent(const llama_model * model) {
|
|
return llm_arch_is_recurrent(model->arch);
|
|
}
|
|
|
|
bool llama_model_is_hybrid(const llama_model * model) {
|
|
return llm_arch_is_hybrid(model->arch);
|
|
}
|
|
|
|
bool llama_model_is_diffusion(const llama_model * model) {
|
|
return llm_arch_is_diffusion(model->arch);
|
|
}
|
|
|
|
const std::vector<std::pair<std::string, ggml_tensor *>> & llama_internal_get_tensor_map(const llama_model * model) {
|
|
return model->tensors_by_name;
|
|
}
|
|
|
|
int32_t llama_model_n_expert(const struct llama_model * model) {
|
|
return model->hparams.n_expert;
|
|
}
|
|
|
|
int32_t llama_model_n_devices(const struct llama_model * model) {
|
|
return (int32_t)model->devices.size();
|
|
}
|
|
|
|
ggml_backend_dev_t llama_model_get_device(const struct llama_model * model, int i) {
|
|
if (i < 0 || i >= (int)model->devices.size()) {
|
|
return nullptr;
|
|
}
|
|
return model->devices[i].dev;
|
|
}
|
|
|
|
//
|
|
// llama_model_base
|
|
//
|
|
|
|
llama_model_base::llama_model_base(const struct llama_model_params & params) : llama_model(params), model(this), tn(model->arch),
|
|
TENSOR_DUPLICATED (llama_model_loader::TENSOR_DUPLICATED),
|
|
TENSOR_NOT_REQUIRED (llama_model_loader::TENSOR_NOT_REQUIRED),
|
|
TENSOR_SKIP (llama_model_loader::TENSOR_SKIP),
|
|
TENSOR_SKIP_IF_VIRTUAL(llama_model_loader::TENSOR_SKIP_IF_VIRTUAL),
|
|
TENSOR_ALLOW_RESHAPE (llama_model_loader::TENSOR_ALLOW_RESHAPE),
|
|
TENSOR_READ_LAZY (llama_model_loader::TENSOR_READ_LAZY) {}
|
|
|
|
ggml_tensor * llama_model_base::create_tensor(const LLM_TN_IMPL & tn, const std::initializer_list<int64_t> & ne, int flags) {
|
|
GGML_ASSERT(ml != nullptr);
|
|
return create_tensor(*ml, tn, ne, flags);
|
|
}
|
|
|
|
void llama_model_base::create_tensor_gate_up_exps(llama_layer & layer, int bid, int64_t n_embd_, int64_t n_ff_, int64_t n_expert_, int flags) {
|
|
layer.ffn_gate_up_exps = create_tensor(tn(LLM_TENSOR_FFN_GATE_UP_EXPS, "weight", bid), {n_embd_, n_ff_ * 2, n_expert_}, TENSOR_NOT_REQUIRED);
|
|
if (layer.ffn_gate_up_exps == nullptr) {
|
|
layer.ffn_gate_exps = create_tensor(tn(LLM_TENSOR_FFN_GATE_EXPS, "weight", bid), {n_embd_, n_ff_, n_expert_}, flags);
|
|
layer.ffn_up_exps = create_tensor(tn(LLM_TENSOR_FFN_UP_EXPS, "weight", bid), {n_embd_, n_ff_, n_expert_}, flags);
|
|
}
|
|
}
|
|
|
|
void llama_model_base::create_tensor_qkv(llama_layer & layer, int bid,
|
|
int64_t n_embd_, int64_t n_embd_q_, int64_t n_embd_k_, int64_t n_embd_v_,
|
|
int flags) {
|
|
const int64_t n_embd_qkv = n_embd_q_ + n_embd_k_ + n_embd_v_;
|
|
|
|
if (flags & TENSOR_SKIP) {
|
|
const int skip = TENSOR_NOT_REQUIRED | TENSOR_SKIP;
|
|
|
|
create_tensor(tn(LLM_TENSOR_ATTN_QKV, "weight", bid), {n_embd_, n_embd_qkv}, skip | TENSOR_SKIP_IF_VIRTUAL);
|
|
create_tensor(tn(LLM_TENSOR_ATTN_QKV, "bias", bid), {n_embd_qkv}, skip | TENSOR_SKIP_IF_VIRTUAL);
|
|
create_tensor(tn(LLM_TENSOR_ATTN_Q, "weight", bid), {n_embd_, n_embd_q_}, skip);
|
|
create_tensor(tn(LLM_TENSOR_ATTN_K, "weight", bid), {n_embd_, n_embd_k_}, skip);
|
|
create_tensor(tn(LLM_TENSOR_ATTN_V, "weight", bid), {n_embd_, n_embd_v_}, skip);
|
|
create_tensor(tn(LLM_TENSOR_ATTN_Q, "bias", bid), {n_embd_q_}, skip);
|
|
create_tensor(tn(LLM_TENSOR_ATTN_K, "bias", bid), {n_embd_k_}, skip);
|
|
create_tensor(tn(LLM_TENSOR_ATTN_V, "bias", bid), {n_embd_v_}, skip);
|
|
return;
|
|
}
|
|
|
|
layer.wqkv = create_tensor(tn(LLM_TENSOR_ATTN_QKV, "weight", bid), {n_embd_, n_embd_qkv}, TENSOR_NOT_REQUIRED | TENSOR_SKIP_IF_VIRTUAL);
|
|
if (layer.wqkv) {
|
|
layer.wqkv_b = create_tensor(tn(LLM_TENSOR_ATTN_QKV, "bias", bid), {n_embd_qkv}, TENSOR_NOT_REQUIRED | TENSOR_SKIP_IF_VIRTUAL);
|
|
} else {
|
|
layer.wq = create_tensor(tn(LLM_TENSOR_ATTN_Q, "weight", bid), {n_embd_, n_embd_q_}, flags);
|
|
layer.wk = create_tensor(tn(LLM_TENSOR_ATTN_K, "weight", bid), {n_embd_, n_embd_k_}, flags);
|
|
layer.wv = create_tensor(tn(LLM_TENSOR_ATTN_V, "weight", bid), {n_embd_, n_embd_v_}, flags);
|
|
layer.wq_b = create_tensor(tn(LLM_TENSOR_ATTN_Q, "bias", bid), {n_embd_q_}, TENSOR_NOT_REQUIRED);
|
|
layer.wk_b = create_tensor(tn(LLM_TENSOR_ATTN_K, "bias", bid), {n_embd_k_}, TENSOR_NOT_REQUIRED);
|
|
layer.wv_b = create_tensor(tn(LLM_TENSOR_ATTN_V, "bias", bid), {n_embd_v_}, TENSOR_NOT_REQUIRED);
|
|
}
|
|
}
|
|
|
|
const int32_t * llama_model_target_layer_ids(const struct llama_model * model) {
|
|
const auto & v = model->target_layer_ids;
|
|
return v.empty() ? nullptr : v.data();
|
|
}
|
|
|
|
uint32_t llama_model_target_layer_ids_n(const struct llama_model * model) {
|
|
return (uint32_t) model->target_layer_ids.size();
|
|
}
|
|
|
|
uint32_t llama_model_get_tok_embd(const struct llama_model * model, float * out) {
|
|
if (model->vocab.n_tokens() == 0 || model->tok_embd == nullptr) {
|
|
return 0;
|
|
}
|
|
|
|
const ggml_tensor * tensor = model->tok_embd;
|
|
const size_t nelements = ggml_nelements(tensor);
|
|
GGML_ASSERT(nelements <= UINT32_MAX); // for the return type
|
|
|
|
if (out == nullptr) {
|
|
return (uint32_t) nelements;
|
|
}
|
|
|
|
if (tensor->type == GGML_TYPE_F32) {
|
|
ggml_backend_tensor_get(tensor, out, 0, nelements * sizeof(float));
|
|
return (uint32_t) nelements;
|
|
}
|
|
|
|
std::vector<uint8_t> buf(ggml_nbytes(tensor));
|
|
ggml_backend_tensor_get(tensor, buf.data(), 0, buf.size());
|
|
|
|
const ggml_type_traits * traits = ggml_get_type_traits(tensor->type);
|
|
if (tensor->type == GGML_TYPE_F16) {
|
|
ggml_fp16_to_fp32_row((const ggml_fp16_t *) buf.data(), out, nelements);
|
|
} else if (tensor->type == GGML_TYPE_BF16) {
|
|
ggml_bf16_to_fp32_row((const ggml_bf16_t *) buf.data(), out, nelements);
|
|
} else if (ggml_is_quantized(tensor->type) && traits->to_float != nullptr) {
|
|
traits->to_float(buf.data(), out, nelements);
|
|
} else {
|
|
GGML_ABORT("unsupported tensor type for dequantization: %s", ggml_type_name(tensor->type));
|
|
}
|
|
|
|
return (uint32_t) nelements;
|
|
}
|