Saina Helm 2 0.8B — MLX
Apple-silicon build of run-saina/saina-helm-2-0.8b
(revision a00e9cda), a decision model that answers typed questions with probabilities.
No text is generated.
FP16 backbone (1.5 GB) plus Helm's FP32 head. Loads with stock mlx-lm (0.32 or later);
score.py in this repo builds the prompt and applies the head.
How it works
Helm 2 answers every question in a request from one pass over a shared prompt: the context and all the questions, followed by one readout slot per question. At each slot, the hidden state goes through Helm's head: a linear layer over the answer codes plus 16 small residual experts, of which the top 2 are routed in. Softmax over the offered codes gives single-choice probabilities; a sigmoid per code gives multi-label ones.
The head isn't linear, so unlike the Helm 1 MLX build it isn't folded into lm_head:
it ships as head.safetensors and experts.safetensors, and score.py runs it in MLX.
Parity. Against the official saina runtime
(PyTorch FP32), probabilities stay within 0.00218 across the test requests, including
three short requests covering multiple questions, eight options, and multi-label outputs. These checks use stored FP16 weights evaluated in FP32 on Linux CPU; Apple GPU FP16 execution has not been revalidated. Don't quantize
the backbone further without re-checking parity: the head reads its hidden states directly.
Usage
pip install mlx-lm
hf download run-saina/saina-helm-2-0.8b-MLX --local-dir saina-helm-2-mlx
import sys; sys.path.insert(0, 'saina-helm-2-mlx')
from mlx_lm import load
from score import Helm
model, tokenizer = load('saina-helm-2-mlx')
helm = Helm(model, tokenizer, 'saina-helm-2-mlx')
helm.ask('Please refund me, the package never arrived and nobody answers the phone.', [
{'question': 'Which team should handle this?',
'choices': ['Billing', 'Technical support', 'Shipping and delivery']},
{'question': 'Which apply?', 'mode': 'multi_label',
'choices': ['Refund request', 'Delivery problem', 'Praise', 'Support unreachable']},
])
# -> [[p_billing, p_technical, p_shipping], single_label: sums to 1
# [p_refund, p_delivery, p_praise, p_unreachable]] multi_label: independent
helm.score('Can we move our call to Thursday?', 'Is a reply needed?', ['Yes', 'No'])
On an M3 Max a short request takes about 55 ms and a 4K-token request about 0.7 s, however many questions it asks.
score.py builds the exact prompt Helm 2 was trained on. If you build prompts yourself,
keep them byte-for-byte identical, or the probabilities drift. Questions take 2 to 588
options. Input is limited to 262,144 tokens; longer input is rejected, never truncated.
The richer request format (yes/no, rating, decision mode with thresholds and explicit
fallbacks) lives in the saina package and the hosted API at
saina.run; this build returns the raw probabilities those are built on.
Intended use and limitations
Same as the main model, whose card has the evaluation, training data and limitations in full. In short: use it for recoverable or reviewed decisions (intent routing, triage, tool shortlisting, review checklists), validate on your own labelled data, and set thresholds there, because probabilities are not calibrated. English only; not for safety filtering or for decisions about people.
Previous model: saina-helm-0.8b-MLX.
License
Apache-2.0, as for the main model. Training data sources keep their own terms; see the main model card.