Skip to content

HIP Flash Next on gfx1151: results and approach of a complete branch, as a reference for the open series #1661

Open

Description

@davidtai

We have a gfx1151 HIP path for Qwen3.8-Flash-Next that we measured and tested on an AMD Strix Halo (gfx1151) machine with 192 GiB of unified memory and ROCm 7.15. This issue records the results and the approach. A draft pull request will refer to it as a reference. It does not compete with the HIP Flash Next series that is open now.

Relation to the open HIP Flash Next work

These issues and pull requests are in the same area, and our branch touches many of the same files:

Areas that overlap, by topic:

atc overlap finds textual conflicts with #1612, #1614, #1622, #1650, #1651 and #1658. #1613 and #1615 merge without conflicts.

We can split our work onto those pull requests, pair with their author, or review them, as the maintainers and the author prefer. We do not ask to replace that series.

Results of the fork's numerics=inexact path (not in #1667)

These numbers and charts were measured with the rounding-class kernels described below, under --policy numerics=inexact. #1667 no longer contains those kernels; its exact-path receipts will be posted there.

Served, greedy, thinking off, one stream, prompt cache off.

Checkpoint TensorFold/Qwen3.8-Flash-Next-MLX-4bit-MTP. --policy numerics=inexact. A 17,715-token coding prompt and 1,024 output tokens. Medians of 4 timed runs, with a fresh server for each arm.

Measure Value
prefill (cold) 1,890 tok/s
serial decode (--no-drafts) 38.19 tok/s
Default MTP decode (engine default drafts) 69.63 tok/s, 77.5 % of drafts accepted

One more change (the next layer's expert down codes read on a side stream during one-row decode) adds +2.6 % to serial decode (38.24 / 38.32 to 39.24 / 39.30 tok/s on two seeds) with the same text. Default MTP and prefill do not change.

A prompt-length sweep (1,024 output tokens each) gives:

Prompt tokens prefill tok/s serial decode tok/s Default MTP decode tok/s Default MTP hit rate TTFT serial / MTP (s) Wall time serial / MTP (s) Peak memory (GiB, GPU + host)
1,031 919.0 38.38 76.58 77.4 % 1.12 / 1.15 27.78 / 14.50 136.9 (106.8 + 30.1)
2,046 1,245.6 38.37 76.73 77.4 % 1.64 / 1.65 28.31 / 14.99 136.9 (106.8 + 30.1)
4,094 1,525.9 38.18 65.79 68.7 % 2.68 / 2.76 29.47 / 18.31 136.9 (106.8 + 30.1)
8,194 1,683.8 38.36 70.64 72.8 % 4.87 / 4.93 31.53 / 19.41 136.9 (106.8 + 30.1)
16,386 1,775.1 37.95 67.21 70.5 % 9.23 / 9.28 36.30 / 24.52 136.9 (106.8 + 30.1)
32,767 1,794.7 37.84 72.18 71.6 % 18.26 / 18.47 45.27 / 32.63 137.3 (107.2 + 30.1)
65,535 1,819.4 37.15 75.31 75.3 % 36.02 / 36.51 63.56 / 50.09 138.3 (108.2 + 30.1)
131,068 1,719.3 36.19 70.87 77.8 % 76.23 / 78.25 104.84 / 93.28 140.2 (110.1 + 30.1)
261,061 1,489.4 34.20 71.28 82.6 % 175.28 / 177.80 205.19 / 192.15 143.9 (113.8 + 30.1)

The first 64K and 128K runs were contended by other host activity; both points were re-measured on a quiet machine, and the table and charts show the re-runs. Default MTP decode follows the text's acceptance more than the length. Peak memory is the Default MTP arm (serial is within 0.6 GiB); host memory is the n-gram tables.

Overview: prefill, decode, TTFT, wall time and peak memory by prompt length

Prefill by prompt length

Serial decode and Default MTP decode by prompt length

Time to first token by prompt length

Wall time by prompt length

Peak memory by prompt length

Findings on the rounding-class kernels (not proposed; to fix before any later proposal)

The fork's rounding-class kernels ran only under --policy numerics=inexact. They are not part of #1667, because they do
not meet CONTRIBUTING's rules:

  1. FP64 references. On identical input rows, every rounding-class kernel's error against an independent float64
    reference is larger than the exact path's, in aggregate and in the worst column. Per change and projection class
    (rms_rel inexact / exact; worst-column error inexact / exact):
Change Class rms_rel worst column
Sparse attention on the matrix cores, prompt rows 24 q heads, 2 kv heads, d 256 1.3e-6 / 6.1e-8 7.4e-7 / 7.0e-8
The same, decode and window rows same 1.6e-7 / 6.1e-8 1.1e-7 / 7.0e-8
Prompt attention with p in bf16 once same 5.5e-4 / 6.0e-8 3.8e-4 / 7.4e-8
Prompt DeltaNet in 32-token chunks (WY) y and state, 6 shapes about 5e-4 / 1e-7 2e-5 to 1.8e-4 / about 2e-8
Prompt dense products, weights dequantized to bf16 K 2560 and 6144 1.7e-3 / 2e-7 0.05 to 0.09 / 1e-5 to 3e-5
Prompt expert down, bf16 weights K 640 1.7e-3 / 9e-8 2.2e-2 / 1.4e-6
Prompt expert gate+up, bf16 weights K 2560 2.5e-3 to 4.1e-3 / 1.1e-3 to 1.6e-3 0.17 to 1.96 / 0.04 to 1.63
Prompt MoE router on the bf16 dense tile 513 x 2560 9.7e-7 / 2.3e-7 1.1e-5 / 2.5e-6
Prompt indexer scores on the matrix cores 4 heads x 128, fp32 q 1.1e-3 / 3.5e-8 1.2e-3 / 6.8e-8
The same, q already bf16 4 heads x 128 3.55e-8 / 3.45e-8 6.9e-8 / 6.9e-8
HC gates, up and mix in one launch (reordered up sum) K 320, N 10,240, 11 rows equal bytes (0 of 28,160 outputs differ) equal

The one-launch HC up+mix wrote the chain's bytes on these rows, but its reordered sum does not guarantee that.
On decode rows the prompt-only changes run the exact kernels. All lines: fp64-references.txt.

  1. Teacher-forced fidelity (numerics=exact against numerics=inexact, 65,520 positions each):
Corpus top-1 agreement differences both margins <= 0.25 PPL inexact / exact
wikitext-2 test 95.75 % 2,785 1,524 3.724347 / 3.723247
CPython 3.13 lib 99.38 % 408 135 1.174939 / 1.175527

Every difference, with both runs' top-1 and top-2 logits and margins:
wikitext-2, CPython.
No tie policy is applied.

  1. A lead: one large deviation. At wikitext position 4031 the exact path predicts the target with a margin of 10.1,
    and the inexact path predicts another token (15.38 against 15.25). That is far beyond rounding noise and points to an
    edge case in one of the rounding-class kernels; it is not bisected yet.

  2. The result depends on how the rows are grouped. Under numerics=inexact, a turn resumed from 10,240 cached
    tokens differed from a fresh run of the full prompt (6 rewind replies), and a sampled decoder changed while a long
    prompt ran beside it. Both contracts hold under numerics=exact. Two causes:

    • some rounding-class kernels are chosen by how many rows a launch holds, so the same row takes another arithmetic
      path in a different batch;
    • the chunked (WY) DeltaNet scan computes each row relative to its 32-token chunk's start, so a row's bytes depend on
      the prompt's chunk grid, and the row-by-row decode scan sums it another way.
  3. Kernel contract gate 4 (sums). On the fork's inexact tip, gfx1151: 104 kernels: 97 pass gates 1-4, 7 fail.
    Every failure is in the sums gate:

    • affine_wmma_dense_inexact<2560> and <6144>, affine_wmma_moe_down_inexact<4> and affine_wmma_router<2560>:
      142x to 219x (router 11.4x) over the exact-sum bound, "not fp32 sums". The matrix cores' accumulation on gfx1151
      is not an exact fp32 sum.
    • tf_gdn_wy_prep and tf_gdn_wy_scan (y): 12,416x over the bound at k 128.
    • tf_fn_idx_scores_mma and the WY state: the stress inputs do not separate fp32 from fp16 sums (2.6x and 0.0x,
      the gate needs 100x), so the gate cannot pass these as written.

Each of these must be fixed, and each change given its own gate here, before any of these kernels is proposed again.

Approach

  • Prompt (in hip: Qwen3.8-Flash-Next on gfx1151, a reference branch for review (to be split) #1667, same bytes as the base path): WMMA tiles for the dense and routed-expert GEMMs, the DeltaNet prompt recurrence and its prework in fewer launches, the indexer at 4 lanes a score, a prompt's picks and route plan, the router tile.
  • Prompt (fork only, rounding class, see Findings): the chunked (WY) DeltaNet scan, the matrix-core indexer scores and sparse attention tiles, bf16-dequantized prompt tiles.
  • Decode and verify: a router with a fused top-k pick, fused small kernels in the verify round, and a side stream that reads the next experts' down codes.
  • hip: Qwen3.8-Flash-Next on gfx1151, a reference branch for review (to be split) #1667 now holds only changes whose output bytes equal the base path's. The rounding-class kernels above stay on a fork branch.

The pull request gives the list of its changes, their contract registration and the test output.

🤖 Generated with Claude Code

Activity

  1. davidtai commented on Oct 30, 2026

    @davidtai
    Author

    /atc claim

  2. davidtai commented on Oct 30, 2026

    @davidtai
    Author

    /atc presence 91b908fde3df6360fb82da69f5b4047401f06ece36a9e73269b7ccd3d7898b9f

  3. ashhart commented on Oct 31, 2026

    @ashhart
    Owner

    Thanks @davidtai for the full write-up, and for keeping #1667 to changes whose bytes equal the base path.

    Your findings on the rounding-class kernels are useful as they stand: FP64 error per class, every top-1 difference
    with margins, and the resumed and concurrent breaks under numerics=inexact. Any of those that comes back later lands
    opt-in only, with its own gate. That includes the wikitext position 4031 lead and the batch-dependent paths fixed first.

    Where #1667 overlaps @gilby's open PRs, please work it out per PR: review or pair on the ones that are further along,
    and keep the rest in #1667. Small PRs, one change each, will review fastest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions