What You'll Build
A local browser computer-use agent (CUA) on the smallest Apple Silicon configuration this site covers: Microsoft's Fara1.5-9B served by llama-server on Metal with the vision projector loaded, so the model actually sees browser screenshots, driven by Microsoft's own fara-cli harness against a local Playwright Chromium.
Hardware data: Apple M2 Pro (16GB unified memory) · Q5_K_M + f16 projector = 7.237 GiB of weights, 8.434 GiB total at a 32,768-token context (derived below) · See benchmark data
⚠️ This model clicks, types and submits in a real browser — and
fara-clidoes not sandbox it. That is not boilerplate; it is the operating requirement, and it is the one thing to get right before anything else on this page. The model card is blunt: "Don't run this model with unrestricted browser access on a machine that has anything sensitive on it." Fara also reads whatever is on the page, so a hostile page is a prompt-injection surface.Be clear about what the tooling gives you.
fara-clidrives a plain local Playwright Chromium — no container, no allow-list, no pause control. The Docker sandbox belongs to Magentic-UI, which the harness README describes as "a sandboxed browser environment with auditable action logging and user prompts at critical points" and offers as what you use "instead of fara-cli you can use Magentic-UI". The vendor's position is a recommendation, not a guarantee shipped in the CLI: "We recommend running it in a sandboxed environment, monitoring its execution, and avoiding sensitive data or high-risk domains." The model card asks for the same thing in stronger terms — "run Fara in an isolated container with no access to host files, environment variables, or sensitive credentials".On a Mac this matters more than usual, because the Mac you would run this on is very likely your daily machine — the same one holding your mail, your keychain and your logged-in sessions. And a 16GB Mac is even more likely to be somebody's only machine. If you follow the
fara-clipath below, you are the sandbox. Practically: run it under a throwaway macOS user account or a VM, let Playwright use its own fresh browser profile (never your daily Chrome), stay logged out of everything, keep tasks on low-stakes public sites, and watch the run rather than leaving it unattended. If you want the container, allow-lists, watch-mode and pause the model card asks for, use Magentic-UI instead — see Running.
ℹ️ Vision is the product, not a bonus. Per the model card, "The model is vision-only at perception time: it sees the browser through screenshots, not the DOM or accessibility tree." A Fara install that only serves text is a broken install — the
--mmprojstep below is mandatory, and Step 4 exists to prove an image really reached the model before you let it drive anything.
⚠️ Runtime status — read before you commit an evening to this. Metal support for every piece of this model is verifiable statically and this recipe shows that evidence, but no published report shows Fara1.5 specifically captioning an image under
llama-serveron Apple Silicon. Treat the first run as the test. There is also no published speed measurement for this pair, and none is invented here (see Results).
Requirements
| Component | Minimum | This recipe |
|---|---|---|
| GPU | 16GB unified memory (Apple Silicon, Metal) | Apple M2 Pro, 16GB unified — not measured; budget below is derived (/contribute) |
| RAM | unified with the GPU — see budget below | — |
| Storage | 7.77 GB (Q5_K_M + f16 projector) | 7.77 GB decimal / 7.237 GiB |
| Software | macOS 14+, llama.cpp b10087+ (Metal), Python 3.10+, Playwright | — |
On a 16GB Mac fit IS the axis — and the big-Mac configuration is out of reach twice over
This is the tightest Apple envelope on the site, and the only Apple configuration where the fit question for the 9B is real rather than rhetorical. Two facts have to be put together honestly.
First, Apple has no dedicated VRAM, and the GPU does not get all of unified memory. Metal's recommendedMaxWorkingSetSize is the practical ceiling that MLX, llama.cpp-Metal and Ollama treat as a hard cap, and below 64 GB it lands near 66% of unified memory. Memory is sold in binary units, so this machine's 16 GB is 16 GiB of physical RAM and roughly 10.5 GiB is GPU-addressable by default. /check's raw 16 GB figure is optimistic by about a third, and every number below is compared against ~10.5 GiB, not against 16. Every budget on this page is stated in GiB so the comparison is like-for-like; where a decimal GB figure appears it is labelled.
Second, the configuration the 32GB-and-larger Macs lead with — Q8_0 at the model's full 262,144-token window — is 17.942 GiB, and it is out of reach here by a wide margin. So is the quant tier on its own: the Q8_0 weights plus their projector are 9.745 GiB, which would consume 93% of the addressable pool before a single KV byte, a single compute buffer, or the vision graph. On this machine you do not choose between Q8_0 and bf16; you choose which K-quant.
Weights — byte counts from the bartowski GGUF tree:
| Component | Bytes | GiB |
|---|---|---|
Fara1.5-9B-Q4_K_M.gguf | 5,910,783,104 | 5.505 |
Fara1.5-9B-Q5_K_M.gguf | 6,852,928,640 | 6.382 |
Fara1.5-9B-Q6_K.gguf | 7,700,259,968 | 7.171 |
Fara1.5-9B-Q8_0.gguf | 9,545,983,104 | 8.890 |
mmproj-Fara1.5-9B-f16.gguf | 918,166,048 | 0.855 |
| Q4_K_M pair | 6,828,949,152 | 6.360 |
| Q5_K_M pair | 7,771,094,688 | 7.237 |
| Q6_K pair | 8,618,426,016 | 8.027 |
| Q8_0 pair | 10,464,149,152 | 9.745 |
KV cache — and this is where Fara1.5 is unusual, and where the context decision becomes the cheap lever rather than the expensive one. Its qwen3_5 architecture is a hybrid. The source config.json spells the split out layer by layer in text_config.layer_types: 24 of 32 layers are linear_attention, only 8 are full_attention (full_attention_interval: 4). llama.cpp models this as a hybrid memory — llm_arch_is_hybrid() in src/llama-arch.cpp returns true for LLM_ARCH_QWEN35, and llama-memory-hybrid.cpp filters the growing cache with !hparams.is_recr(il). Only those 8 layers carry a cache that scales with context.
With num_key_value_heads: 4 and head_dim: 256 from the same config.json:
per layer per token = (4 x 256) + (4 x 256) = 2,048 elements
x 8 full-attention layers = 16,384 elements/token
at f16 = 32 KiB/token
x 32,768 tokens = 1.000 GiB
x 65,536 tokens = 2.000 GiB
x 131,072 tokens = 4.000 GiB
x 262,144 tokens (the model's full context) = 8.000 GiB
A non-hybrid 32-layer model with the same head geometry would cost four times that — and on a 10.5 GiB budget the difference between 32 KiB and 128 KiB per token is the difference between this page existing and not.
The other 24 layers hold a fixed-size recurrent state, sized by llama.cpp's n_embd_r() / n_embd_s() in src/llama-hparams.cpp from linear_conv_kernel_dim: 4, linear_num_value_heads: 32 x linear_value_head_dim: 128 (= 4096), linear_num_key_heads: 16 and linear_key_head_dim: 128:
n_embd_r = (4-1) x (4096 + 2 x 16 x 128) = 24,576
n_embd_s = 128 x 4096 = 524,288
(24,576 + 524,288) x 24 layers x 4 bytes = 50.25 MiB per sequence
x 4 sequences (llama-server's default) = 201.0 MiB = 0.196 GiB
That state is allocated per sequence, not once. src/llama-model.cpp passes /* recurrent_rs_size */ std::max((uint32_t) 1, cparams.n_seq_max), and tools/server/server.cpp resolves its auto default with params.n_parallel = 4, so a plain llama-server reserves four slots' worth. It is a small term either way; it is stated at its real size here because on this machine small terms are the ones that decide a borderline configuration.
Totals against a ~10.5 GiB addressable pool:
| Config | Weights + projector | KV | Recurrent | Total | Fits |
|---|---|---|---|---|---|
| Q4_K_M + f16 mmproj @ 32,768 | 6.360 | 1.000 | 0.196 | 7.556 | yes |
| Q4_K_M + f16 mmproj @ 65,536 | 6.360 | 2.000 | 0.196 | 8.556 | yes |
| Q4_K_M + f16 mmproj @ 131,072 | 6.360 | 4.000 | 0.196 | 10.556 | no |
| Q5_K_M + f16 mmproj @ 32,768 | 7.237 | 1.000 | 0.196 | 8.434 | yes — this recipe |
| Q5_K_M + f16 mmproj @ 65,536 | 7.237 | 2.000 | 0.196 | 9.434 | too tight |
| Q6_K + f16 mmproj @ 32,768 | 8.027 | 1.000 | 0.196 | 9.223 | too tight |
| Q8_0 + f16 mmproj @ 32,768 | 9.745 | 1.000 | 0.196 | 10.942 | no |
Compute buffers and the vision graph are not in the table — they are allocated at load time and this recipe does not derive them. That omission is why 9.223 and 9.434 GiB against ~10.5 GiB are listed as "too tight" rather than "yes": roughly a gigabyte of unaccounted headroom on a machine that is also running macOS and a Chromium is not a margin worth spending an evening on. The lead configuration leaves about 2 GiB instead. Read the buffer-size lines llama-server prints on startup for the real figures on your build, and report what you see.
Why the quant tier gets the headroom — and what capping the context actually costs. The model's job is emitting pixel coordinates, and no published evaluation measures how much grounding accuracy each quantisation tier costs, so the budget goes to Q5_K_M first. Context is genuinely cheap on this architecture — 1.000 GiB per 32,768 tokens, per the derivation above — but on this machine it is not free, and this is the only Apple configuration in this series that has to cap the window below the model's native 262,144. Do that arithmetic before accepting it.
fara-cli runs up to 100 rounds by default: --max_rounds is declared default=100 in run_fara.py, with the help text "Maximum number of rounds for the agent to run". Only the images are capped — three screenshots, 3,780 tokens, derived below. The text trajectory is not: every round's reasoning, action and observation accumulates for the whole run. And llama.cpp does not slide the window when it fills, because context shifting is off by default (ctx_shift = false in common/common.h), so an overrun is a hard error mid-task rather than a quietly truncated history.
So -c 32768 divides as (32,768 − 3,780) / 100 ≈ 290 tokens per round of the model's own reasoning. That is workable for a terse agent and tight for a verbose one, and it is a real cost of this machine rather than a free choice. Two levers, in that order: cap the run to match the window — at --max_rounds 15 the same 32,768 gives roughly 1,930 tokens per round, which is the right shape for a smoke test — or, before a long unattended run, take Q4_K_M at 65,536 (8.556 GiB), which roughly doubles the budget to about 618 tokens per round. Take the second reluctantly and read the Quality notes first: dropping a quant tier on a coordinate-emitting model is not free either. What does not work is pushing Q5_K_M up — 9.434 GiB is the row the table above calls too tight.
No sudo sysctl iogpu.wired_limit_mb raise is needed, and reaching for one here is a mistake. The lead configuration sits ~2 GiB under the default cap. It is tempting to raise the cap to reach Q6_K or a longer window, but the raise moves the boundary in the direction that makes swapping more likely on a 16 GB machine, not less — the documented advice is to leave 8–16 GB for macOS, which on this machine leaves nothing. On 16 GB the honest move is the smaller configuration, not a bigger wired limit.
On a 32GB M2 Pro this page changes. The M2 Pro also ships with 32 GB, which is roughly 21 GiB addressable — enough for Q8_0 at 131,072 (13.942 GiB) with real headroom. If that is your machine, take the Q8_0 file and the longer window; the rest of this page applies unchanged.
The frontmatter floor. min_vram_gb: 16 is the smallest real Apple configuration on which this page's install runs. It is a filter floor, not the measured peak — 8.434 GiB is the accountable total, and it needs a 16 GB Mac's ~10.5 GiB addressable share to sit in.
How much context one screenshot costs
A CUA sends a screenshot every step, so this is the number that decides whether the context budget above is honest. It is not the 768 you may see quoted for this projector — that value derives from num_position_embeddings: 2304 (√2304 x 16 = 768) as a reference for position-embedding interpolation, and llama.cpp treats this projector as dynamic-resolution.
The harness fixes the browser viewport at 1440x900 (fara15_agent.py: viewport_width: int = 1440, viewport_height: int = 900), matching the card's note that "The screen resolution Fara is most commonly trained with is" 1440x900. It then applies Qwen's smart_resize with patch_size: 16 and spatial_merge_size: 2, so the alignment factor is 32 and each 2x2 patch block merges to one token:
900 -> round(900/32)*32 = 896 1440 -> round(1440/32)*32 = 1440
merged grid = (1440/16)/2 x (896/16)/2 = 45 x 28
image tokens = 1,260 tokens per screenshot
The harness keeps max_n_images: int = 3, so 3,780 tokens of any prompt are screenshots; its own conservative default image_token_estimate: int = 1500 sits just above the derived 1,260, which is a useful sanity check on the arithmetic. Against the 32,768-token window this recipe sets, screenshots are about 12% of the budget — so shortening the window costs you accumulated reasoning history, not eyes. That 3,780 is also the fixed part the per-round budget above subtracts before dividing by the round count; everything else in the window is trajectory. The repo's floor is a lower bound that this configuration clears comfortably — "Please ensure that context length is set to at least 15000 tokens and temperature to 0 for best results." — but note that clearing the floor is not the same as having room for 100 rounds.
Why the 9B on this machine
The 9B is the middle of three sizes, and on 16 GB it is the largest one that fits at all — the 27B's smallest usable quant is far past this pool. Microsoft's own end-to-end web-agent numbers, from the model card:
| Model | WebVoyager | Online-Mind2Web | WebTailBench |
|---|---|---|---|
| Fara1.5-4B | 80.8 | 57.3 | 27.4 |
| Fara1.5-9B | 86.6 | 63.4 | 32.3 |
| Fara1.5-27B | 89.3 | 72.3 | 40.2 |
That 4B-to-9B step is the one this machine can afford, and on a bandwidth-bound chip it is not free: at the same Q5_K_M tier the 9B re-reads 2.07x the weight bytes per generated token that the 4B does — 6,852,928,640 against 3,315,586,144, from the 9B and 4B GGUF trees — on a ~200 GB/s bus. So on a 16GB Mac the smaller sibling is a genuine alternative rather than a consolation: it leaves several more gigabytes of headroom, affords a longer window, and streams less than half the bytes per token, at the cost of the benchmark points in the table above.
Installation
1. Install llama.cpp with Metal
Metal needs no flags. Per docs/build.md: "On MacOS, Metal is enabled by default. Using Metal makes the computation run on the GPU." There is no CUDA toolkit, no ROCm, and no flash-attn wheel in this recipe — none of them exist on Apple Silicon.
brew install llama.cpp
Homebrew is a documented install channel for macOS in docs/install.md and its formula tracks releases. If you would rather build from source — worth doing here, because the Metal path for this architecture is young and still being tuned:
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j
Use b10087 or newer — that is the release the GGUF below was quantised on, per the quantiser's card.
What makes this work on Metal, since nobody has published a Fara-on-Mac run. Every op class this model needs has a Metal kernel in ggml-metal.metal: kernel_rope_multi and kernel_rope_vision for the interleaved multimodal RoPE this architecture uses, kernel_gated_delta_net_impl for the 24 linear-attention layers, kernel_ssm_conv_* / kernel_ssm_scan_f32 for their recurrent state, and kernel_im2col / kernel_conv_2d for the vision patch embedding. The projector is not stranded on the CPU either: clip.cpp asks for a generic GPU backend at context creation and logs which one it got (CLIP using <backend> backend) — on a Metal build that line should read Metal, and it is the first thing to check in the startup log. The gated-delta-net kernels landed in PR #20361, superseding the unmerged community PR #20244, and a maintainer is still optimising that path in the open PR #25788 (metal : gated_delta_net cache fusion), whose benchmark table runs qwen35 models on Apple silicon — which is why a recent build is worth the extra five minutes. For the vision half specifically, a community contributor's open PR #21443 speeds up Qwen3-VL image encoding on the Metal backend, so that encoder demonstrably runs there.
That is static and circumstantial evidence, not a tested Fara run. Step 4 below is the test.
2. Download the weights and the projector
Both files are required. The projector is a separate artifact — downloading only the text GGUF gives you a model that cannot see. Note the projector is f16 and not quantised: it is 0.855 GiB whichever text quant you pair it with, and that fixed cost is already in every row of the table above.
pip install -U huggingface_hub
hf download bartowski/Fara1.5-9B-GGUF \
Fara1.5-9B-Q5_K_M.gguf mmproj-Fara1.5-9B-f16.gguf \
--local-dir ~/models/fara1.5-9b
Microsoft publishes no first-party GGUF for Fara1.5 — the microsoft org ships safetensors only. Four independent community quantisers have converted the 9B including its vision tower: bartowski, prithivMLmods, DevQuasar and Tdamre all ship an mmproj file alongside their text quants. Four independent conversions succeeding is the strongest available evidence that the vision path converts cleanly; it is still not a runtime test.
3. Serve it
./build/bin/llama-server \
-m ~/models/fara1.5-9b/Fara1.5-9B-Q5_K_M.gguf \
--mmproj ~/models/fara1.5-9b/mmproj-Fara1.5-9B-f16.gguf \
-ngl 99 -c 32768 --port 5000 \
--image-min-tokens 1024 \
--reasoning-format none \
--temp 0
Four of those flags are load-bearing and worth understanding rather than pasting:
--reasoning-format none. This is the one that decides whether the agent loop works at all, and it is maintainer-acknowledged. Fara opens a<think>tag and never closes it, so under llama-server's default reasoning extraction the whole reply — thoughts and the<tool_call>block — is filed as reasoning, leavingmessage.contentempty; harnesses that readcontentthen get nothing.noneis documented incommon/arg.cppas "none: leaves thoughts unparsed inmessage.content", which is what the harness expects. The report is Fara1.5-27B discussion #4, reproduced on b10107; a Microsoft team member replied there asking for it to be filed upstream, and it now sits as the open fara#82. Nothing about this is Apple-specific — it is a property of the model's chat template, so it bites every serving stack, including the MLX one below.--image-min-tokens 1024. The projector ships noimage_min_pixelskey, so llama.cpp falls back to a low default floor —hparams.set_limit_image_tokens(8, 4096)for this projector type inclip.cpp— and warns: "Qwen-VL models require at minimum 1024 image tokens to function correctly on grounding tasks". Fara is a grounding model — it emits pixel coordinates — so this is precisely the case the warning is about. At the harness's 1440x900 viewport the image is already 1,260 tokens and the floor never binds; it protects you if you shrink the viewport, which on this machine you may be tempted to do to save context. Don't — see Troubleshooting.-c 32768. The context this machine can afford at this quant tier, costing 1.000 GiB of KV per the table above. This is the number that differs most from the 32GB-and-larger Apple siblings, which run the model's native 262,144. Atfara-cli's default of 100 rounds it works out to roughly 290 tokens per round once the three screenshots are subtracted — the derivation is in Requirements, and it is the reason to pass--max_roundsdeliberately rather than accept the default here. Do not raise-cto 65536 on a 16GB Mac unless you have read the startup buffer lines and know you have the room; do not raise it to 262144 at all.-ngl 99. Explicit rather than necessary — a Metal build already runs the computation on the GPU by default, perdocs/build.mdabove.
One lever held in reserve, deliberately: --cache-type-k q8_0. It roughly halves the K half of the cache and it genuinely works here, but at 1.000 GiB of KV the saving is a few hundred megabytes, and the reason to have it is to buy back context at Q5_K_M rather than to make the lead configuration fit. If you need 65,536 tokens on this machine and refuse to drop to Q4_K_M, that is the flag to try — and please report what you observe, because this recipe has not derived its effect on the vision path.
4. Prove the vision path works before going further
This is the step that turns the static Metal evidence above into a fact about your machine. Check the server advertises multimodal, confirm the CLIP backend line in the startup log, then send a real image. The server README instructs clients to check for the capability this way, and documents image_url as a first-class content part whose URL "can be a remote URL, base64 (raw or URI-encoded via" a data URI "or path to local file".
curl -s http://localhost:5000/v1/models | grep -o multimodal
curl -s http://localhost:5000/v1/chat/completions -H "Content-Type: application/json" -d '{
"model": "fara",
"temperature": 0,
"messages": [{"role": "user", "content": [
{"type": "text", "text": "Describe this image in one sentence."},
{"type": "image_url", "image_url": {"url": "https://raw.githubusercontent.com/ggml-org/llama.cpp/master/media/llama0-logo.png"}}
]}]
}' | python3 -m json.tool
If the reply describes the picture, the projector is wired up and Metal is running it. If it answers as though no image were present, or the server never logged an mmproj load, stop here — the agent loop below will silently produce nonsense actions rather than fail loudly. And if content comes back empty while a reasoning field is full, you missed --reasoning-format none.
5. Install the harness — and skip the extra the README hands you
git clone https://github.com/microsoft/fara.git
cd fara
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
playwright install chromium
Use pip install -e ., not the README's pip install -e .[vllm]. That extra pins a CUDA serving stack — vllm==0.19.1 plus nvidia-cutlass-dsl==4.5.2, both visible in pyproject.toml — which is neither installable nor useful on Apple Silicon. The README notes the plain install as the option for people not self-hosting on their own GPU: "If you plan on hosting with Microsoft Foundry only, you can skip the" [vllm] extra. You are self-hosting, just not with vLLM, so take the plain install and point the client at llama-server.
The rest of the harness is genuinely portable: pyproject.toml declares Operating System :: OS Independent and the runtime dependencies are playwright==1.51, openai, pillow and other pure-Python packages, with no CUDA anywhere. Playwright 1.51 publishes a macosx_11_0_arm64 wheel, so Chromium runs native on Apple Silicon with no Rosetta. Screenshots are captured through the browser (page.screenshot() in playwright_controller.py), not off the desktop, so macOS Screen Recording permission is never requested and never needed. The harness's Xvfb code — the usual source of Linux-only breakage in headful browser agents — is gated behind platform.system() == "Linux" in environment.py, so --headful on macOS simply opens a Chromium window.
One 16GB-specific note on this step: Chromium is not free. A browser with a couple of tabs open is comfortably a gigabyte of system memory, and on a unified-memory machine that is the same pool the model is using — and with a 9B model this budget is tighter than it would be with a smaller one. Close what you do not need before a run, and prefer the headless default while you are testing.
Running
Two client paths share the same llama-server endpoint. Path A (fara-cli) is the smoke test — unsandboxed. Path B (Magentic-UI) is what the vendor recommends for anything real. Pick deliberately; they are not equivalent in risk.
Path A — fara-cli, for a first smoke test
fara-cli speaks to any OpenAI-compatible endpoint. Point it at llama-server:
fara-cli \
--base_url http://localhost:5000/v1 \
--api_key sk-no-key-required \
--model fara \
--task "how many pages does wikipedia have" \
--output_folder ./runs/first
Each run writes per-step screenshots and a data_point.json recording the task, actions, observations and outcome to --output_folder. Watch that folder on the first few runs — it is how you tell a grounding failure (clicks landing in the wrong place) from a reasoning failure.
Runs are headless by default; --headful opens the window, and it is worth adding the first few times so you can see what it is doing. Either way this drives a real, unsandboxed Chromium on your Mac — the playwright install chromium in step 5 is what put it there. Keep the task on a low-stakes public site, stay signed out, and stay at the keyboard. Fara is trained to stop and ask before entering personal information, making payments, submitting forms, signing in or sending messages; if you see it sail through one of those, stop the run — that is a wiring problem in your prompt path, not a model quirk to tolerate.
Path B — Magentic-UI, for the sandbox
Everything the model card asks for — the containerised browser, domain allow-lists, watch-mode and a pause control — lives in Magentic-UI, not in fara-cli. This is where a Docker requirement would come from; there is no Docker step in Path A because Path A genuinely does not use one. Do budget for it on this machine: a container runtime plus Chromium plus an 8.4 GiB model allocation is a great deal to ask of 16 GB, and it is the combination most likely to push you into swapping.
Magentic-UI takes the same OpenAI-compatible endpoint you started above, so nothing on this page changes except the client: per the harness README, "instead of fara-cli you can use Magentic-UI". Its setup is documented in its own repository rather than reproduced here, because this recipe has not verified those steps end to end on this machine — follow microsoft/magentic-ui and give it http://localhost:5000/v1 as the model endpoint.
Alternative runtime: MLX-VLM
Apple recipes on this site normally lead with MLX. This page does not, and on this machine the reason is unusually concrete: there is no MLX build of Fara1.5-9B that fits 16 GB.
mlx-vlm does implement this architecture natively — mlx_vlm/models/qwen3_5/ exists in the package tree, and it ships an OpenAI-compatible server (mlx_vlm.server) that accepts image_url content parts. The problem is the artifacts. Exactly two MLX conversions of this model exist, and neither works here:
mlx-community/Fara1.5-9B-8bitis a clean build — MIT-licensed, 9.711 GiB across its two safetensors shards, and itsmodel.safetensors.index.jsonroutes all 333 vision tensors intomodel-00001-of-00002.safetensors, the same shard as the language weights, so stock loaders read them. (That count matches Microsoft's own index exactly.) But 9.711 GiB of weights leaves under a gigabyte of the addressable pool for everything else. Its own card reports a peak of 11.91 GB — 11.09 GiB, measured by the uploader on a 32GB M2 Pro; that is a different memory configuration from the 16GB machine this page targets, and converted to the units this page uses it is above this machine's entire ~10.5 GiB GPU-addressable share. Treat it as the uploader's self-report rather than an independent measurement; either way it is not a number that leaves room here.mlx-community/Fara1.5-9B-OptiQ-4bitis small enough — and broken by construction for this use case. It carries the same 333 vision tensors, and routes every one of them tooptiq/optiq_vision.safetensors, a side artifact stock loaders never open, so under a stockmlx-vlminstall it is a text-only model. It has more downloads than the clean build. A tensor census passing is not proof that vision loads; check which shard the weights point at. For a model whose whole job is looking at a screenshot, a text-only load is not a degraded install, it is a broken one.
There is no plain 4-bit or 5-bit MLX build of the 9B. That is why the GGUF ladder matters more on this machine than anywhere else in the Apple range: bartowski publishes 24 quant tiers for the 9B, and the two the table above actually uses — Q5_K_M and Q4_K_M — have no MLX equivalent at all.
If you have a 32GB M2 Pro and want to try the MLX path anyway, it is short — and two things are non-negotiable:
pip install -U mlx-vlm
mlx_vlm.server --model mlx-community/Fara1.5-9B-8bit --host 127.0.0.1 --port 8080
--host 127.0.0.1is not optional. The server's own default is0.0.0.0(DEFAULT_SERVER_HOSTinmlx_vlm/server/cli.pyandapp.py), which publishes an unauthenticated model endpoint — one that drives a browser — to your whole network.- Do not pass
--enable-thinking.mlx-vlm's splitter returns an emptycontentwhen it finds an opening tag with no closing one — the branchif start_marker in text: return reasoning or None, ""inmlx_vlm/server/responses_state.py, which is the same dead agent loop--reasoning-format noneexists to prevent on the llama.cpp side. What saves you is the default:DEFAULT_ENABLE_THINKING = Falseinmlx_vlm/server/generation.py, and Fara's chat template branches on it to emit a pre-closed<think>\n\n</think>block in the prompt. Passing the flag re-opens the failure.
One more thing that makes both servers work here: fara-cli sends the tool schema inside the system prompt and passes no OpenAI tools array, and it parses the raw text itself, splitting on <tool_call>. Servers that would otherwise rewrite <tool_call> blocks into a structured tool_calls field leave the text alone when no tools array is sent — which is why the harness's raw-text parser keeps working against a stack Microsoft never tested.
Results
- Speed: omitted. No published measurement exists for Fara1.5-9B under llama.cpp on any Apple Silicon chip, and the only hardware the vendor names is datacenter class ("A6000, A100, H100, and B200 have been tested"), which says nothing about a Q5_K_M build on Metal. The Apple-Silicon throughput figures that do exist for this model are the uploader's self-report on the
mlx-community8-bit card described under Alternative runtime: 10.2 tok/s decode for the bf16 source against 19.7 for the 8-bit build, "greedy decoding, on an M2 Pro / 32 GB", alongside peak-RAM figures of 18.97 GB and 11.91 GB. That is one unreplicated self-report, on MLX rather than llama.cpp, for the uploader's own build, on twice this machine's memory — so it is surfaced here rather than carried across as a figure for this page's Q5_K_M-on-llama.cpp install. Expect this chip to be the slowest of the four Apple configurations this site covers regardless: at 200 GB/s — the figure Apple's tech specs give for the M2 Pro on both its 16-core and 19-core GPU bins — it has exactly half the 400 GB/s the same page gives the M2 Max, and token generation is bandwidth-bound. If you run this, please contribute your numbers. - Unified memory usage: 7.237 GiB of weights and projector; 8.434 GiB including KV and recurrent state at the 32,768-token context this recipe sets, derived above from
config.jsonand llama.cpp's memory formulas. That sits about 2 GiB inside the ~10.5 GiB a 16GB M2 Pro makes GPU-addressable. This is a derivation, not an observed peak — compute and vision buffers are additional. See /check/fara1-5-9b/m2-pro. - Quality notes: the vendor's published scores are for the bf16 model on datacenter hardware. Q5_K_M is a lossy quantisation of a model whose main job is predicting exact pixel coordinates, and no published evaluation measures how much grounding accuracy that costs at any tier. Unlike the larger Macs, this one cannot answer the question by stepping up — Q8_0 does not fit, and bf16 is not close — so if clicks land near-but-not-on targets, the diagnostic order here is viewport first,
--image-min-tokenssecond, and quantisation only as a hypothesis you cannot cheaply test on this machine. - Language: English only — the card's out-of-scope list names "Languages other than English (training data is English-only)".
For the full benchmark data, see /check/fara1-5-9b/m2-pro.
Troubleshooting
The server starts but the log says CLIP using CPU backend
The projector fell back to the CPU. Vision will still work, but slowly, and it means the GPU backend failed to initialise. Confirm you are on a Metal build (a source build without -DGGML_METAL=OFF, or the Homebrew formula) and that --mmproj points at a file that exists. clip.cpp prints this line at context creation, so it is visible before the first request — check it during step 4 rather than after a confusing agent run.
It loads, then the machine starts swapping
The most likely 16GB-specific failure, and it is a system-level symptom rather than a model error: the model fits the GPU-addressable pool but the machine has nothing left. Watch Activity Monitor's Memory-Pressure gauge during a run. Close Chromium tabs you are not using, quit other heavy apps, and if it persists, drop to Fara1.5-9B-Q4_K_M.gguf (5.505 GiB instead of 6.382 — same projector, same command otherwise) or halve the context to -c 16384. That still clears the harness's documented 15,000-token floor — but the floor is not the constraint that binds: at --max_rounds' default of 100 rounds, 16,384 leaves roughly 126 tokens per round for the model's reasoning once the screenshots are subtracted, and an overrun errors rather than sliding the window. If you take this route, cap the run to match — --max_rounds 20 puts the per-round budget back near 630. Raising iogpu.wired_limit_mb is the wrong instrument here: it moves the boundary in the direction that makes swapping more likely, not less.
Shrinking the viewport to save context makes clicks worse
A tempting 16GB optimisation that backfires. Each screenshot is 1,260 tokens at the harness's 1440x900 default; a smaller window costs fewer tokens, and it also moves the model off the resolution the card says to match and can push the image under the 1,024-token grounding floor llama.cpp warns about. Buy context back by lowering -c or dropping a quant tier, not by shrinking the browser.
ollama run fara does nothing — there is no Ollama entry
There is no Ollama library entry for Fara1.5 in any form: fara1.5, fara, fara1-5, fara1.5-9b and fara-9b all 404. The GGUF/LM Studio/Ollama line in the harness README refers to Fara-7B, the previous generation, reached with a --fara-7b flag — it is not about Fara1.5. That older path is not trouble-free either: fara#58 is an open community report of fara-cli failing on its first model call against an Ollama-hosted Fara-7B. Whatever server you choose, step 4's image test is the check that matters.
The model answers but ignores the screenshot
Almost always the projector. Confirm --mmproj is on the command line, that the server logged an mmproj load stage, and that step 4's curl describes the image. A text-only build paired with no projector loads and runs perfectly happily — it simply cannot see, which in a CUA presents as confidently wrong actions rather than an error. If you came here from MLX, check first that you are not on the OptiQ build under a stock loader, which is text-only by construction.
The agent dies on round one with an empty response
message.content is empty and everything is in a reasoning field. On llama.cpp that is the missing --reasoning-format none; on mlx_vlm.server it is --enable-thinking being passed. Both are the same underlying behaviour and both are described in step 3.
Keyboard shortcuts do nothing on pages that expect them
A macOS-only asymmetry. The harness intercepts a fixed set of Control-based chords itself before they reach the browser — refresh, back, forward and find, in _BROWSER_CHROME_DISPATCH and _FIND_CHORD in environment.py — so those work on any OS. Other editing chords the model emits as Control+key are passed through to Chromium, where macOS expects Meta (Cmd) instead, and quietly do nothing. The key map in key_mapping.py does translate cmd to Meta, so a run that phrases the chord that way works; there is no setting to flip. Prefer tasks that do not lean on copy/paste shortcuts, and read data_point.json when an action seems to have been swallowed.
Generation is slower than you hoped
Two things, in this order. First, this chip: at 200 GB/s (Apple tech specs, the same figure for both M2 Pro GPU bins) it has half the M2 Max's unified-memory bandwidth, and token generation is bandwidth-bound, so "slower than the Mac I read about" is often just correct — and a 9B at Q5_K_M re-reads 6.4 GiB of weights per token, which on this bus is the dominant cost. Second, the software: the Metal path for this architecture's linear-attention layers is newer than the CUDA one and still being tuned — the open maintainer PR #25788 reports gains on qwen35 models from a single cache-fusion change, and llama.cpp's own op-support table still lists GATED_DELTA_NET on the Metal backend as "Partially supported by this backend". Build from a recent master before drawing conclusions, and compare two builds with llama-bench rather than trusting a single impression. No measurement for this pair exists either way — contribute one.