Skip to content
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
2eab5a1
perf(device): MTLResidencySet pin helper on Device
TheTom May 26, 2026
a47f077
perf(loader): pin model weights in MTLResidencySet at load time
TheTom May 26, 2026
aa833d7
KVCache: pin K/V/GDN/conv buffers in residency set on init
TheTom May 26, 2026
50b45e4
Ops: add scalarFMA family + sigmoidScalarFMAResidual for MoE accumulator
TheTom May 26, 2026
344a565
Ops: add dequantGemvInt4Two/Three/Four — batched int4 dequantGemv
TheTom May 26, 2026
49871a9
Ops: add ropePartialTwo — pair Q+K RoPE in one compute encoder
TheTom May 26, 2026
ec29553
Ops: add gatherTwo — two embedding lookups on one encoder
TheTom May 26, 2026
aae1c60
Ops: add rmsNormRowsTwo — pair RMSNorm rows on one encoder
TheTom May 26, 2026
fb10e38
Ops: add kvCacheUpdateKV — append K and V on one encoder
TheTom May 26, 2026
ae53402
Ops: add siluCastF32PlusCastF32Two — silu+cast plus two plain casts o…
TheTom May 26, 2026
ff27159
Ops: GPU MoE router — moeRouterTopK + dequantGemvInt4ExpertIndexed
TheTom May 26, 2026
322ee15
Ops: sdpaMulti — route head_dim 256 to the d256 kernel variant
TheTom May 26, 2026
90e2695
Ops: add castToF32Two/Three, swigluMany, gatedMixerNormMany
TheTom May 26, 2026
11df54f
Ops: batched-prefill wrappers — ropePartial(Many|ManyTwo), kvCacheUpd…
TheTom May 26, 2026
991c214
Ops: add dequantGemvInt4Many — N projections, distinct inputs, one en…
TheTom May 26, 2026
70013e0
Ops: add sdpaMultiTreeMask — tree-causal SDPA for tree-verify spec de…
TheTom May 26, 2026
fcd448f
Ops: add sdpaDecode2Pass — Flash-Decoding two-pass SDPA
TheTom May 26, 2026
d818375
Ops: add batchedQkvQgemvInt4Fast + batchedQkvQmmFast (fused Q+K+V pro…
TheTom May 26, 2026
01f591c
Ops: add batched4QgemvInt4Fast + batched4QmmFast (fused 4-output for …
TheTom May 26, 2026
97b4ca9
Ops: add rmsNormQgemvInt4Fast + gatedRmsNormQgemvInt4Fast (fused norm…
TheTom May 26, 2026
55461a6
Ops: add moeDownSwigluAccumInt4Chain8 (3-stage MoE fusion)
TheTom May 26, 2026
141f614
KVCache: add appendRangeOnGPUMany + reserveSlotsManyOnHost
TheTom May 26, 2026
798915a
Qwen35AttentionMixer.forward: wire batched QKV + 2-pass SDPA + cached…
TheTom May 26, 2026
13aa4a9
Generation: add Drafter protocol + NGramDrafter + tree-verify scaffol…
TheTom May 26, 2026
eddc74e
Qwen35AttentionMixer.forwardMany: wire batched QKV qmm + sdpaMulti + …
TheTom May 26, 2026
f0ceb95
Qwen35DenseMLP + Qwen35MoEFFN shared expert: use fused Ops.swiglu
TheTom May 26, 2026
0abc058
Qwen35Model: wire fused finalNorm+lmHead via rmsNormQgemvInt4Fast
TheTom May 26, 2026
9439e68
Qwen35GDNMixer.forward: wire batched 4-projection + fused silu+cast2
TheTom May 26, 2026
3d306c6
Qwen35GDNMixer.forwardMany: wire chunked recurrence + conv1d-many
TheTom May 26, 2026
59595ec
Qwen35MoEFFN.forward: shared-encoder batching + fused residual FMA
TheTom May 26, 2026
a0dcfe6
feat(moe): wire GPU router fast path in MoELayer.decode
TheTom May 26, 2026
5c0dbea
feat(moe): wire GPU router fast path in MoELayer.decodeMany + bm64 de…
TheTom May 26, 2026
f960dfc
test(qwen36): add forwardManyBench T=2048 long-context bench case
TheTom May 26, 2026
844ed39
feat(qwen35): add forwardManyAllLogits for per-position spec-decode v…
TheTom May 26, 2026
d0270ac
feat(kvcache): add snapshot/restore primitives + composite cache snap…
TheTom May 26, 2026
d45d609
feat(spec-decode): add greedy speculative-decode driver SpecDecode
TheTom May 26, 2026
8de0f9b
fix(tests): silence MTLComputePipelineState Sendable error in PSOCach…
TheTom May 26, 2026
f1684a1
test(spec-decode): port end-to-end SpecDecode greedy bench against Qw…
TheTom May 26, 2026
d016ba1
test(spec-decode): port verify-cost bench — forwardManyAllLogits vs s…
TheTom May 26, 2026
2529b1c
fix(firered-vad): break up BINUNICODE8 length expression so Swift can…
TheTom May 26, 2026
0466946
test(qwen35-moe): add Qwen3.5-35B-A3B forwardMany + decode T=1 bench …
TheTom May 26, 2026
d42df3b
fix(layers): QuantizedLinear.callMany 4-bit fast path needs outDim % …
TheTom May 26, 2026
d80350a
perf: bump default maxCommandBufferCount 16→64 + Ops.swiglu in Granit…
TheTom May 26, 2026
f56a799
test(qwen35-moe): add T=2048 long-context + decode-after-1024-prefill…
TheTom May 26, 2026
ba1b2b6
test(qwen35-moe): replace T=1024 decode-after-prefill with T=32K long…
TheTom May 26, 2026
0983066
fix(tests): portable cmd.awaitCompletion() bridges Xcode 16.4 + 26.5
TheTom May 26, 2026
2a03114
test: gate bm64_mpp bf16 cells on Apple GPU Family 9+
TheTom May 27, 2026
4d7d4de
chore(qwen35): remove dead let _ = and unused dtype bindings
ekryski May 27, 2026
90aeafc
fix(ops): cross-check K/V packed widths in batched* fast wrappers
ekryski May 27, 2026
ac3ce85
docs(threading): document single-inference-per-instance contract
ekryski May 27, 2026
272d8f1
docs(kvcache): document cachedSnapshot reuse contract
ekryski May 27, 2026
8f3c409
docs: strip internal iteration markers from code comments
ekryski May 27, 2026
d2367da
docs(copyright): credit Tom Turney on co-authored + new files
ekryski May 27, 2026
8c9e863
test(ops): pin sdpaDecode2Pass against single-pass sdpaDecode
ekryski May 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions Sources/FFAI/Device.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ public final class Device: @unchecked Sendable {
public let mtlDevice: MTLDevice
public let commandQueue: MTLCommandQueue

/// Lazy MTLResidencySet that pins the model's weight buffers so
/// every command buffer skips per-allocation residency tracking.
/// Populated after `Model.load` finishes. Typed as `Any?` so the
/// deployment target stays below macOS 15; the cast back to
/// `MTLResidencySet` lives inside the `@available` block in
/// `markWeightsResident`. Initialised under `residencyLock` to
/// single-flight the descriptor build.
private var weightResidencySet: Any?
private let residencyLock = NSLock()

public static let shared: Device = {
// Reuse the same MTLDevice + queue MetalTileSwift uses, so PSOs
// and buffers are guaranteed compatible.
Expand Down Expand Up @@ -51,4 +61,40 @@ public final class Device: @unchecked Sendable {
}
return cb
}

/// Add `buffers` to a persistent MTLResidencySet attached to the
/// command queue. Without this, Apple's Metal driver re-validates
/// per-allocation residency on every command-buffer encode — at
/// model sizes with tens of thousands of dispatches per prefill,
/// the per-dispatch overhead dominates wall time. One residency
/// set is shared across all weight buffers; repeated calls add
/// to it. Requires macOS 15+ / iOS 18+; older OSes silently
/// no-op. Set `FFAI_NO_RESIDENCY_SET=1` to disable for A/B.
public func markWeightsResident(_ buffers: [MTLBuffer]) {
if ProcessInfo.processInfo.environment["FFAI_NO_RESIDENCY_SET"] != nil { return }
guard #available(macOS 15.0, iOS 18.0, *) else { return }
residencyLock.lock()
defer { residencyLock.unlock() }
if weightResidencySet == nil {
let descriptor = MTLResidencySetDescriptor()
descriptor.label = "FFAI weights"
descriptor.initialCapacity = max(buffers.count, 1024)
do {
let set = try mtlDevice.makeResidencySet(descriptor: descriptor)
commandQueue.addResidencySet(set)
weightResidencySet = set
} catch {
// Driver refused to create the set; fall back to default
// residency tracking. Not fatal — just slower.
weightResidencySet = nil
return
}
}
guard let set = weightResidencySet as? MTLResidencySet else { return }
for buf in buffers {
set.addAllocation(buf)
}
set.commit()
set.requestResidency()
}
}
Loading