Skip to content

RFD 0011 U8: native EdgeTAM tracker C-ABI + CoreML accel + encoder-ahead threading (18.55 fps @ 0.98)#2

Draft
njayj wants to merge 14 commits into
rfd-0011-edgetam-runtime-refactorfrom
claude/sam3cpp-coreml-tracker
Draft

RFD 0011 U8: native EdgeTAM tracker C-ABI + CoreML accel + encoder-ahead threading (18.55 fps @ 0.98)#2
njayj wants to merge 14 commits into
rfd-0011-edgetam-runtime-refactorfrom
claude/sam3cpp-coreml-tracker

Conversation

@njayj

@njayj njayj commented Jun 18, 2026

Copy link
Copy Markdown

What

RFD 0011 U8 engine work — the cgo entry point + CoreML acceleration of the
real EdgeTAM tracker. Stacked on #1 (rfd-0011-edgetam-runtime-refactor);
review/merge #1 first.

Draft — automated work + local verification done; engineer owns ready/merge.
All new behavior is env/flag-gated; the default ggml path is unchanged.

Commits

  1. extern "C" tracker C-ABI (coreml/edgetam_capi.{h,cpp}) — POD-only
    seed/track/reset over the visual tracker, compiled into libsam3.a.
    Verified: pure-C link + run (version=1.0.0) against ggml + the CoreML
    frameworks. This is what iris/platform cgo-binds (see platform#885).
  2. CoreML memencode in the tracker (WIP, gated SAM3_COREML_MEMENC) — speed
    path; see feat(cuda): ggml-CUDA EdgeTAM backend (NVIDIA fast-path) #3 for why it's gated off.
  3. memencode parity harness + definitive finding (SAM3_COREML_MEMENC_PARITY).
  4. SAM3_COREML_PAD_BANK — pad the mem bank to full capacity from frame 1 so
    CoreML mem-attn runs every frame.

Measured (M-series, DanceTrack golden clips, seed-once tf@0.5)

Working native trackerPAD_BANK + CoreML enc/mem-attn/decoder + ggml memencode:
dt0004 0.952 / dt0006 0.933 (0–2 wrong) @ 11.4 fps — a 4.4× speedup over
pure-ggml (2.57 fps / 0.938) with accuracy held/improved, 0-wrong-person.
fps ladder: 2.57 → 5.4 → 7.8 → 11.4.

Path to 20 fps (root-caused): CoreML memencode would remove the last ggml stage
(→14.3 fps) but breaks accuracy — proven via the parity harness to be the ggml
perceiver hand-port being ~0.85 off PyTorch
(the CoreML export is correct: 0.996
vs PyTorch). The clean 20 fps route is a fully pure-CoreML real-bank tracker;
de-risked recipe in platform#885's execution-results.md.

Companion

Platform-side (cgo binding + plans + results): tryiris-ai/platform#885.

🤖 Generated with Claude Code


Update — Wave 5: encoder-ahead threading + cgo consumer

  • capi encoder-ahead split (edgetam_capi_encode_slot / edgetam_capi_track_slot,
    producer encoder handle + Go-coordinated 3-slot neck pool) + smoke test
    sam3_coreml_capi_threaded: 18.55 fps @ 0.979 tracked, 0 wrong on dt0004 (up from
    14.3 single-thread). Also dropped rope_k_data from the CoreML mem-attn hot path
    (unused there; accuracy held).
  • This branch (@0df3d64) is vendored as libsam3.a by platform PR
    tryiris-ai/platform#886
    (the in-process cgo hold runtime). In-bridge: 13.5 → 18.6 fps.

20 fps is the consumer-sequential ceiling; a clean 20+ needs a faster/quantized mem-attn
model or smaller memory bank (real ML, separate work).

njayj and others added 10 commits June 17, 2026 22:27
edgetam_capi.{h,cpp}: POD-only seed/track/reset over the visual tracker
(sam3_create_visual_tracker / propagate_frame); CoreML stages enabled from
models_dir. Compiles into libsam3.a; verified pure-C link + run against the
ggml dylibs + CoreML/Foundation/Metal/MetalKit/Accelerate + -lc++.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…RFD 0011 U8 Wave 1)

sam3_encode_memory: SAM3_COREML_MEMENC gate calls edgetam_coreml_memencode and
stores the perceiver slot, skipping the per-frame ggml memencode graph.
SPEED VERIFIED: dt0004 7.8→10.86 fps (mem_encoder 24→0ms, full-CoreML 4-stage).
ACCURACY BROKEN: 0.000 (107 lost) — memencode slot-parity bug (pix_feat/mask
inputs layout-verified correct; output/model-expectation parity is the remaining
unknown; needs a cosine-compare vs edgetam_perceiver_forward). Gated OFF by
default — the ggml memencode remains the correct path. Not for merge as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…FD 0011 U8)

Parity probe (SAM3_COREML_MEMENC_PARITY) cosine-compares CoreML memencode vs the
ggml perceiver slot per axis. Result on dt0004: pos cos=1.000, feats cos≈0.85 on
IDENTICAL pix_feat+mask (transpose verified: H/W-swap=0.43; mask not the driver).
=> representation mismatch between ggml perceiver and CoreML spatial_perceiver,
not a layout/input bug. The ggml assembly+tpos+memattn are calibrated to the ggml
representation, so the 0.85 slot breaks the track. CONCLUSION: 20fps requires a
fully pure-CoreML tracker (consistent representation end-to-end), not a CoreML
memencode drop-in to the ggml hybrid. memencode gate documented DO-NOT-USE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 0011 U8 Wave 1)

SAM3_COREML_PAD_BANK pads the mem bank to full capacity (num_maskmem spatial +
max_obj_ptrs pointer slots) from frame 1, so the CoreML mem-attn runs EVERY frame
(no ggml fallback on early frames). With CoreML enc/memattn/decoder + ggml
memencode (correct slots): dt0004 0.952 / dt0006 0.933 (0-2 wrong) @ 11.4 fps —
a 4.4x speedup of the real tracker with accuracy HELD/IMPROVED (vs pure-ggml
2.57fps/0.938). Padding also fixed the early-frame accuracy churn (0.882→0.94).
20fps blocker: ggml memencode (23ms) — CoreML memencode is 4.6x faster but its
slots are 0.85-off (model-level bug, see prior commit). Also keeps the parity
probe (swapHW variant). All env-gated; default ggml path unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…age 0.94 @ ~14.5 fps (RFD 0011 U8)

ROOT CAUSE: the memencode export wrapped memory_encoder(skip_mask_sigmoid=False)
— sigmoid only — but the tracker feeds sigmoid(mask)*sigmoid_scale_for_mem_enc +
sigmoid_bias_for_mem_enc (EdgeTAM 20.0/-10.0). So the CoreML model saw a [0,1]
mask vs the tracker's [-10,10] → garbage slot → tracker broke (goldeneval 0.000;
Python proto 0.022). The earlier 0.996 "parity" compared against the same
scale/bias-less wrapper, hiding it. PyTorch-vs-CoreML check confirmed.

FIX: convert_memenc_coreml.py bakes sigmoid*scale+bias (skip_mask_sigmoid=True),
+ pins stable output names mem_feats/mem_pos (coremltools auto-names var_NNN
change per export and silently broke the .mm featureValueForName lookup → ggml
fallback). edgetam_coreml.mm updated to the stable names.

RESULT (re-exported model): C++ 4-stage CoreML (PAD_BANK + enc/memattn/decoder/
memencode) dt0004 0.970 @ 14.1 fps, dt0006 0.927 @ 15.2 fps — up from 11.4 fps
(ggml memencode), accuracy held, 0-wrong. Validated independently in the Python
SAM2-predictor proto (1.000 with CoreML enc+memattn+memencode).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…011 U8 Wave 1)

Encoder-ahead pipelining: a producer thread runs preprocess + CoreML encode for
frame N+1 (own MLModel handle, ANE) while the main thread runs the consumer
(assembly + memattn + decode + memencode + bank, via sam3_propagate_frame) for
frame N. Library hook sam3_coreml_set_prefetched_neck + sam3_coreml_preprocess_image
let the consumer's encode step consume the producer's neck (skipping preprocess +
encode). New bench examples/sam3_coreml_tracker_threaded.cpp.

RESULT: dt0004 0.941 @ 18.58 fps, dt0006 0.882 @ 18.62 fps — up from 14.5 fps
single-threaded (~1.28× overlap), accuracy held, 0-2 wrong. 7.2× over pure-ggml
(2.57 fps). At the top of RUNTIME.md's measured 15-20 fps sustained band.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The C-ABI used by platform's in-process cgo tracker (RFD 0011 U8) set only
ENCODER/MEMATTN/DECODER. Without MEMENC + PAD_BANK the CoreML mem-attention
model (static 3648-token input) can't match the live bank and silently falls
back to the ~184ms ggml mem-attention path — correct but ~8x slower.

Setting both brings the capi to the full validated config: encoder ANE +
memenc ANE + mem-attn GPU + decoder ANE. Measured via platform's cgo hold
runtime test: 570ms -> 70ms/frame (dt0004, 29/29 held, 14.3 fps single-thread).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
rope_k_data feeds only the ggml mem-attn fallback graph; the CoreML mem-attn
path uses cached_sinpe_256 and never reads it, so building it every frame was
~2ms of pure waste. Moved its construction to just before the ggml graph build
(reached only when CoreML did not return). Behavior-preserving: dt0004
goldeneval held 0.979 tracked_fraction, 0 wrong.

Honest note: the ~2ms saving is within thermal run-to-run variance — this is a
cleanup that documents the finding, NOT a path to 20fps. Per-frame cost is
dominated by the CoreML stage compute (mem-attn ~17-19ms GPU + encoder ~11ms +
decoder ~8ms + memencode ~5ms) plus preprocess (~5-7ms) and the irreducible
host-side memory-bank assembly; trimming CPU glue cannot close the gap to 20.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…go threading

RFD 0011 U8 Wave 5. Producer encoder handle + a 3-slot neck pool; Go coordinates
slot free/ready via channels (one writer then one reader per slot, no lock).
encode_slot (producer thread) preprocesses+encodes into a slot; track_slot
(consumer thread) consumes the prefetched neck and propagates. Mirrors
examples/sam3_coreml_tracker_threaded.cpp through the C-ABI. Existing
create/seed/track/reset/destroy unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…0011 U8 Wave 5)

Drives edgetam_capi_encode_slot/track_slot via a producer/consumer thread pair —
the exact C-ABI path cgo will use — on dt0004: 18.55 fps (vs 14.3 single-thread),
goldeneval tracked_fraction 0.979, 0 wrong. De-risks the Go wiring: the capi
split reproduces the standalone threaded bench's speedup with accuracy intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@njayj njayj changed the title RFD 0011 U8: native EdgeTAM tracker C-ABI + CoreML accel (11.4 fps @ 0.94) + 20 fps root-cause RFD 0011 U8: native EdgeTAM tracker C-ABI + CoreML accel + encoder-ahead threading (18.55 fps @ 0.98) Jun 18, 2026
njayj and others added 4 commits June 19, 2026 14:12
…tooling

RFD 0011 U8. Applies Egor's METHODOLOGY (per-module compute-unit sweep) to the
M4 instead of chasing his M1 result. sam3_coreml_pipeline now parameterizes all
4 stage units (SAM3_{ENC,MA,DEC,MENC}_UNIT_N); the real tracker reads
SAM3_COREML_{ENC,MA,DEC,MENC}_UNIT for per-chip tuning.

M4 Pro sweep (isolated 20-rep median, 0=ALL 1=ANE 2=GPU 3=CPU):
  encoder   ANE 11.0 | GPU 14.3 | CPU 37.7 | ALL 17.6  -> ANE
  mem-attn  ANE 26.3 | GPU 17.2 | CPU 24.0 | ALL 33.3  -> GPU
  decoder   ANE  7.8 | GPU  8.2 | CPU 13.9 | ALL  8.9  -> ANE
  memenc    ANE  5.0 | GPU  3.9 | CPU  9.9 | ALL  6.7  -> GPU (isolated)

Finding: our M1-era placement (enc ANE, mem-attn GPU, dec ANE, memenc ANE) is
ALREADY M4-optimal — NOT inverted like the ORT/EfficientTAM stack (where the
encoder flips to GPU), because our RepViT encoder is conv-heavy/ANE-friendly and
runs as a standalone .mlpackage (no ORT graph partitioning, no -14). memenc's
isolated GPU edge (3.9 vs 5.0) does NOT survive thermal noise end-to-end (A/B:
GPU 20.5 vs ANE 19.7 at 200 frames, but GPU 13.7 vs ANE 15.3 at 300 frames —
thermal throttling dominates), accuracy identical (0 wrong), so the default
stays ANE. Knobs let a thermally-stable rig re-profile per chip.

Defaults unchanged => behavior-preserving. dt0004 full-clip threaded 0.979, 0 wrong.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(RFD 0011 W0)

Wires GGML_VULKAN behind a SAM3_VULKAN option (non-Apple) so the same ggml EdgeTAM
engine runs on AMD/NVIDIA/Intel via Vulkan, and compiles the cgo C-ABI
(edgetam_capi.cpp) on the Vulkan path WITHOUT the CoreML bridge: all
edgetam_coreml_*/sam3_coreml_* calls + the encoder-ahead producer pool are now
#ifdef SAM3_COREML-guarded, so a Vulkan build runs the pure-ggml path (which lands
on the Vulkan device) and pool_size()==0 (synchronous Track, no threading). Mac
SAM3_COREML build verified unchanged (guards are additive). Build + measure on
real AMD/NVIDIA hardware is still required (W0.4 gate) before this is validated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 0011 W0)

The loader was #ifdef GGML_USE_METAL → Metal, else CPU — so a GGML_VULKAN build
compiled the Vulkan backend but never used it (model loaded on CPU). Add the
GGML_USE_VULKAN branch (ggml_backend_vk_init(0)) + the ggml-vulkan.h include.

VALIDATED on Mac via MoltenVK (the only Vulkan device available here): model now
loads "using Vulkan backend" on the M4 GPU, runs EdgeTAM end-to-end at 2.88 fps
(encoder 102ms, mem-attn 119ms — note ggml-Vulkan mem-attn BEATS ggml-Metal's
184ms). MoltenVK has no matrix cores + translation overhead, so this is a
worst-case proxy, NOT representative of native AMD/NVIDIA Vulkan — the real W0.4
per-vendor gate still needs that hardware. Mac SAM3_COREML build unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The U2 fast path derived the bbox from the low-res logit grid and never built a
mask bitmap, so the cgo tracker had no mask to ship. Now populate a CHEAP
low-res (256x256) binary mask from po.mask_logits on the fast path (no full-res
upscale -> no perf regression, ~12.5fps held) and attach it to the result
detection unconditionally (was gated behind SAM3_FULLRES_MASK). New
edgetam_capi_last_mask(out, cap, *w, *h) copies the last track's mask out.
Verified: dt0004 last frame 256x256, 3.8% foreground.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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