diff --git a/include/llama.h b/include/llama.h
index a3ceecf5e..60a5003e4 100644
@@ -344,6 +344,21 @@ extern "C" {
struct llama_sampler * sampler;
};
+ // Exact decode lifecycle hooks used by safe callback owners. The begin
+ // callback runs before native state mutation. The end callback runs after
+ // every attempted decode and receives whether native decode succeeded.
+ typedef bool (*llama_decode_begin_callback)(
+ const struct llama_batch * batch,
+ void * user_data);
+ typedef bool (*llama_decode_end_callback)(
+ bool native_succeeded,
+ void * user_data);
+
+ // ABI guard: defined only in patched builds. The Rust binding references
+ // this symbol so an unpatched or ABI-mismatched prebuilt libllama fails at
+ // link time instead of silently disabling the decode hooks.
+ LLAMA_API int llama_cpp_rs_decode_hooks_abi_v1(void);
+
// NOTE: changing the default values of parameters marked as [EXPERIMENTAL] may cause crashes or incorrect results in certain configurations
// https://github.com/ggml-org/llama.cpp/pull/7544
struct llama_context_params {
@@ -405,6 +420,12 @@ extern "C" {
// a source/target/parent context
// can be utilized in various ways, for example by sharing results or llama_memory between 2 contexts
struct llama_context * ctx_other;
+
+ // Exact decode lifecycle hooks. Appended at the end of the struct so
+ // every upstream field keeps its original offset — ABI-robust against a
+ // prebuilt libllama built without this patch.
+ llama_decode_begin_callback cb_decode_begin;
+ llama_decode_end_callback cb_decode_end;
};
struct llama_model_tensor_override {
diff --git a/src/llama-context.cpp b/src/llama-context.cpp
index 5ef7becf6..1dac3b056 100644
@@ -136,6 +136,8 @@ llama_context::llama_context(
cparams.cb_eval = params.cb_eval;
cparams.cb_eval_user_data = params.cb_eval_user_data;
+ cparams.cb_decode_begin = params.cb_decode_begin;
+ cparams.cb_decode_end = params.cb_decode_end;
cparams.ctx_other = nullptr;
@@ -3514,6 +3516,8 @@ llama_context_params llama_context_default_params() {
/*.sampler =*/ nullptr,
/*.n_sampler =*/ 0,
/*.ctx_other =*/ nullptr,
+ /*.cb_decode_begin =*/ nullptr,
+ /*.cb_decode_end =*/ nullptr,
};
return result;
@@ -4094,10 +4098,24 @@ int32_t llama_encode(
return ret;
}
+int llama_cpp_rs_decode_hooks_abi_v1(void) {
+ return 1;
+}
+
int32_t llama_decode(
llama_context * ctx,
llama_batch batch) {
+ const auto & cparams = ctx->get_cparams();
+ if (cparams.cb_decode_begin &&
+ !cparams.cb_decode_begin(&batch, cparams.cb_eval_user_data)) {
+ return -4;
+ }
+
const int ret = ctx->decode(batch);
+ if (cparams.cb_decode_end &&
+ !cparams.cb_decode_end(ret == 0, cparams.cb_eval_user_data)) {
+ return -4;
+ }
if (ret != 0 && ret != 1) {
LLAMA_LOG_ERROR("%s: failed to decode, ret = %d\n", __func__, ret);
}
diff --git a/src/llama-cparams.h b/src/llama-cparams.h
index 5018170ed..d0e8ddc30 100644
@@ -62,4 +62,8 @@ struct llama_cparams {
void * cb_eval_user_data;
llama_context * ctx_other;
+
+ // Exact decode lifecycle hooks (appended; see llama.h).
+ llama_decode_begin_callback cb_decode_begin;
+ llama_decode_end_callback cb_decode_end;
};