self-hosted/ai
§01·recipe · multimodal

Fara1.5-4B on RTX 3080 Ti: Browser Computer-Use Agent at Q8_0 in 12GB with llama.cpp

multimodaladvanced12GB+ VRAMJul 31, 2026

This advanced recipe sets up Fara1.5-4B on the RTX 3080 Ti, needing about 12 GB of VRAM.

models
tools
prerequisites
  • NVIDIA RTX 3080 Ti (12GB VRAM) or equivalent
  • Python 3.10+
  • llama.cpp built with CUDA for compute capability 8.6 (release b10087 or newer)
  • A disposable browser profile — fara-cli drives a plain local Playwright Chromium, unsandboxed

What You'll Build

A local browser computer-use agent (CUA): Microsoft's Fara1.5-4B served by llama-server on an RTX 3080 Ti, with the vision projector loaded so the model actually sees browser screenshots, driven by Microsoft's own fara-cli harness against a plain local Playwright Chromium.

Hardware data: RTX 3080 Ti (12GB VRAM) · Q8_0 + f16 mmproj = 4.812 GiB of weights, 7.008 GiB total at 65,536 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 controls the card asks for belong to Magentic-UI / MagenticLite: the card attributes "the browser runs in a Docker container with no access to host files or environment variables" to that deployment and then says "If you integrate Fara1.5-4B directly, you're responsible for these controls." The harness README describes Magentic-UI as "a sandboxed browser environment with auditable action logging and user prompts at critical points" and offers it as what you use "instead of fara-cli you can use Magentic-UI". The vendor's position on the CLI path is a recommendation, not a guarantee shipped in it: "We recommend running it in a sandboxed environment, monitoring its execution, and avoiding sensitive data or high-risk domains."

So if you follow the fara-cli path below, you are the isolation layer. Practically: run it on a throwaway OS user or VM, let Playwright use its own fresh browser profile (never your daily one), 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. llama.cpp support for this architecture is verifiable statically and this recipe shows that evidence, but no published issue thread, user report or docs entry shows Fara1.5 specifically captioning an image under llama-server or llama-mtmd-cli. The qwen35 text arch and the qwen3vl_merger projector are both first-class in llama.cpp master, and four independent quantisers have converted this model's vision tower successfully — but that is static evidence, not a tested path. Treat the first run as the test. There is also no published speed measurement for this pair (see Results).

Requirements

ComponentMinimumThis recipe
GPU12GB VRAM, CUDA compute 8.6not measured — contribute a run
RAM16GB
Storage5.17 GB (Q8_0 + mmproj)5.17 GB decimal / 4.812 GiB
Softwarellama.cpp b10087+ (CUDA), Python 3.10+, Playwright

The BF16 safetensors total 8.455 GiB across two shards per the HF tree API. Microsoft's own Quickstart targets datacenter hardware — the card's requirements line says "A6000, A100, H100, and B200 have been tested", and its highlights claim only that the model "runs on a single A100/H100/B200 with room for the screenshot history". The consumer route is the quantised one documented here.

What 12 GB actually buys — the budget, derived

A 4B model is not going to strain this card's compute. Memory is the only scarce resource in this recipe, and 12 GB is the whole of it, so the interesting question is not "does it fit" but "what should the spare capacity be spent on". There is no benchmark for this pair yet, so every number below is arithmetic from the artifact's own metadata rather than a measurement.

All totals are GiB (2^30 bytes), which is the unit the card is really in — a "12GB" NVIDIA GPU has 12 GiB of physical memory — so they compare directly. HuggingFace reports file sizes in decimal GB, which is why the Storage row above (5.17 GB) and the weights total below (4.812 GiB) describe the same bytes.

Weights — byte counts from the bartowski GGUF tree:

ComponentBytesGiB
Fara1.5-4B-Q8_0.gguf4,493,954,1444.185
mmproj-Fara1.5-4B-f16.gguf672,423,5840.626
Weights total5,166,377,7284.812

KV cache — and this is where Fara1.5 is unusual. Its qwen35 architecture is a hybrid: llama.cpp's llm_arch_is_hybrid() in src/llama-arch.cpp returns true for LLM_ARCH_QWEN35, and llama-memory-hybrid.cpp splits layers with the filter !hparams.is_recr(il). The GGUF reports qwen35.block_count = 32 and qwen35.full_attention_interval = 4, and the source config.json spells the split out layer by layer: 24 of 32 layers are linear_attention, only 8 are full_attention (indices 3, 7, 11 … 31). So only 8 layers carry a KV cache that grows with context.

With qwen35.attention.head_count_kv = 4 and attention.key_length = attention.value_length = 256:

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

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, fed by the GGUF's ssm.conv_kernel = 4, ssm.inner_size = 4096, ssm.group_count = 16, ssm.state_size = 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 MiB = 0.196 GiB

That last multiplication is easy to miss and it is not optional. src/llama-model.cpp sizes the recurrent state with /* recurrent_rs_size */ std::max((uint32_t) 1, cparams.n_seq_max) on the branch that names LLM_ARCH_QWEN35, cparams.n_seq_max comes from params.n_parallel, and tools/server/server.cpp resolves the server's auto default with params.n_parallel = 4 — so the allocation is four sequences' worth whether or not you use them. The KV cache is not multiplied: the same default also sets kv_unified = true, so the four slots share one context window rather than each getting their own. The recurrent term stays context-independent, which is what makes it easy to overlook.

The choice: quant, context, or both

32 KiB/token is a small KV term for a 262,144-context model, so context is unusually cheap here and quality is the expensive axis. Price the two moves against an 8 GB-class configuration (Q4_K_M at 32,768, which totals 4.509 GiB):

  • stepping the quant Q4_K_M → Q8_0 costs 4.812 − 3.313 = 1.499 GiB
  • doubling the context 32,768 → 65,536 costs 1.000 GiB

Together that is 2.499 GiB against the 4 GiB of extra capacity, so this is not an either/or. Take both — the result still leaves 4.992 GiB of the card unspent:

Quant (+ f16 mmproj)WeightsKV @ 65,536RecurrentTotal
Q4_K_M3.313 GiB2.000 GiB0.196 GiB5.509 GiB
Q5_K_M3.714 GiB2.000 GiB0.196 GiB5.910 GiB
Q6_K4.051 GiB2.000 GiB0.196 GiB6.247 GiB
Q8_04.812 GiB2.000 GiB0.196 GiB7.008 GiB
bf168.472 GiB2.000 GiB0.196 GiB10.668 GiB

Why the quant gets the headroom first. Fara's output is pixel coordinates — the card describes the training data as "Curated datasets for predicting actions and pixel coordinates from screenshots, with images, text, and bounding boxes." Quantisation error lands on exactly that faculty, and no published evaluation measures how much grounding accuracy a given tier costs. On an 8 GB card Q8_0 is something you trade context for; here it is affordable outright at 40% of the card, so the sensible move is to take the quantisation variable off the table rather than economise on the model's core competence.

Why not the unquantised bf16 GGUF. This is the tempting move on a card with this much compute, and it is the one to resist. It arithmetically fits — 8.472 GiB of weights plus 1.196 GiB at 32,768 context is 9.668 GiB — but that leaves 2.332 GiB for CUDA context, compute buffers, the vision graph and the GPU-accelerated Chromium this agent drives on the same card. A card of this class is usually also the one driving your display, which is the desktop the agent is clicking on. It is a legitimate option if you dedicate the GPU and output video from something else; it is not a safe default here.

Why 65,536 and not more. The harness asks that you "Please ensure that context length is set to at least 15000 tokens and temperature to 0 for best results.", so 65,536 is more than four times the vendor's floor. Longer still fits, but the native maximum does not:

ContextWeightsKV (f16)RecurrentSum
32,7684.812 GiB1.000 GiB0.196 GiB6.008 GiB
65,5364.812 GiB2.000 GiB0.196 GiB7.008 GiB
131,0724.812 GiB4.000 GiB0.196 GiB9.008 GiB
262,144 (native)4.812 GiB8.000 GiB0.196 GiB13.008 GiB ✗

Those last terms — CUDA context, compute buffers, the vision graph — are not in the tables; they are allocated at load time and this recipe does not derive them. Neither is the browser. Read the CUDA0 compute buffer size lines that llama-server prints on startup, watch nvidia-smi with the agent actually running, and report what you see.

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 in the projector metadata — that value is derived from num_position_embeddings (√2304 × 16 = 768) as a reference for position-embedding interpolation, and llama.cpp treats this projector as dynamic-resolution: clip.cpp groups PROJECTOR_TYPE_QWEN3VL with the cases that compute n_patches as (nx / (patch_size*2)) * (ny / (patch_size*2)), and the --image-min-tokens / --image-max-tokens pair is documented as "only used by vision models with dynamic resolution".

The harness fixes the browser viewport at 1440×900 (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" 1440×900. It then applies Qwen's smart_resize with patch_size=16, merge_size=2, so the factor is 32:

900 -> round(900/32)*32   = 896        1440 -> round(1440/32)*32 = 1440
(1440 / 16) / 2 = 90 / 2 = 45 across   (896 / 16) / 2 = 56 / 2 = 28 down
image tokens = 45 x 28                 = 1,260 tokens per screenshot

The harness keeps max_n_images: int = 3, so 3,780 tokens of any prompt are screenshots and the rest of the growth is text — thoughts, tool calls, observations. 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. That bounded image history is the other reason 65,536 is generous rather than arbitrary: nothing in the loop grows the image share.

Installation

1. Build llama.cpp with CUDA

The RTX 3080 Ti is Ampere, compute capability 8.6 per NVIDIA's CUDA GPUs table — the same target as the rest of the Ampere consumer line, so no special toolkit version applies. Build from a release at or after b10087 — that is the release the GGUF used here was quantised on, per the quantiser's card.

git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
cmake -B build -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=86 -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j

llama.cpp's CUDA CMakeLists puts 86-real in the default architecture list unconditionally and gates only newer architectures behind newer toolkits, so a stock CUDA install is enough.

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-4B-GGUF \
  Fara1.5-4B-Q8_0.gguf mmproj-Fara1.5-4B-f16.gguf \
  --local-dir ~/models/fara1.5-4b

Microsoft publishes no first-party GGUF for Fara1.5 — the microsoft org ships safetensors for all three sizes (and an ONNX repo only for the previous-generation Fara-7B). Four independent community quantisers have converted the 4B including its vision tower — bartowski, prithivMLmods, DevQuasar and runanywhere all ship an mmproj file. 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-4b/Fara1.5-4B-Q8_0.gguf \
  --mmproj ~/models/fara1.5-4b/mmproj-Fara1.5-4B-f16.gguf \
  -ngl 99 -c 65536 --port 5000 \
  --image-min-tokens 1024 \
  --reasoning-format none \
  --temp 0

Three of those flags are load-bearing and worth understanding rather than pasting:

  • --image-min-tokens 1024. The projector ships no minimum-pixel key of its own — its 30 metadata entries cover geometry, normalisation and the projector type, nothing about a token floor — so llama.cpp falls back to its Qwen-VL default and prints a warning: "Qwen-VL models require at minimum 1024 image tokens to function correctly on grounding tasks". Fara is a grounding model, so this is precisely the case the warning is about. At the harness's 1440×900 viewport the image is already 1,260 tokens and the floor never binds; it protects you if you shrink the viewport.
  • --reasoning-format none. Under llama-server's default reasoning extraction, <think> content is pulled out into a separate reasoning_content field. Fara's chat template opens every assistant turn with <think> and never closes it, and the harness parses the raw text itself — it splits on <tool_call> and feeds the preceding text back as the agent's thoughts. Without this flag message.content comes back empty and the agent loop dies on round one. none is documented as "none: leaves thoughts unparsed in message.content", which is exactly what the harness expects.
  • -c 65536. See the budget tables. Do not leave it at the model's native 262,144 — that alone needs 8 GiB of KV.

4. Prove the vision path works before going further

Check the server advertises multimodal, then send a real image. The server README documents image_url as a first-class content part: "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": "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. 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.

5. Install the harness

git clone https://github.com/microsoft/fara.git
cd fara
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
playwright install

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" \
  --headful \
  --output_folder ./runs/first

--headful is not optional here, despite reading like a convenience flag. The harness runs the browser headless by default: run_fara.py declares the flag as "Run the browser in headful mode (show GUI, default is headless)" and passes headless=not args.headful into the browser launch. Omit it and a real Chromium still opens, still clicks and still submits — you simply cannot see it. Every supervision instruction in this section depends on the window being visible, so the flag is what makes the rest of the advice true rather than aspirational.

Each run writes "per-step screenshots and a data_point.json (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.

With that flag the command opens a real, unsandboxed Chromium on your desktop — the playwright install 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 you should tolerate.

Path B — Magentic-UI, for the container

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. 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 GPU — follow microsoft/magentic-ui and give it http://localhost:5000/v1 as the model endpoint.

Results

  • Speed: omitted — no published measurement exists for Fara1.5-4B on this card, or on any consumer GPU. The only hardware the vendor names is datacenter class, and borrowing a number from it would be misleading for a 12 GB Ampere card. This is a card where compute is unlikely to be the limit for a 4B model, so the useful thing to measure is not raw generation but the per-step cost of re-processing a 1,260-token screenshot — llama-bench on the text model gives you the first half of that picture. If you run this, please contribute your numbers so this section can be replaced with a measurement.
  • VRAM usage: 4.812 GiB of weights and projector; 7.008 GiB including KV and the four-sequence recurrent state at 65,536 context, derived above from the GGUF's own metadata and llama.cpp's memory formulas. This is a derivation, not an observed peak — compute buffers, the vision graph and the browser's own GPU memory are additional. See /check/fara1-5-4b/rtx-3080-ti.
  • Quality notes: the vendor's published scores (80.8 WebVoyager, 57.3 Online-Mind2Web, 27.4 WebTailBench) are for the BF16 model on datacenter hardware. Q8_0 is the closest quantised tier to those weights, which is the whole reason it is the lead here; if you drop to Q4_K_M to free VRAM, you are trading against grounding accuracy on a model whose main job is predicting exact pixel coordinates, and no published evaluation measures that trade.
  • Language: English only, per the model card's out-of-scope list.

The desktop is a tenant on this card too

Worth stating plainly, because on a card in this class it is easy to forget: the tables above account for the model and nothing else. fara-cli launches a real Chromium on the same machine, and if that browser renders on the GPU its process holds memory on the same 12 GB. On a card that is also driving your monitors, the framebuffer and the compositor are in there as well — and the desktop the agent is clicking on is that display. Take a nvidia-smi reading with the browser open and the agent mid-run, not with an idle machine, before deciding how much slack you really have.

For the full benchmark data, see /check/fara1-5-4b/rtx-3080-ti.

Troubleshooting

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 and fara-7b 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 a lead aggregator has already mis-attributed it. Likewise, the "run them in your choice of tools" list on the quantiser's card is that quantiser's standard boilerplate, printed with the caveat that "if it's a newly supported model, you may need to wait for an update from the developers"; it is not a statement that LM Studio or Jan has tested this model. llama.cpp built from source is the path this recipe verifies.

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 GGUF 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.

Clicks land near targets but not on them

Check the viewport first. The harness defaults to 1440×900 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.

If both are right, there is one more thing worth ruling out before you blame the quantisation. A community user reported that llama-server degrades fine-grained vision accuracy for PROJECTOR_TYPE_QWEN3VL models relative to llama-cli on the same build, with identical prompt-token counts — llama.cpp issue #22785, filed against the Vulkan backend at release b8545. Read it with three caveats: a maintainer disputed the reporter's bisect and asked for a proper one, the issue was auto-closed as stale and unconfirmed, and nobody re-tested it on a CUDA build or anywhere near b10087. Its value here is as a diagnostic, not a known defect: push the same screenshot through llama-cli and compare what the model says about it. If the CLI reads the page correctly and the server does not, you have reproduced something real and worth reporting upstream.

rope.dimension_sections has wrong array length; expected 4, got 3

This is a real open llama.cpp bug (PR #25334), but it belongs to Ollama-exported qwen35 blobs, not to the GGUF this recipe uses. The Q8_0 artifact's own metadata reads qwen35.rope.dimension_sections = [11, 11, 10, 0] — four entries — so it cannot hit this failure. If you see this error, you are loading a different file than the one downloaded in Step 2.

Generation is slower than a 4B model should be

There is an open, community-reported performance regression affecting exactly the SSM kernels this architecture leans on — llama.cpp issue #25162 reports a 24-42% loss on an RTX 2080 Ti (Turing, SM75) after commit 9e58d4d69, and notes the Qwen35 architecture uses linear-attention layers for about 75% of its blocks (which matches the 24-of-32 split derived above). Three caveats before you act on it: it has no maintainer response; it was measured only on Turing, one architecture generation before the RTX 3080 Ti's Ampere (SM86), and nobody has posted numbers for this card; and the one candidate fix raised in the thread, PR #25185, was merged on 2026-07-01 with a stated scope of flash-attention kernels rather than the SSM path, with no re-measurement posted since. Release b10087 was published on 2026-07-22, so a build from it or newer already contains that merge. Treat the regression as a lead to check with llama-bench across a couple of builds, not as a known property of this card.

Out of memory at load

On 12 GB with the Q8_0 configuration this should not happen at all, so treat it as a signal that something else holds VRAM — see the note above about the browser and the display. If the card really is dedicated, drop to -c 32768 (saves 1 GiB of KV, still double the harness's 15,000-token floor), or quantise the KV cache with --cache-type-k q8_0 --cache-type-v q8_0. That last one roughly halves the KV term rather than exactly halving it: ggml's block_q8_0 is one ggml_half scale plus 32 int8_t quants, so 34 bytes per 32 values against f16's 64 — 17 KiB/token instead of 32. Keep the f16 projector: at 0.626 GiB it is not where the savings are, and the q8_0 projector some quantisers ship trades vision fidelity in a model whose entire job is looking at pixels.

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

About 12 GB — the minimum this recipe targets.

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

RTX 3080 Ti (12 GB).

How hard is this setup?

Advanced — follow the steps above.