Repo layout, the make workflow, and how to regenerate kernels.
cd ~/Development
git clone https://github.com/thewafflehaus/FFAI
git clone https://github.com/thewafflehaus/metaltile # sibling repo, required
cd FFAI
make setup-dev # wraps ./scripts/setup-dev.sh
make install-hooks # point core.hooksPath at scripts/hooks/make setup-dev verifies:
- Xcode CLI tools (
xcode-select -p) xcrun metal(the full Xcode IDE, not just CLI tools, is required)- Swift toolchain (
swift --version) - Cargo (Rust, for
metaltile) swift-format(auto-installed via Homebrew if missing — needed bymake format-checkand the pre-commit hook)- The sibling
metaltilecheckout at../metaltile
Then resolves SPM deps and runs make build to produce kernels.metallib.
make install-hooks points core.hooksPath at scripts/hooks/ so the in-tree git hooks fire on the appropriate events — see the Git hooks section below. Skip it if you'd rather opt out (or run make uninstall-hooks later).
Sources/
FFAI/ User-facing library
MetalTileSwift/ Pre-compiled kernels + dispatch wrappers
Resources/ kernels.metallib + manifest.json (generated)
Generated/ MetalTileKernels.swift (generated)
FFAICLI/ ffai executable
Tests/
MetalTileSwiftTests/ One file per kernel
FFAITests/ Tensor, Module, KVCache, Sampling, ...
ModelIntegrationTests/ Per-family integration tests — load,
greedy-decode, assert coherent output
planning/ Phased build-out + architecture diagrams
documentation/ User-facing docs (you are here)
scripts/ setup-dev.sh, coverage.sh, verify-docs.sh,
release.sh, integration-bisect.sh,
update-license.sh, install-hooks.sh,
commit_hygiene.py, hooks/{pre-commit,
commit-msg,
pre-push}
For the per-Sources-file purpose see documentation/architecture.md § File layout.
make setup-dev # one-time dev environment setup
make install-hooks # install pre-commit / commit-msg / pre-push
make uninstall-hooks # clear core.hooksPath (opt out of the hooks)
make build # regenerate kernels + swift build (debug)
make build-release # regenerate kernels + swift build -c release
make regenerate-kernels # run `tile build --emit all` only
make test # regenerate kernels + swift test (unit + integration)
make test-unit # FFAITests + MetalTileSwiftTests only (fast)
make test-integration # ModelIntegrationTests only (multi-GB downloads)
make coverage # swift test --enable-code-coverage + summary
make format # swift-format the repo in place
make format-check # swift-format lint (no writes)
make docs # verify swift-docc builds clean
make clean # remove .build + generated artifactsmake build and make test always run regenerate-kernels first — no out-of-date kernels in CI or local dev.
make install-hooks sets core.hooksPath = scripts/hooks so the checked-in hooks under scripts/hooks/ fire on git events. Each hook is intentionally scoped to the cheapest check that catches its class of regression:
pre-commit make format-check (~1-3 s)
commit-msg banned-term + trailer-shape scan (~50 ms)
pre-push make build + make test-unit (~2-3 min)
The full integration suite (make test-integration) is deliberately NOT gated by any hook — multi-GB checkpoint downloads + 15-30 min runtime make it too heavyweight for every commit/push. The release workflow covers it pre-tag instead. See .github/workflows/release.yml.
Bypass any individual hook run with --no-verify:
git commit --no-verify -m "..."
git push --no-verifyUninstall with make uninstall-hooks (clears core.hooksPath).
The commit-msg hook + the parallel commit-check.yml workflow on every PR reject AI attribution pollution — Co-Authored-By: / Signed-off-by: / Generated-by: trailers, 🤖 Generated with <tool> footers, and any git-trailer-shaped line in the trailing paragraph (Word: value). Bare mentions of CLAUDE.md / .cursor/ / model names like llama stay fine — only attribution credit is rejected. See scripts/commit_hygiene.py for the full detection ruleset.
Phrase summary lines as Test results — 7/7 rather than Tests: 7/7 to dodge the trailing-block trailer check.
~/Development/metaltile ← Rust kernel source
cargo run --bin tile ← generates → Sources/MetalTileSwift/
-- emit Resources/kernels.metallib
--out Sources/MetalTileSwift Resources/kernels/*.metal
Resources/manifest.json
Generated/MetalTileKernels.swift
↓
Sources/FFAI/Ops.swift uses
the generated typed wrappers
make regenerate-kernels runs cargo run --release --bin tile -- emit --out Sources/MetalTileSwift from the sibling metaltile repo. Cargo runs from the metaltile dir so its rust-toolchain.toml (nightly, 2024 edition) is honored. Eventually the tile binary will ship via Homebrew so this won't need a metaltile checkout — only kernel authors will need the repo.
The generated artifacts are checked into the FFAI repo so end-user SPM consumers don't need Cargo or the metaltile checkout.
Kernels are Rust functions in metaltile, not Swift. The flow:
- Add a
#[kernel]Rust function tocrates/metaltile-std/src/ops/<file>.rsin the metaltile sibling repo. - Annotate it with
#[bench_kernel(op="…", subop="…", class=…, …)]so the registry picks it up. For kernels without an MLX-comparable bench, submit a hand-rolledBenchSpecwithshapes: &[]and the rightkernel_mode: Some(KernelMode::…)instead. make regenerate-kernelsfrom the FFAI repo — picks up the new kernel, regenerateskernels.metallib+MetalTileKernels.swift.- Add a thin
Ops.swiftwrapper if the typedMetalTileKernelssignature isn't ergonomic enough for callers. - Add a
Tests/MetalTileSwiftTests/test against fixed inputs / outputs.
See adding-a-model.md for the full walk-through. TL;DR:
- New
Sources/FFAI/Models/<Family>.swiftwith a<Family>enum declaringmodelTypes+architectures, a<Family>Variantprotocol, and one or more variant structs. - Register the family in
Sources/FFAI/Model.swift→ModelRegistry.dispatchAndLoad. - Add
Tests/ModelIntegrationTests/<Family>IntegrationTests.swift— load the smallest published checkpoint, greedy-decode, and assertexpectCoherentOutput(...).
See testing.md for running tests, the expectCoherentOutput integration model, and coverage targets.
- Swift formatting —
swift-formatper.swift-format. CI gates onmake format-check. - Comments — sparing. Lead with WHY, not WHAT. The Tensor/Module/Layer naming carries the WHAT.
- No mocking the GPU. Every test runs real Metal dispatches on the CI runner (Apple Silicon). Numerical correctness comes from the metaltile-side per-kernel GPU-correctness tests (vs a naive CPU oracle); FFAI integration tests assert the model pipeline produces coherent text. There are no golden fixtures.
- No unused / speculative code. Build only what the active phase needs. Future-phase fields go into
LoadOptionswith a comment pointer to the phase, not stubs in the call path.
- Forgot the metaltile checkout.
make regenerate-kernelswill fail withmetaltile not found at ../metaltile. Clone it. - Used Cargo from the FFAI repo. Will fail with edition=2024 errors. Always
cd metaltile && cargo run …(the Makefile does this for you). - Hit the page-alignment crash when adding a new tensor loader.
MTLBuffer.bytesNoCopyrequires page-aligned (16KiB) pointers; per- tensor offsets aren't aligned. Usedevice.makeBuffer(bytes:length:)instead — the existing SafeTensors path already does. - Sendable warnings on Metal types.
MTLBuffer/MTLDevicearen'tSendable. Wrap holders in@unchecked Sendablerather than fighting the compiler.
- Testing — running tests, integration coherence checks, coverage.
- Adding a model — porting a new architecture.
planning/plan.md— what's in / out of scope per phase.