self-hosted/ai
§01·recipe · llm

Nanbeige4.2-3B on RX 7800 XT: 96K context on a 16 GB gfx1101 ROCm build

llmintermediate16GB+ VRAMAug 10, 2026

This intermediate recipe sets up Nanbeige4.2 3B on the RX 7800 XT, needing about 16 GB of VRAM.

models
tools
prerequisites
  • AMD Radeon RX 7800 XT (16 GB VRAM), ISA target gfx1101 — not the gfx1100 of the 7900 series
  • Linux with ROCm installed — HIP 6.1 is llama.cpp's hard build floor, and AMD supports this board on four distributions only
  • llama.cpp built from source with -DGGML_HIP=ON; b10153 or newer for the nanbeige architecture
  • ~4.5 GB of disk for the Q8_0 GGUF

What You'll Build

A llama-server endpoint running Nanbeige4.2-3B on an RX 7800 XT with a 98,304-token window and near-lossless quantization on both axes — Q8_0 weights and a q8_0 KV cache — for 13.588 GiB accounted inside a card that has 16 GiB to give, on a HIP build of llama.cpp targeting gfx1101.

Hardware data: RX 7800 XT (16 GB VRAM) · Q8_0 weights 4.130 GiB + q8_0 KV at 98,304 tokens 8.766 GiB + reserved logits 0.317 GiB + FlashAttention dequant scratch 0.375 GiB = 13.588 GiB derived · no benchmark submitted yet · See benchmark data

⚠️ The 3B's weights are not the constraint here — the KV cache is. Nanbeige4.2-3B is a Looped Transformer: the model card says its "Looped Transformer architecture reuses the transformer layers to increase model capacity without adding parameters." config.json sets num_loops: 2, so its 22 blocks execute twice per forward pass over one shared set of weights — and each pass keeps its own keys and values. llama.cpp therefore allocates KV for 44 logical layers, and every context figure you would compute from num_hidden_layers: 22 is exactly half the truth.

ℹ️ This recipe is in two halves and they fail independently. The platform half — ROCm, the HIP build, which llama.cpp flags mean what on this backend — is common to RDNA3 Radeons and needs only the target string changed. The memory half is arithmetic against 16 GB and shares nothing with the 24 GB Radeon page beyond the per-token rates. In particular, this card does not reach the model's declared 262,144-token context in VRAM, and the section below shows why rather than asserting it.

Requirements

ComponentMinimumThis recipe
GPU16 GB VRAM, ROCm-supported AMD GPU— not measured; the budget below is derived from file bytes and llama.cpp's allocation rules (/contribute)
RAM8 GB system RAM
Storage4.43 GB for the Q8_0 GGUF (decimal, as HuggingFace lists it)4,434,787,488 bytes from the HF tree API; ~15 GB more if you also clone and build llama.cpp against ROCm
SoftwareLinux, ROCm ≥ 6.1, llama.cpp b10153+, CMake

The gfx target is per-board, and this board is not gfx1100

Everything in the install hangs off one string, and it is the single thing you cannot copy from a 7900-series recipe. AMD's ROCm install-on-linux system-requirements matrix has one row per board with its LLVM target and support status, and the row reads:

GPUArchitectureLLVM targetSupport
AMD Radeon RX 7800 XTRDNA3gfx1101

The RX 7900 XTX, XT and GRE are gfx1100 on the same page; gfx1101 covers this card together with the RX 7700 XT, the RX 7700, the Radeon PRO W7700 and the PRO V710. Both are RDNA3, so everything about the compute path below is shared — but the compiler needs the right one.

Two consequences before you type anything.

  • Read the target off AMD's per-board matrix, not off a series-level compatibility page. llama.cpp's build guide links the LLVM AMDGPU processor list for target ids, and that table does agree — its gfx1101 row names the Radeon RX 7800 XT, RX 7700 XT and RX 7700 in its own product cell. It is a compiler-backend reference, though, not a support matrix: its board column is headed "Example Products", its OS-support column defers explicitly to AMD's runtime release notes, and the gfx1101 cell in that column is empty. Confirm a target id against it if you like; do not conclude anything about ROCm support from it.
  • This board's supported-OS list is shorter than the page's general list. The matrix footnotes the consumer Radeon rows, including this one, with: "only support Ubuntu 24.04.4, Ubuntu 22.04.5, RHEL 10.1, and RHEL 9.7." The distribution table further down that page is broader than what applies to your card. Read the footnote attached to your row.
  • Do not set HSA_OVERRIDE_GFX_VERSION. That variable exists to make an unsupported card masquerade as a supported one; docs/build.md introduces it with "If your GPU is not officially supported". gfx1101 is officially supported, so setting it can only mislead the runtime.

Confirm your own card rather than trusting this paragraph:

rocminfo | grep gfx | head -1 | awk '{print $2}'
# expect: gfx1101

The KV cache doubles, and that is not a bug

1. The vendor designed it that way. config.json carries "num_loops": 2 alongside "num_hidden_layers": 22. A Nanbeige team member, replying on the model's own discussion #18 (leran1995, flagged as an org member): "we did try sharing the KV cache across loop passes, but it noticeably hurt performance, so we kept the full cache in Nanbeige4.2". Cache sharing was built, measured and rejected — the doubling is the design, not a llama.cpp artefact.

2. llama.cpp implements it that way. src/models/nanbeige.cpp expands the logical layer count before anything is allocated — hparams.n_layer_all = (uint32_t) ((size_t) n_layer_phys * (size_t) n_loops); under a comment reading "Expand logical layer count before load_tensors() allocates layers / KV." — then aliases the layer structs across loops under a second comment, "Share physical weights across loops; each slot still has its own KV index." src/llama-kv-cache.cpp sizes the cache with const uint32_t n_layer = hparams.n_layer_all;. For nanbeige with num_loops: 2 that is 44, not 22.

3. Your file has to carry the flag. The loader reads the key as optional with a default of 1uint32_t n_loops_u = 1; ml.get_key(LLM_KV_NUM_LOOPS, n_loops_u, false); — so a GGUF converted by a path that omits it loads happily and runs 22 layers. That is a silently different model, not an error. Check the file you downloaded:

# run from your llama.cpp checkout (see Installation step 2)
pip install -U huggingface_hub numpy
python3 gguf-py/gguf/scripts/gguf_dump.py --no-tensors \
  ../nanbeige4.2-3b/Nanbeige_Nanbeige4.2-3B-Q8_0.gguf | grep -E "num_loops|block_count"

numpy is not a dependency of huggingface_hub, so install both. Two shorter-looking commands do not substitute: llama-gguf <file> r n prints key names only, and piping llama-cli into grep shows nothing because tools/cli/cli.cpp sets params.verbosity = LOG_LEVEL_ERROR; before the metadata is printed. At runtime, llama_kv_cache prints the layer count in its startup line — confirm 44 layers before trusting anything below.

KV cost per token

config.json gives num_key_value_heads: 8 and head_dim: 128, so each logical layer stores 8 × 128 = 1024 elements for K and 1024 for V per token. Over 44 logical layers that is 90,112 elements per token. Per-element cost comes from the block layouts in ggml/src/ggml-common.h, where QK4_0 and QK8_0 are both 32 values per block:

Cache typeBytes per elementBytes per token
f16 (default)2180,224
q8_034/32 = 1.062595,744
q4_018/32 = 0.562550,688

Multiplied out, this is the whole cache bill:

Contextf16 KVq8_0 KVq4_0 KV
32,7685.500 GiB2.922 GiB1.547 GiB
49,1528.250 GiB4.383 GiB2.320 GiB
65,53611.000 GiB5.844 GiB3.094 GiB
98,30416.500 GiB8.766 GiB4.641 GiB
114,68819.250 GiB10.227 GiB5.414 GiB
131,07222.000 GiB11.688 GiB6.188 GiB
196,60833.000 GiB17.531 GiB9.281 GiB
262,14444.000 GiB23.375 GiB12.375 GiB

What is not the cache, and how much VRAM to actually budget

Two more terms sit on top of weights + KV. Neither is AMD-specific — both are backend-agnostic code that compiles into the HIP build unchanged — but at 16 GB both matter.

A flat 0.317 GiB of reserved logits. llama.cpp sizes one worst-case graph at startup, and src/llama-context.cpp sets the logit rows that graph must hold to n_outputs_pp = std::min(n_tokens, cparams.n_outputs_max), where n_tokens is itself std::min(cparams.n_ctx, cparams.n_ubatch) and n_outputs_max defaults to n_batch. At stock settings that is 512 rows regardless of -c, each a full f32 distribution over this model's 166,144-token vocabulary:

512 rows × 166,144 vocab × 4 B = 340,262,912 B = 324.5 MiB = 0.317 GiB

It is large only because the vocabulary is, and the dial that moves it is -ub, not -c — so it is the same 0.317 GiB in every row of the ladder below.

4,096 bytes per context token of FlashAttention dequant scratch — but only on a quantized cache. The kernels read f16, so a quantized cache is expanded into scratch VRAM first, and fattn-common.cuh appends that copy to the attention output tensor's own allocation:

    if (need_f16_K && K->type != GGML_TYPE_F16) {
        data.end = GGML_PAD(data.end, 128);
        data.K   = data.end;
        data.end += ggml_nelements(K)*ggml_type_size(GGML_TYPE_F16);
    }

with a matching block for V. This applies on ROCm, because the HIP backend is the same ggml-cuda tree compiled a second timeggml/src/ggml-hip/CMakeLists.txt globs ../ggml-cuda/*.cu rather than carrying its own kernels. The guard's second conjunct means an f16 cache pays exactly zero: the only place on this page where the default cache is the cheaper one.

Which kernel gets picked decides whether the term is charged, and the answer must come from the reserve graph rather than from what happens at decode. ggml/src/ggml-cuda/fattn.cu sets need_f16_K = true; need_f16_V = true; for the TILE and MMA_F16 kernels; the direct-read vector kernel needs no scratch but is only reachable at Q->ne[1] <= 2. llama.cpp reserves at n_tokens = min(n_ctx, n_ubatch) = 512, which fails that test on every path, and on RDNA3 amd_wmma_available(cc) is true (ggml/src/ggml-cuda/common.cuh defines GGML_CUDA_CC_RDNA3 with the comment "RX 7000, minimum for WMMA", and the macro range covers gfx1101 as well as gfx1100) — so a 512-row reserve takes the WMMA MMA_F16 path and the scratch is budgeted. You cannot dodge this term by shrinking -ub; you dodge it only by not quantizing the cache.

So the per-token price that actually governs a quantized cache is not the number in the table above but that number plus 4,096:

Cache typeKV bytes/token+ dequant scratchEffective
f16180,2240180,224
q8_095,7444,09699,840
q4_050,6884,09654,784

The budget rule for this card: treat 14.5 GiB as the accounted ceiling on 16 GiB. That reserves roughly 1.5 GiB for the HIP context, the per-microbatch activation working set, ROCm's allocation granularity, and the framebuffer if this card also drives your displays — a Radeon in a desktop usually does. None of those is measured on this GPU, which is exactly why the lead configuration below lands at 13.588 GiB instead of pressing the ceiling.

Separately, budget system RAM for the attention mask, which is deliberately absent from every VRAM figure on this page: src/llama-graph.cpp carries GGML_ASSERT(ggml_backend_buffer_is_host(self_kq_mask->buffer));, so it is a host allocation on the HIP backend exactly as on CUDA. At n_kv × n_ubatch × 2 B that is 96.0 MiB at 98,304 tokens and 192.0 MiB at 196,608 — comfortably inside the 8 GB of system RAM the Requirements table asks for. Counting it as VRAM is a common way to over-budget this model.

The 16 GB ladder

Every cell is the largest multiple of 16,384 tokens whose weights + KV + the flat 0.317 GiB logits reservation + the dequant scratch stay under 14.5 GiB accounted. Each cell reads context · GiB accounted, so what a configuration leaves unspent is visible rather than implied. Weight bytes are the bartowski repo's throughout, via the HuggingFace tree API.

Weightsf16 KVq8_0 KVq4_0 KV
Q4_K_M (2.500 GiB)65,536 · 13.817114,688 · 13.481212,992 · 13.684
Q5_K_M (2.865 GiB)65,536 · 14.182114,688 · 13.846212,992 · 14.049
Q6_K (3.349 GiB)49,152 · 11.916114,688 · 14.330196,608 · 13.697
Q8_0 (4.130 GiB)49,152 · 12.69798,304 · 13.588196,608 · 14.478

The cell that looks like an error is not one. Q6_K with an f16 cache stops at 49,152 and leaves 2.584 GiB unspent, because the next rung — 65,536 tokens of f16 — comes to 14.666 GiB and is over the ceiling; the exact maximum there is 64,549 tokens. One weight rung lighter, Q5_K_M reaches 65,536 with 0.318 GiB to spare. That cliff is what an unquantized cache costs at 180,224 bytes per token.

Two things fall out of the table.

f16 KV becomes affordable at 16 GB — at 65,536 tokens, and only on the two lightest weight rungs. That is worth naming: the untouched default cache is what puts small cards out of this model's game, and 65,536 is exactly the max-new-tokens the model card recommends for agentic and tool-use work. A reader who wants to type -ngl 99 -c 65536 and no cache flags can now do that here.

And you should still not do it. Set the two candidates side by side with every term counted:

WeightsKVLogitsDequantTotal
Q4_K_M + f16 @ 65,5362.50011.0000.3170.00013.817 GiB
Q8_0 + q8_0 @ 98,3044.1308.7660.3170.37513.588 GiB

The quantized-cache configuration wins by 0.229 GiB — nearly a wash. What it actually buys, at the same ~13.6 GiB, is 1.5× the context window and a near-lossless weight tier instead of a 4-bit one. That is a good trade, but it is a trade rather than a windfall, and anyone who budgets q8_0 at its sticker 95,744 bytes per token will be about 0.375 GiB short at this context.

Why the declared 262,144 context is not on this page

The model card says "The model supports a context length of up to 262,144 tokens (256K).", and a 24 GB Radeon holds all of it. This card does not, and the gap is not marginal:

Configuration at 262,144 ctxAccounted totalOn a 16 GiB card
Q8_0 · q4_0 KV17.822 GiBover capacity by 1.822 GiB
Q4_K_M · q4_0 KV16.192 GiBover capacity by 0.192 GiB
Q2_K · q4_0 KV15.414 GiBfits on paper, 0.586 GiB of slack

The only arithmetic that closes is 2-bit weights with less than 0.6 GiB left for the HIP context and everything else unmeasured — on a model whose pitch is agentic and reasoning accuracy at 3B. This page does not offer it. If you need the whole 262,144 tokens on this card, --no-kv-offload puts the cache in system RAM and keeps the weights on the GPU; that is a real option and a much slower one, and no measurement of it exists on any AMD card.

The long-context alternative that does fit

q4_0 on both axes reaches 196,608 tokens — 75% of the declared context — at 14.478 GiB accounted:

ConfigurationWeightsKVLogitsFA scratchAccounted totalSlack on 16 GiB
Q8_0 · q8_0 KV · 98,304 ctx (this recipe)4.1308.7660.3170.37513.588 GiB2.412 GiB
Q8_0 · q4_0 KV · 196,608 ctx4.1309.2810.3170.75014.478 GiB1.522 GiB
Q8_0 · q4_0 KV · 131,072 ctx4.1306.1880.3170.50011.135 GiB4.865 GiB
Q8_0 · f16 KV · 49,152 ctx4.1308.2500.3170.00012.697 GiB3.303 GiB

That second row sits on the 14.5 GiB ceiling rather than under it, and it drops the K cache to 4 bits. Take it if long context is the point of your workload and the card is not also driving a desktop; take the 131,072 row if it is.

Installation

1. Install ROCm

Follow AMD's ROCm quick start for Linux for your distribution — but check the footnote on your board's row in the system-requirements matrix first, because this card's supported-OS list is a four-entry subset of the page's general table.

The version floor is not advisory. ggml/src/ggml-hip/CMakeLists.txt hard-stops the build:

if (${hip_VERSION} VERSION_LESS 6.1)
    message(FATAL_ERROR "At least ROCM/HIP V6.1 is required")
endif()

Verify before you go further:

hipconfig --version
rocminfo | grep gfx | head -1 | awk '{print $2}'

2. Build llama.cpp for gfx1101

Mainline support for this architecture arrived in PR #25994, merged 2026-07-27 as commit b77d646751d01c0962bc203b6809e9d94f7d50b7; release b10153 carries exactly that commit, which is why every source link on this page is pinned to that tag. The model card still tells you to clone the vendor's own nanbeige42 fork; that was correct at release and is not any more.

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

That is docs/build.md's own Linux invocation with the target set to this board — the example there ships gfx1030, so the substitution is the whole edit. GPU_TARGETS is optional, and build.md notes that "omitting it will build the code for all GPUs in the current system" — but naming it keeps the compile to one ISA and makes the binary's target explicit.

Note what you are not doing. There is no flash-attn pip step: llama.cpp's FlashAttention is its own HIP kernel set, compiled by the command above, and GGML_CUDA_FA defaults to ON in ggml/CMakeLists.txt. There is no bitsandbytes, no ExLlamaV2 and no FP8 or FP4 weight path — GGUF through this HIP build is the route.

3. Download the weights

Nanbeige publishes no first-party GGUF — enumerating the org returns ten repositories and none carries the gguf tag; its quantized releases are FP8 and GPTQ-Int8. A team member said on discussion #1 that official quantized versions were being prepared, which has not happened yet. Use a community conversion:

pip install -U huggingface_hub

hf download bartowski/Nanbeige_Nanbeige4.2-3B-GGUF \
  --include "Nanbeige_Nanbeige4.2-3B-Q8_0.gguf" \
  --local-dir ./nanbeige4.2-3b

Check provenance on whichever repo you pick, because this model has a date cliff: mainline support merged on 2026-07-27, hours after the canonical repo landed a tokenizer commit the same day, and a quant is a frozen copy of upstream as of the moment it was uploaded. bartowski's card states the quants were built with llama.cpp release b10159, and the HuggingFace API gives that repo createdAt 2026-07-28T14:38Z — comfortably after both events. owao's repo has an earlier createdAt (2026-07-21T20:33Z) but a lastModified of 2026-07-29T13:06Z, so check the individual file's commit before assuming a given rung there is post-merge. Their bytes are not interchangeable either: owao's Q8_0 is 4,434,787,168 B (320 apart, same to three decimals) but its Q4_K_M is a different file at 2,574,807,904 B against bartowski's 2,684,023,968 B. Every figure on this page is bartowski's; do not mix the two in one sum.

Running

The lead configuration

./build/bin/llama-server \
  -m ./nanbeige4.2-3b/Nanbeige_Nanbeige4.2-3B-Q8_0.gguf \
  --host 127.0.0.1 --port 8080 \
  -ngl 99 \
  -c 98304 \
  -fa on \
  -ctk q8_0 -ctv q8_0 \
  --temp 0.6 --top-p 0.95 --top-k 20

13.588 GiB accounted, 2.412 GiB left on the card. Four flags are load-bearing, and it matters which of them are about the runtime rather than the backend, because only the runtime ones are the same advice you would give a CUDA reader:

  • -c 98304 — set it explicitly, and never -c 0. Omitting -c is not the same as passing 0: with -c unset, llama.cpp's -fit on default (common/fit.cpp) interpolates the context down until the model fits free memory with a margin, so you silently get some arbitrary reduced number rather than the one you budgeted. With -c 0, common/arg.cpp sets params.fit_params_min_ctx = UINT32_MAX; — a deliberate "give me the full trained context, do not shrink it" — and on this card that means 262,144 cells of f16 cache, 44.000 GiB, an immediate out-of-memory abort. Backend-independent argument parsing; it reads identically on ROCm.
  • -fa on. A quantized V cache requires FlashAttention; src/llama-context.cpp throws quantized V cache was requested, but this requires Flash Attention otherwise. The auto default will enable it for you, but being explicit makes the failure mode legible.
  • -ctk q8_0 -ctv q8_0 — matched types, and this one really is a backend question, with the same answer. A stock build instantiates FlashAttention kernels only for identical K and V types, and the restriction is not inherited from the CUDA side by accident: the guard in fattn.cu is #ifndef GGML_CUDA_FA_ALL_QUANTS / if (K->type != V->type) { / return BEST_FATTN_KERNEL_NONE; with no backend condition on it, and ggml/src/ggml-hip/CMakeLists.txt carries its own copy of the same option, whose default branch compiles exactly four vector instances: f16-f16, q4_0-q4_0, q8_0-q8_0, bf16-bf16. A clever-looking -ctk q8_0 -ctv q4_0 needs a rebuild with -DGGML_CUDA_FA_ALL_QUANTS=ON — the HIP build file reads the same variable name, so the escape hatch transfers too.
  • Leave --parallel alone. llama-server defaults it to -1, and tools/server/server.cpp resolves the sentinel with params.n_parallel = 4; and params.kv_unified = true; in the same branch. Unified means one shared cache of -c cells that any single slot may consume in full, so the tables above are the whole KV bill and one conversation can still reach 98,304. Passing -np 4 explicitly skips that branch, leaves kv_unified false, and src/llama-context.cpp then computes cparams.n_ctx_seq = cparams.n_ctx / cparams.n_seq_max; — same total memory, a quarter of the context per conversation. If you want one slot, say -np 1, not -np 4. Also runtime, not backend.

The long-context alternative

./build/bin/llama-server \
  -m ./nanbeige4.2-3b/Nanbeige_Nanbeige4.2-3B-Q8_0.gguf \
  --host 127.0.0.1 --port 8080 \
  -ngl 99 \
  -c 196608 \
  -fa on \
  -ctk q4_0 -ctv q4_0 \
  --temp 0.6 --top-p 0.95 --top-k 20

14.478 GiB accounted, 1.522 GiB left — the tightest configuration this page will recommend, and only on a headless card. Drop to -c 131072 for 11.135 GiB if the Radeon is also driving your monitors.

For agentic and tool-use work the model card's sampling table recommends --temp 1.0 with 65,536 new tokens; for reasoning and chat, 0.6 with 131,072.

preserve_thinking is a real decision on this card

The chat template takes preserve_thinking, which controls whether reasoning from earlier assistant turns stays in the context. A Nanbeige team member on discussion #9: "In our evaluations, enabling preserve_thinking generally provides better performance and better kv-cache reuse, so we recommend keeping it enabled when context length and memory allow." Note the conditional at the end. On a 24 GB card at 262,144 tokens it is free; here the window is 98,304 and retained reasoning consumes it, so leave it on by default and turn it off for long multi-turn agentic sessions that start truncating. Pass it through llama-server's OpenAI-compatible endpoint as "chat_template_kwargs": {"preserve_thinking": true}.

Environment variables: two to know, one not to set

  • HIP_VISIBLE_DEVICES selects which GPU the process sees — the documented way to pin a card in a multi-GPU box.
  • GGML_CUDA_ENABLE_UNIFIED_MEMORY=1 exists but is for integrated graphics. docs/build.md is explicit: "However, this hurts performance for non-integrated GPUs". Leave it unset on a discrete Radeon.
  • HSA_OVERRIDE_GFX_VERSION — do not set it, per the gfx-target section above.

Results

  • Speed: omitted. There is no measurement of this model on any AMD GPU. I searched: /check/nanbeige4-2-3b/rx-7800-xt (zero benchmarks); all 28 discussion threads on the canonical HuggingFace repo, fetched individually and grepped as one corpus — the strings amd, rocm, radeon, gfx, 7800 and 7900 appear nowhere except once as the CPU ISA "AMD64" in a build banner; and llama.cpp's issue and PR search, where nanbeige returns 5 items (so the repo is indexed) and nanbeige crossed with gfx1101 returns none. The only throughput figures that exist for this model anywhere are a community report on an 8 GB NVIDIA card, posted by thermi6 on discussion #18 — different vendor, different capacity — and interpolating from them would be inventing a number. If you run this, please contribute your measurement so the next reader gets a real one.
  • VRAM usage: 13.588 GiB accounted at the lead configuration (4.130 GiB weights + 8.766 GiB cache + 0.317 GiB reserved logits + 0.375 GiB FlashAttention dequant scratch), leaving 2.412 GiB of the card's 16 GiB; 14.478 GiB at the 196,608-token alternative. Both derived, not measured — see /check/nanbeige4-2-3b/rx-7800-xt. Separately, budget ~96 MiB of system RAM for the pinned attention mask at this context.
  • Quality notes: I found no evaluation of this model under a quantized KV cache at any tier — not in the model card, not in the 28 canonical discussions, not in either GGUF repo's card, not in llama.cpp's tracker. Treat that as a gap, not as evidence the loss is small. q8_0 cache is generally treated as near-lossless across the llama.cpp ecosystem, which is why it is the lead here rather than q4_0; the 4-bit rung is the aggressive one and it is what the long-context alternative spends to reach 196,608. The vendor publishes no throughput figures of its own at any hardware tier — the card carries quality benchmarks and a sampling table and nothing about speed.
  • Context is trained, not extrapolated: rope_scaling is null in config.json, so the model's 262,144 figure is a training target rather than a RoPE-scaling claim — which is why this page frames 98,304 as a card limit rather than a model one.

For the full benchmark data, see /check/nanbeige4-2-3b/rx-7800-xt.

Troubleshooting

llama_model_load: error loading model architecture: unknown model architecture: 'nanbeige'

Your build predates b10153. Rebuild from mainline master, or use a release tag at or above b10153 — that build's commit is the merge of PR #25994. On discussion #23 one user reports still being unable to load the architecture and a second reports that a later mainline release loads it. Distribution channels lag: the model card itself notes that LM Studio's bundled llama-server does not support nanbeige.

clang: error: cannot find ROCm device library

A ROCm install-layout problem, not a llama.cpp one, and docs/build.md documents the fix: find the directory under HIP_PATH containing oclc_abi_version_400.bc and prepend HIP_DEVICE_LIB_PATH=<that directory> to the cmake command.

VRAM climbs during a long session, or the process OOMs well below the budget above

This is the residual ROCm allocator problem, and on a 16 GB card there is less room to absorb it than on a 24 GB one. llama.cpp defines GGML_USE_VMM on HIP builds unless you pass -DGGML_HIP_NO_VMM=ON, and the startup banner prints VMM: yes or VMM: no per device — but a llama.cpp contributor states on issue #22107 that "virtual memory is completely broken in rocm since ROCM 7.0 with zero movement from AMD", tracking it at ROCm/rocm-systems#2516, which is still open. Without working VMM the runtime falls back to a pool that keeps buffers at their peak size, and that issue's report is of the pool growing until it faults.

Two things bound your exposure, and one of them is the reason this page can lead with a quantized cache at all. The FlashAttention dequant temporaries — historically the worst offender, because they scale with context — are not pool allocations at b10153 and later: ggml_cuda_flash_attn_ext_get_f16_extra_data takes them from the destination tensor's own allocation, and ggml_cuda_pool_alloc is used in that function's callers only for KV_max, dst_tmp and dst_tmp_meta. That is the 0.375 GiB already counted in the budget, not an unbounded leak. And -DGGML_HIP_NO_VMM=ON at build time is the supported switch if you want to take the pool out of the picture and compare. If you see growth, record it and file it: a real gfx1101 trace is worth more than this paragraph.

Out of memory at startup with the default cache type

You almost certainly left -ctk/-ctv at f16. At 98,304 tokens that alone is 16.500 GiB of cache — more than the whole card — and the default -c behaviour makes it worse rather than better. Either pass the cache-type flags as shown, or stay at -c 49152, where f16 fits at 12.697 GiB accounted and pays no dequant scratch at all.

Mismatched -ctk / -ctv silently disables FlashAttention

See the flag notes under Running. A stock HIP build compiles only the four matched vector instances; an asymmetric cache needs -DGGML_CUDA_FA_ALL_QUANTS=ON. The symptom is not an error message — the dispatcher just returns BEST_FATTN_KERNEL_NONE and you lose the kernel you were counting on.

Tool calls come back as plain text instead of executing

Fixed upstream in release tag b10227, and nothing about it was AMD-specific. The defect lived in llama.cpp's chat parser, above the backend: the model sometimes emits <tool_call> followed by a space rather than a newline, the parser matched the marker with the newline attached, and the call came back verbatim as content. The author of PR #26324 put the pre-fix rate at roughly 25%, and the same defect is reported independently on the model's own discussion #17, whose author measured 24 of 29 calls parsing before his patch and 29 of 29 after.

#26324 never merged — a maintainer objected that blanket whitespace trimming had been reverted once already for degrading other models' output — and the repair came through PR #26252, a specialized parser merged on 2026-08-02 and released as b10227; #26324's author closed his own pull request on 2026-08-10 with "Tried b10335 (#26252 was merged in b10227) and nanbeige tool calls work 100% of the times, thank you". Because the parser sits above the backend, this HIP build takes the fix from the same tag as any CUDA or Metal one: b10153 loads the architecture, b10227 parses tool calls. If calls still arrive as text, rebuild.

HTTP 400 — Failed to initialize samplers on a json_schema request

Any request with a response_format of json_schema fails at sampler init on the default jinja chat path, before a token is generated. Two users reproduced it independently on discussion #6, on different builds and quant tiers, and both found the same workaround: add --no-jinja to the llama-server command. Overriding the surface template with --chat-template chatml does not help, and the raw /completion endpoint with the same schema returns conformant JSON — which isolates the fault to the chat-completions grammar trigger rather than the model. The cost of --no-jinja is that you lose the model's own chat template, so apply the prompt format yourself.

ollama run nanbeige/nanbeige4.2:3b-Q4_K_M returns not found

The model card lists that command, but the registry entry it names does not exist, and neither do the library/ paths for either spelling of the name. Use llama-server directly.

A note on Vulkan and on Windows

llama.cpp also has a Vulkan backend that runs on this card, and Windows HIP builds exist. Everything above — the kernel tables, the matched-type restriction, the scratch arithmetic — was verified against the HIP backend on Linux and nothing here should be assumed to carry to either. docs/build.md notes separately that HSA_OVERRIDE_GFX_VERSION is not supported on Windows at all.

Anything else, or a real throughput measurement on this card, is welcome via the submission form.

common questions
How much VRAM does Nanbeige4.2 3B need?

About 16 GB — the minimum this recipe targets.

Which GPUs is Nanbeige4.2 3B tested on?

RX 7800 XT (16 GB).

How hard is this setup?

Intermediate — follow the steps above.