self-hosted/ai
§01·recipe · multimodal

Fara1.5-27B on RX 7900 XTX: Q4_K_M Browser Computer-Use Agent on ROCm gfx1100

multimodaladvanced24GB+ VRAMJul 31, 2026

This advanced recipe sets up Fara1.5-27B on the RX 7900 XTX, needing about 24 GB of VRAM.

models
tools
prerequisites
  • AMD Radeon RX 7900 XTX (24GB VRAM, RDNA3, gfx1100) — an officially ROCm-supported target
  • Linux with a working, current ROCm install — both ROCm and the Fara harness are Linux-first
  • llama.cpp built with `-DGGML_HIP=ON -DGPU_TARGETS=gfx1100` (release b10087 or newer)
  • Python 3.10+
  • A disposable browser profile — fara-cli drives a plain local Playwright Chromium, unsandboxed

What You'll Build

A local browser computer-use agent (CUA) on AMD hardware: Microsoft's Fara1.5-27B served by llama-server built against ROCm/HIP on an RX 7900 XTX, 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. You give it a task in English; it takes a screenshot, reasons, and emits a click, a keystroke or a URL visit, then loops.

This is the largest Fara1.5 the catalogue documents on one consumer card, and it is the page where this card's usual advantage runs out. The 4B and 9B recipes for this same GPU both lead unquantised BF16, because llama.cpp special-cases RDNA3 in BF16's favour and 24 GB has room for it at those sizes. At 27B the BF16 GGUF exists and is 50.113 GiB — 26.113 GiB past this card. So the interesting question here is the same one the NVIDIA 24 GB pages face: which quant, at which window.

Hardware data: RX 7900 XTX (24GB VRAM, gfx1100) · Q4_K_M weights + f16 mmproj = 17.193 GiB · 19.778 GiB accountable at a 32,768-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 the operating requirement, not boilerplate, 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 — the card lists "Prompt injection" among its known model risks by name.

The vendor asks for protections the fara-cli path does not provide. The card's Recommended Deployment: MagenticLite section lists four controls as MagenticLite features — the first of them sandboxing, described there as "the browser runs in a Docker container with no access to host files or environment variables" — and then says: "If you integrate Fara1.5-27B directly, you're responsible for these controls." The Responsible AI section asks you to "run Fara in an isolated container with no access to host files, environment variables, or sensitive credentials". The harness README offers the containerised option as an alternative to the CLI rather than as part of it — "instead of fara-cli you can use Magentic-UI", described in the same sentence as "a sandboxed browser environment with auditable action logging and user prompts at critical points" — and for the CLI path states only a recommendation: "We recommend running it in a sandboxed environment, monitoring its execution, and avoiding sensitive data or high-risk domains."

So on the fara-cli path you are the boundary. Practically: a throwaway OS user or a VM, Playwright's own fresh browser profile (never your daily one), signed out of everything, low-stakes public sites, and eyes on the run rather than leaving it unattended. The command in Running passes --headful for exactly that last reason. If you want the container, the allow-lists, watch-mode and the pause control, use Magentic-UI as the client instead.

ℹ️ 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, on the GPU, before you let it drive anything.

⚠️ This install is off the vendor's supported path, deliberately. The 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 4-bit quantisation Microsoft never published or blessed, on a vendor stack Microsoft names nowhere. Both departures are stated where they bite: quantisation under Results, ROCm immediately below.

⚠️ What is established about this model on ROCm, and what is not. The primary argument is structural, and it is strong. llama.cpp's multimodal layer is backend-agnostic by construction: tools/mtmd/clip.cpp contains zero matches for cuda, hip, rocm or any GGML_USE_* guard. It asks ggml for whatever GPU backend exists — ggml_backend_init_by_type(GGML_BACKEND_DEVICE_TYPE_GPU, nullptr) — and always puts the CPU backend into the same scheduler as a per-op fallback. The backend HIP compiles, ggml/src/ggml-cuda/ggml-cuda.cu, does carry GGML_USE_HIP guards — a VMM/ROCR-Runtime unmap workaround, an NCCL warning suppression, host-buffer registration — but none in the op dispatch or in the supports_op table, which is the half that decides whether an operation runs at all. There is no separate "HIP op set"; it is one backend compiled twice.

There is runtime corroboration, and it is narrower than it first looks. llama.cpp issue #13694 contains the line clip_ctx: CLIP using ROCm0 backend, with ROCm buffer sizes throughout — a real local HIP run of the mtmd path, no RPC involved. Three qualifications, all of which matter. The log is from llama-mtmd-cli, not llama-server. The silicon is an RX 7800 XT, not this card, and its run reports gfx1100 (0x1100) only because the command line sets HSA_OVERRIDE_GFX_VERSION="11.0.0" — so the ISA matches this card's native target while the die does not. And the projector there is qwen2.5v (PROJECTOR_TYPE_QWEN25VL), not the qwen3vl_merger this model ships; the same clip.cpp case block handles both, which is why it transfers at all. All 85 comments were checked and none contains another CLIP using line, so that is the thread's only runtime datapoint.

For our exact projector on ROCm the evidence is thinner still. Issue #20081 does establish that a qwen3vl_merger projector loads and encodes under a non-CUDA backend — the reporter's pasted log reads clip_ctx: CLIP using Vulkan0 backend followed by load_hparams: projector: qwen3vl_merger. On ROCm specifically that thread does offer a positive remark — the reporter, comparing against a Vulkan failure, writes "Which is weird given ROCm (over RPC) works perfectly fine with the same image as aforementioned." Weigh it for what it is: an informal aside rather than a measurement, from a reporter with no project association, and — as the same user confirms elsewhere in the thread with "I am currently using CUDA + RPC-ROCm" — describing the RPC backend rather than a local HIP build. Read it as the weakest datapoint on this page, but not as an absence of one.

Net position: rest on the structure, treat the runs as corroboration. What nobody has published is a run of Fara1.5 on any AMD GPU, or a speed number for it on any consumer GPU at all (see Results). Treat your first run as the test and use Step 4, which reads llama.cpp's own load-time self-report rather than asking you to trust this paragraph.

Requirements

ComponentMinimumThis recipe
GPU24GB VRAM, officially ROCm-supported RDNA3— not measured; budget below is derived (/contribute)
RAM24GB (weights are mmap'd on their way to VRAM; Playwright's Chromium sits on top)
Storage18.46 GB (Q4_K_M + f16 mmproj)18,461,159,904 B = 17.193 GiB, plus the harness and Chromium
SoftwareLinux, a current ROCm, llama.cpp b10087+ (HIP), Python 3.10+, Playwright

Two notes on that table. min_vram_gb is 24, and it is a filter floor rather than a measured peak: the smallest real card capacity on which the install documented below runs. 19.778 GiB is accounted before a single compute buffer, so no 16 GB card holds it at any context setting — the 16 GB RDNA3 sibling is excluded from this page's configuration by arithmetic. The 4B recipe for this same GPU sets 16; both are correct, because a floor describes an install, not a card. And the GPU cell is not a measurement: /check reports unknown with zero benchmark rows, and nothing on this page was run on hardware by us.

Why BF16 is off the table here, and it is worth saying precisely

Every other Fara1.5 page for this card leads unquantised weights, and the reason is a real property of this silicon rather than spare capacity. llama.cpp names RDNA3 explicitly in its BF16 matrix-multiply path. In ggml/src/ggml-cuda/ggml-cuda.cu, a BF16 compute type sets

prefer_f32_output = !GGML_CUDA_CC_IS_RDNA3(cc) && !GGML_CUDA_CC_IS_CDNA(cc);

so RDNA3 and CDNA are the only two architecture families that keep BF16 output instead of taking the F32-output fallback the next branch installs. The constant behind that predicate, in ggml/src/ggml-cuda/common.cuh, is this card's exact gfx target:

#define GGML_CUDA_CC_RDNA3      (GGML_CUDA_CC_OFFSET_AMD + 0x1100) // RX 7000, minimum for WMMA

GGML_CUDA_CC_IS_RDNA3 is a range test spanning [0x1100, 0x1200) rather than an equality test, so the favour extends across RDNA3 generally — but gfx1100 is the constant it is anchored on.

At 27B that favour has an artifact and no room. The bf16 GGUF is published: the quantiser's card labels it "Full BF16 weights." and marks it split across two files. It is 53,808,281,344 bytes — 50.113 GiB, or 26.113 GiB more than this card holds. So the exclusion here is by arithmetic, not by availability, exactly as it is for the 9B on a 16 GB RDNA3 card, and no context setting or offload flag reaches it. Do not read the 4B and 9B pages' BF16 sections as advice that transfers to this size; the mechanism is real and the file is simply too large.

Two other CUDA reflexes are worth subtracting while you are here, because they show up in copy-pasted AMD configs. RDNA3's WMMA units take FP16, BF16, INT8 and INT4 — there is no native FP8 or FP4 on this architecture, so an "FP8 escape hatch" has no hardware basis and an FP8 checkpoint would upcast rather than save memory. And there is no Python attention package to install: llama.cpp's attention lives inside ggml, so no flash-attn build, no xformers, and none of the CUDA-only INT4 kernel stacks (Marlin, ExLlamaV2) are involved in anything on this page.

The VRAM budget, derived

There is no benchmark for this pair, so every number below is arithmetic from the artifacts' own metadata and from llama.cpp's source, not a measurement. Reproduce it rather than trusting it. The card holds 24,576 MiB, i.e. 24.000 GiB, and the whole derivation is in GiB so the comparison is like-for-like.

⚠ The 27B is not a scaled-up 9B, and three constants change. If you have read the 4B or 9B pages for this card, do not carry their arithmetic over. The source config.json declares 64 layers, a layer_types array of 48 linear_attention + 16 full_attention, num_attention_heads: 24, num_key_value_heads: 4, head_dim: 256 and max_position_embeddings: 262144. Against the smaller sizes' 8 attention layers and 16 heads, that doubles the per-token KV cost, triples the recurrent state, and raises the flash-attention-off scores bound by half. The GGUF agrees with the config key for key: the file's own header gives qwen35.block_count 64, attention.head_count 24, attention.head_count_kv 4, key_length 256, value_length 256, full_attention_interval 4, ssm.inner_size 6144, ssm.group_count 16, ssm.state_size 128, ssm.conv_kernel 4.

Weights — byte counts read from the bartowski GGUF tree, quantised on release b10087 with an imatrix:

FileBytesGiB
Fara1.5-27B-IQ4_XS.gguf15,328,839,48814.276
Fara1.5-27B-Q4_K_S.gguf16,474,163,00815.343
Fara1.5-27B-Q4_K_M.gguf17,533,552,44816.329
Fara1.5-27B-Q5_K_M.gguf20,513,802,04819.105
Fara1.5-27B-Q6_K.gguf23,224,145,72821.629
Fara1.5-27B-Q8_0.gguf28,665,067,32826.696
Fara1.5-27B-bf16 (2 shards)53,808,281,34450.113
mmproj-Fara1.5-27B-f16.gguf927,607,4560.864
mmproj-Fara1.5-27B-bf16.gguf931,146,4000.867

(HuggingFace displays these as decimal GB — 17.53 GB for the Q4_K_M file — which is the same bytes, not a different number.)

KV cache — 64 KiB per token, twice the smaller sizes. qwen35 is a hybrid architecture: llm_arch_is_hybrid() in src/llama-arch.cpp returns true for LLM_ARCH_QWEN35, and src/models/qwen35.cpp marks layers recurrent off full_attention_interval. Only the 16 full-attention layers carry a cache that grows with context:

K+V per token per attention layer = 2 × n_head_kv(4) × head_dim(256) × 2 B (f16) = 4,096 B
× 16 full-attention layers                                                       = 64 KiB / token

n_ctx =  32,7682.000 GiB        n_ctx = 131,0728.000 GiB
n_ctx =  65,5364.000 GiB        n_ctx = 262,14416.000 GiB

Context is still cheaper here than on a dense 64-layer model with the same head geometry — that would cost four times as much — but it is not the 1.000 GiB-per-doubling the 4B and 9B pages describe. Every doubling from 32,768 costs 2.000 GiB.

Recurrent state — fixed, and four times a single sequence's worth. The other 48 layers hold a context-independent state, sized by n_embd_r() / n_embd_s() in src/llama-hparams.cpp from this file's own GGUF keys:

conv state : (4−1) × (6144 + 2×16×128)  =  30,720 elems
ssm  state :         128 × 6144         = 786,432 elems
per layer  : 817,152 × 4 B (F32)        = 3.117 MiB
× 48 recurrent layers                   = 149.625 MiB   ← per sequence
× n_seq_max = 4                         = 598.5 MiB = 0.584 GiB

That ×4 is not optional and it is easy to miss. src/llama-model.cpp sizes the recurrent cache with /* recurrent_rs_size */ std::max((uint32_t) 1, cparams.n_seq_max) on the branch that names LLM_ARCH_QWEN35 explicitly, and llama-server does not leave n_seq_max at 1: common/arg.cpp passes params.n_parallel = -1 for the server, and tools/server/server.cpp then resolves that with if (params.n_parallel < 0) { params.n_parallel = 4; params.kv_unified = true; }, which common/common.cpp copies into cparams.n_seq_max; src/llama-memory-recurrent.cpp allocates n_rows = mem_size * (1 + n_rs_seq) with n_rs_seq defaulting to 0. So the recurrent state is allocated once per slot — four times — while the KV cache is not multiplied, because that same default makes it unified: a unified cache holds -c tokens in total rather than per sequence. The same line is why -c 32768 gives a single trajectory the whole 32,768 tokens rather than a quarter of them.

The whole trade, in one table. Accountable allocations (text weights + f16 projector + KV + the 0.584 GiB recurrent state) in GiB, against a 24.000 GiB card. Each cell sums the exact byte-derived inputs and is rounded once at the end — rounding the inputs first moves the third decimal.

ContextIQ4_XSQ4_K_SQ4_K_MQ5_K_MQ6_K
32,76817.72418.79119.77822.55325.078
65,53619.72420.79121.77824.55327.078
131,07223.72424.79125.77828.55331.078
262,144 (native)31.72432.79133.77836.55339.078

Two hard limits fall straight out of it.

The model's native 262,144-token window is unreachable on this card at every quant, and no flag rescues it. The KV cache alone is 16.000 GiB at that setting, and the cheapest total in the table is IQ4_XS at 31.724 GiB — 7.724 GiB past the card. -c must be capped; the vendor's own vLLM Quickstart passes --max-model-len 262144 and that line does not transfer to a 24 GB board.

Q5_K_M and Q6_K are out too. Q5_K_M at the shortest useful window is 22.553 GiB, leaving 1.447 GiB for the HIP context, llama.cpp's compute buffers, the vision encode graph and a desktop compositor — none of which this page derives. Q6_K does not fit at all.

The decision: Q4_K_M at 32,768, and the two runners-up

Q4_K_M @ 32,768 = 19.778 GiB, leaving 4.222 GiB. That is the lead, and it is the same configuration the 24 GB NVIDIA pages for this model reach — at this size the arithmetic, not the vendor, decides. Two alternatives are close enough to deserve an explicit answer rather than silence.

Q4_K_M @ 65,536 (21.778 GiB, 2.222 GiB left) — declined. It fits on paper. What it does not leave is room for the terms this page cannot derive, and on a card that may also be driving a desktop compositor that margin is thinner than it looks. For calibration: the 9B recipe for this same GPU declines its own upgrade rung at 2.259 GiB of remaining slack, on the same reasoning. If you run headless and have measured your own buffer sizes, this is the first upgrade to make — see the per-round budget below for what it buys.

IQ4_XS @ 65,536 (19.724 GiB) — declined, and this is the interesting one. It is not merely close to the lead, it is 0.053 GiB cheaper than it. One quant tier buys exactly one context rung here, at slightly negative VRAM cost. The tiebreak is which side of the trade is measured:

  • Quantisation cost is unmeasured. Fara's output is pixel coordinates — the card describes its grounding data as "Curated datasets for predicting actions and pixel coordinates from screenshots, with images, text, and bounding boxes." Nothing public says what any tier costs that faculty. The quantiser's card carries only generic per-quant descriptors (Q4_K_M is "Good quality, default size for most use cases", IQ4_XS is "Decent quality, smaller than Q4_K_S with similar performance"), which is a size ranking, not a grounding evaluation.
  • Context need is bounded and stated. The harness README asks that "context length is set to at least 15000 tokens and temperature to 0 for best results", and the image half is capped outright — the card says "We only keep the most recent 3 screenshots", which at the derived 1,260 tokens each pins images at 3,780 tokens for a whole run.

So the rung being declined is the one whose value is known and modest; the tier being kept is the one whose cost nobody has priced. Q4_K_S @ 65,536 (20.791 GiB, 3.209 left) sits between the two if you want the window and half a tier of insurance.

How much context you actually get, per round

fara-cli defaults to --max_rounds 100argparse sets default=100 in run_fara.py, and both agent entry points carry the same default in their signatures. Images are capped at three; the text trajectory is not capped at all, and it accumulates for the whole run. So the window divides by rounds:

 32,768 - 3,780 image tokens =  28,988 for text / 100 rounds ~   290 tokens per round
 65,536 - 3,780             =  61,756 for text / 100 rounds ~   618 tokens per round

290 tokens per round is a real budget for a model that emits a chain-of-thought block before every action, but it is a budget, not a guarantee — and context shift is off by default (ctx_shift = false in common/common.h), so an overrun is a hard error rather than a silent truncation of the oldest turns. The practical rule on this card: -c 32768 for interactive work you are watching; if you intend a long unattended run, or you watch a run and see long <think> traces, raise to -c 65536 before you start it and accept the 2.222 GiB margin. Do not raise --max_rounds past 100 without raising -c with it.

One coupling worth knowing about, and it is closed either way

This recipe does not pass --flash-attn, which leaves it at its auto default — the right call on a platform where flash-attention support is build-dependent, because src/llama-context.cpp runs a runtime probe under auto and logs either an enabled line or "not supported, set to disabled" rather than failing.

If that probe comes back disabled on your build, llama.cpp takes the explicit branch in src/llama-graph.cpp, which builds kq = ggml_mul_mat(ctx0, k, q) and pins it with ggml_mul_mat_set_prec(kq, GGML_PREC_F32) — the source's own comment reads "this op tends to require high floating point range". That materialises n_kv × n_ubatch × n_head × 4 B of attention scores, and on this model n_head is 24, not the 16 the smaller sizes carry:

32,768 × 512 × 24 × 4 B = 1.500 GiB      (and 3.000 GiB at -c 65536)

At the lead configuration that would take the footprint to 21.278 GiB, still inside 24 — so min_vram_gb: 24 holds whichever way the probe resolves, which is the reason this page can leave the flag alone rather than pinning it. At -c 65536 it would take it to 24.778 GiB and not fit, which is a second, independent reason the shorter window is the default. If you disable flash attention deliberately, halving -ub halves that term at no cost to trajectory length.

Nothing in the tables above is free space you can plan against to the last byte. Read the buffer-size lines llama-server prints on startup for the real figures on your build, and report what you see.

How much context one screenshot costs

A CUA sends a screenshot every step, so the per-image cost decides whether the budget above is honest. It is not a fixed number baked into the projector: llama.cpp treats this one as dynamic-resolution, which is what --image-min-tokens exists to floor.

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 pre-resizes with Qwen's smart_resize at factor = patch_size × merge_size = 32, so 900 becomes 896. llama.cpp counts merged patches the same way in tools/mtmd/clip.cpp, where the PROJECTOR_TYPE_QWEN3VL branch returns (img->nx() / params.patch_size) / 2 per axis:

(1440 / 32) × (896 / 32) = 45 × 28 = 1,260 tokens per screenshot
× 3 retained screenshots            = 3,780 tokens resident

The source config.json's vision_config agrees on the geometry (patch_size: 16, spatial_merge_size: 2, depth: 27, out_hidden_size: 5120), and so does the projector artifact's own header. The harness's conservative default image_token_estimate: int = 1500 sits just above the derived 1,260, a useful sanity check.

Keep that 1,260 in mind for the ROCm troubleshooting below: it means every single agent step submits a prompt-processing batch well over a thousand tokens. This workload never sits in the "small request" regime, which is exactly the regime where one RDNA3 ROCm bug reported working behaviour.

Installation

1. Build llama.cpp with ROCm/HIP for gfx1100

Build from a release at or after b10087 — that is the release the GGUFs used here were quantised on, per the quantiser's card. The gfx target is not optional to get right, and llama.cpp's own build documentation names this card for it: "The above example uses gfx1100 that corresponds to Radeon RX 7900XTX/XT/GRE."

git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
HIPCXX="$(hipconfig -l)/clang" HIP_PATH="$(hipconfig -R)" \
  cmake -S . -B build -DGGML_HIP=ON -DGPU_TARGETS=gfx1100 -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -- -j 16

AMD's ROCm system-requirements table lists the RX 7900 XTX on gfx1100 as a supported RDNA3 target, alongside the RX 7900 XT and RX 7900 GRE. Rather than trust any table, you can ask your own card — llama.cpp documents the self-check: "Find your gpu version string by matching the most significant version information from rocminfo | grep gfx | head -1 | awk '{print $2}' with the list of processors". On an RX 7900 XTX that prints gfx1100.

-DCMAKE_BUILD_TYPE=Release, not Debug — see the first troubleshooting entry; a Debug build has been reported to emit invalid device code on Radeon under some ROCm versions.

Both halves of the model are first-class in mainline — worth confirming before you download eighteen gigabytes:

grep -n 'LLM_ARCH_QWEN35,' src/llama-arch.cpp        # -> { LLM_ARCH_QWEN35, "qwen35" }
grep -n 'qwen3vl_merger' tools/mtmd/clip-impl.h      # -> PROJECTOR_TYPE_QWEN3VL

There is also an official prebuilt image, ghcr.io/ggml-org/llama.cpp:server-rocm, if you would rather not build a toolchain. Read llama.cpp's own caveat before relying on it — "The GPU enabled images are not currently tested by CI beyond being built." — and note that it pins its own ROCm version (ARG ROCM_VERSION=7.2.1 in .devops/rocm.Dockerfile), which is the variable the first troubleshooting entry is about.

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

That is 18.46 GB over the wire, so start it before you read the rest of the page.

Microsoft publishes no first-party GGUF for Fara1.5 — the microsoft org ships Fara1.5-4B, Fara1.5-9B and Fara1.5-27B as safetensors only, plus an ONNX build of the previous-generation Fara-7B, which is a different model. Four independent community quantisers have converted the 27B including its vision tower — bartowski, prithivMLmods, Abiray and DevQuasar each publish a matching mmproj. Four independent conversions succeeding is the strongest available evidence that the vision path converts cleanly; it is still not a runtime test on this backend. Their quant recipes differ, so the budget above applies only to the file it names.

On the two projector files. bartowski publishes the projector twice, -f16 and -bf16, and one of the two preserves the source bytes — but config.json is the wrong place to work out which. Reading the safetensors headers directly, every shipped tensor is BF16, vision tower included; the 27B's config.json in fact declares "dtype": "float32" under both text_config and vision_config while its top-level dtype says bfloat16, and the weights side with the top level. So mmproj-Fara1.5-27B-bf16.gguf is the byte-faithful conversion here and mmproj-…-f16.gguf is a re-encode — one that keeps every bit of bfloat16's 7-bit mantissa (binary16 has 10) and gives up bfloat16's wider exponent range. On this card the bf16 build has the extra argument that RDNA3 is one of two architectures llama.cpp keeps BF16 output for; it is a one-word substitution costing 3,538,944 B — 3.375 MiB, moving the accountable total from 19.778 to 19.781. This recipe still takes the f16 build so the page stays comparable with every other Fara1.5 recipe in the catalogue, and nobody has measured which grounds better.

You can confirm the artifact you just downloaded declares the projector llama.cpp implements:

strings ~/models/fara1.5-27b/mmproj-Fara1.5-27B-f16.gguf | grep -m1 qwen3vl_merger

3. Serve it

./build/bin/llama-server \
  -m ~/models/fara1.5-27b/Fara1.5-27B-Q4_K_M.gguf \
  --mmproj ~/models/fara1.5-27b/mmproj-Fara1.5-27B-f16.gguf \
  -ngl 99 -c 32768 --host 127.0.0.1 --port 5000 \
  --image-min-tokens 1024 \
  --reasoning-format none \
  --temp 0

Three of those flags are load-bearing and none of them is a default:

  • --reasoning-format none — mandatory. Without it the agent loop silently receives nothing; see Troubleshooting for the mechanism. arg.cpp documents this value as "none: leaves thoughts unparsed in message.content".
  • --image-min-tokens 1024 — this projector ships no minimum-pixel key, so llama.cpp falls back to the floor set by hparams.set_limit_image_tokens(8, 4096) in the PROJECTOR_TYPE_QWEN3VL branch of tools/mtmd/clip.cpp, and warns a few lines later in the same branch: "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. At the harness's viewport the image is already 1,260 tokens and the floor never binds; it protects you if you shrink the viewport.
  • -c 32768 — see the budget table. Do not leave it at the model's native 262,144: at Q4_K_M that is 33.778 GiB and will not load.

Do not pass --parallel. Leaving it alone keeps llama-server's unified KV cache — the same n_parallel = -1 sentinel the recurrent-state derivation above hangs on. Setting it explicitly takes n_parallel out of the auto path, leaves kv_unified at its false struct default, and src/llama-context.cpp then computes n_ctx_seq = n_ctx / n_seq_max, dividing your 32,768-token window between slots. --parallel 4 would leave each slot 8,192 tokens, below the harness's documented 15,000-token floor.

Leave --flash-attn alone. See the coupling section above: the auto probe reports its own answer against the backend it actually got, which is the behaviour you want on a platform where that answer is build-dependent, and the budget holds either way it resolves.

If you would rather not manage files, -hf bartowski/Fara1.5-27B-GGUF:Q4_K_M fetches the model, and per the server docs "mmproj is also downloaded automatically if available".

4. Prove the vision path is running, and running on the GPU

This step matters more on AMD than on CUDA, because it is where the open question of this recipe gets answered on your machine in about ten seconds. llama.cpp reports its own multimodal wiring at load. In the startup log, find three things:

  1. A line of the form clip_ctx: CLIP using <backend> backendclip.cpp emits this as "CLIP using %s backend" with the backend's own name. If it says CPU, the projector is not on the GPU at all and image encoding will be slow.
  2. The flash attention line. Either answer is fine — disabled costs memory, not correctness, and the budget above covers both cases.
  3. Whether the warning block "WARNING: the CLIP graph uses unsupported operators by the backend" appears. clip.cpp walks every op in the vision graph after warmup and lists any the chosen backend cannot accelerate, naming the backend. Absence of that block is a direct, first-party statement that the ROCm backend accelerates the whole qwen3vl_merger graph on your card — which is the claim this recipe deliberately does not make on your behalf. If the block appears, it also prints the exact op names and shapes and asks you to file them upstream; please do, and tell us too.

Then check the documented capability and send a real image. The server README instructs clients to check /models or /v1/models for the multimodal capability before a multimodal request: "Clients should check /models or /v1/models for the multimodal capability before a multimodal request." /props is a useful supplement because it names which modality loaded, the signal you want when a projector fails quietly.

curl -s http://127.0.0.1:5000/v1/models | grep -o multimodal

# supplementary — names the modality that actually loaded
curl -s http://127.0.0.1:5000/props | python3 -c "import sys,json; print(json.load(sys.stdin)['modalities'])"
# expect: {'vision': True}

curl -s http://127.0.0.1:5000/v1/chat/completions -H "Content-Type: application/json" -d '{
  "model": "fara",
  "temperature": 0,
  "messages": [{"role": "user", "content": [
    {"type": "image_url", "image_url": {"url": "https://raw.githubusercontent.com/ggml-org/llama.cpp/master/media/llama0-logo.png"}},
    {"type": "text", "text": "Describe this image in one sentence."}
  ]}]
}' | python3 -m json.tool

If the reply describes the picture, the projector is wired up. If it answers as though no image were present, stop here — the agent loop below will silently produce nonsense actions rather than fail loudly.

5. Install the harness

The harness is hardware-neutral: a Python client that speaks to any OpenAI-compatible endpoint over HTTP and drives Playwright locally, with nothing vendor-specific in it. Its documented primary platform is also Linux — the same platform ROCm requires, so there is no impedance mismatch to work around.

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

Skip the README's [vllm] extra. You are hosting with llama-server, and it pulls a large dependency tree you will not use.

Running

Two clients share the same llama-server endpoint. Path A (fara-cli) is the smoke test, with no isolation of any kind. 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's built-in defaults are for a different size: DEFAULT_ENDPOINT_CONFIG in run_fara.py is {"model": "Fara1.5-9B", "base_url": "http://localhost:5000/v1", "api_key": "not-needed"}. The port lines up; the model name does not, so pass it explicitly.

source .venv/bin/activate
fara-cli \
  --base_url http://127.0.0.1:5000/v1 \
  --api_key sk-no-key-required \
  --model fara \
  --task "how many pages does wikipedia have" \
  --output_folder ./runs/first \
  --save_screenshots \
  --headful \
  --max_rounds 15

⚠️ --headful is not a convenience flag. fara-cli defaults to headless: run_fara.py passes headless=not args.headful at both call sites, and its argparse help reads "Run the browser in headful mode (show GUI, default is headless)". Drop the flag and a real Chromium still opens, still clicks and still submits — you simply cannot see it, which makes the supervision advice above impossible to follow. Either way this drives a real, unconfined Chromium on your machine; the danger is identical, only the visibility changes.

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, because it is how you tell a grounding failure (clicks landing in the wrong place) from a reasoning failure. Note the low --max_rounds 15 above: the budget on this page is sized for the default 100, but a first run should be short.

Keep the task on a low-stakes public site and stay signed out. 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 protections the vendor asks for

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. That is also 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 — give it http://127.0.0.1:5000/v1 as the model endpoint. Whichever client you use, match the trained resolution: the card is explicit — "Match this in your sandbox for the most reliable grounding."

Results

  • Speed: omitted — no published measurement exists for Fara1.5-27B on this card, on any AMD GPU, or on any consumer GPU at all. The only hardware the vendor names is datacenter class, tested against its bf16 vLLM serving path, which says nothing about a llama.cpp/HIP build on RDNA3. Borrowing a number from it would be misleading. If you run this, please contribute your numbers so this section can be replaced with a measurement.
  • VRAM usage: 17.193 GiB of weights and projector; 19.778 GiB accountable including the f16 KV cache and the four-slot recurrent state at a 32,768-token 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 HIP context and the vision encoder's working set sit on top, and 4.222 GiB of the card is left for them. See /check/fara1-5-27b/rx-7900-xtx.
  • Quality notes — read this one. Fara's whole job is emitting precise pixel coordinates, and 4-bit quantisation of a grounding model is a real risk, not a formality. No evaluation of how Q4_K_M affects Fara1.5's grounding accuracy exists — not from Microsoft, not from the community. The vendor's published scores for this size (89.3 WebVoyager, 72.3 Online-Mind2Web, 40.2 WebTailBench, from the model card's evaluation table) are for the bf16 model on datacenter hardware; nothing licenses carrying them to a 4-bit build. This is the one thing the 4B and 9B recipes for this same card do not have to say, because on those sizes the BF16 file fits and RDNA3 runs it well. At 27B the file is 50.113 GiB and the caveat comes back.
  • How this card differs from its 16GB RDNA3 sibling. Same instruction-set family, same BF16 favourability, different capacity. Per AMD's ROCm hardware table the RX 7900 XTX has 96 compute units on gfx1100 against the RX 7800 XT's 60 on gfx1101, and 24 GB against 16. At this model size that difference is decisive rather than incremental: 19.778 GiB does not fit a 16 GB board at any quant in the lead's neighbourhood. Throughput will also be higher here on CU count alone, but that is a directional expectation from the specifications rather than a measurement — which is why the Speed line above is empty rather than estimated.
  • What ROCm changes, and what it does not. The architecture is fully registered in mainline llama.cpp and its multimodal layer contains no backend-specific code, so nothing about this model is CUDA-dependent by design. What ROCm changes is the maturity of the stack under ggml — see the first troubleshooting entry, which is a ROCm toolchain issue rather than anything to do with Fara, and which this configuration is more exposed to than the 9B recipe for this card is.
  • Language: English only — the card puts "Languages other than English (training data is English-only)" out of scope.

For the full benchmark data, see /check/fara1-5-27b/rx-7900-xtx.

Troubleshooting

ROCm error: device kernel image is invalid — a ROCm-version trap this configuration sits inside

Worth knowing about before you start, and it has nothing to do with Fara. llama.cpp issue #23934 — opened on an RX 7900 XTX (gfx1100, ROCm 7.11, clang 22, Ubuntu 22.04), since closed — reports that small requests completed fine while larger ones aborted in the HIP runtime.

Read the scoping carefully, because it lands differently here than on the 9B page for this card. The thread's closing comment scopes its own fix to "with quantized models". The 4B and 9B recipes for this GPU lead unquantised BF16 and therefore sit outside that condition; this recipe leads Q4_K_M and sits inside it. The card that reproduced it is exactly this one, and a second reporter hit the same error on an entirely different die using official Windows HIP builds naming no model at all — so the common factor is the ROCm/clang toolchain version, which is die-agnostic. All participants are community accounts and no llama.cpp maintainer engaged. Treat it as a trap to recognise and pre-empt rather than one to hope you miss.

Two community answers, in order:

  1. Build Release, not Debug. A commenter identified an LLVM-backend defect emitting invalid instructions at -O0 on Radeon under that ROCm version, hitting only larger matrix kernels — which is why small requests looked healthy. Use RelWithDebInfo if you need symbols.
  2. Upgrade ROCm. Release alone did not fix it for the reporter, who closed the issue after moving to a newer ROCm and clang plus a current amdgpu DKMS driver.

Why this matters more here than for a chat model: a CUA submits a 1,260-token screenshot on every single step, so this workload is permanently outside the "small request" regime where the bug stays hidden. If you are going to hit it, you will hit it on step one rather than discovering it a week later. A current ROCm is the cheapest insurance on this page.

The agent does nothing — every step logs an empty response or list index out of range

This is the failure everyone hits, and it is not a bug in your setup. Fara opens a <think> block and never closes it. Under llama-server's default reasoning extraction the parser therefore treats the whole output — reasoning and the <tool_call> block together — as reasoning, returns it in reasoning_content, and leaves message.content empty. The harness parses message.content for <tool_call>, finds nothing, and the loop dies on round one.

Launch with --reasoning-format none. The raw output then begins with a stray unclosed <think> tag, which is cosmetic.

This was reported against this exact model — a community user on llama.cpp b10107 with the 27B, in the model's HF discussions, who documents the repro as "default reasoning format → content: \"\", thoughts + <tool_call> block in the reasoning field, finish_reason: stop" against "--reasoning-format none → full raw output in content, parses fine", and adds "If you have a router/proxy in front doing its own reasoning extraction, disable it there too." Microsoft acknowledged it there — swhitehead, whose HF account carries the org-member flag on that repo, replied "Would you mind posting this as an issue on the Fara1.5 github repo so we can mention this in the hosting instructions?" — which became microsoft/fara#82, still open at the time of writing.

That thread is also, incidentally, the best public evidence that this model runs at all under llama-server — though not on this backend, which is what Step 4 is for.

Don't reach for Vulkan to fix the vision half

For text generation on gfx1100 the Vulkan backend is a legitimate thing to A/B against ROCm, and sometimes wins. That advice does not transfer to the projector. llama.cpp issue #20081 reports vision output badly degraded on Vulkan versus CUDA for specific images with exactly this projector — the reporter's log shows projector: qwen3vl_merger under a CLIP using Vulkan0 backend line. Read it as a caution rather than a verdict: the Vulkan backend maintainer could not reproduce it, and the reporter's Vulkan device was an RDNA3 integrated GPU, not this card. Still, if you are going to move the vision half off ROCm, check its output rather than assuming parity. You can move the projector independently of the text model with the MTMD_BACKEND_DEVICE environment variable, which clip.cpp reads to pick a named backend for the CLIP context only.

You do not need HSA_OVERRIDE_GFX_VERSION on this card

It appears in a great many copy-pasted AMD configs, sometimes with a comment claiming it is critical for the 7900 XTX. It is not. The override exists to make ROCm present one gfx target as another, for libraries that shipped kernels only for a different die; gfx1100 is an officially supported target and ships real kernels, and the build in Step 1 compiles llama.cpp for it directly. Setting it on top of a correctly-targeted build asks the runtime to pretend your card is something else while your binary already contains the right code — at best a no-op. If you have inherited it from a shell profile or a prebuilt ROCm image, unset it before diagnosing anything. (It is also, incidentally, why the ROCm vision datapoint cited at the top of this page is weaker than it looks: that run used the override.)

Do not enable MTP speculative decoding

The qwen35 architecture carries a multi-token-prediction layer — the source config.json declares mtp_num_hidden_layers: 1 inside text_config — and llama.cpp can use such a layer as a self-draft. Don't, for now: issue #23244, "Eval bug: Always OOM with mtp model + rocm", collects OOM-on-first-decode reports with MTP enabled, including one naming gfx1100. Note carefully that this is not an AMD bug despite the issue's title — the same thread has reporters hitting it on NVIDIA hardware too, and the suspected cause is the memory-fit estimator undercounting MTP. The issue is closed not planned rather than fixed. Nothing in this recipe enables it, and the GGUF downloaded in Step 2 carries no blk.*.nextn.* tensors for it to attach to in any case.

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 a CLIP using … line, that /v1/models advertises the multimodal capability, and that Step 4's /props call reports vision: True. 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. Note the automatic projector download only happens on the -hf path; with -m you must pass --mmproj yourself.

Clicks land in the wrong place

Check the viewport first: the harness defaults to 1440×900 because that is what the model was mostly trained at, and 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 should you suspect the weights — and unlike the 4B and 9B pages for this card, here that suspicion is live rather than closed off, because this configuration is quantised and nobody has measured what that costs. The rungs above Q4_K_M do not fit this card at a usable window, so the honest test is a bf16-hosted endpoint elsewhere.

Out of memory at load

In order of what to try, with what each actually frees:

ChangeFreesCost
-c 32768163841.000 GiB~126 tokens per round at --max_rounds 100; still clears the 15,000-token floor
--cache-type-k q8_00.469 GiBK-half only; block_q8_0 is 34 B per 32 values against f16's 64, taking the cache from 64 to 49 KiB/token
Q4_K_M → Q4_K_S0.987 GiBhalf a quant tier, on an unmeasured grounding ladder
Q4_K_M → IQ4_XS2.053 GiBa full tier down
--no-mmproj-offload0.864 GiBprojector stays in system RAM; slower image encoding, once per agent turn

Take the context first — it is the cheapest and the best-understood. Do not add --cache-type-v q8_0 to the K-cache line unless the startup log says flash attention is enabled: src/llama-context.cpp rejects a quantised V cache with flash attention disabled outright, logging "quantized V cache requires flash_attn to be enabled" and returning a null context rather than starting — and on this platform whether it is enabled is a runtime probe result, not something you can assume. K-only is legal either way. If you are running a desktop on the same card, the compositor's own allocation is a real term in this budget; a headless box gives you most of a gigabyte back.

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

This is a real open llama.cpp bug (PR #25334, titled "fix: load qwen35 Ollama GGUF exports"), but it belongs to Ollama-exported qwen35 blobs, not to the GGUF this recipe uses. Reading this file's own header, qwen35.rope.dimension_sections is [11, 11, 10, 0] — four entries, the trailing zero llama.cpp expects. If you see this error, you are loading a different file than the one downloaded in Step 2.

ollama run fara does nothing — there is no Ollama entry

There is no Ollama library entry for Fara1.5 in any form: ollama.com/library/fara, /fara1.5 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 the two should never be mixed. That is a slightly bigger loss on AMD than elsewhere, since Ollama is usually the cleanest ROCm on-ramp — but it does not exist for this model, so llama-server built from source is the path this recipe documents.

Running it against your own accounts

Don't. The model is trained to pause before sign-ins, payments and submissions, and that training is a safety net, not a guarantee — the card lists prompt injection embedded in page content among its known risks, and a vision-only agent has no way to distinguish a legitimate button from a hostile one that renders like it. Keep it in a container, on an allow-list, with no credentials in reach.

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

About 24 GB — the minimum this recipe targets.

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

RX 7900 XTX (24 GB).

How hard is this setup?

Advanced — follow the steps above.