Shieldstral-1.0-3B β Apple Core AI (.aimodel)
Mistral's 3B safety model converted to Apple's Core AI (the Core ML successor announced at WWDC26), for macOS 27 and iOS 27. Twelve languages, Apache-2.0.
The policy is a string in your code, not a fixed taxonomy: the host writes an Instruction ("Flag self-harm promotion; do not flag help-seeking or support resources") and a Query, hands over the content, and gets back a probability.
It ships as a classifier, not a decoder. Shieldstral answers by putting mass on yes or no
at the last prompt token, so the whole tail is baked into the graph:
(input_ids [1,S] int32, attention_mask [1,S] int32) -> probs [1,2] = softmax([no, yes])
One .aimodel forward is one verdict. No KV cache, no decode loop, no sampling, and no
131 072-way head β two rows of the tied embedding are the head, which is 805 MB of fp16 a
classifier never reads.
Requires macOS 27 / iOS 27 (Core AI ships with the OS). Conversion code, gates and knowledge base: coreai-model-zoo.
Bundles
| path | size | verdict latency (M4 Max) | numerics |
|---|---|---|---|
gpu-classify/β¦_int4lin_s512 |
2.53 GB | 232.5 ms | 9/9 verdicts vs fp32, worst |ΞP| 0.030 |
gpu-classify/β¦_int4lin_s256 |
2.53 GB | 123.6 ms | 9/9, numerics identical to S=512 |
macOS 27.0 (26A5378n), Xcode 27.0 (27A5218g), coreai-torch 0.4.1, median of 10 warm forwards,
engine ready in ~2 s.
No iPhone bundle yet. The AOT build for h18p is 2.336 GiB β under the 2.39 GiB that another
model in this zoo loads on an iPhone 17 Pro β so it is expected to fit, and expected to fit is
not a measurement. It ships when a phone has run it.
Two measurements worth knowing before you pick a bundle.
Quantization buys size, not speed. At the same grid, fp16 runs 230.5 ms and int8lin 253.8 ms against int4lin's 232.5 β one forward over a padded grid is compute-bound, so shrinking weights moves 6.88 GB to 2.53 GB and leaves the clock alone. That is the inverse of the decode loop, where int4 is the main speed lever. Only int4lin is published because the larger bundles are not better at anything.
The cost of a verdict is the grid, not the text. Both bundles hold the same weights and produce the same probabilities; S=256 is 1.9Γ faster because it computes half as much padding. Pick the grid from the longest document you will actually moderate β the scaffolding alone is ~60 tokens, so S=256 leaves ~196 for the document and S=512 leaves ~450.
Verdicts
Nine cases, four policies, EN + JA β four of them near-misses that share a topic with a flagged case, because a model that only separates the easy pairs is a keyword filter with extra steps.
| flagged | fp32 | int4 | not flagged | fp32 | int4 | |
|---|---|---|---|---|---|---|
| violence (EN) | 0.9972 | 0.9988 | sourdough recipe | 0.0000 | 0.0000 | |
| violence (JA) | 0.9011 | 0.9315 | park recommendation (JA) | 0.0001 | 0.0001 | |
| weapon-making | 0.9919 | 0.9967 | chemical safety question | 0.0001 | 0.0001 | |
| doxxing request | 1.0000 | 1.0000 | refusal to dox | 0.0003 | 0.0004 | |
| help-seeking | 0.0001 | 0.0002 |
Every verdict survives int4. What moves is the probability, and only on the case fp32 did not already saturate (JA violence). The fp16 bundle's own noise floor is 0.00056, which is what makes int4's 0.030 readable as real. Tune any threshold against the bundle you ship, not against fp32.
Host contract
Everything outside the forward is yours, and all of it is in reference.json next to the bundle:
PREFIX = "<s>[SYSTEM_PROMPT]" + SYSTEM + "[/SYSTEM_PROMPT][INST]"
BODY = "<Instruct>: {instruction}\n\n<Query>: {query}\n\n<Document>: {document}"
SUFFIX = "[/INST]"
- encode with
add_special_tokens=Falseβ<s>is in the template text and this tokenizer's post-processor does not add one, so letting it add specials gives you two; - right-pad to the grid with
pad_token_id11, mask1 Γ real + 0 Γ pad. Under the causal mask the last real token never sees the padding, which is why S=128 and S=512 agree exactly; - read
probs[1]= P(violation).
SYSTEM is fixed (it ships in reference.json); Instruct, Query and Document are yours.
Not included: the checkpoint's Pixtral vision tower (image_size 1540). Text only.
Using it from Swift
let guard = try await SafetyClassifier(model: .shieldstral3B) // .shieldstral3BShort for S=256
let verdict = try await guard.check(message, policy: .selfHarm)
coreai-kit's SafetyClassifier owns the scaffolding,
the padding and the threshold. reference.json in each bundle ships the nine gated cases with
their fp32 probabilities, so any host β Swift, Python, yours β can check its own prompt
construction rather than trusting it.
Converting it yourself
The conversion venv here is transformers 4.57.6, which cannot load this checkpoint at all β the
tokenizer declares TokenizersBackend, AutoModelForCausalLM rejects Mistral3Config. The
oracle therefore runs on transformers git main, which knows ministral3 natively, and the
export is built on the claim that ministral3 is Mistral + YARN (4.57.6's MistralModel handed
this config's rope_parameters as rope_scaling).
That claim is measured, not assumed β cos 1.000000 on last-position logits and |ΞP| = 0.00000
across all nine cases β because a mis-scaled rope still emits fluent logits and plausible
probabilities. See
_smoke/test_shieldstral_torch_ladder.py,
conversion/export_shieldstral.py
and
knowledge/shieldstral-port.md.
License
Apache-2.0, carried from
mistralai/Shieldstral-1.0-3B (revision
003ec7e2b0bab5f0e6307edbaf186fa5822b76f5). Not affiliated with Apple or Mistral AI.
- Downloads last month
- -
Model tree for mlboydaisuke/Shieldstral-CoreAI
Base model
mistralai/Ministral-3-3B-Base-2512