8000 Port to AMD Strix Halo (ROCm), plus portability, safety and tests by gt50 · Pull Request #1 · gt50/mini-AGI · GitHub
[go: up one dir, main page]

Skip to content

Port to AMD Strix Halo (ROCm), plus portability, safety and tests - #1

Merged
gt50 merged 3 commits into
mainfrom
strix-halo
Oct 4, 2026
Merged

gt50 merged 3 commits into
mainfrom
strix-halo

Conversation

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

Summary

Ports mini-AGI to AMD Strix Halo (Ryzen AI Max+ 395, Radeon 8060S, gfx1151) on ROCm PyTorch for Windows. It also fixes the portability, safety and hygiene issues found while reviewing the project.

ROCm builds use the torch.cuda namespace, and the code has no custom kernels, Triton or torch.compile. So the port is mostly about what the hardware is, not about the code.

Hardware / Strix Halo

  • minagi/device.py: backend and arch detection, a unified-memory flag, and a configurable GPU memory budget (hardware.gpu_mem_gb → MINAGI_GPU_MEM_GB → reported size). It also sets environment variables torch reads at import: allocator, AOTriton, TunableOp. A hardware: config block drives all of it.
  • Growth's ROOM brake now measures against that budget. Before, it used the device's reported size, which on an APU is only the firmware carve-out.
  • Expert RAM tier on the GPU when memory is unified, so a page-in is a device-to-device copy. ram_cache: 0 keeps the whole pool.
  • serve.py forwards run under amp (bf16), as training does. They ran fp32 before.
  • Hardware banner at startup.
  • configs/strix_halo.yaml continues the released model: the whole pool in device memory and a 100 GB disk ceiling. resident and context_end stay at the released model's 32 and 4096; see Findings on the GPU below.
  • configs/strix_halo_large.yaml starts a new, larger run: d_model 768, 128 experts at birth, resident 128.
  • tools/bench_hw.py times SDPA kernels, expert batched matmuls, a full training step and paging at a config's shapes.
  • README: an "AMD and Strix Halo" section with install, memory setup and what to expect.

Portability

  • --config FILE / MINAGI_CONFIG select the config for every reader in the process.
  • encoding="utf-8" on every text open(). Without it, building the corpus on Windows crashed on cp1252.
  • Plot refresh no longer shells out to bash.
  • NumPy npz handles are closed, so os.replace works on Windows.
  • build_paged and _split_trunk_pool moved into minagi/build.py. The library no longer imports train.py.

Safety and correctness

  • minagi/lock.py: one writer per weights directory. train.py read refuses a directory that is in use. serve.py falls back to read-only.
  • serve.py accepts JSON only, checks Origin and Host (blocks cross-site and DNS-rebinding POSTs, which got trained into the weights), clamps max_new to 2048, and turns learning off by default when bound to a non-loopback host.
  • build_adamw is on 8000 e shared optimizer builder. Before, the server trained routers at the trunk's 0.1× rate.
  • GradSNR restarts when a watched tensor changes size. This fixes a crash at the first expert growth in a fresh run; it reproduces on main too.
  • The torch seed is mixed with characters read, so a resumed run no longer replays the same random sequence.
  • corpora/fetch.py keeps the HF datasets cache unless --drop-cache is passed.

Hygiene

  • pyproject.toml. torch is deliberately not listed, so a ROCm build is never replaced.
  • tests/: 18 tests covering config, device, lock, a paged create→train→grow→save→reload round trip, and serve refusals.
  • .github/workflows/ci.yml: CPU torch on Windows.

Scope: tested on Windows only. Linux-specific setup notes were left out on purpose; the code stays portable, as upstream's was.

Findings on the GPU

Measured with the released weights (Volotat/mini-AGI-undertrained, 886.8M characters read) on a Ryzen AI Max+ 395:

  • Raising resident is not free. At 64, held-out chat went from 0.60 to 0.92 nats and code from 0.71 to 1.19. The model learned to choose within 32 slots, so the continuation profile keeps 32.
  • Raising context_end is not free. Held-out is read at the ceiling. At 8192 the released weights scored 1.66 nats, against 0.62 at 4096, because positions past 4,096 are untrained. Kept at 4096.
  • TunableOp hurts here. The expert dispatch's matmul shapes change with every forward, so it re-tunes nearly every call: one held-out pass ran past half an hour, with over 2,000 shapes tuned. It is off in both profiles.

Test plan

  • pytest passes: 18/18 with CPU torch 2.14, and with ROCm torch 2.12.0a0+rocm7.13 on the 8060S
  • CPU smoke test: train.py read creates, grows, saves and resumes a tiny model; serve.py answers /api/chat
  • Lock: train.py read refuses a directory held by a learning serve.py, and the lock is released at exit
  • tools/bench_hw.py on the 8060S (Windows, ROCm 7.13 nightly):
    • default config: flash SDPA available; training step 1,078 char/s at 4k context, 5.1 GB peak
    • strix_halo.yaml (resident 64, 8k context, the earlier version of the profile): 1,000 char/s, 7.8 GB peak
    • strix_halo_large.yaml: 530 char/s at 8k context, 17.4 GB peak (expert bmm 18 TFLOP/s fwd)
    • tier→slot copy 0.05 ms with the tier on the device, against 0.53 ms from host
  • 20-minute training session on the GPU with the released weights (--config configs/strix_halo.yaml, 3 local lanes: arithmetic, code, chat):
    • held-out (60 chunks per subject): 0.6218 ± 0.011 before → 0.6055 ± 0.012 after (−0.016)

    • 1.20M characters read; mean training loss 0.585; 1,255 char/s while reading (1.0k char/s over the session, evaluations and samples included)

    • 1.7 experts paged onto the GPU per chunk, 92% from the in-memory cache

    • growth 127 → 128 experts at a 9.8 GB peak

    • checkpoints at step 433,537 and 433,936; final save 3.68 GB; lock released at exit

    • reload check: the saved directory loads (128 experts, step 433,936) and scores like the released weights on the same 4 chunks per subject:

      subject released after the session
      arithmetic 0.582 0.580
      chat 0.535 0.479
      code 0.547 0.564

      Standard error is about 0.05 at that size, so only chat is clearly better.

  • CI green on this PR

🤖 Generated with Claude Code

gt50 and others added 3 commits October 3, 2026 20:44
Hardware:
- minagi/device.py: one place for backend/arch detection, the unified-memory
  flag, a configurable GPU memory budget, and env vars torch reads at import
  (allocator, ROCm AOTriton, TunableOp), driven by a `hardware:` config block
- growth's ROOM brake measures against that budget instead of the device's
  reported size, which on an APU is only the firmware carve-out
- the expert RAM tier can live on the device (default on unified memory), so
  paging onto the card is a device-to-device copy; ram_cache 0 = keep all
- serve.py runs its forwards under amp (bf16), as training does
- hardware banner at startup of train.py read and serve.py
- configs/strix_halo.yaml (continue the released model) and
  configs/strix_halo_large.yaml (a new, larger run); README section
- tools/bench_hw.py: SDPA kernels, expert bmm, a full training step and
  paging latency at a config's shapes

Portability and hygiene:
- --config / MINAGI_CONFIG select the config file for every reader
- encoding="utf-8" on every text open (cp1252 crashed corpus builds)
- plot refresh no longer shells out to bash
- npz handles closed so os.replace works on Windows
- build_paged/_split_trunk_pool moved to minagi/build.py; the library no
  longer imports train.py
- GradSNR restarts when a watched tensor changes size (crashed on the
  first growth in a fresh run)
- pyproject.toml; .gitignore fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- minagi/lock.py: one writer per weights directory (O_EXCL lock file with
  pid/host/role, stale-lock takeover). train.py read refuses a held
  directory; serve.py falls back to read-only instead of learning
- serve.py: JSON-only /api/chat, Origin and Host checks (cross-site and
  DNS-rebinding POSTs were trained into the weights), max_new clamped to
  2048, learning off by default when bound to a non-loopback host
- minagi/build.py build_adamw: one trunk/pool split for train.py read,
  live.py and the replication probe; the server now trains routers at the
  pool rate as training does
- torch seed mixed with characters read, so resumes stop replaying one
  random sequence
- corpora/fetch.py keeps the HF datasets cache unless --drop-cache, and
  honours HF_DATASETS_CACHE / HF_HOME
- tests/ (config, device, lock, paged round trip, serve refusals) and a
  CPU CI workflow on Ubuntu and Windows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gt50
gt50 merged commit 91b4c0d into main Oct 4, 2026
4 checks passed
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