Skip to content

dasLLAMA: dig the last two board reds green — lcpp-shape mx4 kernels + GPU PLE pre-step, fresh M1 board#3558

Merged
borisbat merged 15 commits into
masterfrom
bbatkin/dasllama-reds-dig2
Jul 24, 2026
Merged

dasLLAMA: dig the last two board reds green — lcpp-shape mx4 kernels + GPU PLE pre-step, fresh M1 board#3558
borisbat merged 15 commits into
masterfrom
bbatkin/dasllama-reds-dig2

Conversation

@borisbat

Copy link
Copy Markdown
Collaborator

Digs the two remaining public-board reds — gpt-oss (tg 0.963 / pp 0.88) and E4B (pp 0.856) — with measurement hygiene as position 0. Both cells flip green; every number below is a paired same-process run vs llama.cpp stock on M1 Max at llama-bench shapes.

Position 0 — hygiene

  • dasMetal per-site leak attribution: every live handle now carries a type[bytes] @ file:line tag (metal_live_object_report(), metal_log_leaks_if_any() in das_metal_boost); the 5 dasLLAMA metal test gates assert through it. It named the historic 51-object leak in one shot: the single-decode MoE release ledger forgot u_moe_ss/u_moe_kdim/u_moe_tot — 3 pool_release lines, fam-gptoss 15 leaks -> 0.
  • 27B cpu record refresh (gen_bench_records) + the dn-plane fix in the exec_fmt receipt.
  • q8q8 crown restored on the 12B-Q8 tune sidecar (stale kstep2 pin beat by mr8_budget +6.8%).
  • pdflatex: declare θ (U+03B8) and − (U+2212) so the doc PDF gates keep passing with the tutorial revision.

gpt-oss: mx4 kernels to the lab-winning shape

bench_metal_moe_lab grew a gmx4 section at exact gpt-oss shapes (E=32, nfe=dim=2880, k=4, CPU-ref bit-exact) and laddered the routed expert GEMV from the production 239 wGB/s to 301:

rung wGB/s change
production 239 8 threads/block, 4B uint loads (each loaded twice), device LUTs
tgmem LUT 257 llama.cpp's 16-value LUT copied to threadgroup memory
lcpp mapping 272 16 blocks in flight x 2 threads/block, yl[4] float4 x prefetch, float4 accumulators
+ uint2 loads 275 each weight byte loads exactly once
+ computed e8m0 301 as_type<float> in ALU — kills the mxe->etab dependent device-load chain

Refuted along the way: ALU e2m1 decode (2.5x worse — tables win on AGX), etab-to-tgmem (broadcast reads are already on the uniform path).

Both production mx4 kernels get the winning shape — MetalMoeGemvMx4 (decode GEMV) and MetalMoeMulMmMx4's A-stage (uint4 block load + tgmem LUT + computed e8m0). Both e8m0 device tables are deleted; the ALU form is bit-exact for all 256 exponent bytes including the e<2 denormals.

  • tg128: 86.58 vs 82 = 1.06 GREEN (was 0.963 red)
  • pp512: 986.9 vs 1024 = 0.963 (was 0.88); steady das GPU 515ms vs lcpp wall 500ms — the remaining ~3.5% needs its own prefill chase

msl_emit grows the standing-rule capability behind this: the daslib/math_bits bitcast family lowers to as_type (plus exp2/log2 in the math whitelist), with real-GPU coverage in tests/metal/test_metal_math.das.

E4B: PLE pre-step on GPU

The E-series prefill pre-step (gather 512 token rows off the 3GB Q8_0 per_layer_token_embd, the 14-GMAC bf16 model_proj GEMM, rms/combine) ran on CPU — 126ms beside a 529ms GPU window that already beat lcpp's whole wall. It now runs as three dispatches at the head of the prefill graph:

  • MetalPleGatherQ8 — dequants token-row slices straight off the blob's q8 views (zero-copy on mapped images) into the layer-major side panel, sqrt(ple) folded; the CPU-side transpose memcpy is gone with it
  • MetalBf16MulMm — the q8 mul_mm with its A stage swapped to native BF16 rows (widen = exact bit shift via the new bitcast); model_proj streams from the kept-bf16 plane, no resident f32 copy
  • MetalPleFinish — per (layer, position) slice rms + pnorm + combine, in place

Plumbing: register_ple_gpu_gate — forward_prefill asks the driver before the CPU pre-step and stashes the token rows on yes; the driver falls back to ple_pre_prefill on a late decline, and ple_block_prefill panics loud if a promise ever leaks to the CPU body. Decode PLE (1 row/step) stays CPU by design.

  • pp512: 959.2 vs 916 = 1.05 GREEN (was 0.856 red); steady prefill wall 655 -> 530ms, GPU window +1-3ms
  • tg128: 0.98 (path untouched)

Verification: fam-gptoss tolerance cells bit-identical pre/post the kernel swap; fam-gemma4e green including the token-for-token generate vs CPU truth; model-less kernel-unit gates bit-exact (odd-shape tails, bias and batch-stream arms); llama prefill base arm green; full preflight green.

The fresh board

Full M1 re-sweep after the fixes (all 8 catalog models, cpu+metal, refs adjacent per cell, quiet window, r=5) — records + the hidden site section regenerated. das leads or ties 41 of 48 cells:

model metal pp/tg cpu vs clean pp/tg cpu vs stock pp/tg
Qwen3.6-35B-A3B-MTP 1.02 / 1.20 1.39 / 1.12 1.45 / 1.12
Qwen3.6-27B-MTP 0.91 / 1.04 1.03 / 1.05 1.02 / 1.05
gemma-4-12B 1.04 / 1.16 1.11 / 1.05 1.11 / 1.04
gemma-4-E4B 1.06 / 1.00 1.58 / 1.08 0.96 / 1.11
gpt-oss-20b 0.92 / 1.06 1.74 / 0.98 1.75 / 0.98
Qwen3-30B-A3B 1.00 / 1.14 1.60 / 1.10 1.62 / 1.02
Mistral-Small-24B 1.01 / 1.16 1.06 / 1.00 1.04 / 1.01
gemma-4-26B-A4B 1.07 / 0.98 1.10 / 0.97 0.86 / 0.97

Every sweep-era window-skew red re-paired clean (30B metal tg 0.95 -> 1.14, Mistral pp cells, 27B cpu tg, 12B cpu pp). Remaining sub-parity, honest: 27B metal pp 0.91 (dense k4 mul_mm A-stage — next chase), gpt-oss metal pp 0.92 (0.96 quiet-paired; a fresh-process first-rep PSO-compile stall skews the sweep mean — warmup-rep fix is a tool follow-up), and the AMX-stock cpu-pp class on 26B/E4B (0.86/0.96 — both lead the kernel-fair clean-cpu ref).

Ride-along: the tune confirm pass now echoes its child's output tail when an arm scores 0 (a swallowed 'cannot open gguf' cost a debug round mid-sweep).

🤖 Generated with Claude Code

https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT

borisbat and others added 12 commits July 23, 2026 22:05
The shim's g_metalLiveObjects counter says HOW MANY handles leaked but
not which — the matrix-suite red (51 live objects) has been undiagnosable
from the count alone. Every retain_handle now records "type @ file:line"
of the das creation site (LineInfoArg already reaches every creator);
metal_live_object_report() returns the grouped live set. Volume is low
(planes at load, cb/encoder per step), so the mutex-guarded map costs
nothing measurable.

assert_no_metal_leaks panics with the report; the new
metal_log_leaks_if_any() keeps dastest leak gates one line while a red
run names its leaks — all five dasLLAMA metal test gates swapped onto it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
…ceipt

The 27B-MTP cpu row predated the dn kq-native fix (#3544): pp 31.0/tg 5.97
at 8d6fe07. Fresh same-window trio (das + both refs, gen_bench_records):
das 29.88/6.73 -> tg 1.04x both refs (the board red flipped), pp 1.14x
clean / 1.12x stock.

model_exec_fmt never walked the deltanet fmt tag arrays, so the receipt
hid the dn plane formats (27B ran k5 ssm_out unlisted — the old and new
rows read identically despite the format change underneath). Recurrent
rows now feed the receipt: "weights k4/k5/k6/q8 native".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
The matrix-suite leak (51 objects across the three MoE family gates,
untracked since <=92033b08e) was three uniforms: the single-decode run
state acquires 12 r.u_moe_* buffers but its release ledger freed only 9 —
u_moe_ss / u_moe_kdim / u_moe_tot never came back (the batch path's
deferred list carries all three; the single path forgot them). 3 leaks
per run-state cycle explains every historic count (15/30/36, all
divisible by 3, MoE families only).

Found in one run by the new per-site attribution: the report named
"MetalBuffer[256] @ das_metal_boost.das:160 x15" — the untracked pool's
256-byte floor bucket, i.e. 4-byte uniforms. Buffer tags now carry the
byte size (pooled buffers all create at one das line, so the size bucket
is what tells leaked classes apart).

Verified 0 live objects on fam-gptoss (was 15), fam-gemma4moe, and
fam-qwen35moe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
The dasLLAMA tutorial 00 reader-feedback revision introduced both; CI's
PDF steps are continue_on_error so they shipped silently — the local
pdflatex gate caught them (the π/U+03C0 pattern).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
…riminator

The reds-dig2 chase put the routed mx4 expert GEMVs at 216 wGB/s in-graph
(48% of every gpt-oss decode step) vs the 321 the k4 sibling sustains.
New gmx4 section prices the PRODUCTION MetalMoeGemvMx4 at the exact
gpt-oss shapes (E=32, nfe=dim=2880, k=4, 24 layers, 403MB routed/pass),
CPU-ref-gated bit-exact:

  gmx4_pipe   243 wGB/s   kernel ceiling, no barriers
  gmx4_serial 209 wGB/s   per-dispatch barrier tax (brackets the in-graph 216)
  gmx4_arith   97 wGB/s   REFUTED: ALU e2m1 decode (no vtab loads) loses 2.5x

So ~70% of the gap is kernel-internal and it is NOT the vtab table loads
(disasm: 15 device loads per block iter, 8 vtab — yet computing values in
ALU is far worse; table lookup wins on AGX, re-confirming the 0.40x-era
verdict at the current shape). Remaining suspect: weight-load width — mx4
issues 4B uint weight loads where k4 streams 16B uint4. Next arms: a
uint4-per-block form and the lcpp-verbatim port.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
…refuted

Round 2 of the gpt-oss mx4 chase. llama.cpp's mxfp4 mv copies its 16-value
LUT into threadgroup memory at kernel start; same change on our kernel
(vtab device loads -> tgmem reads, nothing else):

  gmx4_pipe 240.5 wGB/s -> gmx4_tgv 258.4 wGB/s (+7.4%), bit-exact

Also refuted: etab into tgmem regressed (253 vs 260) — its per-block reads
are same-address broadcasts riding the uniform path, cheaper than the 1KB
fill. The dump rail is now built in (DASMETAL_LAB_DUMP_MSL=<dir> writes
the gmx4 kernel sources for applegpu disasm and exits).

Ladder to k4's 321: next arms are weight-load width (mx4 issues 4B uints
where k4 streams 16B uint4) and lcpp's 4-float4 x prefetch. Production
port waits until the lab form clears ~300.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
…ape clears 301 wGB/s

The gmx4 ladder round 3 (gpt-oss shapes, bit-exact vs CPU ref):
  pipe 239 -> tgv 257 -> lcpp 272 -> lcppw 275 -> lcppe 301 wGB/s
- gmx4_lcpp: llama.cpp's mul_mv_mxfp4 thread mapping verbatim (16 blocks in
  flight x 2 threads/block, yl[4] float4 x prefetch shared across 2 rows,
  float4 accumulators, 32-slot tgmem LUT) — +6% over tgv
- gmx4_lcppw: same shape, weight bytes as one uint2 (8B) per row-visit — +1%
- gmx4_lcppe: e8m0 scale computed in ALU (as_type<float>) instead of the
  mxe->etab dependent device-load chain — +10%, clears the ~300 port gate
msl_emit grows the daslib/math_bits bitcast family (uint/int<->float, scalar
+ 2/3/4-lane) lowering to as_type<T>, plus exp2/log2 in the math whitelist;
real-GPU coverage added to tests/metal/test_metal_math.das. Lab dump rail
(DASMETAL_LAB_DUMP_MSL) hoisted to main — no more full-lab wait for a disasm.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
…gpt-oss tg GREEN

MetalMoeGemvMx4 rewritten to the moe-lab lcppe rung (239->301 wGB/s standalone,
serial-encoder 209->266): lcpp's mul_mv_mxfp4 mapping (16 blocks in flight x 2
threads/block, yl[4] float4 x prefetch shared across both rows, float4
accumulators), uint2 weight loads (binding 0 becomes the uint2 view — encoder
unchanged), tgmem value LUT, and the e8m0 scale computed in ALU via
uint_bits_to_float instead of the mxe->etab dependent device-load chain.
MetalMoeMulMmMx4's A-stage gets the same three: uint4 block load (16 byte loads
-> 1), tgmem LUT, computed e8m0. Both e8m0 device tables (g_e8m0_tab,
g_pf_e8m0_tab) deleted — the ALU form is bit-exact for all 256 bytes incl. the
e<2 denormals. Kernel-unit gate updated (tgmem length + dropped etab bind).

gpt-oss-20b paired vs llama.cpp stock (M1 Max, llama-bench shapes):
  tg128  80.20 -> 86.58 vs 81.62  = 1.061 GREEN  (board red was 0.963)
  pp512  908.9 -> 986.91 vs 1024.28 = 0.963      (was 0.88; steady GPU 515ms
         vs lcpp wall 500ms — remaining ~3.5% needs its own prefill chase)
fam-gptoss tolerance cells bit-identical pre/post (maxd 0.2558/0.2596);
kernels suite bit-exact incl. odd-shape tails and the bias/batch-stream arms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
…7 GREEN

The gemma-4 E-series prefill pre-step (gather 512 token rows off the 3GB Q8_0
per_layer_token_embd + the 14-GMAC bf16 model_proj GEMM + rms/combine) ran on
CPU: 126ms beside a 529ms GPU window that already beat lcpp's whole 558ms wall.
Now it runs at the head of the prefill graph, three dispatches on the serial
encoder:
- MetalPleGatherQ8: dequants token-row slices straight off blob_of's q8 pair
  (zero-copy on mapped images) into the LAYER-major side panel, sqrt(ple)
  folded — the CPU-side transpose memcpy is gone with it
- MetalBf16MulMm: MetalQ8MulMm with the A stage swapped to native BF16 rows
  (widen = exact bit shift via the new as_type bitcast) — model_proj streams
  from the kept-bf16 plane, no resident f32 copy
- MetalPleFinish: per (layer, position) slice rms + pnorm + combine, in place
Plumbing: register_ple_gpu_gate — forward_prefill asks the driver BEFORE the
CPU pre-step, stashes token rows on true; the driver falls back to
ple_pre_prefill on a late decline, and ple_block_prefill panics loud if a
promise ever leaks to the CPU body. Decode PLE (1 row/step) stays CPU.

gemma-4-E4B-it-Q8_0 paired vs llama.cpp stock (M1 Max, llama-bench shapes):
  pp512  784.9 -> 959.22 vs 916.13 = 1.047 GREEN  (board red was 0.856)
  tg128  51.27 vs 52.08 = 0.98 (unchanged path)
Steady prefill wall 655 -> 530ms; GPU window +1-3ms. fam-gemma4e cell green
incl. the token-for-token generate vs CPU truth; llama prefill base arm green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
A bare-filename DASLLAMA_CONFIRM_MODEL made every confirm arm die on
'cannot open gguf' — but the only surfaced line was "the FALLBACK arm
produced no pp number (fix the box, rerun)", swallowing the actual error
and costing a debug round. An arm that produces no CONFIRM_PP now prints
the child's last lines before scoring 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
… cells

Fresh paired sweep (all 8 catalog models x cpu+metal, refs adjacent per cell,
quiet window, r=5) after the reds-dig2 kernel work; hidden site section 02
regenerated from the store. Both dug reds confirmed green in sweep conditions:
E4B metal pp 0.856 -> 1.058, gpt-oss metal tg 0.963 -> 1.060. Every sweep-era
window-skew red re-paired clean (30B metal tg 1.14, Mistral metal pp 1.01 /
cpu pp 1.06, 27B cpu tg 1.05, 12B cpu pp 1.11). Remaining sub-parity, honest:
27B metal pp 0.91 (dense k4 mul_mm A-stage — next chase), gpt-oss metal pp
0.92 (0.96 quiet-paired; fresh-process first-rep PSO stall skews the sweep
mean), 26B/E4B cpu pp vs AMX-stock 0.86/0.96 (both lead clean-cpu).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
Copilot AI review requested due to automatic review settings July 24, 2026 10:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR targets the remaining public-board Metal performance regressions for dasLLAMA on Apple Silicon by (1) improving measurement/leak hygiene, (2) reshaping the gpt-oss MXFP4 MoE kernels to the lab-proven mapping, and (3) moving gemma-4 E-series PLE prefill pre-step onto the GPU.

Changes:

  • Add per-creation-site Metal live-handle reporting and integrate it into existing Metal leak gates.
  • Extend Metal MSL codegen to support exp2/log2 and daslib/math_bits bitcasts via MSL as_type, with a new GPU-vs-CPU math/bitcast test.
  • Implement gpt-oss MXFP4 kernel updates and an E-series GPU PLE pre-step (gather + BF16 GEMM + finish), plus refreshed M1 performance records and harness diagnostics.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/metal/test_metal_math.das Expands GPU-vs-CPU math test coverage to include exp2/log2 and bitcast round-trips via daslib/math_bits.
modules/dasMetal/src/dasMetal.mm Tracks live Metal handles with a tagged registry and exposes metal_live_object_report() for leak attribution.
modules/dasMetal/src/dasMetal.h Declares the new metal_live_object_report API for module/AOT integration.
modules/dasMetal/metal/msl_emit.das Allows exp2/log2 and lowers math_bits bitcasts to MSL as_type<>().
modules/dasMetal/metal/das_metal_boost.das Enhances leak assertions/logging using metal_live_object_report() and adds metal_log_leaks_if_any().
modules/dasLLAMA/tests/test_metal_support_matrix.das Switches leak-gate checks to metal_log_leaks_if_any() for better diagnostics on failures.
modules/dasLLAMA/tests/test_metal_prefill_parity.das Uses metal_log_leaks_if_any() for more actionable leak test failures.
modules/dasLLAMA/tests/test_metal_prefill_kernels.das Updates the mx4 GEMV kernel gate for tgmem LUT usage and removes now-obsolete e8m0 table plumbing.
modules/dasLLAMA/tests/test_metal_gemm.das Uses metal_log_leaks_if_any() for improved Metal leak reporting in GEMM tests.
modules/dasLLAMA/performance/records/m1.json Refreshes M1 performance record snapshots and tune metadata.
modules/dasLLAMA/performance/profile_common.das Fixes/extends exec-format “receipt” reporting to include deltanet projection formats for recurrent layers.
modules/dasLLAMA/harness/gen_tune_probe.das Improves tune-confirm diagnostics by printing the child output tail when parsing yields no score.
modules/dasLLAMA/dasllama/dasllama_metal_prefill.das Adds BF16 mul_mm variant, implements GPU PLE pre-step dispatches, and updates mx4 kernels to the new mapping/bitcast path.
modules/dasLLAMA/dasllama/dasllama_metal_llama.das Fixes MoE step resource releases by returning missing uniform buffers to the pool.
modules/dasLLAMA/dasllama/dasllama_metal_kernels.das Removes e8m0 device-table usage and updates mx4 GEMV dispatch to set required threadgroup memory.
modules/dasLLAMA/dasllama/dasllama_metal_common.das Adds bf16_of helper to bind kept-BF16 planes for zero-copy or cached residency.
modules/dasLLAMA/dasllama/dasllama_common.das Introduces a PLE GPU gate mechanism and exposes ple_pre_prefill for late-decline fallback.
modules/dasLLAMA/benchmarks/matmul/bench_metal_moe_lab.das Adds a gpt-oss mx4 “lab” section to validate and time multiple kernel rungs at exact shapes.
doc/source/conf.py Adds LaTeX unicode declarations for θ and typographic minus to keep PDF builds passing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…e-paired

Chasing the two fresh-board tails turned up measurement structure, not
kernels:
- 27B metal pp "0.911" was cell-order bias: das metal ran straight off the
  222s das-cpu heat soak while the lcpp metal ref ran minutes later (das
  110.7 soaked vs 138-143 quiet). gen_bench_records now runs each ratio's
  das cell and its ref ADJACENT, metal pair first, cpu trio after — the
  measurement protocol's interleave rule applied at the pass level.
- Short cells benched into the kernel's ASYNC reclaim of the previous
  child's 10-20GB map (12B pp +-33 at back-to-back, +-0.8 settled): every
  pass now idles --settle seconds (default 12).
- lcpp_bench grows DASLLAMA_BENCH_VERBOSE=1 per-rep tps output — the mean
  +- sd line was hiding which rep carried a blip.

Metal board re-paired under the fair order (cpu rows keep the sweep's):
35B 0.98/1.19, 27B 1.11/1.22 (was the phantom red), 12B 1.04/1.16,
E4B 1.06/0.99, gpt-oss 0.92/1.06 (pp carries its intrinsic one-blippy-rep
+-47 on the receipt; 0.96 on blip-free runs), 30B 0.99/1.14,
Mistral 1.01/1.15, 26B 1.10/1.02. Hidden site section regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
Copilot AI review requested due to automatic review settings July 24, 2026 11:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Comment thread modules/dasMetal/src/dasMetal.mm
The leak-attribution code uses std::string/std::to_string via transitive
includes only — fragile across toolchains/SDKs. One line, file's own style.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
Copilot AI review requested due to automatic review settings July 24, 2026 11:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

modules/dasMetal/src/dasMetal.mm:88

  • If g_metalLiveTags is updated to track retain counts per handle, release_handle should decrement that count and only erase when it reaches zero. Otherwise the tag entry can disappear while the handle is still logically live (multiple retains).
    static void release_handle ( void * h ) {
        if ( !h ) return;
        g_metalLiveObjects.fetch_sub(1, std::memory_order_relaxed);
        {
            std::lock_guard<std::mutex> guard(g_metalLiveTagsMx);
            g_metalLiveTags.erase(h);
        }

modules/dasMetal/src/dasMetal.mm:560

  • metal_live_object_report currently assumes g_metalLiveTags values are plain strings. If g_metalLiveTags tracks (tag,count) per handle, aggregate bySite using the stored count so the report reflects multiple retains of the same ObjC object address.
    char * metal_live_object_report ( Context * ctx, LineInfoArg * at ) {
        std::map<std::string, int64_t> bySite;
        {
            std::lock_guard<std::mutex> guard(g_metalLiveTagsMx);
            for ( auto & kv : g_metalLiveTags ) bySite[kv.second]++;

Comment thread modules/dasMetal/src/dasMetal.mm
…with the counter

Copilot round 3: the same ObjC address can be retained repeatedly
(MTLCreateSystemDefaultDevice is a singleton, and both dasLLAMA drivers
create the device), so a pointer-keyed tag MAP overwrote on re-retain and
erased on first release — a leaked extra retain became invisible in
metal_live_object_report() while metal_live_object_count() said non-zero,
the exact blind spot the detector exists to remove. Tags now stack per
pointer (push on retain, pop on release); the report walks all entries.
dasModuleMetal rebuilt; metal math gate + dasLLAMA kernel units green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XLduse7eHSS1iJ2BG2P4LT
Copilot AI review requested due to automatic review settings July 24, 2026 11:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated no new comments.

@borisbat
borisbat merged commit 57c1bde into master Jul 24, 2026
36 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.

2 participants