The highlighted annotations locate attack entry, possible influence, constraints and observable effects for this concern. They are source-based hypotheses, not a trace of a demonstrated attack. GGUF supplies weights and metadata; llama.cpp builds and executes the computation graph.
Source snapshot: llama.cpp 5266f24da75dc449bd56cbed7addb9c8e4a6a73e. Concrete Llama architecture, ordinary generation path, non-fused attention as configured by this driver. Other architectures and fused kernels can use different graphs. All code below is embedded and expandable; no network or inference is required.
STAGE 01
GGUF → model weights, vocabulary and architecture
GGUF is a model file. The loader reads its metadata and tensors; C++ architecture code constructs the executable graph.
MODEL BASELINE: keep the GGUF identity and inference revision fixed. This concern does not demonstrate an attack on the GGUF loader or a modification of model weights.
562 if (!fname.empty()) {563 // Load the main GGUF564 struct ggml_context * ctx = NULL;565 struct gguf_init_params params = {566 /*.no_alloc = */ true,567 /*.ctx = */ &ctx,568 };569570 metadata_ptr.reset(gguf_init_from_file(fname.c_str(), params));571 metadata = metadata_ptr.get();572 if (metadata == nullptr) {573 throw std::runtime_error(format("%s: failed to load model from %s", __func__, fname.c_str()));574 }575576 get_key(llm_kv(LLM_KV_GENERAL_ARCHITECTURE), arch_name, false);577 llm_kv = LLM_KV(llm_arch_from_string(arch_name));578579 files.emplace_back(new llama_file(fname.c_str(), "rb", use_direct_io));580 contexts.emplace_back(ctx);
llama.cpp implementation
llama.cpp/src/llama.cpp:343–371View code
343344 model->hparams.vocab_only = params.vocab_only;345 model->hparams.no_alloc = params.no_alloc;346347 try {348 model->load_hparams(ml);349 } catch(const std::exception & e) {350 throw std::runtime_error("error loading model hyperparameters: " + std::string(e.what()));351 }352 if (model->arch == LLM_ARCH_CLIP) {353 throw std::runtime_error("CLIP cannot be used as main model, use it with --mmproj instead");354 }355 try {356 model->load_vocab(ml);357 } catch(const std::exception & e) {358 throw std::runtime_error("error loading model vocabulary: " + std::string(e.what()));359 }360361 model->load_stats(ml);362 model->print_info();363364 if (params.vocab_only) {365 LLAMA_LOG_INFO("%s: vocab only - skipping tensors\n", __func__);366 return {0, model_ptr.release()};367 }368369 if (!model->load_tensors(ml)) {370 return {-2, nullptr};371 }
↓
STAGE 02
Rendered text → token IDs
The caller supplies the final prompt. llama_tokenize delegates to the loaded vocabulary; tokenization itself does not execute the transformer.
ATTACK ENTRY: an instruction-like passage is inserted in untrusted source text before tokenization. Region labels are supplied separately by the caller.
Decode → build or reuse graph → set current inputs
process_ubatch applies memory state, builds or reuses the graph, sets new inputs and requests graph computation. Reusing topology still computes the new input.
EXECUTION ENTRY: the current prefix and context state reach the model here. Building or reusing topology is not itself an attack; the relevant influence is identified in the stages below.
1334llm_graph_result * llama_context::process_ubatch(const llama_ubatch & ubatch, llm_graph_type gtype, llama_memory_context_i * mctx, ggml_status & ret) {1335 if (mctx && !mctx->apply()) {1336 LLAMA_LOG_ERROR("%s: failed to apply memory context\n", __func__);1337 ret = GGML_STATUS_FAILED;1338 return nullptr;1339 }13401341 auto * res = gf_res_prev.get();1342 auto * gf = res->get_gf();13431344 // the new graph parameters1345 // in order to correctly reuse a graph, it's full topology has to be uniquely determined by these parameters1346 const auto gparams = graph_params(res, ubatch, mctx, gtype);13471348 if (!graph_reuse_disable && res->can_reuse(gparams)) {1349 //LLAMA_LOG_DEBUG("%s: reusing previous graph\n", __func__);13501351 // with pipeline parallelism, the previous graph_compute_async may still be running1352 // on the GPU. we must synchronize before set_inputs to avoid overwriting input tensors1353 // that the previous compute is still reading.1354 if (cparams.pipeline_parallel) {1355 ggml_backend_sched_synchronize(sched.get());1356 }13571358 n_reused++;1359 } else {1360 res->reset();13611362 ggml_backend_sched_reset(sched.get());1363 ggml_backend_sched_set_eval_callback(sched.get(), cparams.cb_eval, cparams.cb_eval_user_data);13641365 //const auto t_start_us = ggml_time_us();13661367 gf = model.build_graph(gparams);13681369 //LLAMA_LOG_INFO("graph build time: %.3f ms\n", (ggml_time_us() - t_start_us)/1000.0);13701371 if (!gf) {1372 LLAMA_LOG_ERROR("%s: failed to initialize graph\n", __func__);1373 ret = GGML_STATUS_FAILED;1374 return nullptr;1375 }13761377 if (!ggml_backend_sched_alloc_graph(sched.get(), gf)) {1378 LLAMA_LOG_ERROR("%s: failed to allocate graph\n", __func__);1379 ret = GGML_STATUS_ALLOC_FAILED;1380 return nullptr;1381 }1382 }13831384 // set the input data for the input tensors1385 {1386 //const auto t_start_us = ggml_time_us();13871388 // FIXME this call causes a crash if any model inputs were not used in the graph and were therefore not allocated1389 res->set_inputs(&ubatch);13901391 //LLAMA_LOG_INFO("graph set inputs time: %.3f ms\n", (ggml_time_us() - t_start_us)/1000.0);1392 }13931394 const auto status = graph_compute(res->get_gf(), ubatch.n_tokens > 1);1395 if (status != GGML_STATUS_SUCCESS) {1396 LLAMA_LOG_ERROR("%s: failed to compute graph, compute status: %d\n", __func__, status);1397 ret = status;1398 return nullptr;1399 }14001401 ret = GGML_STATUS_SUCCESS;14021403 return res;
llama.cpp implementation
llama.cpp/src/llama-model.cpp:2673–2690View code
2673ggml_cgraph * llama_model::build_graph(const llm_graph_params & params) const {2674 std::unique_ptr<llm_graph_context> llm = build_arch_graph(params);26752676 // add on pooling layer2677 llm->build_pooling(cls, cls_b, cls_out, cls_out_b, cls_norm);26782679 // add backend sampling layers (if any)2680 llm->build_sampling();26812682 // if the gguf model was converted with --sentence-transformers-dense-modules2683 // there will be two additional dense projection layers2684 // dense linear projections are applied after pooling2685 // TODO: move reranking logic here and generalize2686 llm->build_dense_out(dense_2_out_layers, dense_2_out_layers_b, dense_3_out_layers);26872688 llm->res->set_outputs(params);26892690 return llm->res->get_gf();
Tensor dependency order: embeddings → per-layer attention and FFN → final logits. K/V, attention and FFN repeat for each layer. The backend executes these operations during decode; graph construction alone does not compute their values.
STAGE 04
Token embeddings + positions
Token IDs select rows of the GGUF embedding matrix. The Llama graph supplies positions and enters its transformer-layer loop.
INPUT REPRESENTATION: tokens from the source described above become embedding vectors and positional inputs. The embedding lookup does not enforce source trust or label a token as an attack.
llama.cpp implementation
llama.cpp/src/llama-graph.cpp:2303–2328View code
2303ggml_tensor * llm_graph_context::build_inp_embd(ggml_tensor * tok_embd) const {2304 const int64_t n_embd_inp = hparams.n_embd_inp();2305 const int64_t n_embd = hparams.n_embd;23062307 assert(n_embd_inp >= n_embd);23082309 auto inp = std::make_unique<llm_graph_input_embd>(n_embd_inp);23102311 inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, ubatch.n_tokens);2312 cb(inp->tokens, "inp_tokens", -1);2313 ggml_set_input(inp->tokens);2314 res->t_inp_tokens = inp->tokens;23152316 inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_embd_inp, ubatch.n_tokens);2317 cb(inp->embd, "inp_embd", -1);2318 ggml_set_input(inp->embd);23192320 // select one of the 2 inputs, based on the batch contents2321 // ref: https://github.com/ggml-org/llama.cpp/pull/185502322 std::array<ggml_tensor *, 2> inps;23232324 // token embeddings path (ubatch.token != nullptr)2325 {2326 auto & cur = inps[0];23272328 cur = ggml_get_rows(ctx0, tok_embd, inp->tokens);
The graph computes Q/K/V, applies RoPE and adds K/V cache writes. Attention reads the available cache with a position mask.
The injected tokens contribute K/V state while they remain in context. This is ordinary inference state, not a modification of GGUF weights.
llama.cpp implementation
llama.cpp/src/models/llama.cpp:139–161View code
139 // rope freq factors for llama3; may return nullptr for llama2 and other models140 ggml_tensor * rope_factors = model.get_rope_factors(cparams, il);141142 // compute Q and K and RoPE them143 auto [Qcur, Kcur, Vcur] = build_qkv(model.layers[il], cur,144 n_embd_head, n_head, n_head_kv, il);145146 Qcur = ggml_rope_ext(147 ctx0, Qcur, inp_pos, rope_factors,148 n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,149 ext_factor, attn_factor, beta_fast, beta_slow150 );151152 Kcur = ggml_rope_ext(153 ctx0, Kcur, inp_pos, rope_factors,154 n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,155 ext_factor, attn_factor, beta_fast, beta_slow156 );157158 cb(Qcur, "Qcur", il);159 cb(Kcur, "Kcur", il);160 cb(Vcur, "Vcur", il);161
llama.cpp implementation
llama.cpp/src/llama-graph.cpp:2815–2833View code
2815 const auto * mctx_cur = inp->mctx;28162817 // store to KV cache2818 {2819 const auto & k_idxs = inp->get_k_idxs();2820 const auto & v_idxs = inp->get_v_idxs();28212822 ggml_build_forward_expand(gf, mctx_cur->cpy_k(ctx0, k_cur, k_idxs, il));2823 ggml_build_forward_expand(gf, mctx_cur->cpy_v(ctx0, v_cur, v_idxs, il));2824 }28252826 ggml_tensor * kq_mask = inp->get_kq_mask();28272828 ggml_tensor * q = q_cur;2829 ggml_tensor * k = mctx_cur->get_k(ctx0, il);2830 ggml_tensor * v = mctx_cur->get_v(ctx0, il);28312832 ggml_tensor * cur = build_attn_mha(q, k, v, kq_b, kq_mask, sinks, v_mla, 0, kq_scale, il);2833 cb(cur, "kqv_out", il);
↓
STAGE 06
Per layer: QK scores → masked softmax → weighted V
On the non-fused path, kq_soft_max contains attention probabilities. Those weights mix value vectors into the attention output.
POSSIBLE INFLUENCE: a response query can attend to the injected passage. Measure region mass at corresponding heads and positions; high mass does not prove instruction following.
llama.cpp implementation
llama.cpp/src/llama-graph.cpp:2605–2610View code
2605 ggml_tensor * kq = ggml_mul_mat(ctx0, k, q);2606 cb(kq, "kq", il);26072608 // note: this op tends to require high floating point range2609 // while for some models F16 is enough, for others it is not, so we default to F32 here2610 ggml_mul_mat_set_prec(kq, GGML_PREC_F32);
llama.cpp implementation
llama.cpp/src/llama-graph.cpp:2633–2650View code
26332634 if (kq_b) {2635 kq = ggml_add(ctx0, kq, kq_b);2636 cb(kq, "kq_plus_kq_b", il);2637 }26382639 kq = ggml_soft_max_ext(ctx0, kq, kq_mask, kq_scale, hparams.f_max_alibi_bias);2640 ggml_soft_max_add_sinks(kq, sinks);2641 cb(kq, "kq_soft_max", il);26422643 if (!v_trans) {2644 // note: avoid this branch2645 v = ggml_cont(ctx0, ggml_transpose(ctx0, v));2646 cb(v, "v_cont", il);2647 }26482649 ggml_tensor * kqv = ggml_mul_mat(ctx0, v, kq);2650 cb(kqv, "kqv", il);
↓
STAGE 07
Per layer: attention residual → FFN → layer output
Attention output is added to the residual, transformed by the FFN, then added again. l_out feeds the next layer; the layer loop repeats.
POSSIBLE INFLUENCE: attention mixes the passage into representations that propagate through residual and FFN layers. Compare l_out summaries; no individual layer is identified as an attack detector.
Backend executes these graph operations; callbacks observe tensors
The preceding boxes describe tensor dependencies. Numerics execute through this scheduler, which computes graph ranges, synchronizes and invokes requested observation callbacks during execution.
The backend computes the same kinds of operations in both conditions. Callbacks reveal tensors; they neither execute the injected instruction as code nor classify attack success.
1802 if (!sched->callback_eval) {1803 enum ggml_status ec = ggml_backend_graph_compute_async(split_backend, &split->graph);1804 if (ec != GGML_STATUS_SUCCESS) {1805 return ec;1806 }1807 } else {1808 // similar to ggml_backend_compare_graph_backend1809 for (int j0 = 0; j0 < split->graph.n_nodes; j0++) {1810 struct ggml_tensor * t = split->graph.nodes[j0];18111812 // check if the user needs data from this node1813 bool need = sched->callback_eval(t, true, sched->callback_eval_user_data);18141815 int j1 = j0;18161817 // determine the range [j0, j1] of nodes that can be computed together1818 while (!need && j1 < split->graph.n_nodes - 1) {1819 t = split->graph.nodes[++j1];1820 need = sched->callback_eval(t, true, sched->callback_eval_user_data);1821 }18221823 struct ggml_cgraph gv = ggml_graph_view(&split->graph, j0, j1 + 1);18241825 enum ggml_status ec = ggml_backend_graph_compute_async(split_backend, &gv);1826 if (ec != GGML_STATUS_SUCCESS) {1827 return ec;1828 }18291830 // TODO: pass backend to the callback, then the user can decide if they want to synchronize1831 ggml_backend_synchronize(split_backend);18321833 if (need && !sched->callback_eval(t, false, sched->callback_eval_user_data)) {1834 break;1835 }18361837 j0 = j1;1838 }
Raw logits → grammar / sampler stages → selected token
This driver applies its configured samplers after decode. llama_sampler_apply dispatches each stage; a grammar stage filters the candidate distribution.
Keep the sampler chain fixed across clean and injected conditions so filtering and random selection are not mistaken for an input effect.
llama_token_to_piece returns the token bytes. The caller records them and feeds the selected token into the next decode when another prediction is needed.
BEHAVIORAL EVIDENCE: inspect actual generated bytes for the specified task deviation. This is where following the injected instruction can become visible.
Generation feedback: selected token → next decode → the same forward graph with updated context. The final EOG or token-limit token is recorded without another decode. Attack success must be established from the guide’s behavioral criterion; tensor differences alone do not establish it.
The model receives instructions inside material that should be treated as task data. The experiment asks whether changing that material changes the model’s continuation away from the legitimate task.
Controlled experiment
Construct two already-rendered prompts with the same legitimate task and source structure. In the test version, replace one data passage with an instruction-like passage. Keep a record of the exact edited text and tokenization. Do not assume the two prompts retain identical token offsets.
Label the source as an untrusted region and, optionally, label the embedded passage as an overlapping attack region. Align comparison at a defined response boundary before continuations diverge.
Relevant telemetry
Event
What to inspect
prompt, context_region
Actual token IDs and caller-declared provenance of the edited passage.
attention
Per-head mass on that region, row mass and entropy.
hidden_state
Layer summaries or fixed projections at aligned observations.
logit_distribution
Raw uncertainty, selected-token statistics and optional complete distributions.
Capture with this codebase
The following is a configuration overlay, not a standalone configuration. Merge it into examples/capture.json, supply a real model and prompt, and compute any region spans from that prompt.
Inspect generated bytes for the experiment’s task deviation, then use internal signals to characterize the associated change. With full logprobs, call compare_distributions() on explicitly aligned records. Compare attention at the same layer/head and corresponding context boundary; larger attention mass is not automatically more instruction following.
Output example
Illustrative event excerpt: omitted envelope and data fields are intentional. These values are not experiment results or detection thresholds. Complete event envelopes are documented in the implementation guide.
Expand a file-and-line reference to read the actual source. These excerpts are embedded snapshots from this checkout. They show the measurement implementation; the experiments and output examples above remain illustrative.
Rendered input, capture settings and fresh context
src/cst_capture.cpp:107–137View code
107 auto input=tokenize(vocab,prompt,true);108 if(input.empty() || input.size()+(size_t)max_tokens>(size_t)nctx)throw std::runtime_error("prompt plus generation exceeds context_size");109 std::vector<int> layers=j.value("hidden_layers",std::vector<int>{0,llama_model_n_layer(model.get())-1});110 for(int l:layers)if(l<0 || l>=llama_model_n_layer(model.get()))throw std::runtime_error("invalid hidden layer");111 layers.push_back(-1);112 cst_config_t cfg{};cfg.log_path=log.c_str();cfg.jsonl_path=jsonl.empty()?nullptr:jsonl.c_str();cfg.run_id=run.c_str();113 cfg.hidden_state_layers=layers.data();cfg.logit_distribution_top_k=j.value("top_k_capture",20);cfg.emit_token_entropy=1;114 cfg.emit_model_timing=1;cfg.emit_kv_cache_state=j.value("capture_kv_projections",false);cfg.kv_cache_stride=j.value("kv_stride",8);115 cfg.capture_full_logits=j.value("full_logits",false);cfg.hidden_projection_dims=j.value("projection_dims",0);116 cfg.capture_hidden_vectors=j.value("hidden_vectors",false);cfg.capture_attention_rows=j.value("attention_rows",false);117 cfg.capture_prompt_text=j.value("capture_prompt_text",false);cfg.max_tensor_bytes=j.value("max_tensor_bytes",64u*1024u*1024u);118 if(cst_init(&cfg))throw std::runtime_error("telemetry initialization failed");119 cst_trial_boundary(j.value("trial_id",0u),-1,-1);120 event("run_metadata",{{"model_path",model_path},{"model_sha256",hash},{"tokenizer_identity","embedded_in_model_sha256"},121 {"architecture",arch},{"llama_commit",CST_LLAMA_COMMIT},{"configuration",j},122 {"prompt_contract","already_rendered_exact_text; no chat template applied"},{"system_info",llama_print_system_info()}});123 auto cp=llama_context_default_params();cp.n_ctx=nctx;cp.n_batch=1;cp.n_ubatch=1;124 cp.n_threads=j.value("threads",4);cp.n_threads_batch=cp.n_threads;125 cp.flash_attn_type=LLAMA_FLASH_ATTN_TYPE_DISABLED;cp.cb_eval=cst_eval_callback;cp.no_perf=false;126 Context ctx(llama_init_from_model(model.get(),cp),llama_free);127 if(!ctx)throw std::runtime_error("context init failed");128 event("capture_capabilities",{{"mode","diagnostic_single_token_decode"},{"flash_attention",false},129 {"context_size_actual",llama_n_ctx(ctx.get())},{"cache_key_mapping","linear_single_sequence_no_shift"},130 {"stored_kv_rows","unavailable"},{"speculative_decoding","disabled"},{"recurrent_state","unsupported"},131 {"tensor_support","observed tensors reported individually; absence is not zero"}});132 for(const auto & r:j.value("regions",json::array())) {133 auto meta=r.value("metadata",json::object()).dump();std::string src=r.value("source_id",std::string(""));134 int start=r.at("start_token"),end=r.at("end_token");135 if(start<0 || end<=start || end>(int)input.size())throw std::runtime_error("region outside tokenized prompt");136 cst_add_context_region(start,end,r.at("region_type"),r.at("trust_level"),src.c_str(),meta.c_str());137 }
309extern "C" void cst_log_hidden(uint16_t layer,const float * x,int n) {310 if(!valid_vector(x,n)){invalid("invalid hidden vector");return;}311 auto j=stats(x,n);j["layer"]=layer;j["location"]="residual_layer_output";312 auto it=references.find(layer);313 if(it!=references.end() && (int)it->second.values.size()==n) {314 double dot=0,na=0,nb=0,dist=0;315 for(int i=0;i<n;++i){double a=x[i],b=it->second.values[i];dot+=a*b;na+=a*a;nb+=b*b;dist+=(a-b)*(a-b);}316 j["reference_id"]=it->second.id;j["reference_l2_distance"]=std::sqrt(dist);317 j["reference_cosine"]=(na>0 && nb>0)?json(dot/std::sqrt(na*nb)):json(nullptr);318 }319 if(config.hidden_projection_dims) {320 std::vector<double> proj(config.hidden_projection_dims,0);321 // Counter-derived signs, independent of the process RNG. Algorithm ID is part of the record.322 for(int p=0;p<config.hidden_projection_dims;++p) for(int i=0;i<n;++i) {323 uint64_t v=((uint64_t)(p+1)<<32)|(uint32_t)i;324 v+=0x9e3779b97f4a7c15ULL;v=(v^(v>>30))*0xbf58476d1ce4e5b9ULL;325 v=(v^(v>>27))*0x94d049bb133111ebULL;v^=v>>31;326 proj[p]+=(v&1?1.0:-1.0)*x[i]/std::sqrt((double)config.hidden_projection_dims);327 }328 j["projection"]=proj;j["projection_method"]="splitmix64_rademacher_v1";329 }330 if(config.capture_hidden_vectors)j["values"]=std::vector<float>(x,x+n);331 emit("hidden_state",j);
Compare explicitly aligned full distributions
src/cst_log_reader.py:431–447View code
431def compare_distributions(reference, observed):432 """Exact JS divergence in bits. Requires full logprobs and explicit caller alignment."""433 a, b = reference["data"], observed["data"]434 if "logprobs" not in a or "logprobs" not in b:435 raise ValueError("full logprobs required; top-K is insufficient")436 p = np.exp(np.array([float("-inf") if x is None else x for x in a["logprobs"]]))437 q = np.exp(np.array([float("-inf") if x is None else x for x in b["logprobs"]]))438 if p.shape != q.shape or p.size == 0:439 raise ValueError("incompatible vocabularies")440 if not np.isclose(p.sum(), 1) or not np.isclose(q.sum(), 1):441 raise ValueError("invalid distributions")442 m = (p + q) / 2443 def kl(x):444 mask = x > 0445 return float(np.sum(x[mask] * np.log2(x[mask] / m[mask])))446 return {"js_bits": (kl(p) + kl(q)) / 2,447 "entropy_delta_bits": b["entropy_bits"] - a["entropy_bits"]}
Implementation status and limits
Directly supported for a supplied rendered prompt on the documented callback path. Automatic attack construction, semantic success labeling and activation interventions are not implemented. Attention or hidden-state differences alone do not establish that the edited passage caused a harmful decision.
All tensor-based interpretations depend on actual tensor_observed coverage and the tested model/backend. See validation results. There is no universal security score or calibrated threshold in this implementation.