Gemma 4 E2B on an AMD MI300X: Which Weight Format Should…
    Neura Market
    Neura Market
    /CoPilot
    Marketplace
    Directories
    Resources
    CoPilot
    ChatGPTChatGPTClaudeClaudeGeminiGeminiCursorCursorGrokGrokPerplexityPerplexityDeepSeekDeepSeekCoPilotCoPilotStable DiffusionStable DiffusionMidjourneyMidjourney
    OverviewRulesPromptsMCPsAgentsGamesBlogVideosGuidesCoursesCommunityPluginsTrending
    CoPilotBlogGemma 4 E2B on an AMD MI300X: Which Weight Format Should You Serve?
    Back to Blog
    Gemma 4 E2B on an AMD MI300X: Which Weight Format Should You Serve?
    gemma

    Gemma 4 E2B on an AMD MI300X: Which Weight Format Should You Serve?

    xbill October 8, 2026
    0 views

    Ten builds of Gemma 4 E2B (bf16, fp8 in both E4M3 flavours, int8 W8A8 and int4 W4A16, each with and without int4 embedding tables) served one after another on one AMD Instinct MI300X with the same vLLM image. fp8 is the only format that keeps pace with bf16, the 4-bit builds run at 0.14x to 0.63x, and int4 embedding tables cost almost nothing.


    title: "Gemma 4 E2B on an AMD MI300X: Which Weight Format Should You Serve?" published: true description: "Ten builds of Gemma 4 E2B (bf16, fp8 in both E4M3 flavours, int8 W8A8 and int4 W4A16, each with and without int4 embedding tables) served one after another on one AMD Instinct MI300X with the same vLLM image. fp8 is the only format that keeps pace with bf16, the 4-bit builds run at 0.14x to 0.63x, and int4 embedding tables cost almost nothing." tags: gemma, amd, machinelearning, llm cover_image: https://raw.githubusercontent.com/xbill9/gemma4-dev/main/gpu-vllm-mi300x-2b/article/devto-mi300x-formats-cover.355c09b0.jpg

    This article provides a step by step guide to serving ten weight formats of Gemma 4 E2B on one AMD Instinct MI300X through vLLM, with every build timed across a grid of request counts and prompt lengths on the same card, image and day. Every log, report and script is committed.

    On the MI300X, fp8 is the only format that keeps pace with bf16: 0.75x for a single request and up to 1.09x with 8 or 64 at once. int8 W8A8 runs at 0.29x to 0.87x and the 4-bit W4A16 builds at 0.14x to 0.63x. The format that serves fastest also rounds the weights furthest from what Google trained, so the choice is a trade between speed and fidelity.

    https://github.com/xbill9/gemma4-dev/tree/main/gpu-vllm-mi300x-2b


    Why Compare Formats on a 192 GB Card?

    Gemma 4 E2B loads in 9.42 GiB at bf16. One MI300X has 192 GB of HBM, so memory decides nothing here: bf16 leaves room for a KV cache of 9,045,060 tokens, and the smallest build only stretches that to 9,475,223.

    What is left to choose on is speed and precision. The card multiplies some number formats natively and emulates others, and every format below stores Google's quantization-aware-trained (QAT) weights with a different amount of rounding.


    The Ten Builds

    Every quantized build starts from Google's gemma-4-E2B-it-qat-q4_0-unquantized release, whose weights already sit on a 4-bit grid with one scale per group of 32 values. All nine are text only and on Hugging Face:

    BuildLinear layersVocabulary tablesMade with
    bf16bf16, google/gemma-4-E2B-itbf16—
    fp8FP8 E4M3, FP8 activations per tokenbf16fp8_text.py
    fp8fnuzFP8 E4M3FNUZ, the MI300X's own FP8bf16fp8_text.py --fnuz
    fp8emb4FP8 E4M3int4fp8_text.py build-on
    fp8fnuzemb4FP8 E4M3FNUZint4fp8_text.py build-on --fnuz
    w8a8int8 per channel, int8 activations per tokenbf16w8a8_from_qat.py
    w8a8emb4int8 per channelint4w8a8.py
    q4w4a16int4 holding the QAT grid exactly, bf16 activationsbf16repack_q4_0.py, text_only.py
    q4w4a16ple4as q4w4a16int4 per-layer embeddings onlyembed_int4.py
    q4w4a16emb4as q4w4a16int4embed_int4.py

    On Hugging Face:

    • fp8: https://huggingface.co/xbill9/gemma-4-E2B-it-qat-q4_0-fp8-text
    • fp8fnuz: https://huggingface.co/xbill9/gemma-4-E2B-it-qat-q4_0-fp8fnuz-text
    • fp8emb4: https://huggingface.co/xbill9/gemma-4-E2B-it-qat-q4_0-fp8-text-emb4
    • fp8fnuzemb4: https://huggingface.co/xbill9/gemma-4-E2B-it-qat-q4_0-fp8fnuz-text-emb4
    • w8a8: https://huggingface.co/xbill9/gemma-4-E2B-it-qat-w8a8-int8
    • w8a8emb4: https://huggingface.co/xbill9/gemma-4-E2B-it-qat-w8a8-ct-text-emb4
    • q4w4a16: https://huggingface.co/xbill9/gemma-4-E2B-it-qat-q4_0-w4a16-ct-text
    • q4w4a16ple4: https://huggingface.co/xbill9/gemma-4-E2B-it-qat-q4_0-w4a16-ct-text-ple4
    • q4w4a16emb4: https://huggingface.co/xbill9/gemma-4-E2B-it-qat-q4_0-w4a16-ct-text-emb4

    The int4 vocabulary tables are embed_tokens, an untied lm_head and the per-layer embeddings, packed at the same group-of-32 grid QAT trained them on.


    At This Point You Should Have…
    • An AMD Developer Cloud account with an MI300X droplet (gpu-mi300x1-192gb), and its SSH key
    • A Hugging Face token
    • A clone: git clone https://github.com/xbill9/gemma4-dev
    • The droplet prepared with make scaffold from amd-gputools, which installs Docker, adds the GPU groups and pulls the vLLM image

    Step 1 — Build the Formats

    Each build reads Google's QAT checkpoint and writes one format. The FP8 builds take the text-only model's config and tensor list from the 4-bit repack, so every build quantizes the same 276 linear layers:

    hf download google/gemma-4-E2B-it-qat-q4_0-unquantized --local-dir src
    hf download xbill9/gemma-4-E2B-it-qat-q4_0-w4a16-ct-text config.json model.safetensors.index.json --local-dir ct-text
    python3 repack/fp8_text.py build src ct-text/config.json ct-text/model.safetensors.index.json fp8fnuz-text --fnuz
    python3 repack/fp8_text.py verify src fp8fnuz-text --fnuz
    

    verify rereads both checkpoints and compares every quantized value with its QAT original:

    {
     "modules": 276,
     "values": 1876819968,
     "max_rel_err": 0.03333339840173721,
     "copied": 264,
     "copied_identical": 264,
     "relative_rms_error": 0.026415727046737517
    }
    

    build-on makes the emb4 variants by swapping the linear layers of an existing int4-table build for FP8 and leaving its tables untouched. The int8 builds come from w8a8_from_qat.py, and the 4-bit repack from repack_q4_0.py, which recovers each group's trained step and stores the levels as int4 without re-rounding them.


    Step 2 — Time the Card's Matrix Multiplies

    Before serving anything, gemm_decode_shapes.py times every matrix multiply E2B runs per token, at 1, 8 and 64 rows, from a HIP graph so launch overhead stays out of the number:

    | M | dtype | us/token | x bf16 |
    | 1 | bf16 | 1966.2 | 1.00 |
    | 1 | fp8 | 1304.2 | 1.51 |
    | 64 | bf16 | 2383.6 | 1.00 |
    | 64 | fp8 | 1589.4 | 1.50 |
    | 64 | int8 | 12146.4 | 0.20 |
    

    fp8 does the same work in two thirds of bf16's time. int8 is five times slower than bf16 at 64 rows, and below 17 rows PyTorch's int8 multiply refuses to run at all:

    control_8192 M=1 int8: `RuntimeError: self.size(0) needs to be greater than 16, but got 1`
    

    The MI300X has no int4 multiply, so a 4-bit build unpacks its weights to bf16 inside the kernel before each multiply.


    Step 3 — Serve Every Build on One Pinned Image

    dtype_sweep.py serves each build in turn from its own rig directory, on one image digest, with identical serving settings: --max-model-len 32768, --gpu-memory-utilization 0.90, prefix caching on, and no --quantization flag, so vLLM reads the format from each checkpoint:

    python3 dtype_sweep.py --droplet debian-gpu-mi300x1-192gb-devcloud-atl1 \
      --image vllm/vllm-openai-rocm@sha256:ec62abecc13923172cf1225c0270b8cea7d84b652eacd3c8dffe2dd35dd73e37 \
      --run-id 2026-10-07-dtype-sweep-mi300x
    

    Each build is checked at boot for the kernel vLLM chose and the memory it loaded:

    === [3/8] w8a8 (gpu-vllm-mi300x-2b-w8a8)
        ok: loading 7.07 GiB, KV 9025606, kernels ['TritonInt8ScaledMMLinearKernel']
    
    === [4/8] fp8 (gpu-vllm-mi300x-2b-fp8)
        ok: loading 7.07 GiB, KV 9017531, kernels ['RowWiseTorchFP8ScaledMMLinearKernel']
    

    A quantized build that loaded near bf16's 9.42 GiB would have been unpacked to bf16 at load. None was.


    Step 4 — Check Each Build Answers

    verify_capabilities sends a text question, a thinking prompt and a tool call to each build, with known answers:

    ✅ 3/3 capabilities verified on `debian-gpu-mi300x1-192gb-devcloud-atl1`.
    
    | text | ✅ | The AMD MI300X GPU is based on the AMD CDNA 3 architecture |
    | thinking | ✅ | 1704 chars, 587 reasoning tokens |
    | tool calling | ✅ | tool_calls → get_weather{"city": "Reykjavik"} |
    

    All nine quantized builds pass all three. bf16 passes the same three and refuses the image check, because it was served text only like the others.


    Step 5 — Sweep Request Count and Prompt Length

    Each build runs 1, 8 and 64 requests at once against prompts of 128, 1,024 and 8,192 tokens, 512 output tokens each, three repeats per cell, with a fresh random seed for every run so no prompt is served from the prefix cache.


    How Fast Is Each Format?

    Output tokens per second at 1, 8 and 64 parallel requests with 1,024-token prompts, and the range against bf16 across all nine cells:

    BuildWeights1 / 8 / 64 requestsRange vs bf16
    bf169.42 GiB321 / 1,795 / 8,9511.00x
    fp87.07 GiB242 / 1,827 / 9,7000.75x – 1.08x
    fp8fnuz7.07 GiB245 / 1,819 / 9,7390.75x – 1.09x
    fp8emb43.61 GiB234 / 1,742 / 9,4500.72x – 1.08x
    w8a87.07 GiB98 / 757 / 4,7170.29x – 0.87x
    w8a8emb43.60 GiB97 / 746 / 4,6800.29x – 0.79x
    q4w4a166.32 GiB47 / 367 / 3,2880.14x – 0.63x
    q4w4a16emb42.85 GiB47 / 365 / 3,2850.14x – 0.63x

    fp8 trails bf16 by a quarter on a single request and runs at 1.00x to 1.08x with 8 or 64 requests, outside the noisiest cell. The int8 and 4-bit builds are slower than bf16 in every cell.

    For one request the time per output token is 2.89 ms at bf16, 3.83 ms at fp8, 9.94 ms at int8 and 20.91 ms at 4 bits. The 4-bit build loads two thirds of bf16's bytes and takes seven times as long per token, so the unpacking kernel sets its pace. The 4-bit and int8 builds close the gap as prompts grow, which fits more of each step going to the prompt and to attention, where the weight format does not change the work: with 64 requests and 8,192-token prompts q4w4a16 reaches 0.63x.


    How Close Are They to the Trained Weights?

    Each build was checked offline against the QAT values it was made from:

    BuildError against the QAT weights
    q4w4a16none on the grid: 58,650,624 groups, 0 off the grid, 90.0% of values bit-identical, the rest within 1.1% through the scale's rounding
    w8a80.59% to 1.71% relative error per tensor, mean 0.90%
    fp82.64% relative RMS error, worst value 3.57% of its row's largest
    fp8fnuz2.64% relative RMS error, worst value 3.33% of its row's largest

    The order is the reverse of the speed table. The 4-bit build keeps the trained grid exactly, int8 rounds each value to one of 255 levels per row, and FP8's three mantissa bits round furthest. Accuracy of these same checkpoint files on a classification suite, GSM8K and tool calls is in the TPU v5e article linked below; this sweep measured speed only.


    🔎 Tip: E4M3 or E4M3FNUZ, Either Serves

    The MI300X's FP8 is E4M3FNUZ: the same 3-bit mantissa as the E4M3 most published FP8 checkpoints use, a largest value of 240 against 448, and no negative zero. A raw E4M3 matrix multiply raises HIPBLAS_STATUS_NOT_SUPPORTED on this card, yet the E4M3 checkpoint serves, because vLLM's compressed-tensors loader converts it at load:

    Selected RowWiseTorchFP8ScaledMMLinearKernel for CompressedTensorsW8A8Fp8
    

    Built natively in E4M3FNUZ, the same weights load into the same kernel, the same 7.07 GiB and the same 9,017,531-token cache, and run at 0.995x to 1.014x of the E4M3 build in eight of nine cells. Either file works on this card.


    🔎 Tip: int4 Embedding Tables Cost Almost Nothing

    The emb4 builds pack the vocabulary tables, which are a large share of E2B's weights, as int4. Against the same format with bf16 tables, in eight of nine cells:

    PairWeightsOutput tok/s
    fp8emb4 / fp83.61 / 7.07 GiB0.954x – 0.974x
    w8a8emb4 / w8a83.60 / 7.07 GiB0.984x – 0.992x
    q4w4a16emb4 / q4w4a162.85 / 6.32 GiB0.990x – 0.999x

    Half the weight memory for 1% to 5% of speed, and the KV cache grows by about 4%. On this card the memory is not needed; on a smaller one this is the build to reach for.


    Compare and Contrast
    BuildWeights1 request64 requests, 1,024 tokensFidelity to QAT
    🥇 fp8 / fp8fnuz7.07 GiB2599,700 / 9,7392.64% RMS
    🥈 fp8emb43.61 GiB2479,4502.64% RMS
    🥉 bf169.42 GiB3438,951Google's bf16 release
    w8a87.07 GiB1004,7170.59% – 1.71%
    q4w4a166.32 GiB483,288exact grid
    q4w4a16emb42.85 GiB473,285exact grid

    Single-request figures use 128-token prompts; medals rank output at 64 requests.


    So, Which One?

    For a single user, bf16: it is the fastest build at one request, 343 output tokens per second, and memory costs nothing on this card. For many users, fp8 or fp8fnuz, up to 1.09x bf16 with 8 to 64 requests in flight, with fp8emb4 at half the weights if the card is shared. For the weights closest to what Google trained, q4w4a16, at about a seventh of bf16's single-request speed on this card. int8 sits between them on fidelity and below fp8 on speed in every cell but the noisiest, so it is never the best pick here.


    Teardown

    Powering a droplet off does not stop billing. Destroy it in the AMD Developer Cloud console when the sweep finishes; the MI300X droplet bills $1.99 an hour until then.


    Summary

    The goal of this article was to find the best weight format for Gemma 4 E2B on one AMD MI300X. The key to the solution was serving every build on one pinned vLLM image with identical settings, checking the chosen kernel and loaded memory at boot, and timing all of them on the same grid. The results were:

    • 🟢 fp8 is the only format at bf16's pace: 0.75x for one request, up to 1.09x with 8 or 64
    • 🟢 E4M3 and E4M3FNUZ checkpoints serve identically, through vLLM's conversion at load
    • 🟢 int4 vocabulary tables halve the weights for 1% to 5% of speed
    • 🟢 Every quantized build answers text, thinking and tool-call checks
    • ⚠️ The 4-bit builds keep the QAT grid exactly but run at 0.14x to 0.63x bf16 through a kernel that unpacks to bf16
    • ⚠️ fp8 rounds furthest from the trained weights: 2.64% relative RMS error
    • ❌ int8 W8A8 runs at 0.29x to 0.87x bf16, below fp8 in eight of nine cells

    Scope: one MI300X droplet in AMD Developer Cloud's atl1 region, vLLM 0.31.1rc1.dev23+g43b4aaea3 at vllm/vllm-openai-rocm@sha256:ec62abec…, all ten builds served on 2026-10-07 and 2026-10-08, three repeats per cell. The bf16 build is Google's gemma-4-E2B-it, and the nine quantized builds are made from Google's QAT release, so bf16 differs in its weights as well as its format. The cell with 64 requests and 8,192-token prompts varied by up to 15.7% between repeats and is left out of the pairwise ranges; every other cell varied by 4.1% or less, except one fp8emb4 cell at 10.7%. No accuracy benchmark was run on this card. The repacked checkpoints are unofficial and derived from Google's release under Apache 2.0. Parts of the analysis and writing were done with AI assistance (Claude); every figure comes from the committed output files.

    The strategy for choosing a weight format for Gemma 4 on an AMD MI300X was validated with an incremental step by step approach.


    References
    • Code, logs, reports and this article's evidence: https://github.com/xbill9/gemma4-dev/tree/main/gpu-vllm-mi300x-2b
    • The same checkpoints on TPU v5e, with accuracy: https://dev.to/gde/gemma-4-qat-on-one-tpu-v5e-what-runs-and-what-doesnt-2gii
    • FP8 build, E4M3: https://huggingface.co/xbill9/gemma-4-E2B-it-qat-q4_0-fp8-text
    • FP8 build, E4M3FNUZ: https://huggingface.co/xbill9/gemma-4-E2B-it-qat-q4_0-fp8fnuz-text
    • 4-bit repack: https://huggingface.co/xbill9/gemma-4-E2B-it-qat-q4_0-w4a16-ct-text
    • Google's QAT source checkpoint: https://huggingface.co/google/gemma-4-E2B-it-qat-q4_0-unquantized
    • AMD Instinct MI300X: https://www.amd.com/en/products/accelerators/instinct/mi300/mi300x.html
    • AMD Developer Cloud: https://devcloud.amd.com
    • vLLM on ROCm: https://docs.vllm.ai/en/latest/getting_started/installation/gpu.html

    Tags

    gemmaamdmachinelearningllm

    Comments

    More Blog

    View all
    Congrats to the Hacktoberfest Weekend Challenge: Build for a Friend Winners!devchallenge

    Congrats to the Hacktoberfest Weekend Challenge: Build for a Friend Winners!

    We are so excited to announce the winners of the Hacktoberfest Weekend Challenge: Build for a Friend,...

    J
    Jem
    1
    Congrats to Our First MLH x DEV Writing Challenge Winner!mlhacks

    Congrats to Our First MLH x DEV Writing Challenge Winner!

    We just wrapped the very first month of the MLH x DEV Writing Challenge, our new way of celebrating...

    J
    Jem
    1
    Dev Opportunity Radar #20: $2M AI Reasoning Challenge, PayPal AI Hackathon, and Coinbase Analytics Challengediscuss

    Dev Opportunity Radar #20: $2M AI Reasoning Challenge, PayPal AI Hackathon, and Coinbase Analytics Challenge

    TL;DR Welcome back to Dev Opportunity Radar. This is a weekly series where I share opportunities,...

    H
    Hemapriya Kanagala
    1
    Gemma 4 From E2B to 31B on an AMD MI300X: fp8 Overtakes bf16 From 12B Upgemma

    Gemma 4 From E2B to 31B on an AMD MI300X: fp8 Overtakes bf16 From 12B Up

    Every Gemma 4 size from E2B to 31B served on one AMD Instinct MI300X in bf16, fp8, int8 W8A8 and int4 W4A16, on the same vLLM image. fp8 goes from 0.75x bf16 for one request at E2B to 1.23x at 31B and is faster than bf16 in every cell at 12B and 31B. The mixture-of-experts 26B sits at bf16's pace, and the 4-bit builds run at 0.14x to 0.69x at every size.

    X
    xbill
    2
    Stuart Feldman Was Right in 1976: Why Your AI Agent Needs a Makefile, Not a 20-Step Promptai

    Stuart Feldman Was Right in 1976: Why Your AI Agent Needs a Makefile, Not a 20-Step Prompt

    How replacing fragile linear prompt checklists with a 50-year-old declarative build graph eliminates agent amnesia, enables seamless mid-flight session resumption, and mechanizes software engineering governance.

    R
    Randal L. Schwartz
    2
    I got Jev to zero mistakes. I'm still using Flash-Lite.ai

    I got Jev to zero mistakes. I'm still using Flash-Lite.

    Jev is a brilliant decision model. Gemini Flash-Lite is the model nobody talks about, and it’s fast,...

    S
    Swift
    2

    Stay up to date

    Get the latest CoPilot prompts, rules, and resources delivered to your inbox weekly.

    Neura Market LogoNeura Market

    Discover the best AI prompts, plugins, and resources for CoPilot and more.

    Content Types

    • Rules
    • Prompts
    • MCPs
    • Agents
    • Games
    • Blog
    • Videos
    • Guides
    • Courses
    • Community
    • Plugins

    Platforms

    • ChatGPT Directory
    • Claude Directory
    • Gemini Directory
    • Cursor Directory
    • Grok Directory
    • Perplexity Directory
    • DeepSeek Directory
    • CoPilot Directory
    • Stable Diffusion Directory
    • Midjourney Directory
    • All Directories

    Resources

    • Blog
    • Documentation
    • Help Center
    • Marketplace

    Legal

    • Privacy Policy
    • Terms of Service

    © 2026 Neura Market. All rights reserved.

    |

    Not affiliated with any AI platform vendors.

    Neura Market

    Custom AI Systems & Services

    Our team of experienced AI builders will help build custom AI systems, workflows, and solutions.

    Request custom work

    Ready-made automations for this

    Workflows from the Neura Market marketplace related to this CoPilot resource

    • Scrape Every URL on the Web Without Getting Blocked by Anti-Bot Technologies with Scrapeyn8n · $4.99 · Related topic
    • Automate Document Embedding and Q&A with Voyage-Context-3 and MongoDB Atlasn8n · $24.99 · Related topic
    • Automate Vision RAG with Cohere and Qdrant for Image Embeddingsn8n · $19.99 · Related topic
    • Automate Image Embedding Creation for Contextual Searchn8n · $14.99 · Related topic
    Browse all workflows