* qwen4exp: follow up fixes * -kvu NaN collapse fix Assisted-by: Claude * indexer cache ext.x/ext.y restore fix Assisted-by: Claude * kv-cells: rename seq_set to seq_get_all seq_get is already taken by the single-id getter, so the suggested name cannot be overloaded on return type alone. Assisted-by: Claude * memory-hybrid-idx: implement set_input_qsa on the memory class The context held the whole implementation, where the pattern elsewhere is a thin context forwarding to the memory class, as llama_kv_cache_context does for set_input_kq_mask. The body reads no context state, so it moves unchanged and the context keeps a forwarder. Also shortens the seq_get_all comment as suggested. * tests: check that a sequence state survives a save/restore round-trip Saves seq 0, erases it, restores the blob and saves again, requiring the two blobs to match. Compares blobs rather than generated text, which cannot see a field dropped on the way back in. Note this passes on master for qwen4exp, so it does not demonstrate the ext.x/ext.y drop this PR fixes; reaching that needs 2D mrope content. * tests: give the synthetic qwen4exp a PLE so the state test bites has_cell_ext() is n_pos_per_embd() > 1 || ple_n_heads > 0, and the indexer cache sets rope_type = NONE, so without a PLE it serializes no cell ext at all and the round-trip test cannot see a dropped ext.x/ext.y. With one, removing the ext_set restore in state_read_meta fails the test: 198 of 335692 bytes differ, first at offset 282092. Loading such a model needed two fixes: - the row count of per_layer_token_embd came from require_weight(), which a model synthesised from metadata alone has no file to answer. Derive it from the head ranges and prefer the file's padded count where there is one. - the PLE conv history is a row of the recurrent cache, so a PLE on a full attention layer dereferenced a null p_l. Reject it at load time instead. The meta mirror is skipped for qwen4exp. It returned NaN logits before this fixture carried a PLE, which the nmse check passes since a NaN comparison is false, and aborts with one. -sm tensor on real devices works. Assisted-by: Claude * llama: disable -sm tensor for qwen4exp test-llama-archs skipped the tensor split for this arch from inside the test, so the arch still advertised support it does not have. Declare it in llm_arch_supports_sm_tensor instead and drop the test-side exception; the existing llm_arch_supports_sm_tensor branch then does the skipping. Assisted-by: Claude
161 lines
6.1 KiB
C++
161 lines
6.1 KiB
C++
#pragma once
|
|
|
|
#include "llama-memory-hybrid.h"
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
//
|
|
// llama_memory_hybrid_idx
|
|
//
|
|
|
|
// llama_memory_hybrid plus a third cache with one indexer key per token, for block-sparse attention (qwen4exp QSA)
|
|
// the indexer is a side buffer over the attention cells: same size, padding, streams and slots, so cell j is one token in both
|
|
|
|
class llama_memory_hybrid_idx : public llama_memory_hybrid {
|
|
public:
|
|
llama_memory_hybrid_idx(
|
|
const llama_model & model,
|
|
/* attn */
|
|
ggml_type type_k,
|
|
ggml_type type_v,
|
|
bool v_trans,
|
|
uint32_t kv_size,
|
|
uint32_t n_pad,
|
|
uint32_t n_swa,
|
|
llama_swa_type swa_type,
|
|
/* recurrent */
|
|
ggml_type type_r,
|
|
ggml_type type_s,
|
|
uint32_t rs_size,
|
|
/* common */
|
|
uint32_t n_seq_max,
|
|
uint32_t n_rs_seq,
|
|
bool offload,
|
|
bool unified,
|
|
/* layer filters */
|
|
const layer_filter_cb & filter_attn,
|
|
const layer_filter_cb & filter_recr,
|
|
/* the indexer cache exists only if this is given */
|
|
const layer_filter_cb & filter_idx);
|
|
|
|
~llama_memory_hybrid_idx() = default;
|
|
|
|
//
|
|
// llama_memory_i
|
|
//
|
|
|
|
llama_memory_context_ptr init_batch(
|
|
llama_batch_allocr & balloc,
|
|
uint32_t n_ubatch,
|
|
bool embd_all) override;
|
|
|
|
llama_memory_context_ptr init_full() override;
|
|
|
|
llama_memory_context_ptr init_update(llama_context * lctx, bool optimize) override;
|
|
|
|
void clear(bool data) override;
|
|
|
|
bool seq_rm (llama_seq_id seq_id, llama_pos p0, llama_pos p1) override;
|
|
void seq_cp (llama_seq_id seq_id_src, llama_seq_id seq_id_dst, llama_pos p0, llama_pos p1) override;
|
|
void seq_keep(llama_seq_id seq_id) override;
|
|
void seq_add (llama_seq_id seq_id, llama_pos p0, llama_pos p1, llama_pos shift) override;
|
|
void seq_div (llama_seq_id seq_id, llama_pos p0, llama_pos p1, int d) override;
|
|
|
|
std::map<ggml_backend_buffer_type_t, size_t> memory_breakdown() const override;
|
|
|
|
// state write/load
|
|
|
|
void state_write(llama_io_write_i & io, llama_seq_id seq_id = -1, llama_state_seq_flags flags = 0) const override;
|
|
void state_read (llama_io_read_i & io, llama_seq_id seq_id = -1, llama_state_seq_flags flags = 0) override;
|
|
|
|
//
|
|
// llama_memory_hybrid_idx specific API
|
|
//
|
|
|
|
llama_kv_cache * get_mem_idx() const; // nullptr when the model carries no indexer
|
|
|
|
// block-compressed sparse attention (qwen4exp QSA) over the cells of the indexer cache.
|
|
// Blocks cut the position line, not the cell array, so no caller assumes a contiguous layout:
|
|
// cell_blk I32 [n_kv, ns] block each cell belongs to
|
|
// blk_cells I32 [ratio*n_blocks, ns] cells making up each block
|
|
// blk_pos I32 [4*n_blocks*ns] mrope position rows of each block's first token
|
|
// bias F32 [n_kv, n_tokens/ns, ns] -inf where invisible, large where always visible
|
|
// blk_bias asks for the bias per block instead: [n_blocks, n_tokens/ns, ns]
|
|
// the caller then adds the attention mask, the only part of the bias that varies within a block
|
|
void set_input_qsa(ggml_tensor * cell_blk, ggml_tensor * blk_cells, ggml_tensor * blk_pos,
|
|
ggml_tensor * bias, const llama_ubatch * ubatch, uint32_t ratio,
|
|
bool blk_bias) const;
|
|
|
|
private:
|
|
// forget seq_id (all of it if seq_id < 0) in every cache at once, so a failed restore cannot leave the caches out of step
|
|
// seq_id < 0 drops the whole context, as the caches themselves do on a failed restore
|
|
void state_drop(llama_seq_id seq_id);
|
|
|
|
// the indexer cache holds one key head per layer, so it needs its own hparams:
|
|
// llama_kv_cache keeps a reference to what it is given
|
|
llama_hparams hparams_idx;
|
|
|
|
const std::unique_ptr<llama_kv_cache> mem_idx;
|
|
};
|
|
|
|
class llama_memory_hybrid_idx_context : public llama_memory_hybrid_context {
|
|
public:
|
|
using slot_info_vec_t = llama_kv_cache::slot_info_vec_t;
|
|
|
|
// used for errors
|
|
explicit llama_memory_hybrid_idx_context(llama_memory_status status);
|
|
|
|
// used to create a full-cache context
|
|
explicit llama_memory_hybrid_idx_context(llama_memory_hybrid_idx * mem);
|
|
|
|
// used to create an update context
|
|
llama_memory_hybrid_idx_context(
|
|
llama_memory_hybrid_idx * mem,
|
|
llama_context * lctx,
|
|
bool optimize);
|
|
|
|
// used to create a batch processing context from a batch
|
|
llama_memory_hybrid_idx_context(
|
|
llama_memory_hybrid_idx * mem,
|
|
slot_info_vec_t sinfos_attn,
|
|
slot_info_vec_t sinfos_idx,
|
|
std::vector<llama_ubatch> ubatches);
|
|
|
|
~llama_memory_hybrid_idx_context() = default;
|
|
|
|
//
|
|
// llama_memory_context_i
|
|
//
|
|
|
|
bool next() override;
|
|
bool apply() override;
|
|
|
|
//
|
|
// llama_memory_hybrid_idx_context specific API
|
|
//
|
|
|
|
// nullptr with no indexer
|
|
const llama_kv_cache_context * get_idx() const;
|
|
|
|
// streams in the current slot info, the `ns` of get_k/get_v; 1 if unified
|
|
uint32_t get_n_stream() const;
|
|
|
|
void set_input_qsa(ggml_tensor * cell_blk, ggml_tensor * blk_cells, ggml_tensor * blk_pos,
|
|
ggml_tensor * bias, const llama_ubatch * ubatch, uint32_t ratio,
|
|
bool blk_bias) const;
|
|
|
|
private:
|
|
const llama_memory_hybrid_idx * mem = nullptr;
|
|
|
|
// streams per ubatch, read from the slot infos before ctx_idx takes them
|
|
// declared first, so it is initialised while sinfos_idx is still intact
|
|
const std::vector<uint32_t> ns_ubatch;
|
|
|
|
// null unless the model has an indexer
|
|
const llama_memory_context_ptr ctx_idx;
|
|
|
|
// mirrors the base class's ubatch cursor, which is private there
|
|
size_t i_cur = 0;
|
|
};
|