self-hosted/ai
§01·recipe · multimodal

Fara1.5-9B on Apple M4 Max: Browser Computer-Use Agent on llama.cpp Metal

multimodaladvanced32GB+ VRAMJul 31, 2026

This advanced recipe sets up Fara1.5-9B on the Apple M4 Max, needing about 32 GB of VRAM.

models
tools
prerequisites
  • Apple M4 Max (48GB unified memory); the configuration below also runs on any Apple Silicon Mac with 32GB+ unified memory
  • macOS 14 Sonoma or newer
  • Python 3.10+
  • llama.cpp built for Metal (release b10087 or newer)
  • A disposable browser profile — fara-cli drives a plain local Playwright Chromium with no isolation

What You'll Build

A local browser computer-use agent (CUA) running entirely on Apple Silicon: 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 M4 Max (48GB unified memory) · Q8_0 + f16 projector = 9.745 GiB of weights, 17.942 GiB total at the model's full 262,144-token context (derived below) · See benchmark data

⚠️ This model clicks, types and submits in a real browser — and fara-cli does 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-cli drives 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. If you follow the fara-cli path 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 --mmproj step 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-server on 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

ComponentMinimumThis recipe
GPU32GB unified memory (Apple Silicon, Metal)Apple M4 Max, 48GB unified — not measured; budget below is derived (/contribute)
RAMunified with the GPU — see budget below
Storage10.46 GB (Q8_0 + f16 projector)10.46 GB decimal / 9.745 GiB
SoftwaremacOS 14+, llama.cpp b10087+ (Metal), Python 3.10+, Playwright

On this chip the memory question is settled and the bandwidth question is the interesting one

Apple has no dedicated VRAM. 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 — so a 48GB Mac is roughly 32 GB safe / 36 GB optimistic of GPU-addressable memory, and /check's raw 48 GB figure is optimistic by about a third. Every number below is compared against ~32 GB, and every budget is stated in GiB so the comparison is like-for-like. (The 48GB default is the least well-anchored figure in this area: the hard data point nearby is a 32 GB Mac reporting 22906 MB ≈ 71.6%, so check what your own machine reports before planning against 36.)

The lead configuration needs 17.942 GiB, which is not close to that wall, and the heaviest configuration on this page is 25.745 GiB. So on an M4 Max the memory question is answered before it is asked. What is left is the axis this chip is actually distinguished on: how many bytes you re-read per generated token, against 546 GB/s of unified-memory bandwidth — the figure Apple's tech specs give for the M4 Max with 16-core CPU and 40-core GPU, which is the bin Apple sells at 48 GB. That is the fastest in the Apple range this site covers, and 1.365x the 400 GB/s of the M2 Max (111340) and the M3 Max (117736).

The bandwidth figure belongs to the bin, not to the chip name — worth knowing before you compare this page against a number you found elsewhere. Apple ships a second M4 Max, 14-core CPU with a 32-core GPU, at 410 GB/s, and per the same spec sheet it is the only bin offered at 36 GB. An "M4 Max" measurement taken on that part is roughly a quarter slower than this page's premise.

Weights — byte counts from the bartowski GGUF tree:

ComponentBytesGiB
Fara1.5-9B-Q5_K_M.gguf6,852,928,6406.382
Fara1.5-9B-Q6_K.gguf7,700,259,9687.171
Fara1.5-9B-Q8_0.gguf9,545,983,1048.890
Fara1.5-9B-bf16.gguf17,920,697,21616.690
mmproj-Fara1.5-9B-f16.gguf918,166,0480.855
mmproj-Fara1.5-9B-bf16.gguf921,704,9920.858
Q8_0 pair10,464,149,1529.745
bf16 pair18,842,402,20817.548

KV cache — and this is where Fara1.5 is unusual, and where the context decision gets cheap. 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 131,072 tokens                                 = 4.000 GiB
x 262,144 tokens (the model's full context)      = 8.000 GiB

The 9B's KV geometry is identical to the 4B's — same num_key_value_heads, same head_dim, same 32 blocks, same full_attention_interval — so on this axis the larger model costs nothing extra. All of the difference between the two sizes is weights, which is exactly why bandwidth is the axis here.

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.

Totals against a ~32 GB addressable pool:

ConfigWeights + projectorKVRecurrentTotalVerdict
Q5_K_M + f16 mmproj @ 262,1447.2378.0000.19615.434comfortable
Q6_K + f16 mmproj @ 262,1448.0278.0000.19616.223comfortable
Q8_0 + f16 mmproj @ 262,1449.7458.0000.19617.942this recipe
Q8_0 + f16 mmproj @ 131,0729.7454.0000.19613.942comfortable
bf16 + bf16 mmproj @ 131,07217.5484.0000.19621.745fits, watch buffers
bf16 + bf16 mmproj @ 262,14417.5488.0000.19625.745tight against ~32

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 the last row is labelled "tight" rather than "yes". Read the buffer-size lines llama-server prints on startup for the real figures on your build, and report what you see.

What 546 GB/s does and does not change. Token generation is bandwidth-bound: each generated token re-reads the weight set. The bf16 build is 1.877x the bytes of Q8_0 (17,920,697,216 / 9,545,983,104), and that ratio is a property of the files, not of the silicon — it is identical on an M2 Pro and on this chip. Bandwidth does not make the fidelity step-up relatively cheaper anywhere. What it changes is the absolute floor: the same file streams in about 73% of the time it would on a 400 GB/s M2 or M3 Max (400 / 546). So the M4 Max is the 48GB machine on which running the reference-fidelity build day to day hurts least — but Q8_0 still leads here for the same reason it leads on every other Apple chip, and bf16 remains the thing you switch to when you have a specific grounding question to answer, not the default.

No iogpu.wired_limit_mb raise is needed for the lead configuration, and reaching for one to run the 17.942 GiB config would be copying a caveat that does not apply. If you go to bf16 at the full window it becomes a real lever — sudo sysctl iogpu.wired_limit_mb=38912 on macOS Sonoma 14 / Sequoia 15+ (megabytes; older releases use debug.iogpu.wired_limit in bytes), leaving 8–16 GB of headroom for macOS, resetting on reboot. Watch Activity Monitor's Memory-Pressure gauge if you use it.

The frontmatter floor. min_vram_gb: 32 is the smallest real Apple configuration on which this page's install runs: 17.942 GiB needs more than a 24GB Mac's default addressable share, and clears a 32GB Mac's comfortably. It is a filter floor, not the measured peak. The M4 Max also ships in a 36GB configuration — the lead here fits that too, at roughly 24 GB addressable — but that is the 14-core-CPU/32-core-GPU bin at 410 GB/s, not the 546 GB/s part the bandwidth arguments above are written against.

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 262,144-token window above, screenshots are rounding error — the reason to keep the window wide is the accumulated reasoning and action history on a long task, not the images. The repo's floor still applies as a lower bound: "Please ensure that context length is set to at least 15000 tokens and temperature to 0 for best results."

Why the 9B on this machine

The 9B is the middle of three sizes. Microsoft's own end-to-end web-agent numbers, from the model card:

ModelWebVoyagerOnline-Mind2WebWebTailBench
Fara1.5-4B80.857.327.4
Fara1.5-9B86.663.432.3
Fara1.5-27B89.372.340.2

The step from 4B to 9B is the larger one on the two easier benchmarks and costs about 5 GiB here; the step from 9B to 27B is the larger one on WebTailBench and roughly doubles the weight budget again. On this chip the memory for either is available, so the choice is a bandwidth choice: the 9B re-reads less than half the bytes per token that the 27B does, and on a bandwidth-bound machine that difference is the latency of the agent loop.

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 ggml-org member is still optimising that path in the open PR #25788 (metal : gated_delta_net cache fusion). This is the one chip in the Apple range where that work has actually been measured on Metal. The PR's author benchmarks the Accelerate (CPU) backend on an Apple M3; a community contributor's comment in the same thread carries the only Metal rows, on an Apple M4 Maxqwen35 9B Q4_K_M, qwen35 27B Q8_0 and a qwen35moe 35B.A3B Q8_0. That is this silicon, this backend, this architecture family, at this model's parameter count. No throughput figure from it is quoted here — Qwen3.5-9B is not Fara1.5-9B and those rows are a different quant — but it is the closest thing to a Metal datapoint that exists for the code path this recipe depends on. 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.

One M4-specific note: this is the newest silicon in the Apple range this site covers, and llama.cpp's Metal backend is a single code path across Apple GPU generations rather than a per-chip target — there is no M4 equivalent of a CUDA compute-capability flag to set, and no build option to add. Build recent and move on.

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.

pip install -U huggingface_hub

hf download bartowski/Fara1.5-9B-GGUF \
  Fara1.5-9B-Q8_0.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-Q8_0.gguf \
  --mmproj ~/models/fara1.5-9b/mmproj-Fara1.5-9B-f16.gguf \
  -ngl 99 -c 262144 --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, leaving message.content empty; harnesses that read content then get nothing. none is documented in common/arg.cpp as "none: leaves thoughts unparsed in message.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 no image_min_pixels key, so llama.cpp falls back to a low default floor — hparams.set_limit_image_tokens(8, 4096) for this projector type in clip.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.
  • -c 262144. The model's native window (max_position_embeddings: 262144), costing 8.000 GiB of KV per the table above. Halve it to 131072 if you want the total under 14 GiB — on this machine that is a preference, not a requirement.
  • -ngl 99. Explicit rather than necessary — a Metal build already runs the computation on the GPU by default, per docs/build.md above.

One thing you will notice is missing, deliberately: there is no --cache-type-k q8_0 KV-quantisation step. It exists, but at 8 GiB of KV against ~32 GB addressable it buys nothing at the lead configuration and costs precision on a model whose job is coordinates. Keep it in reserve for the bf16-at-262K case.

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.

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.

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, and for the 9B the MLX side is in better shape than it is for the smaller sibling — so the choice is closer than you might expect.

mlx-vlm implements this architecture natively: mlx_vlm/models/qwen3_5/ exists in the package tree, and it ships a real OpenAI-compatible server (mlx_vlm.server) whose /v1/chat/completions accepts image_url content parts — exactly what fara-cli --base_url needs. And unlike the 4B, the 9B has a clean build under the mlx-community org: mlx-community/Fara1.5-9B-8bit, MIT-licensed, 9.711 GiB across its two safetensors shards, whose model.safetensors.index.json routes all 333 vision tensors into model-00001-of-00002.safetensors — the same shard as the language weights, so stock loaders read them. That count matches Microsoft's own index exactly, and the card is explicit that the vision tower was left unquantised in bf16 while only the language model was quantised.

Two things still argue for the GGUF lead on this page:

  • The other mlx-community build for this model is broken by construction. mlx-community/Fara1.5-9B-OptiQ-4bit has the same 333 vision tensors — and routes every one of them to optiq/optiq_vision.safetensors, a side artifact stock loaders never open. 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.
  • The GGUF side has a size ladder and a vetted redistributor. bartowski publishes 24 quant tiers for the 9B from IQ2_M to full bf16, each with a matching projector — which is what makes the Q8_0-or-bf16 choice on this page possible, and it is also the only path to the bf16 reference build, which MLX does not publish for this model at all.

The clean 8-bit build is also one day old with double-digit downloads at the time of writing, and its accuracy figures are the uploader's own self-report with no independent check. If you would rather stay in MLX, the parallel path is real and short:

pip install -U mlx-vlm
mlx_vlm.server --model mlx-community/Fara1.5-9B-8bit --host 127.0.0.1 --port 8080

Then point the harness at http://127.0.0.1:8080/v1. Three things to know before you do:

  • --host 127.0.0.1 is not optional. The server's own default is 0.0.0.0 (DEFAULT_SERVER_HOST in mlx_vlm/server/cli.py and app.py), which publishes an unauthenticated model endpoint — one that drives a browser — to your whole network.
  • The <think> problem exists here too, and the default already handles it. mlx-vlm's splitter returns an empty content when it finds an opening tag with no closing one — the branch if start_marker in text: return reasoning or None, "" in mlx_vlm/server/responses_state.py, which is the same dead agent loop. What saves you is the default: DEFAULT_ENABLE_THINKING = False in mlx_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, so the generated text contains no thinking tag to strip. Do not pass --enable-thinking — it re-opens the exact failure that --reasoning-format none exists to prevent on the llama.cpp side.
  • The memory budget on this page does not transfer to it. Every figure above is llama.cpp's memory plan for a GGUF, derived from llama.cpp's own formulas. MLX allocates its cache differently and this recipe has not derived it — treat the 9.711 GiB weight figure as a starting point and tell us what you measure.

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 or on any consumer GPU; the only hardware the vendor names is datacenter class ("A6000, A100, H100, and B200 have been tested"), and forwarding a number from it would be misleading. The Apple-Silicon figures that do exist for this model are the uploader's self-report on the mlx-community 8-bit card cited under Alternative runtime: 10.2 tok/s decode for the bf16 source against 19.7 for the 8-bit build, with peak RAM of 18.97 GB and 11.91 GB, "greedy decoding, on an M2 Pro / 32 GB". That is a single unreplicated self-report by a community quantiser, on MLX rather than llama.cpp and for that quantiser's own build — so it is surfaced here rather than carried across as a figure for this page. Do not rescale it by bandwidth either: this chip's 546 GB/s (Apple tech specs, the 16-core-CPU/40-core-GPU bin) is 1.365x an M2 or M3 Max's 400 GB/s and roughly 2.7x an M2 Pro's 200 GB/s (111340), and token generation is bandwidth-bound — so a figure from a slower Mac will understate this one, but not by a ratio you can compute from bandwidth alone, because the runtime and the quantisation move it too. If you run this, please contribute your numbers so this section can be replaced with a measurement.
  • Unified memory usage: 9.745 GiB of weights and projector; 17.942 GiB including KV and recurrent state at the full 262,144-token context, derived above from config.json and llama.cpp's memory formulas. That sits about 14 GiB inside the ~32 GB a 48GB M4 Max makes GPU-addressable by default. This is a derivation, not an observed peak — compute and vision buffers are additional. See /check/fara1-5-9b/m4-max.
  • Quality notes: the vendor's published scores are for the bf16 model on datacenter hardware. Q8_0 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. If clicks land near-but-not-on targets, this machine can test the hypothesis relatively cheaply: switch to Fara1.5-9B-bf16.gguf with mmproj-Fara1.5-9B-bf16.gguf, drop to -c 131072 (21.745 GiB) so the buffers have room, and re-test. Expect it to feel slower — 1.877x the bytes per token — but on this chip that is the smallest absolute penalty in the Apple range.
  • 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/m4-max.

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.

bf16 loads, then the machine starts swapping

The 48GB-specific failure. bf16 at the full window is 25.745 GiB of accountable allocations plus underived compute buffers, against a default addressable share near 32 GB on a machine that is also running macOS, Chromium and a Python harness. In order: drop the context to -c 131072 (frees 4.000 GiB), add --cache-type-k q8_0 if you need more, or raise the wired limit as shown in Requirements — in that order, because the first two cost nothing and the third moves the boundary between the GPU and the OS.

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.

Clicks land in the wrong place

Check the viewport first. The harness defaults to 1440x900 because that is what the model was mostly trained at; a different window size changes both the grounding distribution and the token cost derived above. Then confirm --image-min-tokens 1024 is set, since llama.cpp's own warning ties sub-1024-token images to degraded grounding. Only after both, suspect the quantisation — and see the Quality notes above for how to test that on this machine.

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 the bandwidth suggests

Bandwidth sets a ceiling, not a result. 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". On the newest silicon it is especially worth building from a recent master and comparing two builds with llama-bench rather than assuming the hardware is the limit. No measurement for Fara on this chip exists either way — contribute one.

common questions
How much VRAM does Fara1.5-9B need?

About 32 GB — the minimum this recipe targets.

Which GPUs is Fara1.5-9B tested on?

Apple M4 Max (48 GB).

How hard is this setup?

Advanced — follow the steps above.