What You'll Build
A local browser computer-use agent (CUA) running entirely on Apple Silicon: Microsoft's largest Fara1.5 model, served by llama-server on Metal with its vision projector loaded so the model actually sees browser screenshots, driven by Microsoft's own fara-cli harness against a local Playwright Chromium.
The 27B is a memory question on most machines and a latency question on this one. A 48GB M4 Max addresses the same ~32 GiB as a 48GB M3 Max, so the configuration below is the same on both — but this chip moves 546 GB/s of unified-memory bandwidth against the 400 of the 48GB M3 Max, and for a model that re-reads 20.51 GB of weights per generated token, that ratio is the entire user experience of an agent loop. This is the 48GB Mac on which running Fara's largest size is least painful.
Hardware data: Apple M4 Max (48GB unified memory) · Q5_K_M + f16 projector = 19.969 GiB of weights, 24.553 GiB accounted at a 65,536-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 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 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 a 48GB M4 Max is somebody's daily machine — the same one holding their mail, their keychain and their logged-in sessions. 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. This bites hardest on the MLX side, where the most-downloaded build for this model is text-only by construction; see Alternative runtime.
ℹ️ Not a chat model. Fara1.5-27B sits in our
multimodalvertical because it takes images and text, but it is not a general-purpose VLM. It is fine-tuned to emit chain-of-thought followed by a singlecomputer_usetool call against a fixed system prompt. Ask it a normal question and you get agent-shaped output. The harness is not optional garnish — it is the interface.
⚠️ This install is off the vendor's supported path, deliberately. The model card's Requirements section asks for "A set of GPUs with enough memory for a 27B model in bf16 (A6000, A100, H100, and B200 have been tested). We recommend sharding the model over at least 2 GPUs." Everything below runs a community quantisation Microsoft never published or blessed, on silicon the vendor never tested. What that costs is discussed under Results — read it before you trust a click coordinate.
⚠️ Runtime status. Metal support for every piece of this model is verifiable statically and this recipe shows that evidence, but no published report shows Fara1.5 captioning an image under
llama-serveron Apple Silicon. Treat the first run as the test — Step 4 is that test. There is no publishedllama.cppspeed measurement for this pair either, and none is invented here; the one MLX figure that exists is discussed and deliberately not promoted.
Requirements
| Component | Minimum | This recipe |
|---|---|---|
| GPU | 48GB unified memory (Apple Silicon, Metal) | Apple M4 Max, 48GB unified — not measured; budget below is derived (/contribute) |
| RAM | unified with the GPU — see budget below | — |
| Storage | 21.44 GB (Q5_K_M 20.51 GB + f16 projector 0.93 GB, decimal) | ~22 GB, plus ~1 GB for Playwright's Chromium |
| Software | macOS 14+, llama.cpp b10087+ (Metal), Python 3.10+, Playwright | — |
What 48GB actually addresses
Apple has no dedicated VRAM. Metal's recommendedMaxWorkingSetSize is the practical ceiling 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, and /check's raw 48 GB figure is optimistic by about a third. Every number below is compared against ~32 GiB and stated in GiB so the comparison is like-for-like.
That figure is the least well-anchored number on this page: the nearest hard data point is a 32 GB Mac reporting recommendedMaxWorkingSetSize = 22906 MB ≈ 71.6%, and no primary log for a 48GB Mac is published. Check what your own machine reports before planning against 36 rather than 32.
Weights — byte counts from the bartowski GGUF tree, converted at 1024³:
| File | Bytes | GiB |
|---|---|---|
Fara1.5-27B-IQ4_XS.gguf | 15,328,839,488 | 14.276 |
Fara1.5-27B-Q4_K_S.gguf | 16,474,163,008 | 15.343 |
Fara1.5-27B-Q4_K_M.gguf | 17,533,552,448 | 16.329 |
Fara1.5-27B-Q5_K_M.gguf | 20,513,802,048 | 19.105 |
Fara1.5-27B-Q6_K.gguf | 23,224,145,728 | 21.629 |
Fara1.5-27B-Q8_0.gguf | 28,665,067,328 | 26.696 |
mmproj-Fara1.5-27B-f16.gguf | 927,607,456 | 0.864 |
mmproj-Fara1.5-27B-bf16.gguf | 931,146,400 | 0.867 |
| Q5_K_M + f16 projector | 21,441,409,504 | 19.969 |
The ladder does not stop at Q8_0: a full bf16 build exists as a two-shard split in its own Fara1.5-27B-bf16/ subdirectory (53.81 GB decimal, 50.113 GiB), which is why it is absent from a flat listing of the repository root. It is far out of range on a 48GB machine and is mentioned only because "the biggest GGUF is Q8_0" is an easy and wrong assumption to make from the file list.
KV cache — and the 27B is not a scaled-up 9B here. Its qwen3_5 architecture is a hybrid, and the source config.json spells the split out layer by layer in text_config.layer_types: 48 of 64 layers are linear_attention and 16 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 16 layers carry a cache that scales with context — but there are twice as many of them as in the 9B, so the per-token cost doubles. With num_key_value_heads: 4 and head_dim: 256 from the same file:
per layer per token = (4 x 256) + (4 x 256) = 2,048 elements
x 16 full-attention layers = 32,768 elements/token
at f16 = 64 KiB/token
x 32,768 tokens = 2.000 GiB
x 65,536 tokens = 4.000 GiB
x 131,072 tokens = 8.000 GiB
x 262,144 tokens (the model's native window) = 16.000 GiB
The other 48 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: 48 x linear_value_head_dim: 128 (= 6144), linear_num_key_heads: 16 and linear_key_head_dim: 128:
n_embd_r = (4-1) x (6144 + 2 x 16 x 128) = 30,720
n_embd_s = 128 x 6144 = 786,432
(30,720 + 786,432) x 48 layers x 4 bytes = 149.625 MiB per sequence
x 4 sequences (llama-server's default) = 598.5 MiB = 0.584 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) on the branch that names LLM_ARCH_QWEN35 explicitly, common/common.cpp sets cparams.n_seq_max = params.n_parallel, and tools/server/server.cpp resolves the server's auto default with params.n_parallel = 4. So a plain llama-server reserves four slots' worth: 0.584 GiB, not 0.146. The KV cache is not multiplied the same way — kv_unified = true is set on the same line, so the KV figures above are the whole allocation, not a per-slot one. Both terms appear separately below for exactly that reason.
Totals against a ~32 GiB addressable pool. Each figure is the sum of exact byte counts, rounded once at the end:
| Config | Weights + projector | KV | Recurrent | Total | Verdict |
|---|---|---|---|---|---|
| Q4_K_M @ 32,768 | 17.193 | 2.000 | 0.584 | 19.778 | what a 24 GB graphics card is limited to |
| IQ4_XS @ 65,536 | 15.140 | 4.000 | 0.584 | 19.724 | cheapest sane tier |
| Q4_K_M @ 65,536 | 17.193 | 4.000 | 0.584 | 21.778 | comfortable |
| Q5_K_M @ 65,536 | 19.969 | 4.000 | 0.584 | 24.553 | this recipe |
| Q4_K_M @ 131,072 | 17.193 | 8.000 | 0.584 | 25.778 | context-first alternative |
| Q6_K @ 65,536 | 22.493 | 4.000 | 0.584 | 27.078 | fits, watch buffers |
| Q5_K_M @ 131,072 | 19.969 | 8.000 | 0.584 | 28.553 | only ~3.4 GiB of margin — not recommended |
| Q8_0 @ 65,536 | 27.560 | 4.000 | 0.584 | 32.145 | over the safe figure |
| Q4_K_M @ 262,144 | 17.193 | 16.000 | 0.584 | 33.778 | needs a wired-limit raise — see below |
Compute buffers and the vision graph are not in that table — they are allocated at load time and this recipe does not derive them. The lead row leaves about 7.4 GiB for them.
The lead is identical to the 48GB M3 Max's, on purpose. The two chips address the same memory, so the same configuration is correct on both; nothing about 546 GB/s buys a larger quantisation or a wider window. What bandwidth changes is the cost of the choice: at Q5_K_M this configuration re-reads 20.51 GB per generated token, and on this chip that stream takes roughly 73% of the time it takes on a 400 GB/s Apple chip (400 / 546). Neither figure is folklore: Apple's MacBook Pro tech specs split this chip into a "M4 Max with 14-core CPU and 32-core GPU (410GB/s memory bandwidth) or M4 Max with 16-core CPU and 40-core GPU (546GB/s memory bandwidth)", and list "48GB unified memory (M4 Pro and M4 Max with 16-core CPU)" — so a 48GB M4 Max is unambiguously the 546 GB/s part, while the 36GB configuration is the 410 GB/s one. The 27B is the size where that difference stops being academic — on the 4B it is a rounding error, here it is the difference between an agent loop you watch and one you leave running.
Why quality over context here. Both directions are affordable, and this recipe spends the budget on the quantisation tier because Fara predicts pixel coordinates and nobody has published an evaluation of how much grounding accuracy any quantisation costs. Q5_K_M at 65,536 and Q4_K_M at 131,072 differ by only 1.2 GiB, so if your tasks are long rather than fiddly, swapping the two is a one-flag change and defensible — and this is the chip on which a long unattended run is most practical, so it is a live option rather than a theoretical one. What is not defensible is Q5_K_M at 131,072: 28.553 GiB leaves ~3.4 GiB against the safe figure, and the compute buffers are exactly the term this page has not derived.
Reaching 262,144 on a 48GB Mac — the one place a wired-limit raise is warranted. The model's native window needs 33.778 GiB at Q4_K_M, past the default share but inside what macOS will hand over if you ask:
sudo sysctl iogpu.wired_limit_mb=40960 # 40 GiB; macOS Sonoma 14 / Sequoia 15+
On older releases the knob is debug.iogpu.wired_limit in bytes; 0 restores the default; the setting resets on reboot (persist it in /etc/sysctl.conf if you must). That leaves 8 GB for macOS on a 48GB machine, which is the floor of the usual 8–16 GB guidance — treat it as an experiment, watch Activity Monitor's memory-pressure gauge, and quit Chrome and everything else first. Q5_K_M at that window (36.553 GiB) is a step too far on this machine.
On the 64GB and 128GB M4 Max configurations none of this applies: 75% of 64 GB is ~48 GiB, so Q5_K_M at the full 262,144 window (36.553 GiB) is routine, and on a 128GB machine even the bf16 reference build at the native window (67.565 GiB with its bf16 projector) sits inside ~96 GiB addressable. Those are the configurations where this model's context stops being rationed at all.
One flag can move all of this, so check the log. With flash attention off, llama.cpp's non-FA branch at llama-graph.cpp builds kq = ggml_mul_mat(ctx0, k, q) and forces it to F32 ("this op tends to require high floating point range"), an extra buffer bounded by n_kv x n_ubatch x n_head x 4 B. This model's num_attention_heads is 24, so at the default -ub 512 that bound is 3.000 GiB at 65,536 and 12.000 GiB at 262,144 — the term grows with the window, which is why the wired-limit experiment above is riskier than its headline number suggests. It should not apply on Metal: -fa defaults to auto (common/arg.cpp), and Metal's ggml_metal_device_supports_op accepts GGML_OP_FLASH_ATTN_EXT at head size 256 — this model's head_dim — with an F16 cache. llama-context.cpp runs a real probe at load and logs either Flash Attention enabled or Flash Attention not supported, set to disabled. Read that line on startup; the lead row has room for the 3.000 GiB either way, the 262,144 experiment does not.
The frontmatter floor. min_vram_gb: 48 is the smallest real Apple configuration on which this page's install runs. The M4 Max also ships in a 36GB part, where the same ~66% rule gives roughly 24 GB addressable — under this configuration's 24.553 GiB, before compute buffers. A 36GB machine is not excluded from the model, only from this page's lead: Q4_K_M at 65,536 (21.778 GiB) or IQ4_XS at 65,536 (19.724 GiB) are the configurations to try there, and the MLX 4-bit build discussed below is reported by its author to peak at 24.7 GB on exactly that machine. The floor is a filter, not a measured peak.
How much context you actually get per round
A CUA sends a screenshot every step, so the window is spent on trajectory, not imagery — but "spent" is the operative word, and one published Fara recipe elsewhere in this catalogue overstates how little of it gets used. fara-cli's --max_rounds defaults to 100 (run_fara.py, argparse default=100), and while the resident image count is capped, the accumulated text is not.
The image half is fixed and small. The harness pins the 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. Qwen's smart_resize then rounds to the alignment factor patch_size 16 x spatial_merge_size 2 = 32, and each 2x2 patch block merges to one token:
900 -> round(900/32)*32 = 896 1440 -> round(1440/32)*32 = 1440
patch grid = (1440/16) x (896/16) = 90 x 56 = 5,040 patches
merged grid = 45 x 28 = 1,260 tokens per screenshot
max_n_images: int = 3 in the same file caps the resident imagery at 3,780 tokens; the harness's own conservative image_token_estimate: int = 1500 sits just above the derived 1,260, which is a useful check on the arithmetic. Everything else in the window is the system prompt with the full computer_use schema, plus the model's own accumulated reasoning and actions.
So the honest way to read a context setting is per round. Subtracting only the resident screenshots and dividing by the default 100 rounds:
-c | budget per round (before the fixed system prompt) | accountable total at Q5_K_M |
|---|---|---|
| 32,768 | ~290 tokens | 22.553 GiB |
| 65,536 | ~615 tokens | 24.553 GiB |
| 131,072 | ~1,270 tokens | 28.553 GiB |
ctx_shift is false by default in llama.cpp, so an overrun errors rather than sliding the window. If you plan a long unattended run — and on this chip you will be tempted to, because the rounds go by faster — the honest options are to move to Q4_K_M at -c 131072 (25.778 GiB) or to lower --max_rounds. Do not assume the window will simply absorb it. 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 27B rather than the 9B on this machine
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 |
The 27B's margin is largest on WebTailBench — the long-tail task set, which is also where a browser agent earns its keep. What it costs is bandwidth: Q5_K_M streams 20.51 GB per generated token against the 9B's Q8_0 lead of 9.55 GB, 2.149x the bytes, and that ratio is a property of the files rather than the silicon, so it is identical on every Apple chip. What this chip changes is the absolute floor under both. If you have tried the 9B on this machine and found it fast enough to be boring, the 27B is the upgrade the hardware was bought for; if the 9B already felt marginal, the 27B will not rescue it.
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, no flash-attn wheel and no bitsandbytes in this recipe — none of them exist on Apple Silicon, and the quantisation here is a GGUF K-quant rather than GPTQ, AWQ or FP8.
brew install llama.cpp
Homebrew is a documented macOS channel in docs/install.md. 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 GGUFs below were 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, kernel_gated_delta_net_impl for the 48 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 merged PR #20361, 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. For the vision half, a 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-27B-GGUF \
Fara1.5-27B-Q5_K_M.gguf mmproj-Fara1.5-27B-f16.gguf \
--local-dir ~/models/fara1.5-27b
Microsoft publishes no first-party GGUF for Fara1.5 — the microsoft org ships safetensors only, for all three sizes. Four independent community quantisers have converted the 27B including its vision tower — bartowski, prithivMLmods, Abiray and DevQuasar all ship an mmproj 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. Their quant recipes differ, so the byte counts on this page apply only to the bartowski files they name.
On the two projector files. config.json declares "dtype": "float32" inside vision_config, which would suggest both projector builds are down-conversions — but the artifact disagrees with the config. Reading the safetensors header of model-00010-of-00010.safetensors in the source repo, all 333 vision tensors are stored as BF16. So mmproj-Fara1.5-27B-bf16.gguf (general.file_type = 32) is the byte-faithful conversion and the f16 file is the re-encoded one — the opposite of what the config would lead you to assume. config.json declares intent; the safetensors header declares what shipped.
The budget above still uses the f16 file, and the reason is not fidelity-by-default. At the same 16 bits, f16 spends three more of them on mantissa than bf16 does, trading exponent range for precision — and a vision tower, whose activations sit in a well-bounded numeric range, is exactly the place where precision is the better half of that trade. It is also 3.4 MB smaller and the more widely exercised artifact across this catalogue. If you ever see numerically odd vision behaviour, the bf16 projector is the like-for-like copy of the upstream weights and costs 0.003 GiB more — swap it in and compare.
3. Serve it
./build/bin/llama-server \
-m ~/models/fara1.5-27b/Fara1.5-27B-Q5_K_M.gguf \
--mmproj ~/models/fara1.5-27b/mmproj-Fara1.5-27B-f16.gguf \
-ngl 99 -c 65536 --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. 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 andmessage.contentcomes back empty; a client that readscontentsees nothing.noneis documented incommon/arg.cppas "none: leaves thoughts unparsed inmessage.content", which is what the harness expects. The report is on this model's own discussion #4, reproduced on b10107 with the 27B; a Microsoft team member replied there asking for it to be filed upstream, and it now sits as the open fara#82, "Hosting: llama.cpp needs--reasoning-format none". 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 floor —hparams.set_limit_image_tokens(8, 4096)on thePROJECTOR_TYPE_QWEN3VLbranch inclip.cpp— and warns: "Qwen-VL models require at minimum 1024 image tokens to function correctly on grounding tasks", pointing at issue #16842 on bounding-box correctness. 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 each screenshot is already 1,260 tokens and the floor never binds; the flag protects you if you shrink the viewport.-c 65536. Four times the harness's documented floor and twice what a 24 GB graphics card fits at this quantisation, costing 4.000 GiB of KV per the table above. See the per-round table if your tasks run long.-ngl 99. Explicit rather than necessary — a Metal build already runs the computation on the GPU by default, perdocs/build.mdabove.
Two things you will notice are missing, deliberately. There is no sudo sysctl iogpu.wired_limit_mb raise: the lead configuration sits ~7 GiB inside the default share, and the sysctl belongs to the 262,144 experiment described in Requirements, not here. And there is no --cache-type-k q8_0 KV quantisation — it would save memory, but it costs precision on a model whose job is coordinates, and at 4.000 GiB of KV there is nothing to buy.
The projector this loads is qwen3vl_merger with 27 vision blocks — you can read that straight out of the mmproj file's own GGUF metadata (clip.projector_type, clip.vision.block_count), and its clip.vision.is_deepstack_layers array holds 27 entries all set to false, matching the empty deepstack_visual_indexes in config.json. So this tower uses no deepstack layers, and mainline mtmd implements exactly what the file describes.
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 /models or /v1/models for the multimodal capability before a multimodal request", and documents image_url.url as one that "can be a remote URL, base64 (raw or URI-encoded via data:image/...;base64) 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": "Fara1.5-27B",
"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 plus nvidia-cutlass-dsl, both visible in pyproject.toml — which is neither installable nor useful on Apple Silicon. The README itself notes the plain install as the option for people not self-hosting on their own GPU. You are self-hosting, just not with vLLM.
The rest of the harness is genuinely portable: pyproject.toml declares Operating System :: OS Independent and the runtime dependencies are playwright, openai, pillow and other pure-Python packages, with no CUDA anywhere. Playwright 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 \
--base_url http://localhost:5000/v1 \
--api_key not-needed \
--model Fara1.5-27B \
--task "how many pages does wikipedia have" \
--output_folder ./runs/first
--model Fara1.5-27B is not decoration: the harness's built-in DEFAULT_ENDPOINT_CONFIG in run_fara.py names Fara1.5-9B, so leave it off and every request goes out labelled as a different model than the one you loaded.
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 (run_fara.py takes headless: bool = True and computes headless=not args.headful; the flag's own help text reads "Run the browser in headful mode (show GUI, default is headless)"). Add --headful 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.
This chip makes leaving a run unattended more tempting than it should be. --max_rounds defaults to 100 and the browser is real. Cap it.
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 lives in its own repository rather than being reproduced here, because this recipe has not verified those steps end to end — 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 one does not, and the reason is provenance rather than a framework gap: mlx-vlm genuinely implements this architecture — mlx_vlm/models/qwen3_5/ is in the package tree — and ships an OpenAI-compatible mlx_vlm.server whose /v1/chat/completions accepts image_url parts, which is exactly what fara-cli --base_url needs. The problem is which artifacts exist.
Check which MLX build you are about to download, because for the 27B the popular one cannot see. There are two:
| Repo | Vision tensors | Where weight_map points them | Usable as a CUA? |
|---|---|---|---|
mlx-community/Fara1.5-27B-OptiQ-4bit | 333 | optiq/optiq_vision.safetensors | No under stock loaders |
tokimoa/Fara1.5-27B-MLX-4bit | 333 | model-00001-of-00004.safetensors, the same shard as the language weights | Yes |
The tensor counts are identical, so a census passes on both — the discriminator is the shard target. The OptiQ card says so itself: "Vision tower kept at bf16" in that side artifact, and "The one repo loads text-only under stock mlx-lm", with full image+text only through its own optiq package. For a model whose entire job is looking at a screenshot, text-only is not a degraded install, it is a broken one — and the OptiQ build has roughly four times the downloads of the working one.
Note also what this does not generalise to: for the 9B, mlx-community ships a clean 8-bit build and a broken OptiQ one. The org is not a safety signal in either direction, and a verdict from one size of this family does not carry to another. Re-run the check per repo:
curl -s "https://huggingface.co/tokimoa/Fara1.5-27B-MLX-4bit/raw/main/model.safetensors.index.json" \
| python3 -c "
import json,sys,collections
wm=json.load(sys.stdin)['weight_map']
vis=[k for k in wm if 'vision' in k or 'visual' in k]
print('total', len(wm), '| vision', len(vis))
print('vision shards:', dict(collections.Counter(wm[k] for k in vis)))"
The keys are named vision_tower.* under MLX, so a probe that greps for visual alone returns 0 on a healthy build — search for both.
Two further things argue for the GGUF lead. MLX has no 8-bit or bf16 build of the 27B at all — 4-bit is the whole ladder — where bartowski publishes 24 tiers from IQ2_M up to a full bf16 split, which is what makes this page's Q5_K_M lead and its Q6_K fallback possible. And the working MLX build is a single-author repo created three days before this was written, with double-digit downloads.
That said, it is a real path, its card is unusually candid, and it is the only Fara1.5-27B artifact anyone has published a run of on this exact chip:
pip install -U mlx-vlm
mlx_vlm.server --model tokimoa/Fara1.5-27B-MLX-4bit --host 127.0.0.1 --port 8080
Then point the harness at http://127.0.0.1:8080/v1. Three things to know first:
--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.- The
<think>problem exists here too, and the default already handles it.mlx-vlm's splitter returns an emptycontentwhen it finds an opening tag with no closing one, which is the same dead agent loop. What saves you isDEFAULT_ENABLE_THINKING = Falseinmlx_vlm/server/generation.py: 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 noneexists 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. MLX allocates its cache differently and this recipe has not derived it — tell us what you measure.
The one published Apple measurement, and what it is worth
The tokimoa card (written in Japanese) reports the converter's own runs of that 4-bit MLX build on an M4 Max with 36 GB under mlx-vlm 0.6.7, with a full system prompt and a 1440x900 image: 19.5 tok/s generation, 134.8 tok/s prompt processing, and a 24.7 GB peak against 17 GB of weights. It also reports a coordinate-grounding check on a mock UI in which the predicted click landed inside the target element, and it reports that passing the tool schema through the chat template's tools= argument degraded both the tool-call syntax and the grounding, while the canonical form — the schema inside the system prompt, as microsoft/fara's own _prompts.py builds it — worked. That last point corroborates why fara-cli works against a stack Microsoft never tested: it puts the schema in the system prompt and sends no OpenAI tools array, then parses the raw text itself by splitting on <tool_call>.
Read it as a single community self-report, not as a benchmark. It is one person, on the artifact they made, with no independent reproduction: the site's /check row for this pair has zero entries. Note also which M4 Max it is — a 36GB machine is the 14-core-CPU part at 410 GB/s per Apple's specs above, not the 546 GB/s bin a 48GB machine gets, so if anything that figure understates this configuration's silicon rather than flattering it. It is also a different runtime, a different quantisation tier and a different memory configuration from this page's install, so it does not describe the llama.cpp Q5_K_M configuration above and is not carried into Results. What it does establish is that somebody has run Fara1.5-27B end to end on M4 Max silicon, with vision, and got sane coordinates out — which is more than exists for any other chip. If you reproduce or contradict it, send the numbers.
Results
- Speed: omitted for this recipe's configuration. No published measurement exists for Fara1.5-27B under
llama.cppon an M4 Max, 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"). The community-reported MLX figures on this chip are described above, clearly labelled, and belong to a different runtime and quantisation. What can be said without measuring: token generation is bandwidth-bound, this configuration re-reads 20.51 GB of weights per generated token against this chip's 546 GB/s — the highest in the Apple range this site covers — and that same file streams in about 73% of the time it would on a 400 GB/s Apple chip. If you run this, please contribute your numbers. - Unified memory usage: 19.969 GiB of weights and projector; 24.553 GiB including KV and recurrent state at a 65,536-token context, derived above from
config.json, the GGUF byte counts and llama.cpp's own memory formulas. That sits about 7.4 GiB inside the ~32 GiB a 48GB M4 Max makes GPU-addressable by default. It is a derivation, not an observed peak — compute and vision buffers are additional. See /check/fara1-5-27b/m4-max. - Quality notes — read this one. Fara's whole job is emitting precise pixel coordinates, and quantising a grounding model is a real risk, not a formality. The vendor's published scores are for the bf16 model on datacenter hardware, and no evaluation of how any quantisation affects Fara1.5's grounding accuracy has been published — not by Microsoft, not by the community. The nearest thing is the single self-reported spot check on the MLX 4-bit build described above, which is one screenshot, by one person, on a different runtime. Treat a misclick here as possibly the quantisation rather than the model, and if a task depends on precision, verify against a bf16-hosted endpoint before concluding anything. On this machine the one cheap step up is Q6_K at the same window (27.078 GiB); Q8_0 does not fit at 65,536 and the bf16 reference build is out of reach entirely.
- On the headline comparison. The fara README states that Fara1.5-27B "outperforms much larger proprietary systems such as OpenAI Operator and Gemini 2.5 Computer Use on Online-Mind2Web". Its table shows 72.3 against 58.3 and 57.3 — but the same table's footnote says "† denotes numbers sourced from the model's official release or leaderboard rather than re-run by us", and both competitors carry that dagger. It is a vendor-run comparison against self-reported figures, not a controlled head-to-head.
- 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-27b/m4-max.
Troubleshooting
The startup log says Flash Attention not supported, set to disabled
Then the non-FA kq buffer applies: 3.000 GiB on top of the 24.553 accounted at -c 65536, which the lead configuration still absorbs. It is the 262,144 experiment that breaks, where the same term is 12.000 GiB. This should not happen on a Metal build (head size 256 with an F16 cache is in ggml-metal-device.m's supported list), so if it does, check you are not passing --cache-type-k/--cache-type-v with a quantised type — a quantised V cache with flash attention off is a hard startup failure, logged as quantized V cache requires flash_attn to be enabled.
The server starts but the log says CLIP using CPU backend
The projector fell back to the CPU. Vision still works, 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.
The machine starts swapping, or Activity Monitor's memory pressure goes yellow
You are over the addressable share once compute buffers are counted — the likeliest cause on a 48GB machine, since the safe figure here is a third below the nameplate. In order, because the first two cost nothing: drop to Q4_K_M at the same window (21.778 GiB, frees 2.8 GiB); drop to -c 32768 (frees 2.000 GiB and still clears the harness's floor twice over); or, only if you actually need the memory elsewhere, raise the cap with sudo sysctl iogpu.wired_limit_mb=40960 and quit everything else first.
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 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 Quality notes for how far up this machine lets you test.
The run stops mid-task with a context-overflow error
ctx_shift is off by default, so llama.cpp errors rather than sliding the window. At -c 65536 with the default --max_rounds 100 the budget is roughly 615 tokens of model output per round; a task that reasons at length can exhaust it. Either lower --max_rounds, or move to Q4_K_M at -c 131072 (25.778 GiB) — which is the same memory as this page's lead plus 1.2 GiB and doubles the per-round budget.
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.
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 and fara1-5 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, and older repo issues describing 5 GB quants and 16 GB cards are Fara-7B threads. Any GGUF runtime tracking llama.cpp b10087+ that can load a separate mmproj should work in principle, but llama-server is the only path with a public report of this model behind it.
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 llama.cpp measurement for this chip exists either way — contribute one.