mirror of
https://github.com/ikawrakow/ik_llama.cpp.git
synced 2026-07-21 10:15:35 +00:00
recurrent model: implement recurrent kernel checkpoint
This commit is contained in:
@@ -23,7 +23,9 @@ static void log_text(const gpt_params & params_base, const std::string & text) {
|
||||
|
||||
void server_speculative_checkpoint::clear() {
|
||||
valid = false;
|
||||
per_step_enabled = false;
|
||||
n_past = 0;
|
||||
sampled = LLAMA_TOKEN_NULL;
|
||||
data.clear();
|
||||
|
||||
if (sampler != nullptr) {
|
||||
@@ -36,6 +38,7 @@ static void discard_speculative_checkpoint(server_slot & slot, llama_context * c
|
||||
slot.spec_ckpt.clear();
|
||||
|
||||
if (gpu_ckpt) {
|
||||
llama_kv_cache_set_per_step_save(ctx, false, 0);
|
||||
llama_kv_cache_checkpoint_delete(ctx);
|
||||
}
|
||||
}
|
||||
@@ -46,6 +49,16 @@ static bool save_speculative_checkpoint(server_slot & slot, llama_model * model,
|
||||
slot.spec_ckpt.sampled = slot.sampled;
|
||||
|
||||
if (gpu_ckpt) {
|
||||
// Enable per-step SSM state saves for the verification forward pass.
|
||||
// The delta_net kernel saves SSM state after each token step, allowing us
|
||||
// to skip the expensive re-decode on rejection.
|
||||
// Returns false if per-step is unsupported (e.g., split tensors in graph split mode).
|
||||
const int max_tokens = (int)slot.drafted.size() + 1;
|
||||
slot.spec_ckpt.per_step_enabled = llama_kv_cache_set_per_step_save(ctx, true, max_tokens);
|
||||
|
||||
// Always save full checkpoint (conv + SSM state).
|
||||
// On rejection with per-step: restore conv from here, SSM from per-step.
|
||||
// On rejection without per-step: full restore + re-decode (legacy path).
|
||||
slot.spec_ckpt.valid = llama_kv_cache_checkpoint_save(ctx);
|
||||
} else {
|
||||
const size_t ckpt_size = llama_state_seq_get_size(ctx, slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
@@ -3661,59 +3674,86 @@ void server_context::speculative_decoding_accept() {
|
||||
// for recurrent/hybrid models: if any drafts were rejected, restore recurrent state
|
||||
const bool any_rejected = (ids.size() - 1) < n_draft;
|
||||
const bool gpu_ckpt = llama_kv_cache_checkpoint_supported(ctx);
|
||||
const bool has_per_step = slot.spec_ckpt.valid && slot.spec_ckpt.per_step_enabled;
|
||||
if (any_rejected && slot.spec_ckpt.valid) {
|
||||
if (gpu_ckpt) {
|
||||
llama_kv_cache_checkpoint_restore(ctx);
|
||||
} else {
|
||||
llama_state_seq_set_data(ctx, slot.spec_ckpt.data.data(), slot.spec_ckpt.data.size(), slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
}
|
||||
if (has_per_step) {
|
||||
const int step = (int)ids.size() - 1;
|
||||
|
||||
llama_kv_cache_seq_rm(ctx, slot.id, slot.spec_ckpt.n_past, -1);
|
||||
// Keep accepted attention KV from the verification pass and only fix the
|
||||
// recurrent state plus remove rejected positions.
|
||||
llama_kv_cache_per_step_restore(ctx, step);
|
||||
|
||||
// restore sampler state (RNG, grammar, prev tokens)
|
||||
if (slot.spec_ckpt.sampler) {
|
||||
common_sampler_clone(slot.spec_ckpt.sampler, slot.ctx_sampling);
|
||||
}
|
||||
const llama_pos accepted_pos = slot.spec_ckpt.n_past + step;
|
||||
llama_kv_cache_set_cell_pos(ctx, slot.id, accepted_pos);
|
||||
llama_kv_cache_seq_rm(ctx, slot.id, accepted_pos + 1, -1);
|
||||
|
||||
if (!ids.empty()) {
|
||||
// Re-decode to restore the recurrent state after checkpoint restore.
|
||||
const int n_re = (int)ids.size();
|
||||
llama_batch re_batch = llama_batch_init(n_re, 0, 1);
|
||||
common_batch_add(re_batch, slot.spec_ckpt.sampled, slot.spec_ckpt.n_past, { slot.id }, n_re == 1);
|
||||
for (int j = 0; j < n_re - 1; j++) {
|
||||
const bool is_last = (j == n_re - 2);
|
||||
common_batch_add(re_batch, ids[j], slot.spec_ckpt.n_past + 1 + j, { slot.id }, is_last);
|
||||
// Restore sampler state
|
||||
if (slot.spec_ckpt.sampler) {
|
||||
common_sampler_clone(slot.spec_ckpt.sampler, slot.ctx_sampling);
|
||||
}
|
||||
|
||||
if (slot.has_mtp) {
|
||||
llama_set_embeddings(ctx, true);
|
||||
}
|
||||
|
||||
const int ret = llama_decode(ctx, re_batch);
|
||||
if (ret != 0) {
|
||||
SLT_ERR(slot, "failed to re-decode accepted tokens after checkpoint restore: %d\n", ret);
|
||||
}
|
||||
|
||||
if (slot.has_mtp) {
|
||||
llama_set_embeddings(ctx, false);
|
||||
const int n_embd = llama_model_n_embd(llama_get_model(ctx));
|
||||
const float * emb = llama_get_embeddings_ith(ctx, -1);
|
||||
if (emb) {
|
||||
slot.mtp_hidden_state.resize(n_embd);
|
||||
memcpy(slot.mtp_hidden_state.data(), emb, n_embd * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
for (llama_token id : ids) {
|
||||
common_sampler_accept(slot.ctx_sampling, ctx, id, true);
|
||||
}
|
||||
|
||||
llama_batch_free(re_batch);
|
||||
SLT_DBG(slot, "spec checkpoint restored (gpu=%s): re-decoded %d tokens (rejected %d drafts)\n",
|
||||
gpu_ckpt ? "yes" : "no", n_re, (int)(n_draft - (ids.size() - 1)));
|
||||
}
|
||||
SLT_DBG(slot, "per-step restore: step=%d (rejected %d drafts)\n",
|
||||
step, (int)(n_draft - (ids.size() - 1)));
|
||||
|
||||
discard_speculative_checkpoint(slot, ctx, gpu_ckpt);
|
||||
discard_speculative_checkpoint(slot, ctx, gpu_ckpt);
|
||||
} else {
|
||||
// Legacy path: full checkpoint restore + re-decode
|
||||
if (gpu_ckpt) {
|
||||
llama_kv_cache_checkpoint_restore(ctx);
|
||||
} else {
|
||||
llama_state_seq_set_data(ctx, slot.spec_ckpt.data.data(), slot.spec_ckpt.data.size(), slot.id, LLAMA_STATE_SEQ_FLAGS_PARTIAL_ONLY);
|
||||
}
|
||||
|
||||
llama_kv_cache_seq_rm(ctx, slot.id, slot.spec_ckpt.n_past, -1);
|
||||
|
||||
// restore sampler state (RNG, grammar, prev tokens)
|
||||
if (slot.spec_ckpt.sampler) {
|
||||
common_sampler_clone(slot.spec_ckpt.sampler, slot.ctx_sampling);
|
||||
}
|
||||
|
||||
if (!ids.empty()) {
|
||||
// Re-decode to restore the recurrent state after checkpoint restore.
|
||||
const int n_re = (int)ids.size();
|
||||
llama_batch re_batch = llama_batch_init(n_re, 0, 1);
|
||||
common_batch_add(re_batch, slot.spec_ckpt.sampled, slot.spec_ckpt.n_past, { slot.id }, n_re == 1);
|
||||
for (int j = 0; j < n_re - 1; j++) {
|
||||
const bool is_last = (j == n_re - 2);
|
||||
common_batch_add(re_batch, ids[j], slot.spec_ckpt.n_past + 1 + j, { slot.id }, is_last);
|
||||
}
|
||||
|
||||
if (slot.has_mtp) {
|
||||
llama_set_embeddings(ctx, true);
|
||||
}
|
||||
|
||||
const int ret = llama_decode(ctx, re_batch);
|
||||
if (ret != 0) {
|
||||
SLT_ERR(slot, "failed to re-decode accepted tokens after checkpoint restore: %d\n", ret);
|
||||
}
|
||||
|
||||
if (slot.has_mtp) {
|
||||
llama_set_embeddings(ctx, false);
|
||||
const int n_embd = llama_model_n_embd(llama_get_model(ctx));
|
||||
const float * emb = llama_get_embeddings_ith(ctx, -1);
|
||||
if (emb) {
|
||||
slot.mtp_hidden_state.resize(n_embd);
|
||||
memcpy(slot.mtp_hidden_state.data(), emb, n_embd * sizeof(float));
|
||||
}
|
||||
}
|
||||
|
||||
for (llama_token id : ids) {
|
||||
common_sampler_accept(slot.ctx_sampling, ctx, id, true);
|
||||
}
|
||||
|
||||
llama_batch_free(re_batch);
|
||||
SLT_DBG(slot, "spec checkpoint restored (gpu=%s): re-decoded %d tokens (rejected %d drafts)\n",
|
||||
gpu_ckpt ? "yes" : "no", n_re, (int)(n_draft - (ids.size() - 1)));
|
||||
}
|
||||
|
||||
discard_speculative_checkpoint(slot, ctx, gpu_ckpt);
|
||||
}
|
||||
} else {
|
||||
llama_kv_cache_seq_rm(ctx, slot.id, slot.n_past, -1);
|
||||
discard_speculative_checkpoint(slot, ctx, gpu_ckpt);
|
||||
|
||||
@@ -24,6 +24,7 @@ enum slot_command {
|
||||
|
||||
struct server_speculative_checkpoint {
|
||||
bool valid = false;
|
||||
bool per_step_enabled = false; // per-step SSM checkpoints
|
||||
llama_pos n_past = 0;
|
||||
llama_token sampled = LLAMA_TOKEN_NULL;
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
+2
-1
@@ -2526,7 +2526,8 @@ extern "C" {
|
||||
struct ggml_tensor * v,
|
||||
struct ggml_tensor * g,
|
||||
struct ggml_tensor * beta,
|
||||
struct ggml_tensor * state);
|
||||
struct ggml_tensor * state,
|
||||
bool save_all_steps);
|
||||
|
||||
// custom operators
|
||||
|
||||
|
||||
@@ -35,13 +35,14 @@ __global__ void delta_net_recurrent_f32(
|
||||
const float * __restrict__ g, // [n_tokens, 1, n_heads, n_seqs]
|
||||
const float * __restrict__ beta_in, // [1, n_tokens, n_heads, n_seqs]
|
||||
const float * __restrict__ state_in, // [HEAD_DIM, HEAD_DIM*n_heads, 1, n_seqs]
|
||||
float * __restrict__ dst, // output + new_state concatenated
|
||||
float * __restrict__ dst, // output + new_state(s) concatenated
|
||||
const int64_t n_heads,
|
||||
const int64_t gqa_ratio,
|
||||
const int repeat_type,
|
||||
const int64_t n_tokens,
|
||||
const int64_t n_seqs,
|
||||
const int64_t output_offset, // offset where state starts in output
|
||||
const int save_all_states, // 1 = save per-step states, 0 = final only
|
||||
size_t vnb1, size_t vnb2, size_t vnb3) {
|
||||
constexpr int warps_per_head = HEAD_DIM/WARP_SIZE;
|
||||
const int batch_idx = blockIdx.x / (warps_per_head*n_heads);
|
||||
@@ -69,6 +70,9 @@ __global__ void delta_net_recurrent_f32(
|
||||
const int64_t state_head_offset = head_idx * HEAD_DIM * HEAD_DIM;
|
||||
const int64_t state_batch_stride = HEAD_DIM * HEAD_DIM * n_heads;
|
||||
|
||||
// State step stride for save_all_states: HEAD_DIM^2 * n_heads * n_seqs
|
||||
const int64_t state_step_stride = HEAD_DIM * HEAD_DIM * n_heads * n_seqs;
|
||||
|
||||
// Pointers for this batch/head
|
||||
const float * q_ptr = q + batch_idx * qkv_stride_batch_kq + head_idx_kq * qkv_stride_head;
|
||||
const float * k_ptr = k + batch_idx * qkv_stride_batch_kq + head_idx_kq * qkv_stride_head;
|
||||
@@ -157,12 +161,23 @@ __global__ void delta_net_recurrent_f32(
|
||||
state_local[i] = new_state_val;
|
||||
}
|
||||
|
||||
// Save per-step state if requested
|
||||
if (save_all_states) {
|
||||
float * state_step_dst = dst + output_offset + t * state_step_stride + batch_idx * state_batch_stride + state_head_offset;
|
||||
for (int i = 0; i < HEAD_DIM/num_warps; ++i) {
|
||||
int col = num_warps*i + col_idx_0;
|
||||
state_step_dst[col*HEAD_DIM + row_out] = state_local[i];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
__syncthreads();
|
||||
// Copy the final state to its destination
|
||||
for (int i = 0; i < HEAD_DIM/num_warps; ++i) {
|
||||
int col = num_warps*i + col_idx_0;
|
||||
state_dst[col*HEAD_DIM + row_out] = state_local[i];
|
||||
// Copy the final state to its destination.
|
||||
if (!save_all_states) {
|
||||
for (int i = 0; i < HEAD_DIM/num_warps; ++i) {
|
||||
int col = num_warps*i + col_idx_0;
|
||||
state_dst[col*HEAD_DIM + row_out] = state_local[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,9 +195,10 @@ static void delta_net_f32_cuda(
|
||||
const int64_t gqa_ratio,
|
||||
const int repeat_type,
|
||||
const int64_t n_seqs,
|
||||
const int save_all_states,
|
||||
size_t vnb1, size_t vnb2, size_t vnb3,
|
||||
const int device_id,
|
||||
const int cc, // compute capability (e.g., 890 for SM 8.9, 1200 for SM 12.0)
|
||||
const int cc,
|
||||
cudaStream_t stream) {
|
||||
GGML_UNUSED(device_id);
|
||||
GGML_UNUSED(cc);
|
||||
@@ -201,19 +217,19 @@ static void delta_net_f32_cuda(
|
||||
constexpr int threads_per_block = 256;
|
||||
if (head_dim == 64) {
|
||||
delta_net_recurrent_f32<64, threads_per_block><<<num_blocks, threads_per_block, smem_size, stream>>>(
|
||||
q, k, v, g, beta, state_in, dst, n_heads, gqa_ratio, repeat_type, n_tokens, n_seqs, output_offset, vnb1, vnb2, vnb3);
|
||||
q, k, v, g, beta, state_in, dst, n_heads, gqa_ratio, repeat_type, n_tokens, n_seqs, output_offset, save_all_states, vnb1, vnb2, vnb3);
|
||||
} else {
|
||||
delta_net_recurrent_f32<128, threads_per_block><<<num_blocks, threads_per_block, smem_size, stream>>>(
|
||||
q, k, v, g, beta, state_in, dst, n_heads, gqa_ratio, repeat_type, n_tokens, n_seqs, output_offset, vnb1, vnb2, vnb3);
|
||||
q, k, v, g, beta, state_in, dst, n_heads, gqa_ratio, repeat_type, n_tokens, n_seqs, output_offset, save_all_states, vnb1, vnb2, vnb3);
|
||||
}
|
||||
} else {
|
||||
constexpr int threads_per_block = 128;
|
||||
if (head_dim == 64) {
|
||||
delta_net_recurrent_f32<64, threads_per_block><<<num_blocks, threads_per_block, smem_size, stream>>>(
|
||||
q, k, v, g, beta, state_in, dst, n_heads, gqa_ratio, repeat_type, n_tokens, n_seqs, output_offset, vnb1, vnb2, vnb3);
|
||||
q, k, v, g, beta, state_in, dst, n_heads, gqa_ratio, repeat_type, n_tokens, n_seqs, output_offset, save_all_states, vnb1, vnb2, vnb3);
|
||||
} else {
|
||||
delta_net_recurrent_f32<128, threads_per_block><<<num_blocks, threads_per_block, smem_size, stream>>>(
|
||||
q, k, v, g, beta, state_in, dst, n_heads, gqa_ratio, repeat_type, n_tokens, n_seqs, output_offset, vnb1, vnb2, vnb3);
|
||||
q, k, v, g, beta, state_in, dst, n_heads, gqa_ratio, repeat_type, n_tokens, n_seqs, output_offset, save_all_states, vnb1, vnb2, vnb3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,9 +271,12 @@ void ggml_cuda_op_delta_net(ggml_backend_cuda_context & ctx, ggml_tensor * dst)
|
||||
// Verify output tensor size
|
||||
const int64_t output_size = head_dim * n_tokens * n_heads * n_seqs;
|
||||
const int64_t state_size = head_dim * head_dim * n_heads * n_seqs;
|
||||
GGML_ASSERT(ggml_nelements(dst) == output_size + state_size);
|
||||
|
||||
int repeat_type = dst->op_params[0];
|
||||
int save_all_states = dst->op_params[1];
|
||||
|
||||
const int64_t expected_size = save_all_states ? (output_size + n_tokens * state_size) : (output_size + state_size);
|
||||
GGML_ASSERT(ggml_nelements(dst) == expected_size);
|
||||
|
||||
GGML_ASSERT(head_dim <= 256); // Reasonable limit for shared memory
|
||||
|
||||
@@ -274,6 +293,7 @@ void ggml_cuda_op_delta_net(ggml_backend_cuda_context & ctx, ggml_tensor * dst)
|
||||
(const float *)src5->data,
|
||||
(float *)dst->data,
|
||||
head_dim, n_tokens, n_heads, gqa_ratio, repeat_type, n_seqs,
|
||||
save_all_states,
|
||||
src2->nb[1]/sizeof(float), src2->nb[2]/sizeof(float), src2->nb[3]/sizeof(float),
|
||||
device_id, cc,
|
||||
ctx.stream());
|
||||
|
||||
+11
-6
@@ -9940,7 +9940,8 @@ struct ggml_tensor * ggml_delta_net(
|
||||
struct ggml_tensor * v,
|
||||
struct ggml_tensor * g,
|
||||
struct ggml_tensor * beta,
|
||||
struct ggml_tensor * state) {
|
||||
struct ggml_tensor * state,
|
||||
bool save_all_steps) {
|
||||
GGML_ASSERT(ggml_is_contiguous(q));
|
||||
GGML_ASSERT(ggml_is_contiguous(k));
|
||||
GGML_ASSERT(ggml_is_contiguous(state));
|
||||
@@ -9971,9 +9972,11 @@ struct ggml_tensor * ggml_delta_net(
|
||||
const int64_t output_size = S_v * H_v * n_tokens * n_seqs;
|
||||
const int64_t state_size = S_v * S_v * H_v * n_seqs;
|
||||
|
||||
struct ggml_tensor * result = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, output_size + state_size);
|
||||
const int64_t state_slots = save_all_steps ? n_tokens : 1;
|
||||
struct ggml_tensor * result = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, output_size + state_slots * state_size);
|
||||
|
||||
result->op = GGML_OP_DELTA_NET;
|
||||
result->op_params[1] = save_all_steps ? 1 : 0;
|
||||
result->src[0] = q;
|
||||
result->src[1] = k;
|
||||
result->src[2] = v;
|
||||
@@ -22654,17 +22657,19 @@ static void ggml_compute_forward_delta_net_f32(
|
||||
const float * beta_data = (const float *) src4->data;
|
||||
const float * state_in = (const float *) src5->data;
|
||||
float * out_data = (float *) dst->data;
|
||||
float * state_out = out_data + output_size;
|
||||
|
||||
const int ith = params->ith;
|
||||
const int nth = params->nth;
|
||||
|
||||
int repeat_type = dst->op_params[0];
|
||||
// op_params[1] (save_all_steps) is handled by the CUDA backend only;
|
||||
// the CPU path always writes to the single state slot after the output.
|
||||
float * state_working = out_data + output_size;
|
||||
|
||||
if (iqk_fused_delta_net(head_dim, n_heads, gqa_ratio, repeat_type, n_tokens, n_seqs,
|
||||
src2->nb[1]/sizeof(float), src2->nb[2]/sizeof(float), src2->nb[3]/sizeof(float),
|
||||
q_data, k_data, v_data, g_data, beta_data, state_in,
|
||||
out_data, state_out, ith, nth)) {
|
||||
out_data, state_working, ith, nth)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -22694,10 +22699,10 @@ static void ggml_compute_forward_delta_net_f32(
|
||||
const int64_t out_token_stride = head_dim * n_heads;
|
||||
|
||||
for (int64_t i = 0; i < head_dim * head_dim; ++i) {
|
||||
state_out[state_head_offset + i] = state_in[state_head_offset + i];
|
||||
state_working[state_head_offset + i] = state_in[state_head_offset + i];
|
||||
}
|
||||
|
||||
float * state = state_out + state_head_offset;
|
||||
float * state = state_working + state_head_offset;
|
||||
|
||||
for (int64_t t = 0; t < n_tokens; ++t) {
|
||||
const float * q_t = q_data + qkv_head_offset_kq + t * qkv_token_stride;
|
||||
|
||||
+11
-1
@@ -798,12 +798,22 @@ extern "C" {
|
||||
LLAMA_API void llama_kv_cache_clear(
|
||||
struct llama_context * ctx);
|
||||
|
||||
// GPU-resident checkpoint for recurrent/hybrid speculative decoding
|
||||
// Checkpoint for recurrent/hybrid speculative decoding
|
||||
LLAMA_API bool llama_kv_cache_checkpoint_save(struct llama_context * ctx);
|
||||
LLAMA_API bool llama_kv_cache_checkpoint_restore(struct llama_context * ctx);
|
||||
LLAMA_API void llama_kv_cache_checkpoint_delete(struct llama_context * ctx);
|
||||
LLAMA_API bool llama_kv_cache_checkpoint_supported(struct llama_context * ctx);
|
||||
|
||||
// Per-step SSM state checkpoints for speculative decoding
|
||||
// Enables saving recurrent state after each token step during forward pass
|
||||
LLAMA_API bool llama_kv_cache_set_per_step_save(struct llama_context * ctx, bool enable, int max_tokens);
|
||||
// Restore SSM state from per-step checkpoint at given step index
|
||||
LLAMA_API bool llama_kv_cache_per_step_restore(struct llama_context * ctx, int step);
|
||||
|
||||
// Update the position of a recurrent cell for a given sequence.
|
||||
// Used by per-step restore to fix the cell's position to match the restored state.
|
||||
LLAMA_API void llama_kv_cache_set_cell_pos(struct llama_context * ctx, llama_seq_id seq_id, llama_pos pos);
|
||||
|
||||
// Removes all tokens that belong to the specified sequence and have positions in [p0, p1)
|
||||
// Returns false if a partial sequence cannot be removed. Removing a whole sequence never fails
|
||||
// seq_id < 0 : match any sequence
|
||||
|
||||
@@ -59,6 +59,9 @@ struct llama_kv_cache {
|
||||
std::vector<struct ggml_tensor *> v_l;
|
||||
std::vector<struct ggml_tensor *> s_l; // per layer recurrent state storage (Qwen3Next)
|
||||
|
||||
// When true, the delta_net graph builder will enable per-step SSM state saves
|
||||
bool save_per_step_ssm = false;
|
||||
|
||||
std::vector<llama_split_tensor> split_k_l;
|
||||
std::vector<llama_split_tensor> split_v_l;
|
||||
std::vector<llama_split_tensor> split_s_l;
|
||||
@@ -84,6 +87,27 @@ struct llama_kv_cache {
|
||||
|
||||
std::vector<std::vector<ggml_tensor *>> split_s_l_shadow;
|
||||
|
||||
// Per-step SSM state checkpoints for speculative decoding.
|
||||
// One tensor per recurrent layer. Each stores up to max_spec_tokens
|
||||
// copies of the SSM state (not conv state).
|
||||
std::vector<ggml_tensor *> per_step_ssm;
|
||||
|
||||
// Per-step conv feature buffer: stores qkv_mixed features from the
|
||||
// verification forward pass so conv state can be reconstructed at any step.
|
||||
// One tensor per recurrent layer, each sized [conv_dim * max_tokens].
|
||||
std::vector<ggml_tensor *> per_step_qkv;
|
||||
|
||||
int32_t per_step_n_tokens = 0; // actual n_tokens stored in current checkpoint
|
||||
int32_t per_step_max_allocated = 0; // max tokens the per-step buffer can hold
|
||||
int64_t per_step_ssm_state_size = 0; // SSM state size per step per layer
|
||||
int64_t per_step_conv_state_dim = 0; // conv state dim (for partial restore offset)
|
||||
int64_t per_step_conv_dim = 0; // conv_dim = key_dim*2 + value_dim (feature vector length)
|
||||
int32_t per_step_d_conv = 0; // d_conv from hparams
|
||||
|
||||
// Separate storage for per-step allocations (so we can reallocate independently)
|
||||
std::vector<struct ggml_context *> per_step_ctxs;
|
||||
std::vector<ggml_backend_buffer_t> per_step_bufs;
|
||||
|
||||
std::vector<struct ggml_context *> shadow_ctxs;
|
||||
std::vector<ggml_backend_buffer_t> shadow_bufs;
|
||||
|
||||
@@ -97,6 +121,12 @@ struct llama_kv_cache {
|
||||
for (ggml_backend_buffer_t buf : shadow_bufs) {
|
||||
ggml_backend_buffer_free(buf);
|
||||
}
|
||||
for (struct ggml_context * ctx : per_step_ctxs) {
|
||||
ggml_free(ctx);
|
||||
}
|
||||
for (ggml_backend_buffer_t buf : per_step_bufs) {
|
||||
ggml_backend_buffer_free(buf);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -108,6 +138,10 @@ struct llama_kv_cache {
|
||||
bool checkpoint_restore();
|
||||
void checkpoint_delete();
|
||||
|
||||
// Per-step checkpoint: allocate, restore step k's full state (SSM + conv) to cache
|
||||
bool per_step_alloc(int max_tokens);
|
||||
bool per_step_restore(int step);
|
||||
|
||||
~llama_kv_cache() {
|
||||
for (struct ggml_context * ctx : ctxs) {
|
||||
ggml_free(ctx);
|
||||
|
||||
+50
-7
@@ -70,6 +70,7 @@ delta_net::delta_net(llama_context & _lctx, const llama_batch & _batch) : lctx(_
|
||||
GGML_ASSERT((uint32_t) s < qnext_state_slots);
|
||||
}
|
||||
|
||||
save_per_step_states = lctx.kv_self.save_per_step_ssm && batch.n_tokens > 1;
|
||||
}
|
||||
|
||||
delta_net::~delta_net() = default;
|
||||
@@ -77,7 +78,9 @@ delta_net::~delta_net() = default;
|
||||
std::pair<ggml_tensor *, ggml_tensor *> delta_net::build_fused_delta_net(ggml_context * ctx0,
|
||||
ggml_tensor * q, ggml_tensor * k, ggml_tensor * v,
|
||||
ggml_tensor * g, ggml_tensor * beta, ggml_tensor * state,
|
||||
int il, const llm_build_cb & cb, int repeat_type) {
|
||||
int il, const llm_build_cb & cb, int repeat_type,
|
||||
bool save_all_steps,
|
||||
ggml_cgraph * gf, ggml_tensor * per_step_ckpt) {
|
||||
|
||||
const int64_t S_k = q->ne[0];
|
||||
const int64_t H_k = q->ne[2];
|
||||
@@ -119,7 +122,7 @@ std::pair<ggml_tensor *, ggml_tensor *> delta_net::build_fused_delta_net(ggml_co
|
||||
cb(beta, "beta_fused", il);
|
||||
cb(state_flat,"state_fused", il);
|
||||
|
||||
ggml_tensor * fused_result = ggml_delta_net(ctx0, q, k, v, g, beta, state_flat);
|
||||
ggml_tensor * fused_result = ggml_delta_net(ctx0, q, k, v, g, beta, state_flat, save_all_steps);
|
||||
cb(fused_result, "delta_net_fused_raw", il);
|
||||
fused_result->op_params[0] = repeat_type;
|
||||
|
||||
@@ -131,15 +134,35 @@ std::pair<ggml_tensor *, ggml_tensor *> delta_net::build_fused_delta_net(ggml_co
|
||||
ggml_row_size(fused_result->type, S_v),
|
||||
ggml_row_size(fused_result->type, S_v * H_v),
|
||||
ggml_row_size(fused_result->type, S_v * H_v * n_tokens), 0);
|
||||
//output_tokens = ggml_cont_4d(ctx0, output_tokens, S_v, H_v, n_tokens, n_seqs);
|
||||
|
||||
// per-step states are at [output_size, output_size + n_tokens*state_size)
|
||||
const int64_t last_state_offset = save_all_steps
|
||||
? (output_size + (n_tokens - 1) * state_size)
|
||||
: output_size;
|
||||
|
||||
ggml_tensor * new_state_flat = ggml_view_1d(ctx0, fused_result, state_size,
|
||||
output_size * ggml_element_size(fused_result));
|
||||
last_state_offset * ggml_element_size(fused_result));
|
||||
ggml_tensor * new_state = ggml_reshape_4d(ctx0, new_state_flat, S_v, S_v, H_v, n_seqs);
|
||||
|
||||
cb(output_tokens, "output_tokens", il);
|
||||
cb(new_state, "new_state", il);
|
||||
|
||||
// Copy all per-step SSM states to persistent checkpoint tensor
|
||||
if (save_all_steps && per_step_ckpt != nullptr && gf != nullptr && n_tokens > 1) {
|
||||
const int64_t per_step_total = n_tokens * state_size;
|
||||
if (per_step_total <= ggml_nelements(per_step_ckpt)) {
|
||||
ggml_tensor * all_steps_src = ggml_view_1d(ctx0, fused_result, per_step_total,
|
||||
output_size * ggml_element_size(fused_result));
|
||||
ggml_tensor * ckpt_dst = ggml_view_1d(ctx0, per_step_ckpt, per_step_total, 0);
|
||||
auto ckpt_cpy = ggml_cpy(ctx0, all_steps_src, ckpt_dst);
|
||||
cb(ckpt_cpy, "per_step_ckpt_cpy", il);
|
||||
ggml_build_forward_expand(gf, ckpt_cpy);
|
||||
} else {
|
||||
LLAMA_LOG_WARN("%s: per-step checkpoint tensor too small for %lld tokens (need %lld, have %lld), skipping per-step save\n",
|
||||
__func__, (long long)n_tokens, (long long)per_step_total, (long long)ggml_nelements(per_step_ckpt));
|
||||
}
|
||||
}
|
||||
|
||||
return {output_tokens, new_state};
|
||||
}
|
||||
|
||||
@@ -281,7 +304,8 @@ ggml_tensor * delta_net::build_qkv(ggml_context * ctx0, ggml_tensor * state_stor
|
||||
ggml_tensor * qkv_mixed, ggml_tensor * inp_s_seq_qnext, ggml_tensor * beta, ggml_tensor * gate,
|
||||
int64_t head_k_dim, int64_t num_k_heads, int64_t head_v_dim, int64_t num_v_heads, int64_t ssm_d_conv,
|
||||
int64_t state_seq_id_local, uint32_t qnext_state_slots, bool reset_state_local,
|
||||
float eps_norm, int repeat_type, int il, const llm_build_cb & cb, ggml_cgraph * gf) {
|
||||
float eps_norm, int repeat_type, int il, const llm_build_cb & cb, ggml_cgraph * gf,
|
||||
bool save_per_step_states, ggml_tensor * per_step_ckpt) {
|
||||
const int64_t key_dim = head_k_dim * num_k_heads;
|
||||
const int64_t value_dim = head_v_dim * num_v_heads;
|
||||
const int64_t conv_dim = key_dim * 2 + value_dim;
|
||||
@@ -366,7 +390,8 @@ ggml_tensor * delta_net::build_qkv(ggml_context * ctx0, ggml_tensor * state_stor
|
||||
cb(q_conv, "q_conv_normed", il);
|
||||
cb(k_conv, "k_conv_normed", il);
|
||||
|
||||
auto [output, new_state] = build_fused_delta_net(ctx0, q_conv, k_conv, v_conv, gate, beta, state, il, cb, repeat_type);
|
||||
auto [output, new_state] = build_fused_delta_net(ctx0, q_conv, k_conv, v_conv, gate, beta, state, il, cb, repeat_type,
|
||||
save_per_step_states, gf, per_step_ckpt);
|
||||
|
||||
cb(output, "attn_output", il);
|
||||
cb(new_state, "new_state", il);
|
||||
@@ -566,11 +591,29 @@ ggml_tensor * delta_net::build_layer_attn_linear_core(ggml_context * ctx0, ggml_
|
||||
auto [beta, gate] = build_beta_gate(lctx, ctx0, model.layers[il].ssm_beta_alpha, model.layers[il].ssm_beta, model.layers[il].ssm_alpha,
|
||||
model.layers[il].ssm_dt, model.layers[il].ssm_a, num_k_heads, num_v_heads, n_seqs, cur, il, cb, gf);
|
||||
|
||||
// Get per-step checkpoint tensor if available
|
||||
ggml_tensor * per_step_ckpt = nullptr;
|
||||
if (save_per_step_states && il < (int)kv_self.ckpt.per_step_ssm.size()) {
|
||||
per_step_ckpt = kv_self.ckpt.per_step_ssm[il];
|
||||
}
|
||||
|
||||
// Save qkv_mixed features for per-step conv state reconstruction
|
||||
if (save_per_step_states && il < (int)kv_self.ckpt.per_step_qkv.size() && kv_self.ckpt.per_step_qkv[il] != nullptr) {
|
||||
const int64_t conv_dim = qkv_mixed->ne[0];
|
||||
const int64_t n_tok_qkv = qkv_mixed->ne[1] * qkv_mixed->ne[2];
|
||||
ggml_tensor * qkv_flat = ggml_reshape_2d(ctx0, qkv_mixed, conv_dim, n_tok_qkv);
|
||||
ggml_tensor * qkv_dst = ggml_view_2d(ctx0, kv_self.ckpt.per_step_qkv[il],
|
||||
conv_dim, n_tok_qkv, conv_dim * sizeof(float), 0);
|
||||
auto qkv_cpy = ggml_cpy(ctx0, qkv_flat, qkv_dst);
|
||||
ggml_build_forward_expand(gf, qkv_cpy);
|
||||
}
|
||||
|
||||
auto output = build_qkv(ctx0, kv_self.s_l[il], model.layers[il].ssm_conv1d,
|
||||
qkv_mixed, inp_s_seq_qnext, beta, gate,
|
||||
head_k_dim, num_k_heads, head_v_dim, num_v_heads, hparams.ssm_d_conv,
|
||||
state_seq_id_local, qnext_state_slots, reset_state_local, hparams.f_norm_rms_eps,
|
||||
model.layers[il].ssm_beta_alpha ? 0 : 1, il, cb, gf);
|
||||
model.layers[il].ssm_beta_alpha ? 0 : 1, il, cb, gf,
|
||||
save_per_step_states, per_step_ckpt);
|
||||
|
||||
auto gated_output = build_gated_output(lctx, ctx0, model.layers[il].ssm_norm, model.layers[il].ssm_out, output, z, head_v_dim, num_v_heads, n_tok, il, cb);
|
||||
if (inp_out_ids) {
|
||||
|
||||
@@ -8,10 +8,16 @@ struct delta_net {
|
||||
delta_net(llama_context & lctx, const llama_batch & batch);
|
||||
~delta_net();
|
||||
|
||||
// When true, the delta_net kernel saves recurrent state after each token step.
|
||||
// Used for speculative decoding to enable per-step state checkpoint restoration.
|
||||
bool save_per_step_states = false;
|
||||
|
||||
static std::pair<ggml_tensor *, ggml_tensor *> build_fused_delta_net(ggml_context * ctx0,
|
||||
ggml_tensor * q, ggml_tensor * k, ggml_tensor * v,
|
||||
ggml_tensor * g, ggml_tensor * beta, ggml_tensor * state,
|
||||
int il, const llm_build_cb & cb, int repeat_type);
|
||||
int il, const llm_build_cb & cb, int repeat_type,
|
||||
bool save_all_steps = false,
|
||||
ggml_cgraph * gf = nullptr, ggml_tensor * per_step_ckpt = nullptr);
|
||||
|
||||
ggml_tensor * build_layer_attn_linear_core(ggml_context * ctx0, ggml_cgraph * gf,
|
||||
ggml_tensor * cur, ggml_tensor * inp_s_seq_qnext, ggml_tensor * inp_out_ids,
|
||||
@@ -46,7 +52,8 @@ private:
|
||||
ggml_tensor * qkv_mixed, ggml_tensor * inp_s_seq_qnext, ggml_tensor * beta, ggml_tensor * gate,
|
||||
int64_t head_k_dim, int64_t num_k_heads, int64_t head_v_dim, int64_t num_v_heads, int64_t ssm_d_conv,
|
||||
int64_t state_seq_id_local, uint32_t qnext_state_slots, bool reset_state_local,
|
||||
float eps_norm, int repeat_type, int il, const llm_build_cb & cb, ggml_cgraph * gf);
|
||||
float eps_norm, int repeat_type, int il, const llm_build_cb & cb, ggml_cgraph * gf,
|
||||
bool save_per_step_states = false, ggml_tensor * per_step_ckpt = nullptr);
|
||||
|
||||
static ggml_tensor * build_gated_output(llama_context & lctx, ggml_context * ctx0, ggml_tensor * ssm_norm, ggml_tensor * ssm_out,
|
||||
ggml_tensor * output, ggml_tensor * z, int64_t head_v_dim, int64_t num_v_heads, int64_t n_tok, int il, const llm_build_cb & cb);
|
||||
|
||||
+285
-37
@@ -1227,7 +1227,10 @@ bool llama_kv_cache::checkpoint_alloc_shadows() {
|
||||
int split_idx; // -1 for non-split
|
||||
};
|
||||
|
||||
std::map<ggml_backend_buffer_type_t, std::vector<tensor_entry>> buft_entries;
|
||||
const bool conv_only_shadow = save_per_step_ssm && ckpt.per_step_conv_state_dim > 0;
|
||||
std::vector<tensor_entry> nonsplit_entries;
|
||||
|
||||
std::map<ggml_backend_buffer_type_t, std::vector<tensor_entry>> split_buft_entries;
|
||||
|
||||
uint32_t split_s_idx = 0;
|
||||
for (uint32_t il = 0; il < n_layer; ++il) {
|
||||
@@ -1237,25 +1240,21 @@ bool llama_kv_cache::checkpoint_alloc_shadows() {
|
||||
|
||||
auto * extra = s_l[il]->extra;
|
||||
if (extra != nullptr) {
|
||||
// Split tensor — shadow each per-device split tensor individually
|
||||
auto * split_info = (const ggml_split_tensor_t *)extra;
|
||||
for (int d = 0; d < split_info->n_device; ++d) {
|
||||
if (split_info->splits[d] == nullptr) continue;
|
||||
ggml_backend_buffer_type_t buft = ggml_backend_buffer_get_type(split_info->splits[d]->buffer);
|
||||
buft_entries[buft].push_back({split_info->splits[d], il, d});
|
||||
split_buft_entries[buft].push_back({split_info->splits[d], il, d});
|
||||
}
|
||||
split_s_idx++;
|
||||
} else {
|
||||
// Non-split tensor
|
||||
ggml_backend_buffer_type_t buft = ggml_backend_buffer_get_type(s_l[il]->buffer);
|
||||
buft_entries[buft].push_back({s_l[il], il, -1});
|
||||
nonsplit_entries.push_back({s_l[il], il, -1});
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate shadow tensors grouped by buffer type
|
||||
for (auto & [buft, entries] : buft_entries) {
|
||||
if (!nonsplit_entries.empty()) {
|
||||
ggml_init_params params = {
|
||||
/*.mem_size =*/ entries.size() * ggml_tensor_overhead(),
|
||||
/*.mem_size =*/ nonsplit_entries.size() * ggml_tensor_overhead(),
|
||||
/*.mem_buffer =*/ NULL,
|
||||
/*.no_alloc =*/ true,
|
||||
};
|
||||
@@ -1265,35 +1264,63 @@ bool llama_kv_cache::checkpoint_alloc_shadows() {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto & entry : entries) {
|
||||
ggml_tensor * shadow = ggml_dup_tensor(ctx, entry.primary);
|
||||
ggml_format_name(shadow, "shadow_s_l%d_d%d", entry.il, entry.split_idx);
|
||||
entry.primary = shadow; // temporarily store shadow in entry for buffer allocation
|
||||
for (auto & entry : nonsplit_entries) {
|
||||
// Only need the conv portion when per-step is active.
|
||||
const int64_t nelems = conv_only_shadow
|
||||
? ckpt.per_step_conv_state_dim
|
||||
: (int64_t)ggml_nelements(entry.primary);
|
||||
ggml_tensor * shadow = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, nelems);
|
||||
ggml_format_name(shadow, "shadow_s_l%d", entry.il);
|
||||
ckpt.s_l_shadow[entry.il] = shadow;
|
||||
}
|
||||
|
||||
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors_from_buft(ctx, buft);
|
||||
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors_from_buft(ctx, ggml_backend_cpu_buffer_type());
|
||||
if (!buf) {
|
||||
LLAMA_LOG_ERROR("%s: failed to allocate buffer for shadow tensors\n", __func__);
|
||||
LLAMA_LOG_ERROR("%s: failed to allocate CPU buffer for shadow tensors\n", __func__);
|
||||
ggml_free(ctx);
|
||||
return false;
|
||||
}
|
||||
ggml_backend_buffer_clear(buf, 0);
|
||||
LLAMA_LOG_INFO("%s: %10s shadow buffer size = %8.2f MiB\n", __func__,
|
||||
LLAMA_LOG_INFO("%s: CPU shadow buffer = %8.2f MiB (%s)\n", __func__,
|
||||
ggml_backend_buffer_get_size(buf) / 1024.0 / 1024.0,
|
||||
conv_only_shadow ? "conv-state only" : "full recurrent state");
|
||||
ckpt.shadow_ctxs.push_back(ctx);
|
||||
ckpt.shadow_bufs.push_back(buf);
|
||||
}
|
||||
|
||||
// Allocate split shadows on their respective devices
|
||||
for (auto & [buft, entries] : split_buft_entries) {
|
||||
ggml_init_params params = {
|
||||
/*.mem_size =*/ entries.size() * ggml_tensor_overhead(),
|
||||
/*.mem_buffer =*/ NULL,
|
||||
/*.no_alloc =*/ true,
|
||||
};
|
||||
ggml_context * ctx = ggml_init(params);
|
||||
if (!ctx) {
|
||||
LLAMA_LOG_ERROR("%s: failed to create ggml context for split shadow tensors\n", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto & entry : entries) {
|
||||
ggml_tensor * shadow = ggml_dup_tensor(ctx, entry.primary);
|
||||
ggml_format_name(shadow, "shadow_s_l%d_d%d", entry.il, entry.split_idx);
|
||||
entry.primary = shadow;
|
||||
}
|
||||
|
||||
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors_from_buft(ctx, buft);
|
||||
if (!buf) {
|
||||
LLAMA_LOG_ERROR("%s: failed to allocate buffer for split shadow tensors\n", __func__);
|
||||
ggml_free(ctx);
|
||||
return false;
|
||||
}
|
||||
ggml_backend_buffer_clear(buf, 0);
|
||||
LLAMA_LOG_INFO("%s: %10s split shadow buffer = %8.2f MiB\n", __func__,
|
||||
ggml_backend_buffer_name(buf), ggml_backend_buffer_get_size(buf) / 1024.0 / 1024.0);
|
||||
ckpt.shadow_ctxs.push_back(ctx);
|
||||
ckpt.shadow_bufs.push_back(buf);
|
||||
}
|
||||
|
||||
// Build shadow lookup: for non-split layers, store directly in s_l_shadow
|
||||
for (auto & [buft, entries] : buft_entries) {
|
||||
for (auto & entry : entries) {
|
||||
if (entry.split_idx == -1) {
|
||||
ckpt.s_l_shadow[entry.il] = entry.primary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build shadow split tensors
|
||||
// Build split shadow lookup
|
||||
ckpt.split_s_l_shadow.resize(split_s_l.size());
|
||||
split_s_idx = 0;
|
||||
for (uint32_t il = 0; il < n_layer; ++il) {
|
||||
@@ -1307,9 +1334,8 @@ bool llama_kv_cache::checkpoint_alloc_shadows() {
|
||||
|
||||
for (int d = 0; d < split_info->n_device; ++d) {
|
||||
if (split_info->splits[d] == nullptr) continue;
|
||||
// Find corresponding shadow tensor from buft_entries
|
||||
ggml_backend_buffer_type_t buft = ggml_backend_buffer_get_type(split_info->splits[d]->buffer);
|
||||
for (auto & entry : buft_entries[buft]) {
|
||||
for (auto & entry : split_buft_entries[buft]) {
|
||||
if (entry.il == il && entry.split_idx == d) {
|
||||
shadow_split[d] = entry.primary;
|
||||
break;
|
||||
@@ -1330,12 +1356,10 @@ bool llama_kv_cache::checkpoint_save() {
|
||||
|
||||
const uint32_t n_layer = (uint32_t)s_l.size();
|
||||
|
||||
// 1. Snapshot cell metadata (small, host-side)
|
||||
ckpt.cells_snapshot = cells;
|
||||
ckpt.head_snapshot = head;
|
||||
ckpt.used_snapshot = used;
|
||||
|
||||
// 2. Copy tensors: primary -> shadow (GPU-to-GPU when on same device)
|
||||
uint32_t split_s_idx = 0;
|
||||
for (uint32_t il = 0; il < n_layer; ++il) {
|
||||
if (s_l[il] == nullptr) {
|
||||
@@ -1343,7 +1367,6 @@ bool llama_kv_cache::checkpoint_save() {
|
||||
}
|
||||
|
||||
if (s_l[il]->extra != nullptr) {
|
||||
// Split tensor — copy per-device splits individually
|
||||
auto * split_info = (const ggml_split_tensor_t *)s_l[il]->extra;
|
||||
auto & shadow_split = ckpt.split_s_l_shadow[split_s_idx];
|
||||
for (int d = 0; d < split_info->n_device; ++d) {
|
||||
@@ -1353,8 +1376,8 @@ bool llama_kv_cache::checkpoint_save() {
|
||||
}
|
||||
split_s_idx++;
|
||||
} else {
|
||||
// Non-split tensor
|
||||
ggml_backend_tensor_copy(s_l[il], ckpt.s_l_shadow[il]);
|
||||
const size_t nbytes = ggml_nbytes(ckpt.s_l_shadow[il]);
|
||||
ggml_backend_tensor_get(s_l[il], ckpt.s_l_shadow[il]->data, 0, nbytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1370,12 +1393,10 @@ bool llama_kv_cache::checkpoint_restore() {
|
||||
|
||||
const uint32_t n_layer = (uint32_t)s_l.size();
|
||||
|
||||
// 1. Restore cell metadata
|
||||
cells = ckpt.cells_snapshot;
|
||||
head = ckpt.head_snapshot;
|
||||
used = ckpt.used_snapshot;
|
||||
|
||||
// 2. Copy tensors: shadow -> primary (GPU-to-GPU)
|
||||
uint32_t split_s_idx = 0;
|
||||
for (uint32_t il = 0; il < n_layer; ++il) {
|
||||
if (s_l[il] == nullptr) {
|
||||
@@ -1383,7 +1404,6 @@ bool llama_kv_cache::checkpoint_restore() {
|
||||
}
|
||||
|
||||
if (s_l[il]->extra != nullptr) {
|
||||
// Split tensor — copy per-device splits individually
|
||||
auto * split_info = (const ggml_split_tensor_t *)s_l[il]->extra;
|
||||
auto & shadow_split = ckpt.split_s_l_shadow[split_s_idx];
|
||||
for (int d = 0; d < split_info->n_device; ++d) {
|
||||
@@ -1393,7 +1413,7 @@ bool llama_kv_cache::checkpoint_restore() {
|
||||
}
|
||||
split_s_idx++;
|
||||
} else {
|
||||
// Non-split tensor
|
||||
GGML_ASSERT(ggml_nbytes(ckpt.s_l_shadow[il]) == ggml_nbytes(s_l[il]));
|
||||
ggml_backend_tensor_copy(ckpt.s_l_shadow[il], s_l[il]);
|
||||
}
|
||||
}
|
||||
@@ -1405,6 +1425,159 @@ void llama_kv_cache::checkpoint_delete() {
|
||||
ckpt.saved = false;
|
||||
}
|
||||
|
||||
bool llama_kv_cache::per_step_alloc(int max_tokens) {
|
||||
if (ckpt.per_step_max_allocated >= max_tokens) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ckpt.per_step_ssm.empty()) {
|
||||
for (struct ggml_context * ctx : ckpt.per_step_ctxs) {
|
||||
ggml_free(ctx);
|
||||
}
|
||||
for (ggml_backend_buffer_t buf : ckpt.per_step_bufs) {
|
||||
ggml_backend_buffer_free(buf);
|
||||
}
|
||||
ckpt.per_step_ctxs.clear();
|
||||
ckpt.per_step_bufs.clear();
|
||||
ckpt.per_step_ssm.clear();
|
||||
ckpt.per_step_qkv.clear();
|
||||
ckpt.per_step_max_allocated = 0;
|
||||
}
|
||||
|
||||
const uint32_t n_layer = (uint32_t)s_l.size();
|
||||
ckpt.per_step_ssm.resize(n_layer, nullptr);
|
||||
ckpt.per_step_qkv.resize(n_layer, nullptr);
|
||||
|
||||
const int64_t ssm_state_dim = ckpt.per_step_ssm_state_size;
|
||||
const int64_t conv_dim = ckpt.per_step_conv_dim;
|
||||
if (ssm_state_dim <= 0 || conv_dim <= 0) {
|
||||
LLAMA_LOG_ERROR("%s: per_step dimensions not set (ssm=%lld, conv_dim=%lld)\n",
|
||||
__func__, (long long)ssm_state_dim, (long long)conv_dim);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::map<ggml_backend_buffer_type_t, std::vector<std::pair<uint32_t, ggml_backend_buffer_type_t>>> buft_layers;
|
||||
|
||||
for (uint32_t il = 0; il < n_layer; ++il) {
|
||||
if (s_l[il] == nullptr) continue;
|
||||
if (s_l[il]->extra != nullptr) continue; // skip split tensors
|
||||
|
||||
ggml_backend_buffer_type_t buft = ggml_backend_buffer_get_type(s_l[il]->buffer);
|
||||
buft_layers[buft].push_back({il, buft});
|
||||
}
|
||||
|
||||
for (auto & [buft, layers] : buft_layers) {
|
||||
// 2 tensors per layer: SSM states + qkv features
|
||||
ggml_init_params params = {
|
||||
/*.mem_size =*/ layers.size() * 2 * ggml_tensor_overhead(),
|
||||
/*.mem_buffer =*/ NULL,
|
||||
/*.no_alloc =*/ true,
|
||||
};
|
||||
ggml_context * ctx = ggml_init(params);
|
||||
if (!ctx) {
|
||||
LLAMA_LOG_ERROR("%s: failed to create ggml context for per-step checkpoints\n", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto & [il, bt] : layers) {
|
||||
// SSM state: max_tokens * ssm_state_dim
|
||||
ggml_tensor * t_ssm = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, (int64_t)max_tokens * ssm_state_dim);
|
||||
ggml_format_name(t_ssm, "per_step_ssm_l%d", il);
|
||||
ckpt.per_step_ssm[il] = t_ssm;
|
||||
|
||||
// Conv features (qkv_mixed): max_tokens * conv_dim
|
||||
ggml_tensor * t_qkv = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, (int64_t)max_tokens * conv_dim);
|
||||
ggml_format_name(t_qkv, "per_step_qkv_l%d", il);
|
||||
ckpt.per_step_qkv[il] = t_qkv;
|
||||
}
|
||||
|
||||
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors_from_buft(ctx, buft);
|
||||
if (!buf) {
|
||||
LLAMA_LOG_ERROR("%s: failed to allocate buffer for per-step checkpoints\n", __func__);
|
||||
ggml_free(ctx);
|
||||
return false;
|
||||
}
|
||||
ggml_backend_buffer_clear(buf, 0);
|
||||
LLAMA_LOG_INFO("%s: %10s per-step buffer = %8.2f MiB (max_tokens=%d, ssm+qkv)\n", __func__,
|
||||
ggml_backend_buffer_name(buf), ggml_backend_buffer_get_size(buf) / 1024.0 / 1024.0, max_tokens);
|
||||
ckpt.per_step_ctxs.push_back(ctx);
|
||||
ckpt.per_step_bufs.push_back(buf);
|
||||
}
|
||||
|
||||
ckpt.per_step_max_allocated = max_tokens;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool llama_kv_cache::per_step_restore(int step) {
|
||||
if (ckpt.per_step_ssm.empty() || step < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int64_t ssm_state_dim = ckpt.per_step_ssm_state_size;
|
||||
const int64_t conv_state_dim = ckpt.per_step_conv_state_dim;
|
||||
const int64_t conv_dim = ckpt.per_step_conv_dim;
|
||||
const int32_t d_conv = ckpt.per_step_d_conv;
|
||||
if (ssm_state_dim <= 0 || conv_dim <= 0 || d_conv <= 1) return false;
|
||||
|
||||
const int64_t ssm_bytes = ssm_state_dim * sizeof(float);
|
||||
const int64_t conv_bytes = conv_state_dim * sizeof(float);
|
||||
const int32_t d_conv_m1 = d_conv - 1; // number of columns in conv state
|
||||
|
||||
std::vector<float> ssm_buf(ssm_state_dim);
|
||||
std::vector<float> conv_buf(conv_state_dim); // reconstructed conv state
|
||||
std::vector<float> old_conv_buf(conv_state_dim); // pre-spec conv state from shadow
|
||||
const int64_t qkv_needed = (int64_t)(step + 1) * conv_dim;
|
||||
std::vector<float> qkv_buf(qkv_needed);
|
||||
|
||||
const uint32_t n_layer = (uint32_t)s_l.size();
|
||||
int n_restored = 0;
|
||||
for (uint32_t il = 0; il < n_layer; ++il) {
|
||||
if (s_l[il] == nullptr || ckpt.per_step_ssm[il] == nullptr) continue;
|
||||
if (s_l[il]->extra != nullptr) continue;
|
||||
|
||||
ggml_backend_tensor_get(ckpt.per_step_ssm[il], ssm_buf.data(),
|
||||
(size_t)step * ssm_bytes, ssm_bytes);
|
||||
|
||||
if (ckpt.s_l_shadow[il] != nullptr) {
|
||||
ggml_backend_tensor_get(ckpt.s_l_shadow[il], old_conv_buf.data(), 0, conv_bytes);
|
||||
} else {
|
||||
memset(old_conv_buf.data(), 0, conv_bytes);
|
||||
}
|
||||
|
||||
if (ckpt.per_step_qkv[il] != nullptr) {
|
||||
ggml_backend_tensor_get(ckpt.per_step_qkv[il], qkv_buf.data(), 0, qkv_needed * sizeof(float));
|
||||
} else {
|
||||
memset(qkv_buf.data(), 0, qkv_needed * sizeof(float));
|
||||
}
|
||||
|
||||
for (int32_t col = 0; col < d_conv_m1; col++) {
|
||||
int32_t src_token = step - (d_conv_m1 - 1) + col; // e.g., K-2, K-1, K for d_conv=4
|
||||
if (src_token >= 0) {
|
||||
for (int64_t d = 0; d < conv_dim; d++) {
|
||||
conv_buf[col + d * d_conv_m1] = qkv_buf[d + (int64_t)src_token * conv_dim];
|
||||
}
|
||||
} else {
|
||||
int32_t old_col = d_conv_m1 + src_token; // maps to 0, 1, ... for early steps
|
||||
if (old_col >= 0 && old_col < d_conv_m1) {
|
||||
for (int64_t d = 0; d < conv_dim; d++) {
|
||||
conv_buf[col + d * d_conv_m1] = old_conv_buf[old_col + d * d_conv_m1];
|
||||
}
|
||||
} else {
|
||||
for (int64_t d = 0; d < conv_dim; d++) {
|
||||
conv_buf[col + d * d_conv_m1] = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ggml_backend_tensor_set(s_l[il], conv_buf.data(), 0, conv_bytes);
|
||||
ggml_backend_tensor_set(s_l[il], ssm_buf.data(), conv_bytes, ssm_bytes);
|
||||
n_restored++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void llama_kv_cache_clear(struct llama_kv_cache & cache) {
|
||||
for (int32_t i = 0; i < (int32_t) cache.size; ++i) {
|
||||
cache.cells[i].pos = -1;
|
||||
@@ -6676,6 +6849,81 @@ bool llama_kv_cache_checkpoint_supported(struct llama_context * ctx) {
|
||||
return ctx->kv_self.checkpoint_supported();
|
||||
}
|
||||
|
||||
bool llama_kv_cache_set_per_step_save(struct llama_context * ctx, bool enable, int max_tokens) {
|
||||
auto & kv = ctx->kv_self;
|
||||
|
||||
if (!enable) {
|
||||
kv.save_per_step_ssm = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if any recurrent layer has split state (graph split mode).
|
||||
// Per-step restore can't handle split tensors — fall back to legacy path.
|
||||
// Also require ALL recurrent layers to be on GPU (non-host backend),
|
||||
// since per-step saves only work in the CUDA delta_net kernel.
|
||||
// If any recurrent layer is on CPU, the per-step restore would leave
|
||||
// CPU layers at the pre-spec position while GPU layers advance to step K.
|
||||
bool has_gpu_layer = false;
|
||||
bool has_cpu_layer = false;
|
||||
for (const auto * sl : kv.s_l) {
|
||||
if (sl == nullptr) continue;
|
||||
if (sl->extra != nullptr) {
|
||||
kv.save_per_step_ssm = false;
|
||||
return false;
|
||||
}
|
||||
if (sl->buffer && !ggml_backend_buffer_is_host(sl->buffer)) {
|
||||
has_gpu_layer = true;
|
||||
} else if (sl->buffer) {
|
||||
has_cpu_layer = true;
|
||||
}
|
||||
}
|
||||
if (!has_gpu_layer || has_cpu_layer) {
|
||||
// All recurrent layers on CPU or mixed CPU/GPU — per-step not supported.
|
||||
if (has_cpu_layer && has_gpu_layer) {
|
||||
LLAMA_LOG_INFO("%s: per-step disabled — mixed CPU/GPU recurrent layers\n", __func__);
|
||||
}
|
||||
kv.save_per_step_ssm = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize per-step checkpoint dimensions from model hparams (if not already set)
|
||||
if (kv.ckpt.per_step_ssm_state_size <= 0) {
|
||||
const auto & hparams = ctx->model.hparams;
|
||||
const int64_t num_v_heads = hparams.ssm_dt_rank;
|
||||
const int64_t head_v_dim = hparams.ssm_d_inner / num_v_heads;
|
||||
const int64_t head_k_dim = hparams.ssm_d_state;
|
||||
const int64_t num_k_heads = hparams.ssm_n_group;
|
||||
const int64_t key_dim = head_k_dim * num_k_heads;
|
||||
const int64_t value_dim = head_v_dim * num_v_heads;
|
||||
const int64_t conv_dim = key_dim * 2 + value_dim;
|
||||
|
||||
kv.ckpt.per_step_ssm_state_size = head_v_dim * head_v_dim * num_v_heads;
|
||||
kv.ckpt.per_step_conv_state_dim = (hparams.ssm_d_conv - 1) * conv_dim;
|
||||
kv.ckpt.per_step_conv_dim = conv_dim;
|
||||
kv.ckpt.per_step_d_conv = hparams.ssm_d_conv;
|
||||
}
|
||||
|
||||
// Allocate (or reallocate if max_tokens is larger than current)
|
||||
if (!kv.per_step_alloc(max_tokens)) {
|
||||
kv.save_per_step_ssm = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
kv.save_per_step_ssm = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool llama_kv_cache_per_step_restore(struct llama_context * ctx, int step) {
|
||||
return ctx->kv_self.per_step_restore(step);
|
||||
}
|
||||
|
||||
void llama_kv_cache_set_cell_pos(struct llama_context * ctx, llama_seq_id seq_id, llama_pos pos) {
|
||||
auto & kv = ctx->kv_self;
|
||||
if (seq_id >= 0 && (uint32_t)seq_id < kv.size) {
|
||||
kv.cells[seq_id].pos = pos;
|
||||
}
|
||||
}
|
||||
|
||||
bool llama_kv_cache_seq_rm(struct llama_context * ctx, llama_seq_id seq_id, llama_pos p0, llama_pos p1) {
|
||||
return llama_kv_cache_seq_rm(ctx->kv_self, seq_id, p0, p1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user