Uncle Rudy β€” LFM2.5-230M tool-caller β€” Apple Core AI (.aimodel)

A LoRA fine-tune of LiquidAI/LFM2.5-230M that turns a single spoken/typed utterance into a JSON tool call for a to-do app β€” converted to Apple's Core AI .aimodel format and running on the Neural Engine (iOS 27 / macOS 27).

This is a static-shape / Neural Engine bundle (4 entrypoints: load_embeddings, gather_embeddings, extend_*, prompt_opt_*) β†’ Apple's EngineFactory selects the static-shape engine. It is not a gpu-pipelined decode bundle, so it will not load on the coreai-pipelined GPU path that most published Core AI chat bundles use. It also needs a small runtime patch β€” see Runtime requirements.

What it does

Give it one utterance; it emits one tool call.

"Remind me to buy milk"                  β†’ {"name":"create_todo","arguments":{"title":"Buy milk","due":null}}
"Remind me to call the dentist tomorrow" β†’ {"name":"create_todo","arguments":{"title":"Call the dentist","due":"tomorrow"}}
"Delete the dentist task"                β†’ {"name":"delete_todo","arguments":{"target":"Call the dentist"}}

Tool surface: create_todo (title, due) Β· set_status (target, status) Β· update_todo (target, title, due) Β· delete_todo (target).

Apply the bundle's own chat template and feed the raw utterance as the user turn β€” the fine-tune emits the assistant tool call directly. No system prompt / tool-schema preamble is needed (training used mask_prompt, so loss was computed only on the tool-call tokens). Decode greedily.

Bundle

ane-static/uncle_rudy_lfm2_230m_toolcaller_int4pal/
β”œβ”€β”€ uncle_rudy_lfm2_230m_toolcaller_int4pal.aimodel/   # main.mlirb Β· main.hash Β· metadata.json
β”œβ”€β”€ tokenizer/                                          # tokenizer.json Β· tokenizer_config.json
β”‚                                                       # chat_template.jinja Β· generation_config.json
└── metadata.json                                       # LanguageBundle manifest
Base LiquidAI/LFM2.5-230M (14 layers = 8 short-conv + 6 GQA attention, hidden 1024, 16/8 heads, vocab 65536, tied embeddings)
Fine-tune LoRA r=16, scale 16, 400 iters, mask_prompt: true (loss on tool call only), fused
Compression 4bit_weight_palettized_group32 (embedding table int8, per Core AI's iOS path)
Size 144 MB .aimodel (~148 MB with tokenizer)
Max context 512 (prompt + generation) β€” deliberately small; see Context length
Engine static-shape (Neural Engine)

Runtime requirements

  1. iOS 27 / macOS 27 β€” Core AI ships with the OS. Device-only on iOS: CoreAI.framework is in the iPhoneOS SDK but not the iOS Simulator SDK, so this cannot run in the Simulator.
  2. Toolchain β‰₯ beta 3 era. This bundle was exported with coreai-torch 0.4.1 / coreai-core 1.0.0b2 so it carries the versioned-IR location format the beta-3 on-device compiler requires. (Bundles exported with the June-era coreai-torch 0.4.0 / coreai-core 1.0.0b1 fail on beta 3 with expected AICode versioned location … Failed to convert to versioned IR … cannot unwrap empty odiec_module_t.)
  3. A conv-cache extra-state patch on the static-shape engine. LFM2 is a conv+attention hybrid: its short-conv layers carry a rolling conv cache in addition to the KV cache, so the model exports three states (key_cache, value_cache, conv_cache). Apple's StaticShapeEngine hardcodes two. The engine must allocate the extra state, zero-fill it on reset (unlike KV it is read directly, not mask-gated), and bind it by name each step.

Note: the conv state cannot be partially prefix-rewound (it only holds the last L-1 columns), so a conv model should reprocess each request from scratch rather than reuse a partial KV prefix.

Use it

The bundle is a standard LanguageBundle (.aimodel + tokenizer/ + metadata.json) β€” point the runtime at the bundle directory:

import CoreAILanguageModels

let bundle    = try LanguageBundle(at: bundleDir)          // dir containing the .aimodel
let engine    = try await CoreAIRunner(from: bundle).makeInferenceEngine()  // β†’ static-shape / ANE
let tokenizer = try await bundle.loadTokenizer()

let generator = try await TextGeneratorBuilder()
    .withInferenceEngine(engine)
    .withTokenizer(tokenizer)
    .withSampling(configuration: .greedy)
    .build()

// `.prompt` applies the bundle's chat template; the fine-tune emits the tool call.
let json = try await generator.generate(input: .prompt("Remind me to buy milk"), maxTokens: 60)

Catalog entry, for apps that pull Core AI bundles from HF by tree path:

ModelSpec(
    bundleName:   "uncle_rudy_lfm2_230m_toolcaller_int4pal",
    hfRemotePath: "ane-static/uncle_rudy_lfm2_230m_toolcaller_int4pal",
    repoURL:      "https://huggingface.co/sabeshbesh/uncle-rudy-lfm2-230m-CoreAI",
    label:        "Uncle Rudy 230M",
    approxSizeGB: 0.15,
    warmupToken:  1,
    maxContext:   512)

⚠️ A catalog/ModelSpec alone is not sufficient: an app wired to the coreai-pipelined engine must route this bundle to the static-shape engine and carry the conv-cache patch above.

First load is slow β€” by design

The first load on a given device compiles the model's Neural Engine graphs on-device, inside Apple's AIModel(contentsOf:options:). This cannot be shipped precompiled β€” the compiled program is specific to that device + OS. The OS caches the result, so every later load is near-instant.

Cold (first) load, Apple Silicon ANE ~54 s
Warm load (cached) ~0.02–0.1 s

Context length

Exported at 512 context on purpose. The iOS static export fans out one specialized ANE graph per (context_bucket Γ— query_length) per function β€” at 2048 that is 24 graphs and a ~151 s cold compile; at 512 it is 12 graphs and ~54 s. A tool-caller only ever sees a short utterance plus a ≀60-token call, so 512 is ample. Re-export at a larger --max-context-length if you need more, and pay the longer one-time compile.

Measured

Greedy, llm-runner, macOS / Apple Silicon Neural Engine (this bundle):

Prefill ~80–330 tok/s
Decode ~73–83 tok/s

iPhone on-device throughput is not yet published β€” these are Mac-ANE numbers for the same bundle. Treat them as indicative, not as iPhone figures.

Correctness. The re-authored BC1S / Neural-Engine model was gated against the fp32 Hugging Face reference: 100% next-token top-1 match (12/12 positions), logits PSNR 52 dB, on a truncated model covering both layer types (conv + attention). The lower-than-macOS PSNR (70 dB on the GPU/dynamic path) is expected: the iOS path int8-quantizes the embedding table and computes attention per-head, which reassociates fp16 arithmetic β€” every argmax still matches.

Conversion notes

Converted from the fused Hugging Face checkpoint via a custom Core AI recipe on top of apple/coreai-models. Two things were needed beyond the stock pipeline:

  • MLX β†’ PyTorch conv-weight transpose. mlx_lm fuse writes depthwise Conv1d weights in MLX axis order (out, kernel, in) = [1024, 3, 1]; PyTorch's modeling_lfm2 wants (out, in, kernel) = [1024, 1, 3]. Only the 8 conv layers are affected (Linear/embedding/norm layouts are identical), and the fix is an axis swap β€” verified bit-exact against the base weights.
  • Re-authoring for the Neural Engine. BC1S (B, C, 1, S) layout, projections as 1Γ—1 Conv2d, per-head attention (no fused SDPA on ANE), transposed causal mask using -40000 rather than -inf, and the short conv as a depthwise Conv2d(D, D, (1, L), groups=D) over the sequence axis, with the conv cache threaded as a third functional state.

License

Weights derive from LiquidAI/LFM2.5-230M and are redistributed under the LFM Open License v1.0 (LICENSE) β€” Apache-style grants, but commercial use is licensed only for entities under US$10M annual revenue (qualified non-profits exempt for non-commercial/research use). Review the LICENSE before any commercial deployment.

Downloads last month
94
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for sabeshbesh/uncle-rudy-lfm2-230m-CoreAI

Finetuned
(28)
this model