@octocodeai/freellama-mcp-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +426 -0
- package/adapters/agent_context.py +663 -0
- package/adapters/agent_transport.py +84 -0
- package/adapters/bash_agent.py +370 -0
- package/adapters/octocode_agent.py +394 -0
- package/dist/index.js +22971 -0
- package/docs/ADR_RESOURCE_AWARE_BACKEND_ROUTING.md +55 -0
- package/docs/ARCHITECTURE.md +416 -0
- package/docs/CLI.md +219 -0
- package/docs/CPU_GPU_ROUTING.md +352 -0
- package/docs/ECONOMICS.md +105 -0
- package/docs/INDEX.md +15 -0
- package/docs/MODEL_SELECTION.md +65 -0
- package/docs/OLLAMA_SIDECAR.md +76 -0
- package/docs/OLLAMA_SYSTEM_OPTIMIZATION.md +254 -0
- package/docs/PRODUCTION.md +195 -0
- package/docs/PRODUCT_POSITIONING.md +291 -0
- package/docs/TESTING.md +125 -0
- package/native/index.d.ts +158 -0
- package/native/index.js +45 -0
- package/native/package.json +7 -0
- package/package.json +69 -0
package/docs/CLI.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# CLI reference
|
|
2
|
+
|
|
3
|
+
The `freellama` command exposes local diagnostics, deterministic model routing, managed task
|
|
4
|
+
execution, benchmarking, and the Ollama-compatible proxy. The front page covers the shortest path;
|
|
5
|
+
this page is the complete command map.
|
|
6
|
+
|
|
7
|
+
## Choose a command
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart TD
|
|
11
|
+
Q{"What do you need?"}
|
|
12
|
+
Q -->|"Prepare first use"| I["init"]
|
|
13
|
+
Q -->|"Inspect this machine"| D["doctor or machine"]
|
|
14
|
+
Q -->|"Inspect or choose a model"| M["models, route, or recommend"]
|
|
15
|
+
Q -->|"Execute a managed request"| T["session, natural-route, or task"]
|
|
16
|
+
Q -->|"Run the control plane"| S["serve"]
|
|
17
|
+
Q -->|"Preserve only Ollama APIs"| P["proxy"]
|
|
18
|
+
Q -->|"Measure or compare"| B["bench-all, run, or eval"]
|
|
19
|
+
Q -->|"Create a routing policy"| E["policy-from-eval"]
|
|
20
|
+
Q -->|"Compare CLI and MCP"| L["tools"]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
| Command | Purpose | Needs `freellama serve` |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `init` | Inspect prerequisites and print a side-effect-free first-run plan | No |
|
|
26
|
+
| `serve` | Run the control plane and Ollama-compatible proxy | Starts it |
|
|
27
|
+
| `auth-token` | Create a new mode-0600 bearer-token file without printing the secret | No |
|
|
28
|
+
| `models` | List installed models, capabilities, residency, and evidence | Yes |
|
|
29
|
+
| `machine` | Print portable host RAM, CPU, OS, architecture, disk, and the local Ollama endpoint | Yes |
|
|
30
|
+
| `session` | Create an affinity scope for related tasks | Yes |
|
|
31
|
+
| `route` | Choose a model and request profile without executing it | Yes |
|
|
32
|
+
| `recommend` | Return an installed route or a reviewed installation plan | Yes |
|
|
33
|
+
| `natural-route` | Convert natural language to a route intent locally, then route it | Yes |
|
|
34
|
+
| `task` | Route and execute one nonstreaming task | Yes |
|
|
35
|
+
| `proxy` | Run only the Ollama-compatible retry and telemetry sidecar | No |
|
|
36
|
+
| `bench-all` | Measure installed models by capability group | No |
|
|
37
|
+
| `policy-from-eval` | Generate policy from quality-evaluation pass rates | No |
|
|
38
|
+
| `tools` | Print MCP tools and their CLI equivalents | No |
|
|
39
|
+
| `doctor` | Inspect Ollama, hardware, versions, and effective settings | No |
|
|
40
|
+
| `run` | Run a frozen suite against one Ollama build | No |
|
|
41
|
+
| `eval` | Compare the same frozen suite against stock and candidate builds | No |
|
|
42
|
+
|
|
43
|
+
Run `npx @octocodeai/freellama <command> --help` for every accepted flag and enum value. The executable's help
|
|
44
|
+
is authoritative; this page explains how the commands fit together.
|
|
45
|
+
|
|
46
|
+
## Start the control plane
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx @octocodeai/freellama serve --recommendation-catalog recommendations.example.toml
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The default listener is `http://127.0.0.1:11435`, and the default Ollama upstream is
|
|
53
|
+
`http://127.0.0.1:11434`. Use `--listen` and `--upstream` to change them. Nonloopback listeners
|
|
54
|
+
require `--allow-remote` and a token from `--auth-token-file` or
|
|
55
|
+
`FREELLAMA_AUTH_TOKEN_FILE`. Authentication applies to managed and passthrough routes.
|
|
56
|
+
|
|
57
|
+
`serve` persists bounded adaptive feedback under the platform data directory by default. Override
|
|
58
|
+
the path with `--feedback-file`; use `--ephemeral-feedback` only for disposable runs. See the
|
|
59
|
+
[production runbook](PRODUCTION.md) for token generation, state files, and ingress requirements.
|
|
60
|
+
|
|
61
|
+
`serve` exposes both managed routes under `/_freellama/v1/*` and byte-preserving Ollama routes under
|
|
62
|
+
`/api/*` and `/v1/*`. Use `proxy` when you need only the latter. See
|
|
63
|
+
[Architecture](ARCHITECTURE.md) for the request flow.
|
|
64
|
+
|
|
65
|
+
### Assign exact models to CPU
|
|
66
|
+
|
|
67
|
+
Use `--cpu-upstream` with one or more `--cpu-model` values to send named models to a second Ollama
|
|
68
|
+
process. Other managed models and all raw passthrough traffic remain on the primary process.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx @octocodeai/freellama serve \
|
|
72
|
+
--upstream http://127.0.0.1:11434 \
|
|
73
|
+
--cpu-upstream http://127.0.0.1:11436 \
|
|
74
|
+
--cpu-model nomic-embed-text:latest
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
FreeLlama rejects a CPU upstream with no assignments, nonloopback upstreams, and endpoints that are
|
|
78
|
+
aliases for the same socket. It also sets `options.num_gpu=0` on CPU-assigned managed requests.
|
|
79
|
+
Read [CPU and GPU model routing](CPU_GPU_ROUTING.md) before deploying this layout.
|
|
80
|
+
|
|
81
|
+
After startup, require `contracts.placement_observation` and `placement_evidence_gate`, then inspect
|
|
82
|
+
the declared upstreams:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
curl --silent http://127.0.0.1:11435/_freellama/v1/health |
|
|
86
|
+
jq '{contracts, backends}'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
A missing contract identifies a stale `serve` binary; rebuild and restart it before evaluating
|
|
90
|
+
placement.
|
|
91
|
+
|
|
92
|
+
### Bound admission
|
|
93
|
+
|
|
94
|
+
`--max-concurrent-tasks` is the primary/GPU cost budget, not a request count; it defaults to 2.
|
|
95
|
+
`--cpu-max-concurrent-tasks` controls the independent CPU pool and defaults to 1. Embedding costs 1,
|
|
96
|
+
chat costs 2, and vision costs 4 (capped to the selected backend's pool). A saturated GPU pool does
|
|
97
|
+
not consume CPU permits. `--max-queue-wait-seconds` defaults to 120; when no permit becomes
|
|
98
|
+
available, FreeLlama refuses the task with HTTP 503 instead of waiting forever.
|
|
99
|
+
|
|
100
|
+
These are conservative workload-unit defaults, not detected core or RAM counts. Tune them from
|
|
101
|
+
queue-wait receipts and resident-memory observations on the target host. Ollama still controls
|
|
102
|
+
decoding concurrency with `OLLAMA_NUM_PARALLEL`. It also owns a separate internal queue through
|
|
103
|
+
`OLLAMA_MAX_QUEUE`: managed tasks enter that queue only after FreeLlama admission, while raw proxy
|
|
104
|
+
traffic enters it directly. Start an unmeasured deployment with one loaded model and one parallel
|
|
105
|
+
stream per Ollama process.
|
|
106
|
+
|
|
107
|
+
`route`, `recommend`, and `task` accept
|
|
108
|
+
`--execution-preference auto|prefer-cpu|prefer-gpu`. This is a fallback-capable hint over models
|
|
109
|
+
already assigned by the operator; it never rewrites raw passthrough and never makes an ineligible
|
|
110
|
+
model eligible. Preview the route and inspect the `execution` receipt to confirm whether the hint
|
|
111
|
+
was satisfied. Add `--min-placement-evidence observed` to refuse cold or physically mismatched
|
|
112
|
+
placement; the default `configured` accepts the operator assignment and observes after execution.
|
|
113
|
+
|
|
114
|
+
## Inspect and route
|
|
115
|
+
|
|
116
|
+
Start with the read-only guided receipt, then inspect available models. `init` never pulls a model;
|
|
117
|
+
it stops at exact-tag approval and prints the next prerequisite, serve, managed-task, and MCP steps.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npx @octocodeai/freellama init
|
|
121
|
+
npx @octocodeai/freellama doctor
|
|
122
|
+
npx @octocodeai/freellama models
|
|
123
|
+
npx @octocodeai/freellama machine
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Preview a deterministic decision without spending generation tokens:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npx @octocodeai/freellama route --task coding --objective fastest
|
|
130
|
+
npx @octocodeai/freellama route \
|
|
131
|
+
--task vision \
|
|
132
|
+
--objective fastest \
|
|
133
|
+
--required-capability vision
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Task kinds are `completion`, `coding`, `code-repair`, `tools`, `browser`, `vision`, `embedding`, and
|
|
137
|
+
`long-context`. Objectives are `fastest`, `balanced`, and `quality`. `fastest` can use capability
|
|
138
|
+
and local benchmark evidence alone; `balanced` and `quality` require a policy for that task.
|
|
139
|
+
|
|
140
|
+
`recommend` is side-effect-free. If no installed model qualifies, it can return an installation
|
|
141
|
+
plan from the reviewed recommendation catalog, but it never pulls a model.
|
|
142
|
+
|
|
143
|
+
`natural-route` asks the configured local intent model to produce structured intent, validates that
|
|
144
|
+
intent, and then invokes the same deterministic router as `route`. It does not let the intent model
|
|
145
|
+
choose the final model directly.
|
|
146
|
+
|
|
147
|
+
## Execute tasks
|
|
148
|
+
|
|
149
|
+
The prompt for `task` is positional:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npx @octocodeai/freellama task --task completion --objective fastest "Reply with exactly OK."
|
|
153
|
+
npx @octocodeai/freellama task --task coding --min-confidence medium "Explain this patch."
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Useful task options include:
|
|
157
|
+
|
|
158
|
+
- `--model` for an exact installed model.
|
|
159
|
+
- `--session` for affinity across related requests.
|
|
160
|
+
- `--context-tokens` for a minimum context requirement.
|
|
161
|
+
- repeatable `--required-capability` constraints.
|
|
162
|
+
- repeatable `--image` paths for vision tasks.
|
|
163
|
+
- `--input-file` for batched embedding input, one item per line.
|
|
164
|
+
- `--min-confidence low|medium` to refuse insufficiently evidenced routes before generation.
|
|
165
|
+
|
|
166
|
+
Create a session with `session`, then pass its identifier to related `route` or `task` calls. A
|
|
167
|
+
route preview honors an existing affinity but does not create or change one. Only a successfully
|
|
168
|
+
admitted task execution binds the session to its selected model. Reuse never bypasses capability,
|
|
169
|
+
policy, or memory checks.
|
|
170
|
+
|
|
171
|
+
The CLI has no keep-alive flag. MCP `run_task` accepts `keepAlive`: `"0"` unloads after the request,
|
|
172
|
+
`"-1"` pins the runner, and omission leaves Ollama's default in place.
|
|
173
|
+
|
|
174
|
+
## Earn medium routing confidence
|
|
175
|
+
|
|
176
|
+
Every route starts at low confidence. Medium confidence requires both a task policy and a local
|
|
177
|
+
benchmark record for the selected model.
|
|
178
|
+
|
|
179
|
+
```mermaid
|
|
180
|
+
flowchart LR
|
|
181
|
+
Q["Quality evaluation aggregate"] --> P["policy-from-eval"]
|
|
182
|
+
P --> PF["platform.toml"]
|
|
183
|
+
T["Local throughput benchmark"] --> BR["benchmark-report.json"]
|
|
184
|
+
PF --> S["freellama serve"]
|
|
185
|
+
BR --> S
|
|
186
|
+
S --> C["Medium confidence for configured, measured routes"]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Generate the policy from correctness data and the benchmark report from local runtime data:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
npx @octocodeai/freellama policy-from-eval \
|
|
193
|
+
--aggregate benchmark/local/results/<model>/aggregate.json \
|
|
194
|
+
--task coding \
|
|
195
|
+
--min-pass 0.8 \
|
|
196
|
+
--out platform.toml
|
|
197
|
+
|
|
198
|
+
npx @octocodeai/freellama bench-all --output benchmark-report.json
|
|
199
|
+
npx @octocodeai/freellama serve --recommendation-catalog recommendations.example.toml
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`serve` discovers `platform.toml` and `benchmark-report.json` in its working directory. Explicit
|
|
203
|
+
`--policy-file` and `--benchmark-report` values take precedence.
|
|
204
|
+
|
|
205
|
+
`policy-from-eval` reads `pass_at_1`, not the throughput produced by `bench-all`. It refuses expired
|
|
206
|
+
aggregates and fewer than three trials unless `--allow-smoke` is explicit, and it skips models that
|
|
207
|
+
are not installed. This prevents speed data from being mislabeled as quality evidence.
|
|
208
|
+
|
|
209
|
+
## Compare the CLI and MCP surfaces
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
npx @octocodeai/freellama tools
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
The command prints the maintained parity map. MCP-only operations are `delegate_research` and the
|
|
216
|
+
online `models { view: "library" }` view. CLI-only operations include `serve`, `proxy`, `session`,
|
|
217
|
+
`recommend`, `natural-route`, `bench-all`, `policy-from-eval`, `run`, and `eval`.
|
|
218
|
+
|
|
219
|
+
For the six MCP tool contracts, read the [MCP package reference](../packages/mcp/README.md).
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# Run models on CPU and GPU
|
|
2
|
+
|
|
3
|
+
Use two Ollama processes when a large model should remain on the GPU while smaller helper models run
|
|
4
|
+
on the CPU. FreeLlama assigns exact model tags to the secondary process, pins those managed requests
|
|
5
|
+
with `num_gpu: 0`, and reports the selected backend in catalog and task responses.
|
|
6
|
+
|
|
7
|
+
This configuration is useful for embeddings, intent interpretation, and other small helper models.
|
|
8
|
+
Benchmark large CPU workloads before adopting them. Apple silicon shares memory bandwidth; systems
|
|
9
|
+
with discrete GPUs have different contention and transfer costs, so neither result transfers
|
|
10
|
+
without measurement.
|
|
11
|
+
|
|
12
|
+
## Adapt the design to your hardware
|
|
13
|
+
|
|
14
|
+
The two-process contract is portable; the device-control mechanism and useful workload are
|
|
15
|
+
machine-specific:
|
|
16
|
+
|
|
17
|
+
| Host | Primary process | CPU process defense in depth | Capacity evidence |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| Apple silicon | Metal auto-detection | Managed `num_gpu: 0`; `OLLAMA_LLM_LIBRARY=cpu` is only a hint | Total unified memory plus `/api/ps` |
|
|
20
|
+
| Linux or Windows with NVIDIA | CUDA auto-detection | `CUDA_VISIBLE_DEVICES=-1` plus managed `num_gpu: 0` | Host RAM, `nvidia-smi`, and `/api/ps` |
|
|
21
|
+
| Linux or Windows with AMD | ROCm or Vulkan auto-detection | `ROCR_VISIBLE_DEVICES=-1` or `GGML_VK_VISIBLE_DEVICES=-1`, plus managed `num_gpu: 0` | Host RAM, vendor tools, and `/api/ps` |
|
|
22
|
+
| CPU-only | CPU | A second process can isolate helpers, but it does not create a second accelerator | Host RAM and measured overlap |
|
|
23
|
+
|
|
24
|
+
`doctor` reports `machine.memory_bytes` on macOS, Linux, and Windows. It reports
|
|
25
|
+
`machine.unified_memory_bytes` only when unified memory is known; that compatibility field is not a
|
|
26
|
+
VRAM estimate on discrete-GPU hosts. Model-library fit is therefore a host-memory preflight, and
|
|
27
|
+
actual accelerator placement must come from Ollama's resident-runner report.
|
|
28
|
+
|
|
29
|
+
An agent can choose `auto`, `prefer_cpu`, or `prefer_gpu`, but it cannot invent topology. The
|
|
30
|
+
operator owns the backend endpoints and exact CPU-eligible tags. This split lets the same schema
|
|
31
|
+
work on a laptop, a CPU-only server, or a multi-GPU workstation without silently moving arbitrary
|
|
32
|
+
client requests.
|
|
33
|
+
|
|
34
|
+
## Understand the boundary
|
|
35
|
+
|
|
36
|
+
The primary upstream owns the compatibility API and GPU-default managed tasks. The secondary
|
|
37
|
+
upstream owns only exact tags configured with `--cpu-model`:
|
|
38
|
+
|
|
39
|
+
```mermaid
|
|
40
|
+
flowchart TB
|
|
41
|
+
CLIENT["Client"] --> F["FreeLlama :11435"]
|
|
42
|
+
F --> K{"Request path"}
|
|
43
|
+
K -->|"Raw /api/* or /v1/*"| GP["Primary Ollama :11434"]
|
|
44
|
+
K -->|"Managed /_freellama/v1/tasks"| R["Route exact model"]
|
|
45
|
+
R -->|"Default assignment"| GP
|
|
46
|
+
R -->|"--cpu-model match"| O["Set options.num_gpu = 0"]
|
|
47
|
+
O --> CP["Secondary Ollama :11436"]
|
|
48
|
+
GP --> GPU["GPU runner"]
|
|
49
|
+
CP --> CPU["CPU runner"]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
FreeLlama does not inspect or rewrite raw passthrough request bodies. A direct Ollama client that
|
|
53
|
+
needs model-aware CPU placement must use the managed task endpoint instead.
|
|
54
|
+
|
|
55
|
+
## Start the backends
|
|
56
|
+
|
|
57
|
+
Start the primary Ollama process with a conservative local-only baseline:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
OLLAMA_HOST=127.0.0.1:11434 OLLAMA_NO_CLOUD=1 \
|
|
61
|
+
OLLAMA_MAX_LOADED_MODELS=1 OLLAMA_NUM_PARALLEL=1 ollama serve
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Start a second process on another loopback port:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
OLLAMA_HOST=127.0.0.1:11436 OLLAMA_NO_CLOUD=1 \
|
|
68
|
+
OLLAMA_MAX_LOADED_MODELS=1 OLLAMA_NUM_PARALLEL=1 \
|
|
69
|
+
OLLAMA_LLM_LIBRARY=cpu ollama serve
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The library override is experimental and varies by Ollama build. On the measured Apple Metal 0.33.2
|
|
73
|
+
server, Ollama accepted `OLLAMA_LLM_LIBRARY=cpu` but still selected Metal until the request included
|
|
74
|
+
`num_gpu: 0`. FreeLlama adds that option to CPU-assigned managed requests.
|
|
75
|
+
|
|
76
|
+
On other GPU backends, Ollama also documents process-level device controls:
|
|
77
|
+
|
|
78
|
+
- NVIDIA: `CUDA_VISIBLE_DEVICES=-1`
|
|
79
|
+
- ROCm: `ROCR_VISIBLE_DEVICES=-1`
|
|
80
|
+
- Vulkan: `GGML_VK_VISIBLE_DEVICES=-1`
|
|
81
|
+
|
|
82
|
+
Use a device control appropriate for the Ollama build in addition to FreeLlama's request option when
|
|
83
|
+
you need defense in depth.
|
|
84
|
+
|
|
85
|
+
## Assign models
|
|
86
|
+
|
|
87
|
+
Start FreeLlama with the two upstreams and at least one exact CPU model tag:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
./target/release/freellama serve \
|
|
91
|
+
--upstream http://127.0.0.1:11434 \
|
|
92
|
+
--cpu-upstream http://127.0.0.1:11436 \
|
|
93
|
+
--cpu-model nomic-embed-text:latest
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Repeat `--cpu-model` to assign more tags:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
./target/release/freellama serve \
|
|
100
|
+
--upstream http://127.0.0.1:11434 \
|
|
101
|
+
--cpu-upstream http://127.0.0.1:11436 \
|
|
102
|
+
--cpu-model nomic-embed-text:latest \
|
|
103
|
+
--cpu-model qwen2.5:0.5b
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Use the exact name returned by Ollama, including `:latest` when present. FreeLlama rejects these
|
|
107
|
+
unsafe or ambiguous configurations:
|
|
108
|
+
|
|
109
|
+
- A CPU upstream without a CPU model assignment
|
|
110
|
+
- A CPU model assignment without `--cpu-upstream`
|
|
111
|
+
- Empty model names
|
|
112
|
+
- The same socket expressed through aliases such as `localhost:11434` and `127.0.0.1:11434`
|
|
113
|
+
- A non-loopback listener or upstream
|
|
114
|
+
|
|
115
|
+
Set independent admission budgets when the defaults do not fit the workload:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
./target/release/freellama serve \
|
|
119
|
+
--upstream http://127.0.0.1:11434 \
|
|
120
|
+
--cpu-upstream http://127.0.0.1:11436 \
|
|
121
|
+
--cpu-model nomic-embed-text:latest \
|
|
122
|
+
--max-concurrent-tasks 2 \
|
|
123
|
+
--cpu-max-concurrent-tasks 1
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The defaults above admit one ordinary primary-backend chat (cost 2) and one CPU embedding (cost 1)
|
|
127
|
+
at the same time. They are conservative workload units for the common `OLLAMA_NUM_PARALLEL=1`
|
|
128
|
+
layout, not a profile of the development Mac. Increase them only after measuring queue wait,
|
|
129
|
+
resident memory, and Ollama's own parallel setting on the target host.
|
|
130
|
+
|
|
131
|
+
## Let an agent express placement intent
|
|
132
|
+
|
|
133
|
+
MCP `run_task` and the managed HTTP route accept `executionPreference`: `auto`, `prefer_cpu`, or
|
|
134
|
+
`prefer_gpu` (HTTP: `execution_preference`). `minPlacementEvidence` accepts `configured` or
|
|
135
|
+
`observed` (HTTP: `min_placement_evidence`). Always preview consequential work:
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"task": "embedding",
|
|
140
|
+
"objective": "fastest",
|
|
141
|
+
"executionPreference": "prefer_cpu",
|
|
142
|
+
"minPlacementEvidence": "observed",
|
|
143
|
+
"preview": true
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
A preview accepts routing constraints only. Add the embedding input when submitting the execution
|
|
148
|
+
request after accepting the route.
|
|
149
|
+
|
|
150
|
+
The agent does not gain authority to assign models. FreeLlama first applies model installation,
|
|
151
|
+
capability, policy, context, explicit-model, and session-affinity constraints. It only honors the
|
|
152
|
+
preference if an eligible model is already assigned to that backend. Inspect
|
|
153
|
+
`execution.preference_satisfied`, `execution.backend`, `execution.observation`, and
|
|
154
|
+
`execution.reason`. `observed` fails closed for cold models: warm once with `configured`, inspect
|
|
155
|
+
the receipt, then retry with observed evidence.
|
|
156
|
+
|
|
157
|
+
## Verify placement
|
|
158
|
+
|
|
159
|
+
Inspect FreeLlama's declared backend contract:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
curl --silent http://127.0.0.1:11435/_freellama/v1/health | jq '.backends'
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The output is similar to the following:
|
|
166
|
+
|
|
167
|
+
```json
|
|
168
|
+
{
|
|
169
|
+
"gpu": {
|
|
170
|
+
"upstream": "http://127.0.0.1:11434"
|
|
171
|
+
},
|
|
172
|
+
"cpu": {
|
|
173
|
+
"upstream": "http://127.0.0.1:11436",
|
|
174
|
+
"models": ["nomic-embed-text:latest"]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Run a managed embedding task:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
curl --silent http://127.0.0.1:11435/_freellama/v1/tasks \
|
|
183
|
+
--header 'content-type: application/json' \
|
|
184
|
+
--data '{
|
|
185
|
+
"task": "embedding",
|
|
186
|
+
"objective": "fastest",
|
|
187
|
+
"model": "nomic-embed-text:latest",
|
|
188
|
+
"input": "placement check",
|
|
189
|
+
"keep_alive": "-1"
|
|
190
|
+
}' | jq '{execution, admission, route}'
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The `execution` object separates the configured `backend`, requested compatibility `placement`, and
|
|
194
|
+
post-run `observation`. The source is Ollama `/api/ps`; direct inspection remains useful:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
curl --silent http://127.0.0.1:11434/api/ps | jq '.models[] | {name, size_vram}'
|
|
198
|
+
curl --silent http://127.0.0.1:11436/api/ps | jq '.models[] | {name, size_vram}'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
A fully CPU-loaded model reports `size_vram:0`; a fully GPU-loaded model reports positive resident
|
|
202
|
+
VRAM. `observation.status` is `verified`, `mismatch`, or unavailable. Unknown, mixed, and mismatched
|
|
203
|
+
samples are reported but excluded from adaptive feedback.
|
|
204
|
+
|
|
205
|
+
## Understand concurrency
|
|
206
|
+
|
|
207
|
+
Each backend has its own weighted admission pool and its own resident/shared and
|
|
208
|
+
transition/exclusive lock. CPU and GPU model transitions therefore do not block each other, and a
|
|
209
|
+
GPU burst cannot spend the CPU helper's permit:
|
|
210
|
+
|
|
211
|
+
```mermaid
|
|
212
|
+
flowchart TD
|
|
213
|
+
T["Managed task"] --> B{"Selected backend"}
|
|
214
|
+
B -->|"GPU"| GA["GPU admission pool<br/>default 2 units"]
|
|
215
|
+
B -->|"CPU"| CA["CPU admission pool<br/>default 1 unit"]
|
|
216
|
+
GA --> GL{"Resident?"}
|
|
217
|
+
CA --> CL{"Resident?"}
|
|
218
|
+
GL -->|"Yes"| GR["GPU shared permit"]
|
|
219
|
+
GL -->|"No"| GX["GPU exclusive transition"]
|
|
220
|
+
CL -->|"Yes"| CR["CPU shared permit"]
|
|
221
|
+
CL -->|"No"| CX["CPU exclusive transition"]
|
|
222
|
+
GR --> GO["Primary Ollama"]
|
|
223
|
+
GX --> GO
|
|
224
|
+
CR --> CO["Secondary Ollama"]
|
|
225
|
+
CX --> CO
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
FreeLlama can overlap requests across the two servers even when each Ollama process uses
|
|
229
|
+
`OLLAMA_NUM_PARALLEL=1`. Raising `OLLAMA_NUM_PARALLEL` affects parallel requests within one server
|
|
230
|
+
and multiplies K/V-cache memory; it is a separate tuning decision. `OLLAMA_MAX_QUEUE` likewise
|
|
231
|
+
bounds each Ollama process after FreeLlama admission. It does not replace either backend's weighted
|
|
232
|
+
FreeLlama budget or queue-wait deadline.
|
|
233
|
+
|
|
234
|
+
## Understand automatic feedback
|
|
235
|
+
|
|
236
|
+
With `executionPreference: "auto"`, unpinned `fastest` and `balanced` work can select a backend from
|
|
237
|
+
observed warm task duration. The loop is deliberately bounded:
|
|
238
|
+
|
|
239
|
+
```mermaid
|
|
240
|
+
flowchart LR
|
|
241
|
+
X["Successful resident task"] --> O["Record duration by<br/>task + backend"]
|
|
242
|
+
O --> N{"At least 3 samples<br/>on both backends?"}
|
|
243
|
+
N -->|"No"| D["Keep deterministic default"]
|
|
244
|
+
N -->|"Yes"| C{"More than 10% faster<br/>per work unit?"}
|
|
245
|
+
C -->|"Yes"| P["Prefer measured backend"]
|
|
246
|
+
C -->|"No"| D
|
|
247
|
+
P --> R["Return reason + placement receipt"]
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Generation compares decode nanoseconds per output token; embeddings compare total nanoseconds per
|
|
251
|
+
input token. Buckets are model-specific and reset when the selected model changes. Only physically
|
|
252
|
+
verified placement contributes; cold transitions and assignment mismatches do not. A difference
|
|
253
|
+
of 10% or less is treated as noise. Feedback never overrides an explicit model, session affinity,
|
|
254
|
+
capability or policy guards, or the `quality` objective. The CLI persists the bounded, versioned,
|
|
255
|
+
prompt-free aggregate snapshot atomically by default; `--ephemeral-feedback` opts into reset-on-
|
|
256
|
+
restart behavior. `GET /_freellama/v1/health` exposes persistence status, sample counts, readiness,
|
|
257
|
+
and per-backend admission capacity so an agent can inspect the loop instead of guessing.
|
|
258
|
+
|
|
259
|
+
## Interpret the measured Mac result
|
|
260
|
+
|
|
261
|
+
The local benchmark warmed both models, then ran three matched sequential and parallel trials using
|
|
262
|
+
a 100-line Qwen completion and a 256-item Nomic embedding batch:
|
|
263
|
+
|
|
264
|
+
| Trial | Sequential | Parallel |
|
|
265
|
+
|---|---:|---:|
|
|
266
|
+
| 1 | 34.586 s | 27.499 s |
|
|
267
|
+
| 2 | 38.988 s | 28.233 s |
|
|
268
|
+
| 3 | 37.997 s | 38.167 s |
|
|
269
|
+
| Median | 37.997 s | 28.233 s |
|
|
270
|
+
|
|
271
|
+
The median parallel result was 1.346 times faster, a 25.70% wall-time reduction. The third trial
|
|
272
|
+
shows why this feature is workload-dependent: CPU work can slow Metal work through host and unified-
|
|
273
|
+
memory contention.
|
|
274
|
+
|
|
275
|
+
The placement and compatibility guardrails also passed:
|
|
276
|
+
|
|
277
|
+
| Guardrail | Observed result |
|
|
278
|
+
|---|---|
|
|
279
|
+
| GPU placement | `qwen3.8:27b-mlx`, `size_vram: 19,175,677,668` |
|
|
280
|
+
| CPU placement | `nomic-embed-text:latest`, `size_vram: 0` |
|
|
281
|
+
| Managed execution | Every recorded request returned HTTP 200 with the correct upstream receipt |
|
|
282
|
+
| Admission | `queue_wait_ms: 0` for every request |
|
|
283
|
+
| GPU output | Identical output length in every sequential and parallel trial |
|
|
284
|
+
| Raw compatibility | `/api/version` through FreeLlama returned primary Ollama 0.33.2 |
|
|
285
|
+
|
|
286
|
+
An earlier, lighter overlap check paired a small CPU embedding with a cold GPU completion. The CPU
|
|
287
|
+
request completed in 60 ms while the GPU request continued and completed in 7.391 seconds. This is
|
|
288
|
+
the intended sweet spot: a small helper can finish without waiting for unrelated GPU generation.
|
|
289
|
+
|
|
290
|
+
A fresh post-build smoke check repeated the concurrent path through the persistent services. Both
|
|
291
|
+
requests returned HTTP 200 in 8.303 seconds wall time, both reported zero queue wait, Qwen returned
|
|
292
|
+
`OK` from the primary backend, and Nomic returned one embedding from the CPU backend. That runner
|
|
293
|
+
snapshot reported 18,486,900,456 GPU bytes for Qwen and zero for Nomic. The positive GPU byte count
|
|
294
|
+
is the placement invariant; its absolute value can change with the request profile and runner state.
|
|
295
|
+
|
|
296
|
+
A production-gate adversarial trial assigned `qwen3.8:27b-mlx` to the CPU endpoint with
|
|
297
|
+
`num_gpu:0`. Ollama still reported all 18,490,183,380 resident bytes in VRAM. FreeLlama returned
|
|
298
|
+
`observation.status:"mismatch"`, `feedback.accepted:false`, kept CPU samples at zero, and refused a
|
|
299
|
+
subsequent `min_placement_evidence:"observed"` preview with HTTP 422. This is why assignment and
|
|
300
|
+
physical processor are distinct fields.
|
|
301
|
+
|
|
302
|
+
Immediate unload is a managed transaction. For `keep_alive:0`, FreeLlama temporarily retains the
|
|
303
|
+
runner, observes `/api/ps`, records feedback only when placement is verified, requests an explicit
|
|
304
|
+
unload, and verifies that the runner is no longer resident. Inspect both `execution.observation`
|
|
305
|
+
and `execution.lifecycle`; a failed unload is reported without discarding the completed task output.
|
|
306
|
+
|
|
307
|
+
Prefer the following assignments:
|
|
308
|
+
|
|
309
|
+
- Small embedding models
|
|
310
|
+
- Lightweight intent models
|
|
311
|
+
- Short extraction or classification helpers with measured CPU behavior
|
|
312
|
+
|
|
313
|
+
Benchmark these assignments before adopting them:
|
|
314
|
+
|
|
315
|
+
- Large generative models on CPU
|
|
316
|
+
- Large embedding batches that saturate memory bandwidth
|
|
317
|
+
- Two workloads whose combined resident memory approaches system capacity
|
|
318
|
+
|
|
319
|
+
The local measurement receipt is written to
|
|
320
|
+
`.octocode/evals/cpu-gpu-concurrency-benchmark.md`. Git intentionally ignores that path, so the
|
|
321
|
+
trial table in "Interpret the measured result" carries the durable result while the local artifact
|
|
322
|
+
retains the full run detail.
|
|
323
|
+
|
|
324
|
+
## Troubleshoot the secondary backend
|
|
325
|
+
|
|
326
|
+
If an assigned model does not appear, check the following conditions:
|
|
327
|
+
|
|
328
|
+
1. Confirm that the exact tag appears in `GET http://127.0.0.1:11436/api/tags`.
|
|
329
|
+
2. Confirm that the secondary server is listening on the configured port.
|
|
330
|
+
3. Run a managed task, then confirm `size_vram: 0` on the secondary `/api/ps` response.
|
|
331
|
+
4. Inspect the secondary Ollama log for a runner command containing `-ngl 0` or equivalent CPU
|
|
332
|
+
placement evidence.
|
|
333
|
+
5. Confirm that the request used `/_freellama/v1/tasks`, not raw `/api/chat` passthrough.
|
|
334
|
+
|
|
335
|
+
A configured but unreachable CPU backend causes model discovery to fail closed. GPU-managed tasks
|
|
336
|
+
can also be unavailable until the secondary server returns. Raw passthrough to the primary server
|
|
337
|
+
continues to work because it does not depend on catalog discovery.
|
|
338
|
+
|
|
339
|
+
For the surrounding runtime settings, see
|
|
340
|
+
[Ollama and FreeLlama optimization](OLLAMA_SYSTEM_OPTIMIZATION.md). For all `serve` flags, see the
|
|
341
|
+
[CLI reference](CLI.md).
|
|
342
|
+
|
|
343
|
+
## Upstream sources
|
|
344
|
+
|
|
345
|
+
- [Ollama hardware support](https://docs.ollama.com/gpu) documents GPU visibility controls and
|
|
346
|
+
Apple Metal support.
|
|
347
|
+
- [Ollama FAQ](https://docs.ollama.com/faq) documents concurrency, residency, `/api/ps` processor
|
|
348
|
+
reporting, and K/V-cache behavior.
|
|
349
|
+
- [Ollama troubleshooting](https://github.com/ollama/ollama/blob/main/docs/troubleshooting.mdx)
|
|
350
|
+
documents the experimental `OLLAMA_LLM_LIBRARY` override.
|
|
351
|
+
- [Ollama API types](https://github.com/ollama/ollama/blob/main/api/types.go) defines `num_gpu` as a
|
|
352
|
+
runner-load option.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Token economics
|
|
2
|
+
|
|
3
|
+
This reference keeps benchmark detail out of the README's "what, why, and run it" path. The
|
|
4
|
+
per-call rows come from one machine (M4 Pro, 48 GB unified memory); values marked with `~` and the
|
|
5
|
+
session projection are estimates derived from those calls.
|
|
6
|
+
|
|
7
|
+
## Per-call, measured
|
|
8
|
+
|
|
9
|
+
| Work | Source tokens | Returned tokens | Preserved |
|
|
10
|
+
|---|---:|---:|---:|
|
|
11
|
+
| 6 grounded code questions | 59,208 | 1,742 | **97.1%** |
|
|
12
|
+
| 4 text embeddings (307 ms) | 17,600 | ~200 | **98.9%** |
|
|
13
|
+
| 1 image, OCR, byte-exact | 1,970 | 37 | **98.1%** |
|
|
14
|
+
| Historical tool-schema surface, per turn | <3.2k | 0 | **100%** |
|
|
15
|
+
|
|
16
|
+
## Current MCP-envelope audit (2026-09-03)
|
|
17
|
+
|
|
18
|
+
The earlier table describes a historical workload on an M4 Pro. This audit measures the current
|
|
19
|
+
eight-tool MCP build on the current host. JSON tokenization varies by client and model, so it records
|
|
20
|
+
bytes first and gives `characters / 4` only as a deliberately rough token estimate.
|
|
21
|
+
|
|
22
|
+
| Current result | Canonical structured chars | Compact text chars | Duplicate chars avoided | Rough duplicate-token estimate |
|
|
23
|
+
|---|---:|---:|---:|---:|
|
|
24
|
+
| `doctor {view:"summary"}` | 3,099 | 35 | 3,064 | ~766 |
|
|
25
|
+
| `doctor {view:"scheduler"}` | 3,933 | 35 | 3,898 | ~975 |
|
|
26
|
+
| `doctor {view:"config"}` | 7,373 | 86 | 7,287 | ~1,822 |
|
|
27
|
+
| `doctor {view:"full"}` | 10,836 | 185 | 10,651 | ~2,663 |
|
|
28
|
+
| `models {view:"raw", limit:1}` | 447 | 43 | 404 | ~101 |
|
|
29
|
+
|
|
30
|
+
These are **not automatically billed-token savings**. They are the second serialized text copy
|
|
31
|
+
that FreeLlama does not emit. An MCP host that feeds both `structuredContent` and text into a model
|
|
32
|
+
can avoid roughly the listed amount; a host that already keeps only one representation does not.
|
|
33
|
+
The canonical structured payload still exists for deterministic clients that need it.
|
|
34
|
+
|
|
35
|
+
The current eight-tool schema plus initialization instructions measured 17,935 characters, or
|
|
36
|
+
~4,484 tokens using the same rough conversion. The integration suite enforces a ceiling below 4,500
|
|
37
|
+
estimated tokens. This is tool-schema **rent**, not a saving: clients pay it whenever they expose
|
|
38
|
+
the entire tool list to a model. Read the bundled documentation resource on demand and do not expose
|
|
39
|
+
unneeded tools in a client that supports tool filtering.
|
|
40
|
+
|
|
41
|
+
Delegated research has a different economics boundary. It keeps source files and full tool
|
|
42
|
+
observations out of the calling agent's active prompt, but it spends local Ollama input/output
|
|
43
|
+
tokens in the delegated agent. The historical research rows below remain useful workload evidence;
|
|
44
|
+
they are not a claim that the current installed model or host can reproduce those savings. Measure
|
|
45
|
+
the same question, source size, model, context policy, and client transcript before using a number
|
|
46
|
+
as a production budget.
|
|
47
|
+
|
|
48
|
+
## Scaled to a session
|
|
49
|
+
|
|
50
|
+
20 grounded questions, 200 turns, one embedding index over 322 chunks:
|
|
51
|
+
|
|
52
|
+
| Component | Tokens preserved |
|
|
53
|
+
|---|---:|
|
|
54
|
+
| Research offload | 191,553 |
|
|
55
|
+
| Embedding vectors withheld | 354,200 |
|
|
56
|
+
| Schema rent avoided, after ~90% prompt-cache discount | 59,740 |
|
|
57
|
+
| **Total** | **≈ 605,000 — about 3 × a 200K window** |
|
|
58
|
+
|
|
59
|
+
## Measurement and projection limits
|
|
60
|
+
|
|
61
|
+
Schema rent is nominally **597,400** tokens (200 turns × the former eight-tool schemas). Including
|
|
62
|
+
the nominal figure puts the headline at **1.14M — nearly double**. This report discounts it by ~90%
|
|
63
|
+
because prompt caching makes cached input far cheaper,
|
|
64
|
+
and because the rent only accrues at all if a *separate* small model holds the tool schemas. The
|
|
65
|
+
first two rows are the uncontestable part: that data physically never reaches the orchestrator.
|
|
66
|
+
|
|
67
|
+
**Do not lead with this number.** Providers, caching strategies, context windows, and pricing all
|
|
68
|
+
move, and a token count moves with them. The durable benefit is **context isolation** — the
|
|
69
|
+
orchestrator never receives whole files, raw vectors, OCR output, intermediate research, or
|
|
70
|
+
repetitive schemas. That improves context *quality* as well as cost, and it does not depend on
|
|
71
|
+
anyone's price list.
|
|
72
|
+
|
|
73
|
+
## The cost side
|
|
74
|
+
|
|
75
|
+
| | |
|
|
76
|
+
|---|---|
|
|
77
|
+
| Grounded question latency | **7–62 s** vs ~1 s for a frontier model that already holds the file |
|
|
78
|
+
| Where it goes | **51% prompt re-evaluation**, 42% generation, 7% model load (measured on a 4-turn run) |
|
|
79
|
+
| Scaling | turn-dominated: `seconds ≈ 9.8 × tool_calls`, correlation **0.811** over 60 runs |
|
|
80
|
+
| Break-even | past ~1k tokens of source the token maths wins; below that, read the file directly |
|
|
81
|
+
| Judgment ceiling | ~67% accurate, in the same confident tone as the ~99% case |
|
|
82
|
+
|
|
83
|
+
**Correction (measured same day): prefix-cache reuse WORKS on this path.** An earlier revision of
|
|
84
|
+
this file claimed the opposite by misreading `prompt_eval_count`, which counts *all* prompt tokens
|
|
85
|
+
whether or not they were served from cache. The durations tell the truth:
|
|
86
|
+
|
|
87
|
+
| Probe (raw `/api/chat`, 2,462-token prefix) | prompt_eval |
|
|
88
|
+
|---|---:|
|
|
89
|
+
| turn 1, cold | 18,631 ms |
|
|
90
|
+
| turn 2, warm — tiny suffix appended | **281 ms** |
|
|
91
|
+
| a *different* conversation interjected | 22,467 ms (its own cold prefill) |
|
|
92
|
+
| turn 3 of the original, after the interjection | **285 ms — cache survived** |
|
|
93
|
+
|
|
94
|
+
So the per-turn cost is **prefill of NEW tokens plus generation**, not re-reading old context, and
|
|
95
|
+
the cache even survives interleaved conversations. The `seconds ≈ 9.8 × tool_calls` relationship
|
|
96
|
+
still holds empirically — each turn adds a fresh observation to prefill (~130 tok/s measured) and a
|
|
97
|
+
generation (~40 tok/s) — but the mechanism is new-token cost, not context re-evaluation. Fewer,
|
|
98
|
+
better-aimed commands remain the lever; "the cache is broken" is not the reason.
|
|
99
|
+
|
|
100
|
+
**One consequence worth acting on: context compaction is cache-hostile.** `fit_to_context` rewrites
|
|
101
|
+
old observations when the window is nearly full, which changes the byte prefix and invalidates the
|
|
102
|
+
cache from that point — the next turn re-prefills everything after the edit. The cheap mitigation is
|
|
103
|
+
headroom: `OLLAMA_KV_CACHE_TYPE=q8_0` roughly halves KV memory (flash attention is already auto-on,
|
|
104
|
+
measured and documented in `doctor`), which buys double the context for the same memory and makes
|
|
105
|
+
compaction — and therefore invalidation — rare.
|