Ornith 1.0 9B is an MIT-licensed reasoning model built for agentic coding. It runs pi through llama.cpp on a 16 GB MacBook Air. No API key, no cloud, works offline.
| download | 5.98 GB (UD-Q4_K_XL) |
| resident | about 7 GiB at 64k context |
| generation | 13 to 15 tokens/second on an M4 |
| SWE-bench Verified | 69.4 |
| licence | MIT |
This gives you the llama binary. Confirm with llama version.
macOS -- Homebrew:
brew install llama.cppLinux -- installama.sh detects your CPU and GPU
(CUDA / ROCm / Vulkan) and drops llama into ~/.local/bin:
curl -fsSL https://angt.github.io/installama.sh | shNo chat template to download or patch. Unsloth's GGUF already carries a fixed one, and llama.cpp reads it out of the model file.
Put this in ~/.zshrc (macOS) or ~/.bashrc (Linux):
alias ornith-9b='mkdir -p ~/.llama-logs && llama serve -hf unsloth/Ornith-1.0-9B-GGUF:UD-Q4_K_XL -c 65536 -fa 1 -ctk q8_0 -ctv q8_0 -ctxcp 8 --prio 2 --no-ui --jinja --parallel 1 --cache-ram 0 --no-mmproj --temp 1.0 --top-p 0.95 --top-k 20 --min-p 0 --reasoning on 2>&1 | tee ~/.llama-logs/ornith-9b-$(date +%Y%m%d-%H%M%S).log'source ~/.zshrc
ornith-9bThe first run downloads 5.98 GB with no output for several minutes. The server is up when the log
prints server is listening on http://127.0.0.1:8080.
| flag | what it does |
|---|---|
-c 65536 |
Context. 64k leaves memory free; the model trains to 262144 and -fit will grant all of it on 16 GB if you ask. |
-fa 1 |
Flash attention. Required for a quantized KV cache. |
-ctk q8_0 -ctv q8_0 |
8-bit KV cache, roughly half the size of f16. |
-ctxcp 8 |
Cap context checkpoints. Each is 50 MiB and the default of 32 can hold 1.5 GiB. |
--prio 2 |
High thread priority, so the OS interrupts inference less. |
--parallel 1 |
One slot, so the whole context serves one conversation. |
--cache-ram 0 |
No cross-chat KV cache in RAM, which keeps memory pressure low. |
--no-mmproj |
Skip the 922 MB vision projector. Text only. |
--temp 1.0 --top-p 0.95 --top-k 20 --min-p 0 |
Ornith's recommended sampling. Low temperature sends this model into repetition loops. |
--reasoning on |
Parse the model's <think> blocks. |
Only 8 of Ornith's 32 layers use full attention; the rest are linear. That is why 256k of context costs 4.25 GiB rather than the ~17 GiB a conventional 9B would need.
curl -fsSL https://pi.dev/install.sh | shpi install npm:pi-llama-cpp
pi install npm:pi-smart-fetch
pi install npm:pi-smart-web-search
pi install npm:pi-platepi-llama-cppfinds the local server.pi-smart-fetchreads web pages.pi-smart-web-searchsearches the web, no API key. Requires pi-smart-fetch.pi-platesupplies the date, the machine and your git state. Without it, models search for last year's information.
# terminal 1
source ~/.zshrc && ornith-9b# terminal 2
mkdir -p ~/Code/demo && cd ~/Code/demo && piBuild a todo app in ./todo with separate index.html, style.css and app.js. No dependencies, no build step.
Features: add, toggle complete, delete, filter all/active/done, clear completed, item count, persist to localStorage.
Use one delegated click handler. Keep ids as strings everywhere. When the files are written, re-read app.js and trace each feature once to confirm the logic holds.
Dark theme, readable at half-screen width. Then run npx serve.
About 15 turns and 5 minutes. The last paragraph earns its place: without it the first attempt
compared a numeric id against dataset.id, which is always a string, so delete and toggle silently
did nothing.
A small model writes plausible TypeScript. Type-aware linting is what tells it when plausible is
wrong. This turns on typescript-eslint's strictest type-checked rules, its stylistic rules, and
eslint-plugin-repertoire, which catches
characters nobody typed -- em dashes, curly quotes, invisible zero-width joiners.
npm install --save-dev eslint typescript typescript-eslint eslint-config-prettier eslint-plugin-repertoire// eslint.config.mjs
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import prettier from "eslint-config-prettier";
import repertoire from "eslint-plugin-repertoire";
export default tseslint.config(
{ ignores: ["dist/**", "node_modules/**"] },
{
files: ["**/*.ts"],
extends: [
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
plugins: { repertoire },
rules: { "repertoire/no-undeclared-characters": "error" },
},
prettier,
);strictTypeChecked reads the type checker rather than the syntax, so it catches a floating promise,
an unnecessary condition, or an unsafe any that parsing alone cannot see. Those are the mistakes a
9B model makes most.
Point the agent at the output:
Run npx eslint . and fix every error.
Be warned that ESLint flat config is the hardest thing to ask a small local model to write. It is
recent enough that training data is dominated by the old .eslintrc format, and models regress to it
even with the current documentation in front of them. Write the config yourself and let the model fix
what it reports.
Add markdown, JSON and YAML to the character check with their parsers:
npm install --save-dev @eslint/markdown jsonc-eslint-parser yaml-eslint-parser{
files: ["**/*.md"],
language: "markdown/gfm",
plugins: { markdown, repertoire },
rules: { "repertoire/no-undeclared-characters": "error" },
},Writing in another language? Name it, and its letters are permitted:
"repertoire/no-undeclared-characters": ["error", { languages: ["de", "pl"] }]