self-hosted/ai
§01·recipe · multimodal

Fara1.5-4B on RX 7800 XT: Unquantised Browser Computer-Use Agent on ROCm gfx1101

multimodaladvanced16GB+ VRAMJul 31, 2026

This advanced recipe sets up Fara1.5-4B on the RX 7800 XT, needing about 16 GB of VRAM.

models
tools
prerequisites
  • AMD Radeon RX 7800 XT (16GB VRAM, RDNA3, Navi 32, gfx1101) — an officially ROCm-supported target
  • Linux with a working ROCm install — both ROCm and the Fara harness are Linux-first
  • llama.cpp built with `-DGGML_HIP=ON -DGPU_TARGETS=gfx1101` (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 a mid-range AMD card: Microsoft's Fara1.5-4B served by llama-server built against ROCm/HIP on an RX 7800 XT, 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: RX 7800 XT (16GB VRAM, gfx1101) · BF16 weights + f16 mmproj = 8.472 GiB, 10.668 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.

The vendor asks for protections the fara-cli path does not provide. The harness README says "We recommend running it in a sandboxed environment, monitoring its execution, and avoiding sensitive data or high-risk domains." — but fara-cli itself drives a plain local Playwright Chromium: no container, no allow-list, no pause control. Those controls belong to Magentic-UI / MagenticLite. The same 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", while the model card attributes the Docker-contained browser — "the browser runs in a Docker container with no access to host files or environment variables" — to MagenticLite and then says plainly: "If you integrate Fara1.5-4B directly, you're responsible for these controls."

So if you follow the fara-cli path below, you are the boundary. 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, on the GPU, before you let it drive anything.

⚠️ What is established about this model on ROCm, and what is not. llama.cpp's multimodal layer is backend-agnostic by construction: tools/mtmd/clip.cpp is 260 KB of source containing zero occurrences of cuda, hip, rocm or any backend #ifdef. It asks ggml for whatever GPU backend exists (ggml_backend_init_by_type(GGML_BACKEND_DEVICE_TYPE_GPU, ...)) 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 beyond the vendor-API shim — a VMM/ROCR-Runtime 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. There is no separate "HIP op set"; it is one backend compiled twice.

There is runtime evidence too, and it is on this exact card — but read closely what it is, because it is easy to overstate and the distinction turns out to matter. llama.cpp issue #13694 was opened by a user on an RX 7800 XT whose reproduction describes llama-server with an --mmproj. The log they actually pasted, though, is from llama-mtmd-cli, launched as HSA_OVERRIDE_GFX_VERSION="11.0.0" ./build/bin/llama-mtmd-cli … — so that run's own device line reads Device 0: AMD Radeon RX 7800 XT, gfx1100 (0x1100). The card's native gfx1101 (0x1101) appears in the report's separate --version block, which is not a run. What the log does show unambiguously is clip_ctx: CLIP using ROCm0 backend, with ROCm buffer sizes throughout. So the mtmd projector demonstrably loads and encodes on RX 7800 XT silicon under ROCm — through the gfx1100 ISA under that override, and from the CLI rather than the server. That is real evidence, and it is narrower than the headline suggests; note also that it leans on an override this recipe tells you not to set, which is why the structural argument above carries the weight here and this corroborates it. Separately, in issue #20081 a community reporter writes "ROCm (over RPC) works perfectly fine with the same image" about the qwen3vl_merger projector this model actually uses — though over the RPC backend, on a different card, and as a passing remark rather than a measurement.

Neither report is about this model, and #13694's projector is the older qwen2.5v (PROJECTOR_TYPE_QWEN25VL) rather than qwen3vl_merger — the same clip.cpp case block handles all three, which is why it transfers at all. What nobody has published is a run of Fara1.5-4B 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. Issue #13694 is also worth knowing about for a second reason — it is a grounding-accuracy thread, and its findings are directly useful to a model that clicks on coordinates. See Clicks land in the wrong place.

Requirements

ComponentMinimumThis recipe
GPU16GB VRAM, officially ROCm-supported RDNA3RX 7800 XT (16GB, gfx1101) — not measured; budget below is derived (/contribute)
RAM16GB
Storage9.10 GB (BF16 + mmproj)9.10 GB decimal / 8.472 GiB, plus the harness and Playwright's Chromium
SoftwareLinux, ROCm, llama.cpp b10087+ (HIP), Python 3.10+, Playwright

Two notes on that table. min_vram_gb is 16 because that is what the install documented here actually needs — 10.668 GiB accounted at 65,536 context, on a card that holds 16,384 MiB — not because 16GB is this card's badge. And the GPU cell is not a measurement: /check/fara1-5-4b/rx-7800-xt returns verdict: unknown with zero benchmarks, and nothing on this page was run on hardware by us.

This 16GB card runs the model at the vendor's own precision

The 8GB RTX 3060 Ti recipe for this model has to quantise to Q4_K_M, and it says openly that no evaluation exists for what 4-bit costs a model whose entire job is predicting pixel coordinates. That trade-off does not exist here, and that is the headline of this card. The model card's Requirements section asks for "A GPU with enough memory for a 4B model in bf16 (A6000, A100, H100, and B200 have been tested)". The BF16 GGUF is 7.846 GiB and the projector adds 0.626 GiB — 8.472 GiB of weights against 16 GiB of GDDR6. So a 16GB RDNA3 card runs this model unquantised, at the precision the vendor specifies, with room for a 65,536-token context on top.

That is worth stating plainly because it is easy to assume otherwise: BF16 on a 4B model is normally filed under "24GB card" territory, and for many models it would be. Fara1.5-4B is small enough, and its hybrid attention (below) cheap enough in KV, that a mid-range card clears it.

Why BF16 is specifically favoured on this die, and how we know

llama.cpp names RDNA3 explicitly in its BF16 matrix-multiply path. At ggml-cuda.cu:1511 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 being downgraded to an F32-output fallback. The sibling recipe for the RX 7900 XTX leans on this — but it is a gfx1100 card, and the constant in that macro is written 0x1100. That is not automatically your card, so it is worth following the macro rather than assuming the family label carries.

It does carry, and here is the chain, all in ggml/src/ggml-cuda/common.cuh:

#define GGML_CUDA_CC_RDNA3      (GGML_CUDA_CC_OFFSET_AMD + 0x1100) // RX 7000, minimum for WMMA
#define GGML_CUDA_CC_RDNA3_5    (GGML_CUDA_CC_OFFSET_AMD + 0x1150)
#define GGML_CUDA_CC_IS_RDNA3_0(cc) (cc >= GGML_CUDA_CC_RDNA3 && cc < GGML_CUDA_CC_RDNA3_5)
#define GGML_CUDA_CC_IS_RDNA3_5(cc) (cc >= GGML_CUDA_CC_RDNA3_5 && cc < GGML_CUDA_CC_RDNA4)
#define GGML_CUDA_CC_IS_RDNA3(cc)   (GGML_CUDA_CC_IS_RDNA3_0(cc) || GGML_CUDA_CC_IS_RDNA3_5(cc))

GGML_CUDA_CC_IS_RDNA3 is a half-open range test, not an equality test against 0x1100. And the cc it is fed is parsed straight from the ROCm device name: ggml_cuda_parse_id() in ggml-cuda.cu strips the leading gfx, reads the last two characters as the minor version and the rest as the major, both base-16, then returns OFFSET_AMD + major * 0x100 + minor. For gfx1101 that is major 0x11, minor 0x01, giving OFFSET_AMD + 0x1101 — inside [0x1100, 0x1150), so GGML_CUDA_CC_IS_RDNA3_0 is true and the whole RDNA3 predicate is true.

Conclusion: the BF16 favourability is a property of the RDNA3 range, and Navi 32 sits inside it. The comment on the constant — "RX 7000, minimum for WMMA" — describes the family, and the code matches the comment. RDNA3's WMMA units take FP16 and BF16 natively on this die exactly as they do on gfx1100; the RX 7800 XT simply has 60 CUs and 120 of those units rather than 96 and 192.

Which file, and why

Two file-level choices follow from the artifacts rather than from taste. The text weights are BF16 because the source model is BF16 ("dtype": "bfloat16"), so Fara1.5-4B-bf16.gguf is a file-format conversion with no numeric loss — bartowski's card calls it "Full BF16 weights." The projector is the f16 build, not the bf16 one, on precision grounds: at equal size F16 carries 10 mantissa bits against BF16's 7, and the projector is the stage that turns patch embeddings into the coordinates this model clicks on — the half where you would rather spend precision than range. (Do not justify this from vision_config.dtype: "float32" in config.json. That field does not describe what is in the repo — text_config.dtype says float32 too, while the dtype census over the actual weights is {"BF16": 4539265536}, one dtype across all 723 tensors. The stored weights are BF16 throughout, vision included, which makes mmproj-Fara1.5-4B-bf16.gguf the byte-faithful build and the f16 taken here a deliberate re-encode.)

The VRAM budget, derived

There is no benchmark for this pair, so every number below is arithmetic from the artifacts' own metadata rather than a measurement. The card holds 16,384 MiB, i.e. 16.000 GiB, and the whole derivation is in GiB so the comparison is like-for-like.

Weights — byte counts from the bartowski GGUF tree:

ComponentBytesGiB
Fara1.5-4B-bf16.gguf8,424,393,5687.846
mmproj-Fara1.5-4B-f16.gguf672,423,5840.626
Weights total9,096,817,1528.472

KV cache — and this is where Fara1.5 is unusual, and why a 4B model at full precision fits a mid-range card at all. 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). Reading the GGUF header of the exact file downloaded below gives qwen35.block_count = 32 and qwen35.full_attention_interval = 4, and the source config.json spells the split out layer by layer: its layer_types array is 24 linear_attention entries and only 8 full_attention. So only 8 of 32 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                             = 201 MiB = 0.196 GiB

That last multiplication is easy to miss and it is not optional. llama-model.cpp sizes the recurrent state with std::max((uint32_t) 1, cparams.n_seq_max), and llama-server sets n_parallel = 4 when you do not pass --parallel, so the allocation is four sequences' worth whether or not you use them. It is context-independent, which is what makes it easy to overlook.

Totals on this 16.000 GiB card, BF16 weights throughout:

ContextWeightsKV (f16)Recurrent stateSumNominal slack
32,7688.472 GiB1.000 GiB0.196 GiB9.668 GiB6.33 GiB
65,5368.472 GiB2.000 GiB0.196 GiB10.668 GiB5.33 GiB
131,0728.472 GiB4.000 GiB0.196 GiB12.668 GiB3.33 GiB
262,144 (native)8.472 GiB8.000 GiB0.196 GiB16.668 GiBdoes not fit

This recipe uses 65,536 — four times the harness's documented floor, with a large margin — and on this card that is a decision the arithmetic makes for you rather than a preference. The 262,144 native-context row is out: 16.668 GiB does not fit 16.000 GiB, and that is before a single compute buffer. 131,072 does fit on paper, but 3.33 GiB of nominal slack is thin once you subtract the desktop compositor, the HIP context and the vision encoder's working set — none of which this recipe derives. 65,536 leaves 5.33 GiB and is still far more context than a CUA loop fills: per-round user text is bounded and tiny (a URL, one fixed instruction, and an observation string capped at 1,000 characters for one action type), so the variable term is the model's own reasoning, and at the harness's default --max_rounds there is no mechanism that consumes 65,536 tokens.

The slack column is not 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.

If you would rather have context than precision, this card makes you choose — and the choice is real. Stepping the text weights to Fara1.5-4B-Q8_0.gguf (4.185 GiB) brings the full native 262,144 context into reach: 4.812 GiB of weights + 8.000 GiB of KV + 0.196 GiB = 13.008 GiB. That is a genuine alternative configuration, not a fallback, and it is the trade a 24GB card never has to make. This recipe leads BF16 because the model emits pixel coordinates and no published evaluation measures what quantisation costs grounding accuracy — an unmeasured quality risk beats a measured context ceiling — but if your trajectories are genuinely long, Q8_0 at native context is the defensible other answer. Smaller steps exist too: Fara1.5-4B-Q6_K.gguf is 3.425 GiB and Fara1.5-4B-Q4_K_M.gguf is 2.687 GiB; all are one-word substitutions in Step 2 and Step 3, and none of them change the KV or recurrent-state rows.

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: --image-min-tokens is documented as the "minimum number of tokens each image can take, 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. The projector's own GGUF header gives clip.vision.patch_size = 16 and clip.vision.spatial_merge_size = 2, so Qwen's smart_resize factor is 16 × 2 = 32:

smart_resize:  1440 -> round(1440/32)*32 = 1440
                900 -> round(900/32)*32  =  896

merged token grid, per axis = (pixels / patch_size) / spatial_merge_size
  width   (1440/16)/2 = 90/2 = 45
  height   (896/16)/2 = 56/2 = 28

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. 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. Add the computer_use schema in the system prompt plus accumulated reasoning and action history and the harness's own floor makes sense — the repo asks that you "Please ensure that context length is set to at least 15000 tokens and temperature to 0 for best results."

Keep that 1,260 in mind for the ROCm section 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 gfx1101

Build from a release at or after b10087 — that is the release the GGUFs used here were quantised on.

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=gfx1101 -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -- -j 16

gfx1101 is the whole point of this build line, and it is the one thing people get wrong on this card. llama.cpp's own build documentation only worked examples for other targets — "The above example uses gfx1100 that corresponds to Radeon RX 7900XTX/XT/GRE." — and copying that line onto a 7800 XT builds kernels for the wrong die. AMD's ROCm system-requirements table is explicit and granular: AMD Radeon RX 7800 XT · RDNA3 · gfx1101 · Supported, alongside the RX 7700 XT and RX 7700 on the same target, and the RX 7900 XTX/XT/GRE on gfx1100. Do not confuse it with gfx1102, which is the RX 7600 (Navi 33).

Rather than trust any table, 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 7800 XT that prints gfx1101.

Three things this build deliberately does not do, all of them habits carried over from CUDA recipes:

  • No flash-attn package, no xformers. llama.cpp's attention lives inside ggml; there is no Python attention library to install, and the CK build of the standalone FlashAttention project does not target consumer RDNA3 anyway. The vision tower probes its own flash-attention support against whichever backend it got and reports the result — Step 4 shows you where.
  • No quantisation-kernel extras. No CUDA-only INT4 kernel path applies here and none is needed: BF16 is a first-class ggml type on this architecture, for the reason derived above.
  • -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.

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 (ROCM_VERSION=7.2.1 in .devops/rocm.Dockerfile), which is the variable the first troubleshooting entry is about. Check which targets that image was compiled for before assuming it covers gfx1101; building locally with the flag above removes the question.

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-bf16.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, a different model). Four independent community quantisers have converted the 4B including its vision tower — bartowski, prithivMLmods, DevQuasar and runanywhere all ship an mmproj file, and three of them ship a full-precision text GGUF as well. 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.

3. Serve it

./build/bin/llama-server \
  -m ~/models/fara1.5-4b/Fara1.5-4B-bf16.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 image_min_pixels key, so llama.cpp falls back to its Qwen-VL default floor of 8 tokens and prints a warning: "Qwen-VL models require at minimum 1024 image tokens to function correctly on grounding tasks". Fara is a grounding model — it emits pixel coordinates — so this is precisely the case the warning is about. At the harness's 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. By default llama-server extracts <think> content out of the reply and into a separate reasoning_content field. Fara's chat template opens every assistant turn with <think> and never closes it, so under that default the whole reply — including the <tool_call> block the harness needs — lands in reasoning_content and message.content comes back empty, killing the agent loop on round one. The harness parses the raw text itself, splitting on <tool_call>. none is documented as "none: leaves thoughts unparsed in message.content", which is exactly what the harness expects.
  • -c 65536. See the budget table. On this card the native 262,144 is not merely expensive, it does not fit alongside BF16 weights — do not raise this without re-running the arithmetic.

Do not pass --parallel. Leaving it alone keeps llama-server's unified KV cache; setting it divides your context window between sequences, which is the opposite of what you want at 65,536.

Leave --flash-attn alone. The vision context probes flash-attention support against the backend it actually got and falls back with a warning if the backend cannot serve it, which is the behaviour you want on a platform where that answer is build-dependent.

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

This step matters more on AMD than it does elsewhere, 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. flash attention is enabled or flash attention is disabled. Either is fine. disabled costs memory, not correctness.
  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. A public log shows the older qwen2vl projector selecting ROCm0 on this card (see above); nobody has published the same for qwen3vl_merger on gfx1101, and this check settles it for your build in seconds. 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 send a real image. The server README documents image_url as a first-class content part, accepting a remote URL, base64, or a local path:

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

The first line is the documented capability probe: llama.cpp's server README instructs clients to check /models or /v1/models for the multimodal capability. /props is a useful supplement — it returns a modalities object naming which modality loaded, which is the signal you want when a projector fails quietly.

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: it is a Python client that speaks to any OpenAI-compatible endpoint over HTTP and drives Playwright locally, with nothing vendor-specific anywhere in it. Its documented primary platform is also Linux — the README's Installation section leads with "The following instructions are for Linux systems, see the Windows section below for Windows instructions." — which is 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 [vllm] extra the README offers. You are hosting with llama-server, and it pulls a large dependency tree you will not use.

Running

Two client paths 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 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 \
  --headful \
  --task "how many pages does wikipedia have" \
  --output_folder ./runs/first

Each run writes per-step screenshots and a data_point.json recording the task, actions, observations and outcome to --output_folder. Watch that folder on the first few runs — it is how you tell a grounding failure (clicks landing in the wrong place) from a reasoning failure.

⚠️ --headful is not a convenience flag. fara-cli defaults to headless: run_fara.py passes headless=not args.headful at both call sites (lines 321 and 334), 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 below impossible to follow.

With that flag the command opens a real Chromium on your desktop with no confinement around it — 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 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. 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, on any AMD GPU, or on any consumer GPU at all. The only hardware the vendor names is datacenter class, and borrowing a number from it would be misleading here. If you run this, please contribute your numbers so this section can be replaced with a measurement.
  • VRAM usage: 8.472 GiB of weights; 10.668 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 and vision buffers are additional. See /check/fara1-5-4b/rx-7800-xt.
  • How this card differs from the 24GB RDNA3 sibling. Same architecture, same instruction set, same BF16 favourability — the difference is width and capacity, not capability. Per AMD's own ROCm hardware table, the RX 7800 XT is Navi 32 with 60 compute units (gfx1101) against the RX 7900 XTX's 96 (gfx1100); RDNA3 carries two WMMA units per CU, so 120 against 192. Both a CUA's 1,260-token prompt batches and its token generation will therefore be slower here than on a 7900 XTX. That is a directional expectation from the specifications, not a measurement — neither card has a published number for this model, which is why the Speed line above is empty rather than estimated. The capacity difference is the one with a hard consequence: 16 GiB rules out the 262,144-token native context at BF16, which 24 GiB accommodates.
  • Quality notes: unlike the smaller-card recipes for this model, nothing here is quantised, so the usual "does 4-bit hurt a coordinate-predicting model" caveat does not apply — this is the vendor's own BF16 precision. The vendor's published scores for this size — 80.8 ± 2.3 WebVoyager, 57.3 ± 4.3 Online-Mind2Web, 27.4 ± 2.8 WebTailBench v1.5, averaged over three runs — are therefore at the same precision you are running, which is not something the quantised recipes for this model can say. The paper does not state which hardware produced those evals, so treat the precision match as the only transferable part; nothing about them speaks to an RDNA3 card.
  • 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 Troubleshooting, where the one RDNA3-adjacent failure this workload can actually hit is a ROCm toolchain issue rather than anything to do with Fara.
  • Language: English only, per the model card's out-of-scope list.

For the full benchmark data, see /check/fara1-5-4b/rx-7800-xt.

Troubleshooting

ROCm error: device kernel image is invalid — a ROCm-version trap this workload is shaped to trigger

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), closed 2026-07-02 — reports that small requests completed fine while anything past roughly 200 tokens of batch aborted in the HIP runtime.

Read the scoping carefully in both directions, because it cuts both ways here.

Reasons it may not apply to you: the reporter's card was gfx1100, not this die; and the model that reproduced it was a quantised one (Qwen3.6-27B-Q4_K_M.gguf), with the closing comment scoping its own fix to "with quantized models". This recipe leads unquantised BF16 on gfx1101, so it sits outside both of the conditions the thread names.

Reasons not to dismiss it: a second reporter hit the same error on an entirely different die — an R9700, i.e. RDNA4 — using official Windows HIP builds, and named no model at all. So the failure is not a gfx1100 property; the common factor in the thread is the ROCm/clang toolchain version, which is die-agnostic. All four participants are community accounts (author_association: NONE), none are llama.cpp maintainers, and no maintainer engaged. Treat it as a trap to recognise rather than one to expect.

Two community answers, in order:

  1. Build Release, not Debug. A commenter identified "with ROCm 7.11 on Radeon GPUs where the LLVM backend emits invalid instructions at -O0" as a known ROCm-side defect, 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 later closed the issue with: "If you're hitting ROCm error: device kernel image is invalid on gfx1100 (RX 7900 XTX) with quantized models, this is the fix." — the fix being a move from ROCm 7.11 / clang 22 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. That makes a current ROCm the cheapest insurance on this page.

Don't reach for Vulkan to fix the vision half

For text generation on RDNA3 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 a qwen3vl_merger projector — the reporter's log shows projector: qwen3vl_merger under CLIP using Vulkan0 backend. 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 a discrete 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=11.0.0 on this card

This one is aimed squarely at the RX 7800 XT, because this card is the classic subject of that workaround. HSA_OVERRIDE_GFX_VERSION=11.0.0 makes ROCm present a gfx1101 device as gfx1100, and it exists for libraries that shipped kernels only for the flagship die. You will find it pasted into a great many 7800 XT guides.

It is not hypothetical that people set it on this card: the RX 7800 XT log in issue #13694 shows the same machine reporting Device 0: AMD Radeon RX 7800 XT, gfx1101 (0x1101) in one run and Device 0: AMD Radeon RX 7800 XT, gfx1100 (0x1100) in the next, the difference being HSA_OVERRIDE_GFX_VERSION="11.0.0" on the second command line. That is the override doing exactly what it says — and it also shows why it is not harmless bookkeeping: llama.cpp derives its architecture id by parsing that reported name, so the override silently changes which code path the runtime thinks it is on. (Not the BF16 one, as it happens — 0x1100 and 0x1101 both fall inside the RDNA3 range derived earlier, so that particular favour survives the masquerade. Kernel target selection does not.)

You do not need it here. AMD's system-requirements table lists the RX 7800 XT as an officially supported gfx1101 target, ROCm ships real kernels for it, and the build in Step 1 compiles llama.cpp for gfx1101 directly. Setting the override 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, at worst a confusing mismatch when something does go wrong. If you have inherited it from a shell profile or a prebuilt ROCm image, unset it before diagnosing anything.

Do not enable MTP speculative decoding

The qwen35 architecture carries a multi-token-prediction layer (mtp_num_hidden_layers: 1 in this model's config.json), and llama.cpp can use it as a self-draft with --spec-type draft-mtp. Don't, for now: issue #23244 collects OOM-on-first-decode reports with MTP enabled, including one on RDNA3. 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, but it was closed by a staleness bot rather than by a fix, so treat the advice as current. Nothing in this recipe enables it. On a 16GB card with 5.33 GiB of nominal slack you have less room to absorb an estimator error than the 24GB sibling does, which is one more reason to leave it off.

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, 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 in the wrong place

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. On this card there is no third suspect to reach for on the precision side — you are already running unquantised weights, which is the step a smaller-VRAM install has to hold in reserve.

If you are building your own client rather than using fara-cli, there is a fourth suspect worth knowing, and it is the most interesting thing in llama.cpp issue #13694 — the long-running investigation into Qwen-VL bounding boxes landing "all over the place", opened, as it happens, on an RX 7800 XT. Message ordering changed the answer. After months of debugging, one investigator reported: "I managed to make transformers' output match vllm's accurate output just by moving image BEFORE the text", summarising it as "If image is given before text, vllm and transformers both give very accurate result" — and confirmed llama.cpp then agreed. Two things follow.

First, the good news: Fara's own harness already does this. fara15_agent.py builds every turn's content as the image object followed by the text prompt, at both of its call sites, so the fara-cli path in this recipe produces the ordering that grounds accurately. If you hand-roll a client and put your instructions before the screenshot, you may quietly get worse coordinates with no error anywhere.

Second, the scoping. That thread is about qwen2vl-architecture models, not this one, and its root cause was traced to M-RoPE position handling in llama.cpp's batching — an architecture-level defect that reproduced in transformers and vLLM too, i.e. not an AMD or ROCm problem, and not something the card did. Two community pull requests for it were closed unmerged; the accuracy work that did land came from maintainers, including #16842 on QwenVL image pre-processing sizes — which is the same lever --image-min-tokens exposes to you. The issue closed in October 2025, long before the b10087 build this recipe pins. Treat it as background for why the flags above matter, not as an open bug you are inheriting.

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. Reading the header of Fara1.5-4B-bf16.gguf directly gives qwen35.rope.dimension_sections = [11, 11, 10, 0] — four entries — so this file cannot hit that failure. 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: fara1.5, fara, fara1-5, fara1.5-4b 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. Likewise, the "run them in your choice of tools" list on the quantiser's card is that quantiser's standard boilerplate, not a statement that any of those runtimes has tested this model. llama.cpp built from source is the path this recipe documents. 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 it is.

Out of memory at load

You have fewer levers here than a 24GB card, so use them in this order: drop to -c 32768 (saves 1.0 GiB of KV, still far above the harness's 15,000-token floor), then step the text weights down to Fara1.5-4B-Q8_0.gguf (saves 3.66 GiB), then Fara1.5-4B-Q6_K.gguf (saves a further 0.76 GiB). --no-mmproj-offload keeps the vision projector in system RAM and saves 0.626 GiB at the cost of slower image encoding — reach for it last, and prefer it over swapping in a q8_0 projector: at 0.626 GiB the f16 file is not where the savings are, and shrinking it trades vision fidelity in a model whose entire job is looking at pixels. 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.

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

About 16 GB — the minimum this recipe targets.

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

RX 7800 XT (16 GB).

How hard is this setup?

Advanced — follow the steps above.