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.
Semantic search
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.
Indexing and search
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.
- kjarni.ai - documentation and worked examples
- GitHub - source, issues, releases
- NuGet - the same engine from C#
- npm - the same engine in the browser, via WebAssembly
- Go module - the same engine from Go
- Semantic Search in C++ - the C ABI this package wraps, used directly
- Why I Built a Native ML Inference Engine in Rust - what is underneath all of it
- ML from the Command Line - the same models as a UNIX tool
License
MIT or Apache-2.0.
Release files for kjarni 0.1.10
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| kjarni-0.1.10-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| kjarni-0.1.10-py3-none-manylinux_2_17_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| kjarni-0.1.10-py3-none-manylinux_2_17_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| kjarni-0.1.10-py3-none-macosx_11_0_arm64.whl | Python 3 | none | macOS 11.0+ ARM64 | Details |
Total release size: 35.1 MB
Release files / kjarni-0.1.10-py3-none-win_amd64.whl
| Download URL | kjarni-0.1.10-py3-none-win_amd64.whl |
|---|---|
| Size | 9.3 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
c3d089ead2987c93bf926b90e81edce4280ac655a6a2af95d30a8925db40f958
|
|
BLAKE2b-256 checksum How to use checksums |
091dd09fd65f08ba2ab2a381f10f716bc5873db2b52b44a2c8b7dd88c6cdde9b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|
Release files / kjarni-0.1.10-py3-none-manylinux_2_17_x86_64.whl
| Download URL | kjarni-0.1.10-py3-none-manylinux_2_17_x86_64.whl |
|---|---|
| Size | 9.1 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
2144938ce883f1fd4eb39a4906c2bbb191edc12ea657b4e5d20f2111b47167dd
|
|
BLAKE2b-256 checksum How to use checksums |
6febef68c21268bfdd9934c352748614d7b98afb71f67350ca94d16a10399ed3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|
Release files / kjarni-0.1.10-py3-none-manylinux_2_17_aarch64.whl
| Download URL | kjarni-0.1.10-py3-none-manylinux_2_17_aarch64.whl |
|---|---|
| Size | 8.7 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
bf087b440eba325f85c7d4cc9c60303989a49830377c27a32dfc69672da9958c
|
|
BLAKE2b-256 checksum How to use checksums |
102cf95800c54400d6b2e8163a1237f7eea582ace6261e17cb3b80ea6f82bc37
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|
Release files / kjarni-0.1.10-py3-none-macosx_11_0_arm64.whl
| Download URL | kjarni-0.1.10-py3-none-macosx_11_0_arm64.whl |
|---|---|
| Size | 8.0 MB |
| Tags | Python 3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
d3525bf76208a07ea5d15501a40246a12abe5351ba54e9c56648fdb95449a79b
|
|
BLAKE2b-256 checksum How to use checksums |
16c5d1c803de0a3a20f8d2f93bc8c62798146f95d31a4241561f4e5c86ee1204
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.16
|