Skip to main content

Kjarni for Python

Embeddings, classification, reranking and semantic search, running locally on CPU inside your process. No PyTorch, no ONNX Runtime, no model conversion, no server.

pip install kjarni           # no dependencies at all
pip install kjarni[numpy]    # adds ndarray returns from encode_batch
import kjarni

embedder = kjarni.Embedder("minilm-l6-v2")
print(embedder.similarity("doctor", "physician"))  # 0.8598
print(embedder.similarity("doctor", "banana"))     # 0.3379

The model downloads on first use (about 90MB for minilm-l6-v2) and caches under ~/.cache/kjarni. Nothing leaves your machine.

Kjarni is a Rust inference engine. This package is a thin ctypes wrapper over the same C ABI that the C#, Go, C++ and WebAssembly packages call, so there is one implementation of every kernel and no second engine to drift from the first.

Why this instead of sentence-transformers

The vectors are the same. This is all-MiniLM-L6-v2 encoding "Hello world":

embedder.encode("Hello world")[:5]
# [-0.03447728, 0.03102318, 0.00673499, 0.02610899, -0.03936202]
# sentence-transformers, same model
# [-0.03447726, 0.03102319, 0.00673499, 0.02610895, -0.03936201]

Same weights, same output, without the multi-gigabyte dependency tree. The trade is scope: Kjarni implements BERT-style encoders, cross-encoders, Llama-family decoders, T5, BART and Whisper. For an arbitrary research model, use PyTorch.

Embeddings

embedder = kjarni.Embedder("minilm-l6-v2")

vector = embedder.encode("Hello world")
len(vector)          # 384
embedder.dim         # 384, a property rather than a call

vectors = embedder.encode_batch(["first", "second", "third"])
vectors.shape        # (3, 384) with numpy installed, else a list of lists

The package has no hard dependencies. encode_batch imports numpy on demand and returns an ndarray when it is available, so it drops straight into whatever you already use for vector math; without numpy it returns a list of lists and everything else works unchanged. Vectors are normalised by default, which makes cosine similarity a plain dot product.

Encode the documents once, encode the query at search time, sort by similarity:

This one uses numpy for the matrix multiply, so it wants kjarni[numpy]:

import kjarni, numpy as np

embedder = kjarni.Embedder("minilm-l6-v2")

docs = [
    "How do I reset my password?",
    "What is your refund policy?",
    "Do you ship internationally?",
    "How do I update my billing address?",
    "Where can I track my order?",
]

corpus = embedder.encode_batch(docs)
query = np.array(embedder.encode("I need to change my login credentials"))

for score, doc in sorted(zip(corpus @ query, docs), reverse=True):
    print(f"  {score:7.4f}  {doc}")
   0.5981  How do I reset my password?
   0.4067  How do I update my billing address?
   0.0767  Where can I track my order?
  -0.0027  What is your refund policy?
  -0.0451  Do you ship internationally?

"Change my login credentials" matches "reset my password" at 0.60 while sharing no words with it. That gap is the whole idea behind semantic search, and it is explained at length in Semantic Search in C#, which uses the same model and prints the same numbers.

Classification

classifier = kjarni.Classifier("roberta-sentiment")

for text in ["I love this product!", "Terrible quality, broke after one day."]:
    result = classifier.classify(text)
    print(f"  {result.label:<9} {result.score * 100:.1f}%  {text}")
  positive   98.5%  I love this product!
  negative   94.1%  Terrible quality, broke after one day.

classify_batch takes a list. result.top_k(2) gives the runners up, and result.above_threshold(0.5) filters. There is more on model choice, emotion and toxicity in Sentiment Analysis in C#.

Reranking

A cross-encoder reads the query and the document together rather than comparing two independently produced vectors. Slower, much more precise, and meant as a second pass over whatever the embeddings retrieved:

reranker = kjarni.Reranker()

docs = [
    "Machine learning is a subset of artificial intelligence.",
    "Deep learning uses neural networks with many layers.",
    "The weather today is sunny.",
]

for r in reranker.rerank("What is machine learning?", docs):
    print(f"  {r.score:9.4f}  {docs[r.index]}")
    10.5139  Machine learning is a subset of artificial intelligence.
    -5.5301  Deep learning uses neural networks with many layers.
   -11.1001  The weather today is sunny.

The scores are logits, not probabilities: what matters is the ordering and the size of the gap. rerank returns indices into your list rather than copies of the text, so whatever IDs and permissions came with your documents stay attached to them. Build a Document Search Engine in C# walks through combining keyword search, embeddings and reranking into one pipeline.

For a corpus that outlives the process, Indexer builds an on-disk index and Searcher queries it:

kjarni.Indexer().create("./docs", "index.kj")

searcher = kjarni.Searcher("index.kj")
for hit in searcher.search("how do I change my password", top_k=5):
    print(f"  {hit.score:.4f}  {hit.text}")

SearchMode selects keyword, semantic or hybrid retrieval, and the searcher can carry a reranker for the second pass.

Choosing a model

Model Dimensions Input limit Notes
minilm-l6-v2 384 256 tokens Default. Fast, good quality per byte
mpnet-base-v2 768 384 tokens Higher quality, slower
nomic-embed-text 768 8192 tokens Long documents, though trained at 2048
bge-m3 1024 8192 tokens Large, multilingual

Mind the input limit. minilm-l6-v2 reads 256 tokens, roughly 900 characters, and silently drops the rest: no error, no warning, just a vector computed from the first part of your text. If your documents are longer, chunk them or pick a longer window. The cost is measured in Your MiniLM Embeddings Are Probably Truncating at 256 Tokens.

Notes

Threading. Handles are not individually thread safe. Give each thread its own, or serialise calls. The engine already parallelises across cores inside a single call.

GPU. Pass device="gpu" to any constructor. It uses WebGPU, so there is no CUDA toolkit to install.

Errors. Failures raise kjarni.KjarniException, which carries the engine's message and error code.

The same engine elsewhere

There is one Rust engine behind all of these, and one set of kernels. The numbers on this page are the numbers the other packages print.

License

MIT or Apache-2.0.

Metadata

Release files for kjarni 0.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for kjarni 0.2.3
File
kjarni-0.2.3-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
kjarni-0.2.3-py3-none-manylinux_2_17_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details
kjarni-0.2.3-py3-none-manylinux_2_17_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details
kjarni-0.2.3-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details

Total release size: 35.4 MB

Release files / kjarni-0.2.3-py3-none-win_amd64.whl

Download URL kjarni-0.2.3-py3-none-win_amd64.whl
Size 9.3 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
9147dacedd853cb52016206998847da7653d44403f061a95937213fc6e2888cc
BLAKE2b-256 checksum
How to use checksums
52f36c40852a25712cf968aaf1976b628041c53a03fbcaa3440d2a8c4f9d61ac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.17

Release files / kjarni-0.2.3-py3-none-manylinux_2_17_x86_64.whl

Download URL kjarni-0.2.3-py3-none-manylinux_2_17_x86_64.whl
Size 9.2 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
3da7f9abf2c3524092226600083373cef83b0fc42c2c19fea007f850cc2ba925
BLAKE2b-256 checksum
How to use checksums
891f1b4ca6af4a9ed07452cb25851c14ce3c81ccc715a0f6e92d83d7fc8228f0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.17

Release files / kjarni-0.2.3-py3-none-manylinux_2_17_aarch64.whl

Download URL kjarni-0.2.3-py3-none-manylinux_2_17_aarch64.whl
Size 8.8 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
b0c9d275fcf83374e89c36ff4fd5d81779e00ceca50cfde099c6c492501bb422
BLAKE2b-256 checksum
How to use checksums
61bb8c58aae9a8292535761b31e670a51d18e231e6bfd02dd0d92466c4d66e56
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.17

Release files / kjarni-0.2.3-py3-none-macosx_11_0_arm64.whl

Download URL kjarni-0.2.3-py3-none-macosx_11_0_arm64.whl
Size 8.1 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
60f63592ad1ec56b4e05f8912152d5c91b6e6bd8162e6bb0c3d737dc31ec0a43
BLAKE2b-256 checksum
How to use checksums
37861ce1e828a081e4e3fa442def9de8fbef352bc8f5b2149e8c8cea04934c1c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.17

Release history Release notifications | RSS feed

This release

0.2.3 This release

4 release files

0.2.0

4 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page