CAMeLBERT-msa-MLX

An MLX build of CAMeL-Lab/bert-base-arabic-camelbert-msa for on-device Arabic inference on Apple Silicon — the same checkpoint as the Core AI build in this series, in Apple's array framework instead of the .aimodel container.

mlx_lm has no BERT-shaped architecture, so for a long time this looked impossible; mlx_vlm carries bert, modernbert and xlm_roberta in full, including the classification heads. That is where this build comes from.

Verified equivalence

Compared against the original PyTorch model on 8 Arabic sentences (including one dialect sentence and one bare proper noun), mean-pooled and L2-normalised so the embeddings mean the same thing in both builds:

Input max abs diff cosine
الخدمة كانت ممتازة وأنصح به بشدة. 9.46e-07 1.0000000000
تجربة سيئة جدا ولن أعود مرة أخرى. 7.70e-07 1.0000000000
المنتج وصل أمس. 7.03e-07 1.0000000000
فريق الدعم ساعدني بسرعة وحل المشكلة. 3.41e-07 1.0000000000
الطلب اتأخر أسبوعين وما حد رد عليا. 6.87e-07 1.0000000000
كيف أستعيد كلمة المرور؟ 4.83e-07 1.0000000000
ابغى اعرف ايش صار بالظبط 4.52e-07 1.0000000000
الرياض 1.14e-06 1.0000000000

Worst absolute difference 1.143e-06 · minimum cosine 1.0000000000.

The Core AI build of the same checkpoint, measured against the same reference, lands at 2.831e-07. The two differ because the frameworks accumulate in a different order, not because either is wrong: both graphs are compared against the identical PyTorch outputs in validation.json.

Arithmetic equivalence only — no quality benchmark, no accuracy score, and no comparison with upstream's reported numbers.

Use

import json, numpy as np, mlx.core as mx
from transformers import AutoTokenizer
from mlx_vlm.models import bert as B

cfg = json.load(open("config.json"))
model = B.Model(B.ModelConfig.from_dict(cfg))
model.load_weights(list(mx.load("model.safetensors").items()), strict=False)
tok = AutoTokenizer.from_pretrained(".")

enc = tok(["نص عربي"], padding="max_length", truncation=True, max_length=512, return_tensors="np")
out = model(mx.array(enc["input_ids"].astype("int32")),
            attention_mask=mx.array(enc["attention_mask"].astype("int32")))
hidden = np.asarray(out.last_hidden_state, dtype="float32")   # [1, 512, 768]
mask = enc["attention_mask"][..., None]
pooled = (hidden * mask).sum(1) / mask.sum(1).clip(1e-9)
emb = pooled / np.linalg.norm(pooled, axis=-1, keepdims=True)

What is in here

File
model.safetensors weights (fp32)
config.json architecture config
validation-mlx.json every measured number above
tokenizer files so the tokenizer loads from this repository

Limits

  • Fixed at 512 tokens; longer inputs need chunking.
  • 8 sentences measured. Dialect text, long documents and domain vocabulary were not.
  • No quality benchmark and no speed or memory measurement in this card.
  • The graph returns hidden states; pooling, normalisation and any task head are the caller's steps.

Licence and attribution

apache-2.0, as declared by upstream. A derivative work of CAMeL-Lab/bert-base-arabic-camelbert-msa; credit for the model is theirs.

Downloads last month
35
Safetensors
Model size
0.1B params
Tensor type
F32
·
MLX
Hardware compatibility
Log In to add your hardware

Quantized

Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for ahmedandaloes/CAMeLBERT-msa-MLX

Finetuned
(20)
this model