Back to the full write-up on hijoseromero.com  ·  Watch the video  ·  Read it on Substack

Qwen 3.8-Flash-Next on a 128 GB desktop: does it run, how fast, and what breaks at 1-bit

Qwen's 125B mixture-of-experts (6B active) on a llama.cpp build of the not-yet-merged support PR. Four Unsloth quants, four one-shot tests, thinking medium. The 27B I already run is the reference. Every output, thinking transcript, log and number is linked.

The short version

Three questions: does it run on 128 GB, how fast, and what breaks as the file shrinks. The 27B is the reference, not a rival. Six builds, four one-shot tests, thinking medium, same samplers and seed. Every leg finished every test, no crashes, no cut-offs.

How this ran before llama.cpp officially supported it

The problem

Flash-Next is a new architecture. The night of this run llama.cpp did not know it yet: Unsloth's prebuilt (b10472) refused the file, and the support lived in an open PR from Unsloth's Daniel Han, llama.cpp PR #27742, "model: add Qwen3.8-Flash-Next (qwen4exp)". So I built llama.cpp from the PR branch.

Update, Aug 27: the PR merged into llama.cpp master the next day, so a current mainline build loads the model with no tricks. If you are reading this later, skip step 1's fetch/checkout and build master; the rest of the recipe is unchanged. MTP was still marked WIP at merge time.

The recipe

On the GMKtec EVO-X2 (Ryzen AI Max+ 395, 128 GB, Radeon 8060S), CachyOS. Commit 213df585b, build b10667.

1. Get the PR's code.

git clone https://github.com/ggml-org/llama.cpp tools/llama.cpp-qwen4exp
cd tools/llama.cpp-qwen4exp
git fetch origin pull/27742/head:pr-27742
git checkout pr-27742

2. Build tools. The first build failed on the missing spirv-headers; install, rebuild.

sudo pacman -S cmake vulkan-headers spirv-headers vulkan-radeon
vulkaninfo --summary   # AMD Radeon 8060S Graphics (RADV STRIX_HALO)

3. Build with Vulkan. Only the binaries the run needs. Build log linked below.

cmake -B build -DGGML_VULKAN=ON -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release -j $(nproc) --target llama-server llama-cli llama-gguf-split

4. Download the quants from unsloth/Qwen3.8-Flash-Next-GGUF, three shards each, about 42 minutes per file at 25 MB/s.

hf download unsloth/Qwen3.8-Flash-Next-GGUF --include "UD-IQ1_M/*"
hf download unsloth/Qwen3.8-Flash-Next-GGUF --include "UD-Q2_K_XL/*"
hf download unsloth/Qwen3.8-Flash-Next-GGUF --include "UD-Q3_K_XL/*"
hf download unsloth/Qwen3.8-Flash-Next-GGUF --include "UD-IQ4_XS/*"

5. Smoke test. Point the server at shard 1 (it finds the rest). Loaded in 19 s, answered "OK" with a reasoning field, 23.9 tok/s.

build/bin/llama-server -m <path>/UD-IQ1_M/Qwen3.8-Flash-Next-UD-IQ1_M-00001-of-00003.gguf \
  --host 127.0.0.1 --port 8898 -np 1 -c 8192 -ngl 999 --jinja --no-webui
curl -s http://127.0.0.1:8898/v1/chat/completions -H 'Content-Type: application/json' \
  -d '{"messages":[{"role":"user","content":"Say OK."}],"max_tokens":16,"temperature":0}'

6. The real launch. One server per build, full context, thinking on at medium, flash attention on. Prompts sent one shot to /v1/chat/completions with the card's samplers (1.0 / 0.95 / 20 / 0), seed 42, 120k output budget.

build/bin/llama-server -m <first shard>.gguf --host 127.0.0.1 --port 8899 -np 1 -c 131072 -ngl 999 \
  --jinja --no-webui --flash-attn on \
  --chat-template-kwargs '{"enable_thinking": true, "reasoning_effort": "medium"}'

Gotchas: this build says --chat-template-kwargs enable_thinking is deprecated (use --reasoning on on newer builds; it still worked). MTP is off, so every Flash-Next speed here is plain decoding; MTP can only make it faster.

7. Unattended. A watcher waited for the build and downloads, ran the smoke test, launched the runner with nohup. Scripts and logs linked below. Four Flash-Next legs: 1.6 hours. The 27B BF16 leg: 48 minutes.

ROCm vs Vulkan, like I'm five

Your GPU is a factory of tiny workers. You need a language to hand them work. On AMD there are two.

ROCm is AMD's own language, the cousin of Nvidia's CUDA. Fastest when it works, because AMD tuned it for their chips. The catch: you need AMD's toolkit installed, your chip has to be on AMD's list, and a source build needs the right chip flag. Unsloth's prebuilt uses it; that is the "ROCm" in the 27B rows.

Vulkan is the public road. Every GPU driver already speaks it because games use it. On Linux the AMD driver is RADV (Mesa), open source, already installed. No toolkit, one cmake flag, works on AMD, Nvidia and Intel alike. Usually a bit slower on heavy matrix math.

For a source build of an unmerged PR at 10 pm, Vulkan was the obvious pick. It cost little here: with 6B active parameters the run is limited by memory reads, not matrix math, so a 125B file on the "slow" backend ran at 20 to 23 tok/s while the 27B on the "fast" one sat at 20. Rule on this page: speeds are named with their backend, never turned into a ratio.

ROCm build, if you want it: cmake -B build -DGGML_HIP=ON -DAMDGPU_TARGETS=gfx1151 -DCMAKE_BUILD_TYPE=Release (gfx1151 = Strix Halo). Not run here.

The ladder: one machine, four files, same exam

Same prompts, same settings, only the file gets bigger. rows are the 27B reference. "Whole exam" = wall time for all four tests; "thinking" = its share of that. The next file up (111 GB) does not fit in 123 GB usable.

BuildFilePeak memory
of 123 GB usable
LoadGenerationWhole examOf which thinkingOutput tokens
Flash-Next 1-bit
UD-IQ1_M, 74.5 GB
74.5 GB82 GB
20 s23.1 tok/s25m 21s14m 08s34,631
Flash-Next 2-bit
UD-Q2_K_XL, 78.9 GB
78.9 GB86 GB
20 s23.4 tok/s20m 29s8m 45s28,355
Flash-Next 3-bit
UD-Q3_K_XL, 90 GB
90.0 GB97 GB
30 s19.9 tok/s27m 20s11m 21s32,368
Flash-Next IQ4_XS
UD-IQ4_XS, 93.7 GB, the biggest that fits
93.7 GB99 GB
25 s20.4 tok/s19m 53s4m 24s24,148
27B 4-bit
Qwen 3.8-27B UD-Q4_K_XL, 17.9 GB (from the thinking-dial video)
17.9 GB38 GB
9 s19.9 tok/s21m 57s7m 43s25,676
27B BF16
Qwen 3.8-27B BF16, 54.7 GB
54.7 GB73 GB
49 s8.7 tok/s47m 56s15m 16s24,564

† 27B reference rows ran on Unsloth's prebuilt llama.cpp (ROCm), with MTP on; the Flash-Next rows ran on the PR #27742 build (Vulkan), no MTP. All six builds finished 4/4 tests.

Rig: GMKtec EVO-X2, Ryzen AI Max+ 395, 128 GB unified, CachyOS, default flags. Peak memory = total system RAM at the run's highest point, OS included. Flash-Next speed = llama-server's generation timing; 27B 4-bit = output tokens / wall time (a little lower). 27B had MTP on; the PR build has none.

First impressions, written the night of the run before any output was opened

Written the night of the run, before any output was opened.

It runs. The 1-bit (74.5 GB) loaded in 20 s on the PR build and finished all four tests. Peak memory 82 GB, so about 40 GB to spare on 128.

Fast for its size. 22 to 24 tok/s at 131k context, no MTP. The 27B 4-bit sits around 19 to 20; 27B BF16 with MTP ran 4 to 13 in the MTP video.

It thinks more than the 27B at the same setting. About 14 of the 25-minute exam was thinking, versus under 8 of the 27B's 22. The faster generation bought a longer scratchpad.

The 2-bit runs too. Same 20 s load, 86 GB peak, same 23 to 24 tok/s. "96 GB recommended" is headroom, not a wall.

Degradation test by test

Per test: a mid-play frame of every build (click to open the real output), the stats, what changed from 1-bit to IQ4_XS.

BLOCKFALL a complete falling-blocks game, one prompt

Browser check: All six: loads clean (0 console errors), all 5 keys work, next preview, score, stacking, game over + restart.

Flash-Next 1-bit BLOCKFALL
Flash-Next 1-bit: 7-bag randomizer, ghost piece
Flash-Next 2-bit BLOCKFALL
Flash-Next 2-bit: random pieces, no ghost
Flash-Next 3-bit BLOCKFALL
Flash-Next 3-bit: random pieces, ghost piece
Flash-Next IQ4_XS BLOCKFALL
Flash-Next IQ4_XS: 7-bag randomizer, ghost piece
27B 4-bit BLOCKFALL
27B 4-bit: random pieces, ghost piece
27B BF16 BLOCKFALL
27B BF16: random pieces, ghost piece, P = pause
BuildTotal timeThinking (share)Output tokensGen speedHTML sizeFiles
Flash-Next 1-bit3m 51s0m 35s (15%)5,51024.1 tok/s12 KBoutput · thinking · raw
Flash-Next 2-bit3m 48s0m 40s (17%)5,41524.0 tok/s12 KBoutput · thinking · raw
Flash-Next 3-bit4m 02s0m 10s (4%)4,96720.6 tok/s12 KBoutput · thinking · raw
Flash-Next IQ4_XS3m 47s0m 03s (1%)4,71020.9 tok/s12 KBoutput · thinking · raw
27B 4-bit2m 58s0m 03s (1%)3,85121.6 tok/s10 KBoutput · thinking · raw
27B BF168m 51s0m 13s (2%)4,9479.3 tok/s12 KBoutput · thinking · raw

All six: zero console errors, all five keys work, next-piece preview, score, stacking, game over, restart. 1-bit and IQ4_XS use a 7-bag randomizer; 2-bit has no ghost piece. Same layout everywhere. No degradation.

The exact prompt (one shot, sent as-is)
Create a complete falling-blocks puzzle game in a single self-contained HTML file. No external libraries, no internet resources, no images: everything inline (HTML, CSS, JavaScript, canvas rendering).

Requirements:

- 10 wide by 20 tall playfield rendered on a canvas.
- Seven distinct piece shapes, each made of four squares, each with its own color.
- Controls: left/right arrows move, up arrow rotates, down arrow soft-drops, spacebar hard-drops.
- Rotation must respect walls and stacked blocks (no clipping through anything).
- Completed horizontal lines clear, rows above fall down, and clearing multiple lines at once scores more.
- Score, lines cleared, and level displayed. Speed increases with level.
- Next-piece preview box.
- Game over when the stack reaches the top, with a visible game-over state and a restart key.
- Clean, readable dark visual style.

Output only the complete HTML file.
open as plain text ↗

ERUPTION a volcano physics simulation with live controls

Browser check: All six: 60 fps, three live sliders, particle + FPS counter, pressure indicator. Frames below are at 10.5 s.

Flash-Next 1-bit ERUPTION
Flash-Next 1-bit: 1,142 particles, big glowing blobs (fireworks look)
Flash-Next 2-bit ERUPTION
Flash-Next 2-bit: 661 particles, lava slides down both slopes
Flash-Next 3-bit ERUPTION
Flash-Next 3-bit: 1,410 particles, lava river hugs the slope, ash cloud
Flash-Next IQ4_XS ERUPTION
Flash-Next IQ4_XS: ~200 particles at the default 280/s rate, crater plume
27B 4-bit ERUPTION
27B 4-bit: 2,295 particles, lava on slopes, wind-blown ash plume
27B BF16 ERUPTION
27B BF16: 0 particles at 10 s (still building pressure), HUD overlaps gauge
BuildTotal timeThinking (share)Output tokensGen speedHTML sizeFiles
Flash-Next 1-bit5m 21s2m 30s (46%)7,45623.4 tok/s11 KBoutput · thinking · raw
Flash-Next 2-bit6m 06s3m 34s (58%)8,43523.2 tok/s10 KBoutput · thinking · raw
Flash-Next 3-bit9m 22s5m 13s (55%)10,94719.5 tok/s13 KBoutput · thinking · raw
Flash-Next IQ4_XS6m 02s2m 16s (37%)7,30320.2 tok/s12 KBoutput · thinking · raw
27B 4-bit7m 15s2m 49s (38%)8,07218.6 tok/s12 KBoutput · thinking · raw
27B BF1617m 59s9m 38s (53%)8,3877.8 tok/s10 KBoutput · thinking · raw

All six run at 60 fps with three live sliders and counters. 1-bit: a fountain of big glowing blobs (1,142 particles), fireworks more than lava. 2-bit: lava sliding down both slopes (661). 3-bit: lava river hugging the slope, ash cloud (1,410), the best-looking volcano. IQ4_XS: crater plume, but only ~200 particles at its default rate. 27B 4-bit: 2,295 particles, ash drifting with the wind, the busiest scene. 27B BF16: still 0 particles at 10 s, HUD overlaps the gauge.

The exact prompt (one shot, sent as-is)
Create a 2D physics simulation of a volcano erupting, in a single self-contained HTML file. No external libraries: everything inline, rendered on a canvas.

Requirements:

- Side view: a volcano cone with a crater, ground, and sky.
- A pressure meter that builds over time; when it maxes out, the volcano erupts, then pressure resets and builds again on its own.
- Eruptions launch glowing lava particles from the crater with varied velocities. Particles follow gravity in a realistic arc, land on the slopes, and slide or settle downhill.
- Lava cools over its lifetime: bright yellow-orange when launched, fading to dark rock, then disappearing.
- An ash cloud of smoke particles that rises from the crater and drifts with the wind.
- Three sliders the user can move while it runs: eruption power, wind strength and direction, and particle amount.
- A live on-screen counter showing active particle count and frames per second. The simulation must stay smooth with at least 1000 active particles.

Output only the complete HTML file.
open as plain text ↗

THE LEDGER clean a messy CSV and build a correct dashboard

Browser check: All six: every pivot cell, both quarter totals and the $32,083.00 grand total exactly right; duplicate 1008 dropped; exactly 3 rows excluded; 3 regions; refunds negative; California headline.

Flash-Next 1-bit THE LEDGER
Flash-Next 1-bit: complete, chart OK
Flash-Next 2-bit THE LEDGER
Flash-Next 2-bit: complete, grouped Q1/Q2 chart
Flash-Next 3-bit THE LEDGER
Flash-Next 3-bit: complete, chart OK, lists excluded ids
Flash-Next IQ4_XS THE LEDGER
Flash-Next IQ4_XS: BUG: bar chart empty (NaN in SVG, console error); totals row missing
27B 4-bit THE LEDGER
27B 4-bit: complete, chart OK
27B BF16 THE LEDGER
27B BF16: complete, chart OK, no excluded-rows detail
BuildTotal timeThinking (share)Output tokensGen speedHTML sizeFiles
Flash-Next 1-bit7m 44s4m 43s (61%)10,26822.4 tok/s9 KBoutput · thinking · raw
Flash-Next 2-bit6m 07s2m 20s (38%)8,23222.8 tok/s12 KBoutput · thinking · raw
Flash-Next 3-bit5m 44s1m 28s (25%)6,74419.9 tok/s11 KBoutput · thinking · raw
Flash-Next IQ4_XS5m 16s1m 11s (22%)6,26720.1 tok/s11 KBoutput · thinking · raw
27B 4-bit4m 52s1m 10s (23%)6,10620.9 tok/s10 KBoutput · thinking · raw
27B BF1610m 48s1m 35s (14%)6,0379.3 tok/s11 KBoutput · thinking · raw

All six exactly right: California 16,091.26, Ohio 6,498.24, Texas 9,493.50, grand total 32,083.00, duplicate 1008 dropped, 3 rows excluded, 3 regions, dates parsed, refunds negative. A 1-bit build doing data cleaning with zero slips. One bug: IQ4_XS draws an empty chart (NaN in the SVG) and skips the totals row.

The exact prompt (one shot, sent as-is)
Below is a messy sales CSV export. Build a single self-contained HTML dashboard file (no external libraries, everything inline) that cleans this data and reports on it.

Cleaning rules:

- order_id must be unique: if an id appears twice, keep the first row and drop the rest.
- Rows whose amount is not a number (like N/A or unknown) are excluded from revenue, but report how many you excluded.
- Negative amounts are refunds and must count as negative revenue.
- Region names must be normalized (TX, tx, Texas, texas are all Texas; CA is California; OH is Ohio).
- Dates appear in multiple formats; parse them all. Q1 = Jan-Mar, Q2 = Apr-Jun.

The dashboard must show:

1. A pivot table of total revenue by region (rows) and quarter (columns), with row totals, column totals, and a grand total, formatted as dollars with two decimals.
2. A bar chart of revenue by region drawn with inline SVG or canvas (no chart libraries).
3. A data quality panel: rows received, duplicates dropped, rows excluded, rows counted.
4. A one-line headline stating the top region and its total.

The numbers displayed must be computed correctly from the data. Output only the complete HTML file.

```
order_id,order_date,region,product,amount_usd
1001,2025-01-06,Texas,widget,"1,240.00"
1002,1/9/2025,CA,gadget,$980
1003,2025-01-14,TX,gizmo,312.50
1004,"Jan 21, 2025",california,widget,"1,764.00"
1005,2025-01-27,Ohio,gadget,455
1006,2/3/2025,texas ,widget,N/A
1007,2025-02-10,OH,gizmo,"1,102.25"
1008,"Feb 14, 2025",California,widget,"2,310.00"
1009,2025-02-18,TX,gadget,-450.00
1010,2025-02-24,Texas,gizmo,870.10
1011,3/3/2025,Ohio,widget,"$1,999.99"
1012,2025-03-08,CA,gizmo,640.00
1008,"Feb 14, 2025",California,widget,"2,310.00"
1013,"Mar 12, 2025",tx,gadget,"1,580.00"
1014,2025-03-19,ohio,gizmo,225.75
1015,2025-03-27,California,gadget,"3,050.00"
1016,4/2/2025,TX,widget,990.00
1017,2025-04-07,Ohio,gadget,unknown
1018,"Apr 11, 2025",CA,widget,"1,420.50"
1019,2025-04-16,Texas,gizmo,"2,075.00"
1020,2025-04-22,OH,widget,$760.25
1021,5/1/2025,California,gizmo,"1,111.11"
1022,2025-05-06,TX,gadget,845.00
1023,"May 13, 2025",Ohio,gizmo,-220.00
1024,2025-05-19,texas,widget,"1,300.00"
1025,2025-05-28,CA,gadget,"2,940.40"
1026,6/4/2025,Ohio,widget,515.00
1027,2025-06-10,California,widget,"1,875.25"
1028,"Jun 17, 2025",TX,gizmo,N/A
1029,2025-06-23,OH,gadget,"1,660.00"
1030,2025-06-30,Texas,gadget,730.90
```
open as plain text ↗

BLIND ARTIST draw a scene in pure SVG, no eyes allowed

Browser check: All six: valid SVG, dog + hard hat on head, forklift with forks, 3 servers with lights on the forks, dog in seat with wheel, wheels on ground, sun top-left, 2 clouds, house right, layering correct.

Flash-Next 1-bit BLIND ARTIST
Flash-Next 1-bit: signed "claude-sonnet-4-6"
Flash-Next 2-bit BLIND ARTIST
Flash-Next 2-bit: signed "Claude (Anthropic)", cleanest hard hat
Flash-Next 3-bit BLIND ARTIST
Flash-Next 3-bit: signed "Claude (Anthropic)", richest scene
Flash-Next IQ4_XS BLIND ARTIST
Flash-Next IQ4_XS: signed "drawn by Claude", biggest dog
27B 4-bit BLIND ARTIST
27B 4-bit: signed "Gemini"
27B BF16 BLIND ARTIST
27B BF16: signed "Claude", mast dips below ground line
BuildTotal timeThinking (share)Output tokensGen speedHTML sizeFiles
Flash-Next 1-bit8m 25s6m 19s (75%)11,39722.7 tok/s6 KBoutput · thinking · raw
Flash-Next 2-bit4m 27s2m 11s (48%)6,27323.7 tok/s7 KBoutput · thinking · raw
Flash-Next 3-bit8m 12s4m 30s (54%)9,71019.8 tok/s10 KBoutput · thinking · raw
Flash-Next IQ4_XS4m 48s0m 54s (18%)5,86820.5 tok/s10 KBoutput · thinking · raw
27B 4-bit6m 52s3m 41s (53%)7,64718.6 tok/s6 KBoutput · thinking · raw
27B BF1610m 18s3m 50s (37%)5,1938.4 tok/s7 KBoutput · thinking · raw

All six: valid SVG, dog with hard hat on its head, forklift, three servers on the forks, dog in the seat, wheels on the ground, sun, two clouds, house. Layering correct everywhere. 3-bit is the richest scene, IQ4_XS the best dog. Signatures all wrong (see above).

The exact prompt (one shot, sent as-is)
Using only raw inline SVG code (no JavaScript, no external images, no libraries), draw this exact scene as a single self-contained HTML file:

A happy dog wearing a yellow hard hat is driving a forklift. The forklift is lifting a rack of three stacked computer servers on its front forks. The servers have small colored indicator lights. The scene is outdoors: flat ground, a sun in the top left, two clouds, and a small house far in the background on the right. The forklift has two wheels touching the ground and the dog sits in the driver seat holding the steering wheel.

Layering must be correct (wheels touch the ground, the dog is inside the forklift, the hat is on the dog's head, the servers sit on the forks). Sign the artwork by writing your own model name in small text in the bottom right corner.
open as plain text ↗
How the browser check was done

Each of the 24 outputs opened in headless Chromium, console captured. Blockfall: all keys pressed, then 60 hard drops. Eruption: 10.5 s wait, counters read. Ledger and Blind Artist: checked against the answer key by hand. The screenshots on this page are those frames.

Errors and did-not-finish

No errors. Every leg that started finished every test. The one bug inside an output (the IQ4_XS Ledger chart) is described in the Ledger section.

Every log, every raw file

LegFiles
Flash-Next 1-bitresults.json · server.log · server-timings.log · 01-blockfall.raw.txt · 01-blockfall.response.json · 03-eruption.raw.txt · 03-eruption.response.json · 04-the-ledger.raw.txt · 04-the-ledger.response.json · 05-blind-artist.raw.txt · 05-blind-artist.response.json
Flash-Next 2-bitresults.json · server.log · server-timings.log · 01-blockfall.raw.txt · 01-blockfall.response.json · 03-eruption.raw.txt · 03-eruption.response.json · 04-the-ledger.raw.txt · 04-the-ledger.response.json · 05-blind-artist.raw.txt · 05-blind-artist.response.json
Flash-Next 3-bitresults.json · server.log · server-timings.log · 01-blockfall.raw.txt · 01-blockfall.response.json · 03-eruption.raw.txt · 03-eruption.response.json · 04-the-ledger.raw.txt · 04-the-ledger.response.json · 05-blind-artist.raw.txt · 05-blind-artist.response.json
Flash-Next IQ4_XSresults.json · server.log · server-timings.log · 01-blockfall.raw.txt · 01-blockfall.response.json · 03-eruption.raw.txt · 03-eruption.response.json · 04-the-ledger.raw.txt · 04-the-ledger.response.json · 05-blind-artist.raw.txt · 05-blind-artist.response.json
27B 4-bitresults.json
27B BF16results.json · server-timings.log · 01-blockfall.raw.txt · 01-blockfall.response.json · 03-eruption.raw.txt · 03-eruption.response.json · 04-the-ledger.raw.txt · 04-the-ledger.response.json · 05-blind-artist.raw.txt · 05-blind-artist.response.json
The night itself (build, smoke test, launchers, runner logs)build-llamacpp.log · smoke-q1.log · launch.log · launch-up.log · flashnext.log · flashnext-up.log · contestants-flashnext.json · contestants-flashnext-up.json

Sources

Claim on this pageDirect source
Model facts (125B total, 6B active, 51B n-gram, 262K context), samplers Qwen3.8-Flash-Next model card
The files that ran, sizes, "96 GB recommended" Unsloth guide · the GGUF repo
Why a custom build was needed, and its MTP status llama.cpp PR #27742, "model: add Qwen3.8-Flash-Next (qwen4exp)"
llama.cpp Vulkan and HIP build flags llama.cpp build docs
The 27B 4-bit medium reference (same tests, samplers, effort) The thinking-dial results page
The video these results are from I ran Qwen 3.8-Flash-Next at 1-bit. Nothing broke (YouTube) · the Substack post · the site letter
The first-look video this follows Qwen 3.8-Flash-Next just dropped. Can I even run it?

These are affiliate links. If you buy through them I may earn a small commission at no extra cost to you. As an Amazon Associate I earn from qualifying purchases.