LLM inference in C/C++
34
stars
8,587
commits
C++
primary language
Aug 1, 2026
updated
A llama.cpp fork that extends the reasoning-budget sampler with staged, in-context budget signaling.
Reasoning models generate an unbounded <think> block prior to their final answer, and the length and quality of that block are sensitive to sampling parameters. At the temperature and top-p/top-k settings needed to avoid degenerate, low-entropy output elsewhere in generation, the reasoning block is prone to failure modes that are distinct from ordinary sampling artifacts:
The standard/naive mitigation is a hard token-count cutoff enforced by the sampler: once N tokens have been generated inside the reasoning block, </think> is forced regardless of position in the sequence. This bounds worst-case length but does not address any of the above — the cutoff has no dependency on the model's actual generation state and truncates at whatever token index it happens to hit, including mid-token-sequence for a partial word, mid-clause, or mid-computation. It suppresses the symptom (unbounded length) without altering the sampling behavior that produces the loop or the non-convergence in the first place.
This fork adds a state machine around the existing hard-cutoff sampler, with two additional stages that inject fixed text into the reasoning stream at defined points:
{budget} placeholder), e.g. "I'm allowed to think for 512 tokens, so my reasoning should be concise. Let me start by". This gives the model an explicit, in-context reference for its own generation length before reasoning begins."I've used up half of my thinking budget, let me start working towards a conclusion".budget + grace_tokens in all cases.Injected text is only inserted at newline or paragraph boundaries, not mid-token or mid-sentence. If the model emits its own </think> before a forced stage would trigger, the natural close takes precedence.
Each stage is opt-in and independently configurable via LLAMA_ARG_THINK_BUDGET_* environment variables at server startup, or as per-request overrides in the API call itself:
| Environment variable | Purpose |
|---|---|
LLAMA_ARG_THINK_BUDGET | Token budget for the reasoning block (existing upstream variable) |
LLAMA_ARG_THINK_BUDGET_INTRO_MESSAGE | Templated intro message, supports a {budget} placeholder |
LLAMA_ARG_THINK_BUDGET_SOFT_RATIO | Fraction of budget at which the soft warning fires (e.g. 0.7) |
LLAMA_ARG_THINK_BUDGET_SOFT_MESSAGE | Templated soft-warning message |
LLAMA_ARG_THINK_BUDGET_MESSAGE | Templated hard-stop closing message |
LLAMA_ARG_THINK_BUDGET_GRACE_TOKENS | How long to wait for a paragraph break before forcing the hard stop |
Default values preserve upstream's existing hard-cutoff behavior; the new stages are disabled unless configured.
Planned follow-up work: generalize this mechanism into a configurable reasoning template/grammar, rather than a fixed set of budget-based checkpoints.
All results below use Qwen3.6-27B, UD-Q4_K_XL quantization, with MTP speculative decoding (3 draft tokens). A separate pass without speculative decoding produced consistent results and is omitted here for brevity.
Four configurations are compared at several reasoning budgets, each adding one more piece of the mechanism on top of the last:
</think> is force-injected immediately, with no grace period and no in-context signaling of any kind. This is the behavior described in the Problem section above, and the baseline this fork is trying to improve on.grace_tokens for a paragraph boundary before closing the block.Two further reference points appear in the charts and tables: Baseline (unlimited), where the reasoning block runs to its own natural </think>, and — for LiveCodeBench only — No reasoning, where the reasoning block is disabled entirely.
Benchmarks: HumanEval+ (n=164) and LiveCodeBench (release_v6, n=200). Reported token counts are average total completion tokens per test (reasoning block plus final answer), matching the chart axes.
| Budget (tokens) | Naive: tok / pass@1 | Hard-limit only: tok / pass@1 | Soft + hard: tok / pass@1 | Intro + soft + hard: tok / pass@1 |
|---|---|---|---|---|
| 300 | 499 / 92.7% | 489 / 92.1% | 422 / 92.1% | 391 / 92.7% |
| 500 | 749 / 91.5% | 672 / 93.3% | 592 / 93.9% | 569 / 93.3% |
| 750 | 963 / 93.3% | 906 / 93.9% | 809 / 93.9% | 863 / 91.5% |
| 1250 | 1363 / 92.7% | 1348 / 92.7% | 1221 / 93.9% | 1360 / 95.7% |
| Unlimited (baseline) | 2776 / 92.7% | — | — | — |
Two results stand out here:
| Budget (tokens) | Naive: tok / pass@1 | Hard-limit only: tok / pass@1 | Soft + hard: tok / pass@1 | Intro + soft + hard: tok / pass@1 |
|---|---|---|---|---|
| 500 | 6955 / 61.0% | 4827 / 58.5% | 3894 / 61.5% | 2930 / 56.5% |
| 1000 | 7624 / 64.0% | 5820 / 65.5% | 4582 / 64.5% | 3334 / 60.0% |
| 1750 | 7779 / 62.0% | 6556 / 62.5% | 4864 / 68.5% | 4862 / 66.0% |
| 4000 | 16324 / 70.5% | 11251 / 65.5% | 10277 / 68.5% | 7693 / 69.5% |
| Unlimited (baseline) | 36293 / 72.0% | — | — | — |
| No reasoning | — / 57.0% | — | — | — |
LiveCodeBench is far more reasoning-intensive at baseline (36293 tokens/task on average, versus 2776 for HumanEval+), and the ordering seen above holds even more cleanly here: naive > hard-limit only > soft + hard > intro + soft + hard in total token count, at every single budget tested, with no exceptions. At the 4000-token budget, naive uses 16324 tokens for 70.5% pass@1, while intro + soft + hard uses 7693 tokens — 47% of naive's token count — for 69.5%, a 1-point difference well within what n=200 sampling noise would produce.
Accuracy differences between configurations at a fixed budget are generally small (a few points, consistent with n=200 noise) and don't show a systematic penalty for the more guided configurations — in most cases they hold accuracy roughly level with naive while using a fraction of the tokens.
A separate effect shows up in how each configuration's accuracy responds to increasing the budget. For soft + hard and intro + soft + hard, pass@1 rises monotonically as budget increases from 500 to 4000, with no reversals. Naive and hard-limit-only do not show this: naive drops from 64.0% (1000 tokens) to 62.0% (1750 tokens) before jumping to 70.5% (4000 tokens), and hard-limit-only drops from 65.5% (1000 tokens) to 62.5% (1750 tokens). The guided configurations turn additional budget into a predictable accuracy gain; naive and hard-limit-only do not — this is the clearest "reduced noise" effect in this data.
None of the four budget-constrained configurations fully recovers the unlimited baseline's 72.0% at any tested budget.
| Difficulty | Baseline (unlimited) | 500-token budget (range across 4 configs) | 4000-token budget (range across 4 configs) |
|---|---|---|---|
| Easy (n=53) | 85% | 96–98% | 94–96% |
| Medium (n=61) | 72% | 61–72% | 72–80% |
| Hard (n=86) | 64% | 26–36% | 42–50% |
This breakdown clarifies where the token savings come from, and echoes the HumanEval+ result above. On easy problems, every budget-constrained configuration at every tested budget scores at or above the unlimited baseline (96–98% vs. 85%) — again consistent with a capped, guided reasoning block reducing the chance the model overthinks its way into a wrong answer on a problem it could already solve. Medium problems are roughly flat to slightly improved at the higher budget. Hard problems are the exception: accuracy stays well below the unlimited baseline at both the smallest (26–36% vs. 64%) and largest (42–50% vs. 64%) budgets tested, for every configuration including intro + soft + hard. Budget-based control, however it's implemented, does not close this gap — the hardest problems still lose accuracy when reasoning length is capped.
Configuration is set via LLAMA_ARG_THINK_BUDGET_* environment variables, and can be overridden per-request in the API call — see server API docs for the request-level parameters.
Docker on macOS cannot pass the GPU through to a container, so there is no Metal-accelerated Docker image. Build natively instead, following upstream's build guide (Metal is enabled by default on Apple Silicon):
git clone https://github.com/laurencehardman/llama-mindcontrol
cd llama-mindcontrol
cmake -B build
cmake --build build --config Release -j
LLAMA_ARG_THINK_BUDGET="350" \
LLAMA_ARG_THINK_BUDGET_SOFT_RATIO="0.7" \
LLAMA_ARG_THINK_BUDGET_GRACE_TOKENS="64" \
./build/bin/llama-server -m /path/to/your-model.gguf
A pre-built Docker image is provided. Example docker-compose.yml:
services:
llama-server:
image: ghcr.io/laurencehardman/llama-mindcontrol:cuda
gpus: all
ports:
- "8080:8080"
volumes:
- ${MODEL_DIR:-./models}:/models:ro
environment:
LLAMA_ARG_THINK_BUDGET: "350"
LLAMA_ARG_THINK_BUDGET_INTRO_MESSAGE: " I have {budget} tokens to reason through this - that's enough room to work through it carefully, so I'll think it through step by step rather than rushing to a conclusion."
LLAMA_ARG_THINK_BUDGET_MESSAGE: " [!!NOTE TO SELF] I've used all of my thinking budget, I am now going to wrap up and provide the user their answer."
LLAMA_ARG_THINK_BUDGET_SOFT_RATIO: "0.7"
LLAMA_ARG_THINK_BUDGET_SOFT_MESSAGE: " [!NOTE TO SELF] I'm partway through my budget - I should start consolidating toward an answer, but I still have room to finish the important points."
command:
- "--model"
- "/models/your-model.gguf"
MODEL_DIR=/path/to/models docker compose up
Requires the nvidia-container-toolkit on the host.
llama-server exposes the standard OpenAI-compatible API at http://localhost:8080. See the upstream documentation below for other configuration options.
Test it out:
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "local-model",
"stream": true,
"messages": [
{"role": "user", "content": "Explain how Flash Attention works."}
],
"reasoning_budget_tokens": 350,
"reasoning_budget_message": "I have reached my reasoning budget - I have enough here to answer now.",
"reasoning_budget_soft_ratio": 0.7,
"reasoning_budget_soft_message": "I am partway through my budget - I should start consolidating toward an answer, but I still have room to finish the important points.",
"reasoning_budget_intro_message": "I have {budget} tokens to reason through this - that is enough room to work through it carefully, so I will think it through step by step rather than rushing to a conclusion.",
"reasoning_budget_grace_tokens": 50,
"reasoning_control": true
}'
LLM inference in C/C++
manifesto / ggml / ops / maintainer PRs / dev branches / compile times / lib llama API / llama-server REST API
A few options to get llama.cpp installed on your machine:
Once installed:
# Download and run a model directly from Hugging Face
llama cli -hf ggml-org/Qwen3.5-0.8B-GGUF
# Launch OpenAI-compatible API server
llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF
|
|
|
The main goal of llama.cpp is to enable LLM (and VLM) inference with minimal setup and state-of-the-art performance on
a wide range of hardware - locally and in the cloud.
The llama.cpp project is build on top of the ggml library.
| Backend | Target devices |
|---|---|
| BLAS | All |
| BLIS | All |
| CANN | Ascend NPU |
| CUDA | Nvidia GPU |
| HIP | AMD GPU |
| Hexagon [In Progress] | Snapdragon |
| IBM zDNN | IBM Z & LinuxONE |
| MUSA | Moore Threads GPU |
| Metal | Apple Silicon |
| OpenCL | Adreno GPU |
| OpenVINO [In Progress] | Intel CPUs, GPUs, and NPUs |
| RPC | All |
| SYCL | Intel GPU |
| VirtGPU | VirtGPU APIR |
| Vulkan | GPU |
| WebGPU | All |
| ZenDNN | AMD CPU |
llama.cpp repo and merge PRs into the master branchllama-server - MIT license(top 30 of 447)
C++
55.2%
C
16.5%
Python
7.1%
Cuda
5.6%
TypeScript
3.9%
HTML
2.3%
Svelte
2.2%
Metal
1.4%
Jinja
1.2%
GLSL
1.0%
LLM inference in C/C++
34
stars
8,587
commits
C++
primary language
Aug 1, 2026
updated
A llama.cpp fork that extends the reasoning-budget sampler with staged, in-context budget signaling.
Reasoning models generate an unbounded <think> block prior to their final answer, and the length and quality of that block are sensitive to sampling parameters. At the temperature and top-p/top-k settings needed to avoid degenerate, low-entropy output elsewhere in generation, the reasoning block is prone to failure modes that are distinct from ordinary sampling artifacts:
The standard/naive mitigation is a hard token-count cutoff enforced by the sampler: once N tokens have been generated inside the reasoning block, </think> is forced regardless of position in the sequence. This bounds worst-case length but does not address any of the above — the cutoff has no dependency on the model's actual generation state and truncates at whatever token index it happens to hit, including mid-token-sequence for a partial word, mid-clause, or mid-computation. It suppresses the symptom (unbounded length) without altering the sampling behavior that produces the loop or the non-convergence in the first place.
This fork adds a state machine around the existing hard-cutoff sampler, with two additional stages that inject fixed text into the reasoning stream at defined points:
{budget} placeholder), e.g. "I'm allowed to think for 512 tokens, so my reasoning should be concise. Let me start by". This gives the model an explicit, in-context reference for its own generation length before reasoning begins."I've used up half of my thinking budget, let me start working towards a conclusion".budget + grace_tokens in all cases.Injected text is only inserted at newline or paragraph boundaries, not mid-token or mid-sentence. If the model emits its own </think> before a forced stage would trigger, the natural close takes precedence.
Each stage is opt-in and independently configurable via LLAMA_ARG_THINK_BUDGET_* environment variables at server startup, or as per-request overrides in the API call itself:
| Environment variable | Purpose |
|---|---|
LLAMA_ARG_THINK_BUDGET | Token budget for the reasoning block (existing upstream variable) |
LLAMA_ARG_THINK_BUDGET_INTRO_MESSAGE | Templated intro message, supports a {budget} placeholder |
LLAMA_ARG_THINK_BUDGET_SOFT_RATIO | Fraction of budget at which the soft warning fires (e.g. 0.7) |
LLAMA_ARG_THINK_BUDGET_SOFT_MESSAGE | Templated soft-warning message |
LLAMA_ARG_THINK_BUDGET_MESSAGE | Templated hard-stop closing message |
LLAMA_ARG_THINK_BUDGET_GRACE_TOKENS | How long to wait for a paragraph break before forcing the hard stop |
Default values preserve upstream's existing hard-cutoff behavior; the new stages are disabled unless configured.
Planned follow-up work: generalize this mechanism into a configurable reasoning template/grammar, rather than a fixed set of budget-based checkpoints.
All results below use Qwen3.6-27B, UD-Q4_K_XL quantization, with MTP speculative decoding (3 draft tokens). A separate pass without speculative decoding produced consistent results and is omitted here for brevity.
Four configurations are compared at several reasoning budgets, each adding one more piece of the mechanism on top of the last:
</think> is force-injected immediately, with no grace period and no in-context signaling of any kind. This is the behavior described in the Problem section above, and the baseline this fork is trying to improve on.grace_tokens for a paragraph boundary before closing the block.Two further reference points appear in the charts and tables: Baseline (unlimited), where the reasoning block runs to its own natural </think>, and — for LiveCodeBench only — No reasoning, where the reasoning block is disabled entirely.
Benchmarks: HumanEval+ (n=164) and LiveCodeBench (release_v6, n=200). Reported token counts are average total completion tokens per test (reasoning block plus final answer), matching the chart axes.
| Budget (tokens) | Naive: tok / pass@1 | Hard-limit only: tok / pass@1 | Soft + hard: tok / pass@1 | Intro + soft + hard: tok / pass@1 |
|---|---|---|---|---|
| 300 | 499 / 92.7% | 489 / 92.1% | 422 / 92.1% | 391 / 92.7% |
| 500 | 749 / 91.5% | 672 / 93.3% | 592 / 93.9% | 569 / 93.3% |
| 750 | 963 / 93.3% | 906 / 93.9% | 809 / 93.9% | 863 / 91.5% |
| 1250 | 1363 / 92.7% | 1348 / 92.7% | 1221 / 93.9% | 1360 / 95.7% |
| Unlimited (baseline) | 2776 / 92.7% | — | — | — |
Two results stand out here:
| Budget (tokens) | Naive: tok / pass@1 | Hard-limit only: tok / pass@1 | Soft + hard: tok / pass@1 | Intro + soft + hard: tok / pass@1 |
|---|---|---|---|---|
| 500 | 6955 / 61.0% | 4827 / 58.5% | 3894 / 61.5% | 2930 / 56.5% |
| 1000 | 7624 / 64.0% | 5820 / 65.5% | 4582 / 64.5% | 3334 / 60.0% |
| 1750 | 7779 / 62.0% | 6556 / 62.5% | 4864 / 68.5% | 4862 / 66.0% |
| 4000 | 16324 / 70.5% | 11251 / 65.5% | 10277 / 68.5% | 7693 / 69.5% |
| Unlimited (baseline) | 36293 / 72.0% | — | — | — |
| No reasoning | — / 57.0% | — | — | — |
LiveCodeBench is far more reasoning-intensive at baseline (36293 tokens/task on average, versus 2776 for HumanEval+), and the ordering seen above holds even more cleanly here: naive > hard-limit only > soft + hard > intro + soft + hard in total token count, at every single budget tested, with no exceptions. At the 4000-token budget, naive uses 16324 tokens for 70.5% pass@1, while intro + soft + hard uses 7693 tokens — 47% of naive's token count — for 69.5%, a 1-point difference well within what n=200 sampling noise would produce.
Accuracy differences between configurations at a fixed budget are generally small (a few points, consistent with n=200 noise) and don't show a systematic penalty for the more guided configurations — in most cases they hold accuracy roughly level with naive while using a fraction of the tokens.
A separate effect shows up in how each configuration's accuracy responds to increasing the budget. For soft + hard and intro + soft + hard, pass@1 rises monotonically as budget increases from 500 to 4000, with no reversals. Naive and hard-limit-only do not show this: naive drops from 64.0% (1000 tokens) to 62.0% (1750 tokens) before jumping to 70.5% (4000 tokens), and hard-limit-only drops from 65.5% (1000 tokens) to 62.5% (1750 tokens). The guided configurations turn additional budget into a predictable accuracy gain; naive and hard-limit-only do not — this is the clearest "reduced noise" effect in this data.
None of the four budget-constrained configurations fully recovers the unlimited baseline's 72.0% at any tested budget.
| Difficulty | Baseline (unlimited) | 500-token budget (range across 4 configs) | 4000-token budget (range across 4 configs) |
|---|---|---|---|
| Easy (n=53) | 85% | 96–98% | 94–96% |
| Medium (n=61) | 72% | 61–72% | 72–80% |
| Hard (n=86) | 64% | 26–36% | 42–50% |
This breakdown clarifies where the token savings come from, and echoes the HumanEval+ result above. On easy problems, every budget-constrained configuration at every tested budget scores at or above the unlimited baseline (96–98% vs. 85%) — again consistent with a capped, guided reasoning block reducing the chance the model overthinks its way into a wrong answer on a problem it could already solve. Medium problems are roughly flat to slightly improved at the higher budget. Hard problems are the exception: accuracy stays well below the unlimited baseline at both the smallest (26–36% vs. 64%) and largest (42–50% vs. 64%) budgets tested, for every configuration including intro + soft + hard. Budget-based control, however it's implemented, does not close this gap — the hardest problems still lose accuracy when reasoning length is capped.
Configuration is set via LLAMA_ARG_THINK_BUDGET_* environment variables, and can be overridden per-request in the API call — see server API docs for the request-level parameters.
Docker on macOS cannot pass the GPU through to a container, so there is no Metal-accelerated Docker image. Build natively instead, following upstream's build guide (Metal is enabled by default on Apple Silicon):
git clone https://github.com/laurencehardman/llama-mindcontrol
cd llama-mindcontrol
cmake -B build
cmake --build build --config Release -j
LLAMA_ARG_THINK_BUDGET="350" \
LLAMA_ARG_THINK_BUDGET_SOFT_RATIO="0.7" \
LLAMA_ARG_THINK_BUDGET_GRACE_TOKENS="64" \
./build/bin/llama-server -m /path/to/your-model.gguf
A pre-built Docker image is provided. Example docker-compose.yml:
services:
llama-server:
image: ghcr.io/laurencehardman/llama-mindcontrol:cuda
gpus: all
ports:
- "8080:8080"
volumes:
- ${MODEL_DIR:-./models}:/models:ro
environment:
LLAMA_ARG_THINK_BUDGET: "350"
LLAMA_ARG_THINK_BUDGET_INTRO_MESSAGE: " I have {budget} tokens to reason through this - that's enough room to work through it carefully, so I'll think it through step by step rather than rushing to a conclusion."
LLAMA_ARG_THINK_BUDGET_MESSAGE: " [!!NOTE TO SELF] I've used all of my thinking budget, I am now going to wrap up and provide the user their answer."
LLAMA_ARG_THINK_BUDGET_SOFT_RATIO: "0.7"
LLAMA_ARG_THINK_BUDGET_SOFT_MESSAGE: " [!NOTE TO SELF] I'm partway through my budget - I should start consolidating toward an answer, but I still have room to finish the important points."
command:
- "--model"
- "/models/your-model.gguf"
MODEL_DIR=/path/to/models docker compose up
Requires the nvidia-container-toolkit on the host.
llama-server exposes the standard OpenAI-compatible API at http://localhost:8080. See the upstream documentation below for other configuration options.
Test it out:
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "local-model",
"stream": true,
"messages": [
{"role": "user", "content": "Explain how Flash Attention works."}
],
"reasoning_budget_tokens": 350,
"reasoning_budget_message": "I have reached my reasoning budget - I have enough here to answer now.",
"reasoning_budget_soft_ratio": 0.7,
"reasoning_budget_soft_message": "I am partway through my budget - I should start consolidating toward an answer, but I still have room to finish the important points.",
"reasoning_budget_intro_message": "I have {budget} tokens to reason through this - that is enough room to work through it carefully, so I will think it through step by step rather than rushing to a conclusion.",
"reasoning_budget_grace_tokens": 50,
"reasoning_control": true
}'
LLM inference in C/C++
manifesto / ggml / ops / maintainer PRs / dev branches / compile times / lib llama API / llama-server REST API
A few options to get llama.cpp installed on your machine:
Once installed:
# Download and run a model directly from Hugging Face
llama cli -hf ggml-org/Qwen3.5-0.8B-GGUF
# Launch OpenAI-compatible API server
llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF
|
|
|
The main goal of llama.cpp is to enable LLM (and VLM) inference with minimal setup and state-of-the-art performance on
a wide range of hardware - locally and in the cloud.
The llama.cpp project is build on top of the ggml library.
| Backend | Target devices |
|---|---|
| BLAS | All |
| BLIS | All |
| CANN | Ascend NPU |
| CUDA | Nvidia GPU |
| HIP | AMD GPU |
| Hexagon [In Progress] | Snapdragon |
| IBM zDNN | IBM Z & LinuxONE |
| MUSA | Moore Threads GPU |
| Metal | Apple Silicon |
| OpenCL | Adreno GPU |
| OpenVINO [In Progress] | Intel CPUs, GPUs, and NPUs |
| RPC | All |
| SYCL | Intel GPU |
| VirtGPU | VirtGPU APIR |
| Vulkan | GPU |
| WebGPU | All |
| ZenDNN | AMD CPU |
llama.cpp repo and merge PRs into the master branchllama-server - MIT license(top 30 of 447)
C++
55.2%
C
16.5%
Python
7.1%
Cuda
5.6%
TypeScript
3.9%
HTML
2.3%
Svelte
2.2%
Metal
1.4%
Jinja
1.2%
GLSL
1.0%