8000 Faster writing: run only the chosen experts; uncap the large profile by gt50 · Pull Request #3 · gt50/mini-AGI · GitHub
[go: up one dir, main page]

Skip to content

Faster writing: run only the chosen experts; uncap the large profile - #3

Merged
gt50 merged 1 commit into
mainfrom
decode-speed
Oct 4, 2026
Merged

gt50 merged 1 commit into
mainfrom
decode-speed

Conversation

@gt50
@gt50 gt50 commented Oct 4, 2026
Copy link
Copy Markdown
Owner

Summary

Writing is much faster, and the large profile no longer drops most of its routing.

Why writing was slow. Each character the model writes is a forward pass of its own, and it routes to top_k = 8 experts. The dispatch, though, ran its batched matmuls over every expert on the card, converting each one's fp32 weights to bf16 on every call. With 128 on the card (strix_halo_large.yaml), every recurrent row read 604M weights in order to use 8 experts. Writing was limited by memory bandwidth.

Changes

  • minagi/pool.py: when at most half the card's experts were chosen, the dispatch gathers just those weights before the matmuls.

    • Under half, the gathered fp32 copy is no larger than the old full-card bf16 cast, so peak memory can't rise.
    • Training chunks reach nearly every expert and keep the old path. On a training step, off vs on measured 4,794 vs 4,798 ms.
  • Fewer per-row host syncs and tiny launches, 134 → 67 syncs per character:

    • a cached slot-index tensor in PagedPool, instead of a host→device copy on every routing call
    • last_seen read once per _place, instead of one sync per slot
    • telemetry EMAs (pressure, want_k) kept on the device
    • dispatch capacity known without a sync for a single character
    • admit() indexes a Python list rather than a tensor per expert
    • one write to ever per admission
  • configs/strix_halo_large.yaml: capacity_factor 1.5 → 0 (uncapped). A fresh model with 128 experts on the card piles onto a few of them.

    capacity_factor char/s (train step) peak routing dropped
    1.5 412 17.8 GB 59.0%
    3 368 18.4 GB 40.8%
    6 367 19.4 GB 25.0%
    0 302 22.8 GB 0%

    A real run's first minutes dropped 75.6% at 1.5.

  • tools/bench_hw.py: a writing benchmark (--weights DIR uses a real directory, read-only) and the dropped-routing share on the training-step line.

Results (Radeon 8060S, Windows, ROCm 7.13 nightly)

model before after
large (weights-strix, 128 on the card) 2.4 char/s 9.4 char/s
trained continuation (weights, 32 on the card) 7.8 char/s 11.0 char/s

What remains is fixed per-row overhead: about 150 small kernels plus the halting syncs. The untrained large model still runs nearly all 24 rows per character. HIP graphs or torch.compile would be the next step.

Test plan

  • pytest: 19/19, including the new tests/test_dispatch.py. It checks that the subset dispatch gives the same outputs and slot gradients as the full one, and that the subset path is actually taken for a single character
  • Decode benchmark before and after, on both models
  • Training-step A/B with the subset path off and on: identical time, memory and drops
  • CI green

🤖 Generated with Claude Code

Writing a character was memory-bandwidth bound. Each character is a
forward of its own and routes to top_k experts, but the dispatch ran its
batched matmuls - and cast fp32 weights to bf16 - over every expert on the
card. With 128 on the card that read 604M weights per row to use 8.

- pool.py: when at most half the card's experts were chosen, gather just
  those before the matmuls. Training chunks reach nearly every expert, so
  they keep the old path (A/B on a training step: 4,794 vs 4,798 ms).
  tests/test_dispatch.py checks outputs and slot gradients match.
- Fewer per-row host syncs and tiny launches (134 -> 67 syncs a character):
  cached slot index tensor in PagedPool, last_seen read once per _place,
  telemetry EMAs kept on the device, cap known for a single character,
  admit() indexes a list instead of a tensor per expert, one write to
  `ever` per admission.

Writing speed on the 8060S (tools/bench_hw.py --weights):
  large model (128 on the card)   2.4 -> 9.4 char/s
  trained model (32 on the card)  7.8 -> 11.0 char/s

- configs/strix_halo_large.yaml: capacity_factor 1.5 -> 0. At 1.5 a fresh
  model dropped 59% of routing (76% in a real run); uncapped a step is 27%
  slower and peaks at 22.8 GB instead of 17.8.
- tools/bench_hw.py: a writing benchmark (--weights to use a real
  directory) and the dropped-routing share on the training-step line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gt50
gt50 merged commit 85f267a into main Oct 4, 2026
2 checks passed
@gt50
gt50 deleted the decode-speed branch October 4, 2026 08:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

0