Skip to content

fix: parse_messages_json drops content when key order is content-before-role#2

Open
ronaldw07 wants to merge 414 commits into
mainfrom
fix/parse-messages-content-role-order
Open

fix: parse_messages_json drops content when key order is content-before-role#2
ronaldw07 wants to merge 414 commits into
mainfrom
fix/parse-messages-content-role-order

Conversation

@ronaldw07

Copy link
Copy Markdown
Owner

Summary

  • parse_messages_json searched for "content" starting from role_end instead of the object start, so a message object serializing content before role (e.g. {"content":"hi","role":"user"}) would never match content within the object's bounds and silently drop it.
  • Anchors the content search on obj_start, matching the order-independent pattern already used for role, images, audio, and name in the same function.
  • Adds test_json_parsing.cpp covering role-first, content-first, and multi-message mixed key ordering.

Fixes cactus-compute#757

Test plan

  • Built test_json_parsing via CMake and confirmed all 3 cases pass with the fix
  • Verified the regression: reverted the fix locally, rebuilt, and confirmed the content-first and mixed-order cases fail exactly as described in the issue
  • Rebuilt test_index to confirm no regression from the header change

nshejwalkar and others added 30 commits March 3, 2026 20:40
Signed-off-by: Neel Shejwalkar <nshejwalkar7@gmail.com>
Signed-off-by: jakmro <kubamroz124@gmail.com>
Co-authored-by: Henry Ndubuaku <ndubuakuhenry@gmail.com>
Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>
* fix: treat merged LoRA temp dir as local model path (cactus-compute#490)

When `cactus convert` merges a LoRA adapter, it saves the merged model to a temp
directory and tries to “download” it as if it were a Hugging Face repo id.
The fix checks if the model_id is a local directory and passes the temp path directly into weight conversion by skipping the HF path

Signed-off-by: Lennart <lvoelz@outlook.de>

* fix: tokenizer detection failed for LoRA merged models

- copy sentence piece model from LoRA directory (or base model in the HF cache as a fallback) and read the tokenizer config locally if possible
- the model_id is a path in the merging case, which poisend the whole conversion chain -> replaced with a name variable for name-based detection (e.g. gemma special tokens)

Signed-off-by: Lennart <lvoelz@outlook.de>

* fix: copy tokenizer_config.json from LoRA adapter if available

Signed-off-by: Lennart <lvoelz@outlook.de>

---------

Signed-off-by: Lennart <lvoelz@outlook.de>
Co-authored-by: Lennart <lvoelz@outlook.de>
* simplify and align sdks

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Add fine-tuning guide and runtime compatibility documentation links to README

Signed-off-by: jakmro <kubamroz124@gmail.com>

---------

Signed-off-by: jakmro <kubamroz124@gmail.com>
…to_hf.yml (cactus-compute#495)

Signed-off-by: jakmro <kubamroz124@gmail.com>
…nscribe (cactus-compute#499)

Signed-off-by: jakmro <kubamroz124@gmail.com>
…nder rustc (cactus-compute#498)

* Set MACOSX_DEPLOYMENT_TARGET=14.0 for SME2 linker symbols

Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>

* fix rust again

Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>

* fix

Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>

* fix

Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>

* fix

Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>

* fix

Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>

* fix

Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>

---------

Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>
…cactus-compute#497)

* Add FFI log control: cactus_log_set_level and cactus_log_set_callback

Expose the existing C++ Logger::set_level() and Logger::set_callback()
through the C FFI, allowing embedders to suppress or redirect log
output instead of inheriting the default stderr behavior.

Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>

* Update cactus_ffi.h

---------

Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>
Co-authored-by: Henry Ndubuaku <ndubuakuhenry@gmail.com>
* qwen 3.5 27B runs

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* optimized deltanet

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* removed unnecessary change

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* more cleanup

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* a little more cleanup

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* update readme

Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>

* removed kernel from graph ops to dedicated kernel file

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* cleanup

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* more cleanup

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* slower but more stable now with fp32 accumulation

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* added separate model enum for qwen3.5

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* reverted a line of logic

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* simplified model detection

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* v1.10

Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>

* Updates

Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>

---------

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>
Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>
Co-authored-by: HenryNdubuaku <ndubuakuhenry@gmail.com>
Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>
Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>
Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>
* display RAM usage in chat stats

Signed-off-by: Roman Shemet <shemet0roman@gmail.com>

* add --system flag for system prompt injection

Signed-off-by: Roman Shemet <shemet0roman@gmail.com>

* add /image command and --image flag for VLM chat

Signed-off-by: Roman Shemet <shemet0roman@gmail.com>

* guard image features for non-VLM models

Signed-off-by: Roman Shemet <shemet0roman@gmail.com>

* clean up trailing whitespace in chat.cpp

Signed-off-by: Roman Shemet <shemet0roman@gmail.com>

* add cactus list command and centralize model registry

- Add `cactus list` to show all supported models grouped by type
- Add `--downloaded` flag to filter to local models only
- Extract model registry from workflow YAML into shared models.json
- Both CLI and publish workflow now read from single source of truth
- Add `quantization=` field to config.txt for accurate precision display
- Green ⬇ indicator, tags, disk size, and quantization for downloaded models

Signed-off-by: Roman Shemet <shemet0roman@gmail.com>

* add Qwen3.5-0.8B and Qwen3.5-2B to model registry

Signed-off-by: Roman Shemet <shemet0roman@gmail.com>

---------

Signed-off-by: Roman Shemet <shemet0roman@gmail.com>
…actus-compute#451)

Signed-off-by: ammesatyajit <ammesatyajit@gmail.com>
Co-authored-by: ammesatyajit <ammesatyajit@gmail.com>
* weight saving

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* WORKING!

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* making embeddings int8 for int4

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* no rsqrt2 hard code float

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* sparse 16-group matmul

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* group size 32 kernel

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* Revert "group size 32 kernel"

This reverts commit 3416df3.

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* Revert "sparse 16-group matmul"

This reverts commit 941c2f8.

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* clearer/safer config params

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* small inaccuracy fixes and prefill optimization (40% skipped)

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* testing helpers

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* Revert "testing helpers"

This reverts commit 2edf384.

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* finishing touches

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* adding gemma3n to readme

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* new approximation for exponent on (0,1)

Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>

* DEBUGGED AND WORKING GEMMA 3N

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* Removing all debugging files and debugging code from gemma 3n.

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* mode adaptive and safe gaussian topk filtering

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* fused altup

Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>

* Further optimisations

Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>

---------

Signed-off-by: Noah Cylich <noahcylich@gmail.com>
Signed-off-by: Karen Mosoyan <karen.mossoyan@gmail.com>
Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>
Co-authored-by: Henry Ndubuaku <ndubuakuhenry@gmail.com>
…mple, fix tool response wrapping) (cactus-compute#502)

Remove hardcoded “When you decide to call…” guidance and arg example from developer turn

Fix FunctionGemma trigger string (no trailing period) and append tools declarations directly

Wrap tool responses in a developer turn and allow stacking multiple tool responses

Stop wrapping tool outputs in value:; pass through {...} payload as-is

Close pending tool-response developer turn before next user/model turn to avoid malformed prompts

Signed-off-by: Lennart <lvoelz@outlook.de>
Co-authored-by: Lennart <lvoelz@outlook.de>
* added bias back to greedy sampling

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* fix: add early vocab_size==0 guard to both sampling functions

Moves the empty-vocab guard to the top of cactus_sample_f32 and
cactus_sample_f16 so it covers all paths (not just greedy), preventing
UB from std::max_element on empty vectors.


Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* small safety assert check

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* fix: replace assert with runtime guard in cactus_altup_predict_f16

assert(n <= 8) is compiled out in release builds, leaving the fixed-size
float c[8] buffer unprotected. Replace with an early return.


Signed-off-by: Noah Cylich <noahcylich@gmail.com>

---------

Signed-off-by: Noah Cylich <noahcylich@gmail.com>
* - Add strip_tag_blocks generic helper and refactored strip_thinking_block
    that delegates to it, supporting multiple blocks and unclosed tags
  - Add enable_thinking_if_supported param to parse_options_json and
    thinking field to construct_response_json
  - Refactor KV cache to store head_dim per-layer via LayerCache.head_dim,
    get_layer_head_dim(), and vector-based init() signature
  - Remove dim parameter from update_from_graph; derive per-layer inside loop
  - Update set_window_size to iterate per-layer, skipping dim=0 layers
  - Add KVCache::remove_token_range for surgical token removal from cache
  - Add KVCache::compact_to_windows for per-layer compaction to target windows
  - Add Model::remove_thinking_tokens (back-to-front multi-range removal)
  - Add Model::compact_kv_cache virtual and get_kv_layer_dims virtual
  - Add GemmaModel3n::get_kv_layer_dims override returning 0 for shared layers
  - Add tool call tag auto-detection in gemma::parse_function_calls
    supporting both <start_function_call> and <|tool_call> formats
  - Add enable_thinking_if_supported param to format_chat_prompt and
    format_qwen_style with thinking injection for Qwen models
  - Add test_qwen_thinking.cpp with 6 tests for thinking option parsing,
    tag stripping, response JSON, and prompt injection
  - Update test_kv_cache.cpp for new init signature and fix pre-existing
    bug where graph.input() returning node ID 0 caused layer skipping

Signed-off-by: Noah Cylich <noahcylich@gmail.com>

* removed qwen thinking test and updated existing qwen exhaustive test and added cli updates

---------

Signed-off-by: Noah Cylich <noahcylich@gmail.com>
* timestamps

Signed-off-by: jakmro <kubamroz124@gmail.com>

* timestamps 2

Signed-off-by: jakmro <kubamroz124@gmail.com>

* timestamps 3

Signed-off-by: jakmro <kubamroz124@gmail.com>

* sdk

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Fix language option handling in transcribe process

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Remove unused token handling in transcription process

Signed-off-by: jakmro <kubamroz124@gmail.com>

---------

Signed-off-by: jakmro <kubamroz124@gmail.com>
Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>
* Add versioned docs with quickstart and SDK chooser

- Versioned docs via mike (deploy on release only)
- Dynamic quickstart with snippet markers from SDK READMEs
- SDK decision matrix page
- Custom theme (Geist font, black bg, Cactus logo)
- Version warning banner for old versions
- Fix cactus_init signature in engine docs
- Add vision note to Swift/Kotlin/Flutter/Rust READMEs

Signed-off-by: test <test@test.com>

* cleanup

Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>

---------

Signed-off-by: test <test@test.com>
Signed-off-by: HenryNdubuaku <ndubuakuhenry@gmail.com>
Co-authored-by: test <test@test.com>
Co-authored-by: HenryNdubuaku <ndubuakuhenry@gmail.com>
Signed-off-by: Yujong Lee <yujonglee.dev@gmail.com>
* improve model weights discoverability

Signed-off-by: jakmro <kubamroz124@gmail.com>

* clean

Signed-off-by: jakmro <kubamroz124@gmail.com>

---------

Signed-off-by: jakmro <kubamroz124@gmail.com>
Co-authored-by: Henry Ndubuaku <ndubuakuhenry@gmail.com>
…ute#519)

Signed-off-by: Roman Shemet <shemet0roman@gmail.com>
HenryNdubuaku and others added 30 commits June 21, 2026 12:35
Signed-off-by: jakmro <kubamroz124@gmail.com>
…te#736)

The MoE layer's activation switch only handled GELU, GELU_ERF, RELU and
SILU, with SIGMOID and TANH silently falling through to the SILU default.
Both activations are valid `Activation` enum values accepted by the
`moe_layer` builder and both kernels (cactus_sigmoid_f16 / cactus_tanh_f16)
already exist, so a graph built with either produced wrong results.

Add the two missing cases and a regression test that runs an identity-weight
single-expert MoE layer for every activation and checks the output matches
the corresponding standalone activation kernel.

Signed-off-by: yus100 <90424373+yus100@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ANE (cactus-compute#734)

The audio attention masked-fill constant (-1e9) overflows to -inf in the
FLOAT16 NPU graph: fully-masked rows softmax to NaN and partial rows diverge
on the Neural Engine. Clamp it to a finite fp16-safe -1e4 before export
(soft-capped logits stay within +/-50, so masking is unchanged), and route
the gemma4 audio encoder to CPU_AND_NE via the bundle manifest.

Signed-off-by: Noah Cylich <noahcylich@gmail.com>
Signed-off-by: Yash sharma <trooper0018080@gmail.com>
* MOE

* made lfm moe and gemma4 moe work

* cleanup

---------

Co-authored-by: Karen Mosoyan <karen.mossoyan@gmail.com>
- Removed the `on_token` callback from `generate_batch` method in Model class.
- Introduced `supports_dynamic_batch` method to check for dynamic batch capabilities.
- Replaced `batch_slot_capacity` with `set_decode_slots` to manage KV-cache slots dynamically.
- Updated `decode_batch` and `generate_batch` methods to utilize dynamic slots.
- Enhanced tests for batch processing to validate dynamic batch functionality.
- Removed deprecated flags from CLI for model conversion and updated documentation accordingly.
- Cleaned up unused tests related to batch scheduling and reshaping.
* Add cactus-code coding agent + integration

Signed-off-by: jakmro <kubamroz124@gmail.com>

* fix python workflow

Signed-off-by: jakmro <kubamroz124@gmail.com>

* parsing fixes

---------

Signed-off-by: jakmro <kubamroz124@gmail.com>
…ompute#748)

gemma_tools.h (reached transitively by the test binaries via utils.h) includes
picojson.h from libs/, but that dir was a PRIVATE include of cactus_engine, so it
was not propagated to targets that link the engine. Make libs a PUBLIC include.

Signed-off-by: rshemet <shemet0roman@gmail.com>
)

Reuse the Needle char-trie machinery for the Gemma tool-call path: fixes
function-name prefix shadowing (e.g. send vs send_message) and enforces
required arguments and enum values on a single forced call. Tokenizer-agnostic
(validates decoded token strings); Needle path unchanged. Adds regression tests.

Signed-off-by: rshemet <shemet0roman@gmail.com>
* gemma-metal

Signed-off-by: jakmro <kubamroz124@gmail.com>

* cleanup

Signed-off-by: jakmro <kubamroz124@gmail.com>

* cleanup v2

Signed-off-by: jakmro <kubamroz124@gmail.com>

* faster

Signed-off-by: jakmro <kubamroz124@gmail.com>

* even faster

Signed-off-by: jakmro <kubamroz124@gmail.com>

* converter

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Fix C++ test build: expose vendored libs include (picojson) (cactus-compute#748)

gemma_tools.h (reached transitively by the test binaries via utils.h) includes
picojson.h from libs/, but that dir was a PRIVATE include of cactus_engine, so it
was not propagated to targets that link the engine. Make libs a PUBLIC include.

Signed-off-by: rshemet <shemet0roman@gmail.com>
Signed-off-by: jakmro <kubamroz124@gmail.com>

* Trie-based grammar constraints for Gemma force_tools (cactus-compute#747)

Reuse the Needle char-trie machinery for the Gemma tool-call path: fixes
function-name prefix shadowing (e.g. send vs send_message) and enforces
required arguments and enum values on a single forced call. Tokenizer-agnostic
(validates decoded token strings); Needle path unchanged. Adds regression tests.

Signed-off-by: rshemet <shemet0roman@gmail.com>
Signed-off-by: jakmro <kubamroz124@gmail.com>

* fixes + speedups

Signed-off-by: jakmro <kubamroz124@gmail.com>

* cq2/3

Signed-off-by: jakmro <kubamroz124@gmail.com>

* cleanup

Signed-off-by: jakmro <kubamroz124@gmail.com>

* cleanup2

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Metal execution for vision and audio encoder towers

Whisper audio embed 40.9ms vs CoreML 54.2ms, LFM2-VL vision embed 61.7ms
vs CoreML 77.6ms, gemma-4 towers 3-5x faster than CPU. Adds dense fp16
GEMM via MPS with in-encoder fallback, flash attention for fp16 K/V with
mask/GQA/logit-cap support, layer norm, softmax, GLU, clamp, conv1d_k3
and fp32 elementwise kernels; generic executor gains an fp16 retype pass
for cast-bounded fp32 regions, GPU-dirty lazy sync, periodic mid-graph
flush, and dense fp16 matmul/attention/layernorm/conv encode paths.
Tower graphs stay resident on the Metal backend, vDSP FFT setups are
cached, and lfm2 image embeddings resolve projector output names.

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Generalize gemma decode into cactus-graph with automatic Metal fusion

Replaces the hardcoded gemma-4-E2B execute_gpu_fused path with a
general fusion-plan compiler in the graph executor: per-shape plans
pattern-match the loaded graph once and fuse decomposed rope + q/k/v
norms + KV append + cached attention into the fused attention kernel
(owner and KV-sharing variants), sibling quantized gemvs into
transform_batch/gemv_cat with consumer-ordering guards, residual
rms-norm chains with folded layer scales into rms_norm_add_rms,
gelu-swiglu into swiglu_transform+gemv, PLE pairs into transform_gemv,
and the orthogonal lm_head with softcap into the GPU argmax/sampling
tail. Plans add a liveness-scanned activation arena, a compact
execution list, an embedding-fold prologue, and encode-failure
rollback to the generic path. Decode matches the removed hand-fused
path (119-121/111-114/95-100 tps vs 122/115/101) and prefill improves
(L=16 205 vs 124 tps); tokens stay identical to CPU for cq4/cq3 and
all models in the weights directory pass llm/stt/vlm suites on both
backends. Renames graph backend identifiers from gpu to metal, adds
ComputeBackend::METAL with Python binding, merges overlapping no-copy
host buffer wraps, and runs batch generation at a fixed slot-count
shape so every batch size executes identically.

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Metal pipeline optimization: fix flushed-command-buffer sync hazard, batch-fuse unrolled tower subgraphs, MPS attention path

- session_sync now waits on the last committed command buffer; previously flushed-but-unfinished GPU work was never awaited, corrupting CPU reads that raced it (output depended on flush cadence)
- wrapHostPtr wraps whole VM regions, eliminating overlapping MTLBuffer wraps over the same memory
- metal plan rule 8: CAT-of-matmuls over dense INDEX chains -> single batched gemm (fp16 or fp32 lhs, absorbed casts)
- metal plan rule 9: CAT-of-depthwise-conv1d slices -> single depthwise conv kernel
- metal plan rule 10: CAT-of-transposes at fixed source stride -> single strided copy
- activation arena restricted to attention-clustered (decode) plans
- MPS-based dense attention for large non-causal sequences with optional boolean mask folded via beta=1
- vectorized strided copy for contiguous inner rows; scalar precision casts execute on CPU when inputs are host-resident
- gemma-3n audio tower 414ms -> 192ms, vision tower 440ms -> 376ms, outputs match CPU; llm/vlm/stt suites pass; decode tps unchanged

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Metal encodes for the full elementwise, reduction, conv, norm, rope, and gather op set

- scalar/unary/binary Metal kernels extended with exp, sqrt, cos, sin, log, abs, pow, not-equal, leaky-relu, gelu-erf, and sigmoid codes matching CPU semantics
- new kernels: axis reductions (sum/mean/variance/min/max), cumsum, two-input concat, fp32-index gather, full rope (NeoX and GPT-J), maxpool1d, bilinear interpolation, generic conv1d, depthwise NLC conv1d (causal and same-k9), conv2d (dense/depthwise/pointwise), batchnorm, groupnorm, row bias add
- conv1d_pointwise routed through the fp16 gemm path with fused bias pass
- retype pass no longer retypes terminal nodes; their buffers are externally visible
- padded cache snapshot/rollback made ring-aware for Metal-resident sliding-window caches, matching the kv_append_ring slot mapping; fixes batch-decode rollback corruption past the attention window
- new test_metal_parity suite runs every newly covered op on CPU and Metal and compares outputs

Signed-off-by: jakmro <kubamroz124@gmail.com>

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Generic elementwise chain fusion for the Metal plan compiler

Rule 11 fuses maximal single-consumer chains of activations, scalar ops,
same-shape binaries, and clamps into one elemwise_chain_f16 dispatch with up
to eight steps and three side inputs. Values round to fp16 between steps so
fused output is bit-identical to the unfused per-node path. Specialized rules
keep priority; chains only claim nodes no other rule clustered.

gemma-3n audio tower 181ms -> 170ms, vision 376ms -> 354ms; decode unchanged;
graph, parity, and engine suites pass.

Signed-off-by: jakmro <kubamroz124@gmail.com>

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Style and DRY pass over the Metal branch

- remove dead code: rms_norm_rope encode path and kernel, unused gsc/smul
  locals, sampling_needs_adjust and stale step flags in metal_runtime
- deduplicate kv-append encode variants behind shared static helpers and the
  persistent-activation allocation into one lambda in the executor
- guard ATTENTION_CACHED plan matching against unmapped query input; add
  ctx/W guards to quant matmul encodes for consistency
- free the ortho scratch buffer before regrowing; rename a local shadowing
  the buffer helper; drop an inverted empty branch in the CAT rules
- update stale --backend help text that claimed gemma-only Metal support

Signed-off-by: jakmro <kubamroz124@gmail.com>

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Release Metal prefill dequant cache after each completion

The prefill dense-gemm path caches fp16-dequantized weights (~3.8GB for
gemma-3n-e2b cq4) that decode never reads. A guard in cactus_complete now
trims the cache when the turn finishes: steady-state footprint after a
completion drops from ~7.3GB to 3.5GB while benchmark prefill (2060 tps @256)
and decode (110 tps) are unchanged; the next turn re-dequantizes lazily during
its prefill. The MPS attention scores buffer is likewise released on
invalidate_host_wraps instead of living in a function-local static.

Signed-off-by: jakmro <kubamroz124@gmail.com>

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Fix MatMul CQ staleness, pin retained outputs against fusion, PR review fixes

- the tq expand cache added in 3811cbc keyed entries by the packed-weights
  pointer alone; remapping different weights at the same address (as the
  MatMul CQ test does per bit width) served stale expansions. Entries now
  carry a shape-and-content fingerprint and re-expand on mismatch. Fixes the
  unit-test failure present on main since June 21
- Metal quant matmul eligibility now requires the 4-row interleaved layout
  the kernels assume; synthetic flat-layout weights fall back to CPU
- plan builder takes the retained/persistent node set and refuses to cover
  pinned nodes, so fusion clusters can no longer swallow graph outputs
- run.py reads --backend via getattr like every other CLI entry point;
  cactus_set_backend exported from the package root; test bundle discovery
  prefers the highest-quality quant when several are present

Signed-off-by: jakmro <kubamroz124@gmail.com>

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Flash attention kernel and kernel-level bandwidth pass for the towers

- attn_flash_f16: simdgroup-matrix flash attention (online softmax, 64-query
  blocks, diag-rescale with skip when the running max is stable, boolean mask
  support); replaces the MPS decomposition for fp16 non-causal D=64 attention.
  Vision attention 101ms -> 71ms with no 152MB scores buffer
- rms_norm_simd_f16: simdgroup-per-row reduction, four rows per threadgroup;
  vision norms 42ms -> 9ms
- elementwise chains now span precision-cast boundaries (per-step rounding
  matches unfused execution) and skip retype-claimed nodes; chain, flat
  binary/scalar/unary/clamp (f16 and f32) kernels process four elements per
  thread; broadcast binary gets a row variant without per-element div chains
- tiled 32x32 transpose for trailing-dim swaps, vectorized strided scatter
  rows for CAT, conv2d computes four outputs per thread
- gemma-3n towers: audio 168ms -> 142ms, vision 353ms -> 241ms, outputs match
  CPU; prefill 2225 tps @256, decode unchanged; llm/vlm/stt and all graph
  suites pass

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Epilogue-style fusion: broadcast chain sides and fused residual-add rms_norm

- elementwise chains accept broadcast side inputs (per-row and per-column)
  so post-matmul scale and bias passes fold into the surrounding chain; side
  buffer bindings are sized per side
- rule 12 fuses ADD/ADD_CLIPPED feeding a multi-row RMS_NORM into one
  rms_norm_add_simd_f16 dispatch that writes both the residual sum (kept via
  the skip-but-preallocate action) and the normalized output, matching the
  unfused fp16 rounding exactly

All graph, parity, and engine suites pass; towers audio 143ms vision 242ms,
decode and prefill unchanged.

Signed-off-by: jakmro <kubamroz124@gmail.com>

* Review pass: close latent correctness holes and remove dead Metal paths

Broadcast NOT_EQUAL now computes != in both bcast kernels instead of
falling through to add. The disable-and-reexecute failure paths restore
KV cache lengths so appends cannot double-apply. Flash attention edge
tiles stage Q/O in an explicitly shared K/V pool instead of aliasing
past the K array. The plan cache signature covers node count and op
types, resets and the destructor invalidate plans and persistent
activations, rule 12 gains the write-order guard rule 2 already had,
rule 8 refuses retyped parents and anchors, and the rule 3 scale must
be a graph input. Removed the dead rope_f16/lmhead_rotate encode
chains, the unreachable executor branches, and leftover scaffolding;
unified absent-bias binding on the dummy buffer and vectorized
unary_f32 like its siblings. Parity suite grows a broadcast case and
fails loudly on empty Metal output.

Signed-off-by: jakmro <kubamroz124@gmail.com>

* fixes

Signed-off-by: jakmro <kubamroz124@gmail.com>

* c

Signed-off-by: jakmro <kubamroz124@gmail.com>

* fixes

* fixes

* remove compilation warnings

* fix

* optimize gemma 4 26b

* more optim

* fixes

* cleanup

* more kernels

* ram optimizations

* update default model IDs to match existing configurations

---------

Signed-off-by: jakmro <kubamroz124@gmail.com>
Signed-off-by: rshemet <shemet0roman@gmail.com>
Co-authored-by: rshemet <42696921+rshemet@users.noreply.github.com>
Co-authored-by: HenryNdubuaku <ndubuakuhenry@gmail.com>
* fix testing infrastructure

* add benchmark test

* update benchamrks

* add audio embed bench

* refine benchmark descriptions and update table formatting

---------

Co-authored-by: HenryNdubuaku <ndubuakuhenry@gmail.com>
- Updated README.md to include benchmark results for iPad M5.
- Refactored test_embed.cpp to remove timing measurements and simplify output.
- Removed benchmark functions from test_index.cpp to clean up test suite.
- Simplified test_llm.cpp by removing redundant tests and improving output clarity.
- Cleaned up test_rag.cpp by removing unnecessary initialization timing.
- Streamlined test_stt.cpp by consolidating transcription tests and improving output.
- Removed performance logging from test_utils.cpp and test_utils.h.
- Cleaned up test_vlm.cpp by removing unused prefill tests.
- Updated test.py to provide clearer instructions for enabling Developer Mode on iOS devices.
* remove coreml

* fixes + cleanups

* cleanups

* fixes
Updated performance metrics for various models in README.
content_pos was searched starting from role_end instead of obj_start,
so a message object serializing content before role (e.g.
{"content":"hi","role":"user"}) would never match content within the
object bounds and silently drop it. Anchor on obj_start to match the
existing order-independent pattern already used for role, images,
audio, and name in the same function.

Adds test_json_parsing.cpp covering role-first, content-first, and
multi-message mixed ordering.
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.

parse_messages_json drops a message's content when the JSON key order is content before role