CLOUD
AI

Arabic Embedding Models Compared: Which One Actually Understands Your Text

CLOUD|October 24, 2026|8 min read|99 views
Arabic Embedding Models Compared: Which One Actually Understands Your Text

For most Arabic RAG projects start with bge-m3 or multilingual-e5-large at 1024 dimensions, and try Qwen3-Embedding if you have a GPU. Do not trust public leaderboards alone; test 100 real questions from your own data and measure Recall@5 before deciding.

An embedding model turns text into a vector of numbers, and everything after it depends on the quality of that conversion: semantic search, RAG, ticket classification and duplicate detection. If the model cannot tell that "wesh asawwi if I forgot my password" in Saudi dialect is close to "account recovery", the strongest language model downstream will not save you, because it receives the wrong passages in the first place. The direct answer: for most Arabic projects, start with one of two open models that run on your own server, BAAI/bge-m3 or intfloat/multilingual-e5-large. Both produce 1024-dimensional vectors, handle Modern Standard Arabic well and Saudi dialect reasonably. If you have a GPU, try Qwen3-Embedding-0.6B, which is newer and usually stronger on long text. If resources are very tight, multilingual-e5-small at 384 dimensions uses a quarter of the storage and runs at a reasonable speed on CPU. The most important rule, though, is not to pick from a public leaderboard. Take 100 real questions from your staff or customers and measure how often the correct passage appears in the top 5 results (Recall@5). That test takes about two hours and saves you months of weak search results.

Why Arabic is hard for embedding models

Arabic is a root-and-pattern language, so the root k-t-b produces kitab (book), maktaba (library), katib (writer) and maktub (written), and a good model understands how they relate. Models trained mostly on English split Arabic words into many meaningless fragments and lose those morphological links. Three practical issues add to that: missing or wrong hamzas (اسعار versus أسعار), taa marbuta written as haa (فاتورة versus فاتوره), and dialect vocabulary that differs from MSA entirely ("abgha" instead of "urid" for "I want"). Saudi company text mixes all three, with policies in MSA, employee questions in dialect and English technical terms inside Arabic sentences.

The first fix is cheap and reliable: normalize the text before embedding, applying the same function to documents and queries. Strip diacritics and tatweel, unify alef forms to a bare alef, and map alef maqsura to yaa. On real support text this step alone typically raises Recall by roughly 3 to 6 percentage points depending on the model, for the cost of a few lines.

import re
def normalize_ar(t: str) -> str:
    t = re.sub(r"[\u064B-\u0652\u0640]", "", t)   # diacritics, tatweel
    t = re.sub("[إأآ]", "ا", t)
    t = t.replace("ى", "ي")
    return t.strip()

Comparing the open models

The table below summarizes the models most used for Arabic today. Speeds are rough figures for 256-token passages on 4 CPU cores and vary with the processor and batch size, so treat them as relative comparisons rather than final numbers.

ModelDimsSizeMax lengthCPU approx.Note
multilingual-e5-small384118M51240 to 60 passages/sLight, weaker on dialect
multilingual-e5-large1024560M5126 to 10 passages/sNeeds query: and passage: prefixes
bge-m31024568M81926 to 10 passages/sDense plus sparse, great for long documents
gte-multilingual-base768305M819215 to 25 passages/sGood balance of size and quality
Qwen3-Embedding-0.6B1024600M32K4 to 8 passages/sStrong, supports reduced dimensions

All of these ship under licenses that allow commercial use (MIT or Apache 2.0). Be careful with models that look attractive on leaderboards but carry non-commercial licenses, and read the license file before building a product on one.

Details that sink results if ignored

The e5 models expect the prefix "query: " before a question and "passage: " before a document chunk, and forgetting them noticeably lowers quality. Qwen3-Embedding expects a short instruction before the query, such as "Instruct: Given a question, retrieve relevant passages\nQuery: ". bge-m3 needs no prefix and returns sparse BM25-like weights alongside the dense vector, and combining the two helps a lot with product names and clause numbers in contracts.

Vector size and its effect on storage

Each dimension is stored as a float32 at four bytes, so the math is direct: one million chunks at 1024 dimensions take 4GB for the vectors alone, and an HNSW index in pgvector or Qdrant adds roughly 30% to 50% on top. The same million at 384 dimensions takes 1.5GB. The difference shows up in memory more than disk, because the index has to stay in RAM to answer in under 50ms.

Chunks384 dims768 dims1024 dims
100Kabout 150MBabout 300MBabout 400MB
1Mabout 1.5GBabout 3GBabout 4GB
5Mabout 7.5GBabout 15GBabout 20GB

Most mid-sized companies have between 50 and 300 thousand chunks across policies, contracts, past tickets and FAQs, so 1024 dimensions fit comfortably and there is no reason to sacrifice quality. If you need to shrink later, int8 quantization cuts size to a quarter with usually a small quality loss, or you can use a model that supports reduced dimensions such as Qwen3-Embedding.

CPU or GPU

Embedding has two different workloads. The first is the initial indexing of every document, done once, and the second is embedding queries at search time. A single query through a 1024-dimension model on CPU takes 40 to 120 milliseconds, which is fine for an internal app. Initial indexing is the costly part: 200 thousand chunks at 8 per second is about 7 hours on CPU, while a mid-range GPU finishes in under 15 minutes.

The practical answer for most companies is to run the initial indexing overnight on a cloud server with enough resources, then update the index incrementally as documents are added. The Business plan with 4 cores and 8GB at 11,500 SAR per year runs bge-m3 with pgvector for around 300 thousand chunks, and if load grows you add 2GB RAM for 600 SAR or a vCPU for 500 SAR per year. Use a dedicated inference server such as Hugging Face text-embeddings-inference instead of loading the model inside your app, since it batches requests and multiplies throughput:

docker run -d --name tei -p 127.0.0.1:8081:80 \
  -v /srv/models:/data \
  ghcr.io/huggingface/text-embeddings-inference:cpu-1.5 \
  --model-id BAAI/bge-m3 --max-batch-tokens 16384

Testing 100 questions from your own data

This part matters more than everything above. Public leaderboards such as MTEB score models on general data, while your text has its own vocabulary and dialect. The test goes like this:

  1. Collect 100 real questions from support logs or staff, half in dialect and half in MSA, with some containing English terms.
  2. For each question, mark by hand which chunk or chunks answer it. This is the longest step and takes about an hour and a half.
  3. Embed all documents with each candidate model, search with every question, and compute Recall@5 and MRR.
from sentence_transformers import SentenceTransformer
import numpy as np

def recall_at_k(model_id, docs, qs, gold, k=5, qp="", dp=""):
    m = SentenceTransformer(model_id)
    D = m.encode([dp+normalize_ar(x) for x in docs], normalize_embeddings=True)
    Q = m.encode([qp+normalize_ar(x) for x in qs], normalize_embeddings=True)
    top = np.argsort(-Q @ D.T, axis=1)[:, :k]
    return np.mean([any(g in row for g in gold[i]) for i, row in enumerate(top)])

print(recall_at_k("intfloat/multilingual-e5-large", docs, qs, gold,
                  qp="query: ", dp="passage: "))
print(recall_at_k("BAAI/bge-m3", docs, qs, gold))

As a rule of thumb, Recall@5 above 0.85 is excellent for an internal RAG system, 0.70 to 0.85 is acceptable once you add a reranker, and below 0.70 usually means the problem is in how documents are chunked rather than in the model. Try chunks of 300 to 500 words with 15% overlap before you switch models.

When to add a reranker

If Recall@5 reaches, say, 0.75 and the correct chunk often lands at position 7 or 12 instead of first, the fix is not a bigger embedding model but a second stage that reorders results. Pull the top 30 chunks from vector search, then pass each (question, chunk) pair through a cross-encoder such as bge-reranker-v2-m3, which reads the question and chunk together and returns a more precise relevance score. It is multilingual, supports Arabic and has about 568M parameters. The cost is latency: reranking 30 chunks on CPU takes 300 to 800 milliseconds, which is fine for an internal assistant answering in two or three seconds. The usual result is a clear jump in top-1 accuracy, and that first position is what the language model relies on when it writes the answer.

Why embeddings stay on your server

Embedding touches every document you own, so using an external API means sending your entire archive of contracts and policies outside the Kingdom. Running the model locally solves that, removes per-request cost and gives you stable latency. The AI hosting templates come with Docker and Jupyter ready, so you can start testing the same day.

The decision in four steps

  1. Start with bge-m3 and multilingual-e5-large, and normalize Arabic text before embedding.
  2. Test 100 questions from your own data and measure Recall@5 instead of relying on a public leaderboard.
  3. Choose 1024 dimensions while you have fewer than a million chunks, and budget memory at four bytes per dimension plus 50% for the index.
  4. Keep the embedding server separate from your app, run initial indexing overnight, then check pricing and plans to size the server.
Share