0.5.0 on NuGet · 13 Stable profiles, more in Experimental

Local AI in .NET.
No Python. No API key.

Embeddings, entity extraction, image and video search, and chat as ordinary NuGet packages. They run in-process on ONNX Runtime, on CPU or CUDA, with no Python sidecar and no cloud API. A model is only called Stable once it has passed every release gate against the upstream reference.

$ dotnet add package Starlings.Models.E5.Extensions.AI
No Python sidecar No cloud API Microsoft.Extensions.AI CPU & CUDA SHA-256 pinned weights MIT OR Apache-2.0

Quickstart

A model is a PackageReference.

Requires the .NET 10 SDK. Add a Starling package plus exactly one native ONNX Runtime package. DownloadAsync() fetches the pinned files once. After that, inference never touches the network.

dotnet add package Starlings.Models.E5.Extensions.AI
dotnet add package Microsoft.ML.OnnxRuntime --version 1.30.0
Program.cs
using System.Numerics.Tensors;
using Microsoft.Extensions.AI;
using Starlings.Models.E5;
using Starlings.Models.E5.Extensions.AI;

// One-time download, pinned by commit and checked by SHA-256. Inference never touches the network.
E5ModelFiles files = await E5ModelAssets.DownloadAsync();
using var embedder = E5EmbeddingGenerator.Create(files);

// e5 expects "passage: " on documents and "query: " on queries.
string[] docs = ["passage: The Eiffel Tower is in Paris.",
                 "passage: Der Rhein fließt durch Köln.",
                 "passage: 富士山は日本一高い山です。"];
var docVectors = await embedder.GenerateAsync(docs);
var query = await embedder.GenerateVectorAsync("query: Which river runs through Cologne?");

foreach (var (doc, v) in docs.Zip(docVectors))
    Console.WriteLine($"{TensorPrimitives.CosineSimilarity(query.Span, v.Vector.Span):F3}  {doc}");
dotnet run
0.680  passage: The Eiffel Tower is in Paris.
0.807  passage: Der Rhein fließt durch Köln.
0.688  passage: 富士山は日本一高い山です。

Want a whole program? The samples cover semantic search, reranking, local RAG, ticket triage in ML.NET, and e5 in the browser.

Recipes

What you can build.

Three complete programs, built on the published packages. Each downloads its models once. After that, it runs with the network off.

Ask a folder of docs

LocalRag answers questions over your Markdown and text files, using EmbeddingGemma 2 and Qwen3.5 through Microsoft.Extensions.AI. About 4 GB, once. It's a small-folder demonstration: it re-embeds on every question, and a 0.8B model can be wrong, so it prints its sources.

Triage tickets in any language

TicketTriage trains a router on English tickets in an ordinary ML.NET pipeline, then routes German, Spanish, French and Japanese tickets too, with GLiNER2 pulling out the entities. About 1.3 GB, once.

Search photos with text

EmbeddingGemma2Search ranks a folder of images, or videos, against a text query. About 2.9 GB, once, and about 4.5 s per photo on a 12-thread CPU, so start small or use CUDA.

What's new

Three releases since 0.2.0.

Everything below is on NuGet. New models start as Experimental, and each release record lists what changed package by package.

0.5.0

Qwen3.5 gets an int4 variant for CPU, and guided decoding works on the hosted files. GLiNER2 adds text classification on CPU, and new helpers put a reranker after a vector search. All of these are Experimental. Every model package now has a generated capability record.

0.4.0

The BERT family arrives with seven embedding and reranking profiles, including bge-m3. e5 runs in the browser on WebAssembly and WebGPU, and LFM3-1.7B joins as a generator. Generation packages split into a model definition and an ONNX Runtime package, which is a breaking change for direct users.

0.3.0

Kev decision models reach NuGet. They return a calibrated probability per option, with no generated text. Qwen3-0.6B and Qwen2.5-0.5B-Instruct add small text chat on CPU, and batching becomes an opt-in for the embedding and extraction packages.

Upgrading? Move every Starlings.* model and runtime reference to the same version together. Starlings.Tokenizers stays at the version they pin, which is 0.3.4 for 0.5.0. All releases →

No lock-in

Swap the provider, keep your code.

Starling implements the vendor-neutral Microsoft.Extensions.AI abstractions: IEmbeddingGenerator and IChatClient. Whatever sits behind them today, a local model is one line away. The libraries depend only on Microsoft.Extensions.AI.Abstractions, and CI rejects any Azure or OpenAI SDK dependency.

Services.cs
- IChatClient chat = /* your hosted provider */;
+ using IChatClient chat = Qwen35ChatClient.Create(files);

  var reply = await chat.GetResponseAsync(prompt);

Models

Open models, faithfully packaged.

Each model is pinned to an exact upstream commit and its files are verified by SHA-256. The four below are Stable, so their outputs have passed every gate against the original PyTorch/Python implementation.

Text embeddings~490 MB

multilingual-e5-small

384-dimension embeddings in 100 languages. Small, fast, and a strong default for semantic search.

Starlings.Models.E5
Multimodalup to ~2.9 GB

EmbeddingGemma 2

768-dimension Matryoshka embeddings, with text, images and video in one shared space.

Starlings.Models.EmbeddingGemma2
Entity extraction~840 MB

GLiNER2

Zero-shot named entity recognition. Pass any labels you like per call, with no fine-tuning.

Starlings.Models.Gliner2
Chat & generation~3 GB

Qwen3.5-0.8B

Local streaming chat and tool calling through IChatClient, on ONNX Runtime GenAI, CPU or CUDA.

Starlings.Models.Qwen35.Extensions.AI

These four are Stable on Linux x64, on CPU and on CUDA with an NVIDIA GeForce RTX 3090. Other GPUs aren't covered by the Stable contracts. Windows CPU is experimental for e5, GLiNER2 and EmbeddingGemma 2 text, and untested otherwise. macOS and ARM64 are untested. ML.NET transformers are available for e5 and EmbeddingGemma 2. Full status per platform and provider is in the compatibility matrix. Want another model? Request one.

Experimental, on NuGet

You can install these today. Release gates G0 to G7 haven't finished for them, so expect their contracts to change.

TaskPackageModels and where they run
Embeddings & rerankingStarlings.Models.Bertall-MiniLM-L6-v2, bge-small-en-v1.5, bge-base-en-v1.5, bge-m3, nomic-embed-text-v1.5, and the rerankers ms-marco-MiniLM-L6-v2 and bge-reranker-v2-m3 · CPU
DecisionsStarlings.Models.Kev.OnnxKev-0.8B and Kev-4B: a calibrated probability per option · CPU and CUDA
Browser embeddingsStarlings.Models.E5.Browsermultilingual-e5-small on onnxruntime-web · WebAssembly and WebGPU, checked in Chromium on Linux
Small chatStarlings.Models.QwenChat.OnnxGenAIQwen3-0.6B and Qwen2.5-0.5B-Instruct · CPU
Smaller Qwen3.5Starlings.Models.Qwen35.OnnxGenAIQwen3.5-0.8B int4 · CPU
Text classificationStarlings.Models.Gliner2GLiNER2's classification head · CPU
GenerationStarlings.Models.Lfm3LFM3-1.7B · CPU. Not MIT or Apache-2.0: under the LFM Open License v1.0, commercial use is licensed only below USD 10 million annual revenue

Whisper large-v3-turbo, PaddleOCR and Qwen3-Embedding-0.6B are in the repository but not on NuGet, so build them from source. Status for every profile is in the model catalog.

Pipeline

Every model runs the same line.

Twelve stages take a model from an upstream release to a package that runs offline in your app. The original Python implementation runs once to cut a golden master: protected fixtures and tolerances only the maintainer can change. The tokenizer, the exported graph and the C# path are each measured against it, and the first stage that diverges is named.

Machine view of the model delivery pipeline: twelve stations on a conveyor, from discovering an upstream model, through admission, pinning, the Python reference, export, the .NET implementation, fidelity checks, optimization, integration and gates G0 to G7, to a maintainer-approved release that runs offline in your app. (full size, opens in a new tab)

The stages are defined in the delivery plan. Open the full-size view (opens in a new tab) →

Evidence

“It works” is a claim. Here are the receipts.

A profile is Stable only when every release gate has passed, and each gate links to the run that passed it in the model catalog. As of 0.5.0, 13 profiles are Stable, with the same contracts as in 0.2.0: no gate, tolerance or protected fixture has changed. Profiles added since then are Experimental until their gates run. Qualification runs weekly on CPU, and on every release tag it also tries an RTX 3090.

  1. G0ProvenanceExact upstream commit, SHA-256 on every file
  2. G1–G3Parity & qualityNumerical parity and task quality against the Python reference
  3. G4Leak soakA one-hour soak, watching for leaks
  4. G5BenchmarksLatency measured against Python on the same backend
  5. G6Clean installInstalled from the published packages
  6. G7IntegrationIntegration checks pass

Not every run is green. Since late October, including on the 0.4.0 and 0.5.0 tags, the CPU video gates couldn't download their pinned FFmpeg builds (#277), which is a fetch failure and not a model regression. For 0.5.0, no RTX 3090 was available, so the GPU regression run didn't happen. Both are still owed. Two jobs for Experimental models that report but don't gate are also failing (#278). Qualification runs →

As fast as Python, from C#.

Measured with multilingual-e5-small, C# library vs the Python reference on the same ONNX Runtime backend. The chart shows the ratio of medians with 95% bootstrap confidence intervals. 1.0 means parity, and lower means C# is faster.

Once warm, latency is within noise of Python on CPU and slightly faster on CUDA. Cold start, meaning the first call including model load, is currently slower in C#. We show that too.

Text path, concurrency 1. CUDA runs on an NVIDIA GeForce RTX 3090. Full results, 2026-10-09 →

C# ÷ Python median latency (multilingual-e5-small)
Show as table
ProviderWorkloadC# msPython msRatio95% CI

Why Starling

The trade-offs, without the trade-offs.

If you would otherwise…The trade-offWith Starling
Call a hosted embedding or chat APIData leaves the machine, every call costs money, and the model can change under youThe model runs in-process. Weights are pinned by commit and hash.
Run a Python sidecarA second runtime to deploy, secure and scaleOne PackageReference. The Python reference is what we test against.
Call ONNX Runtime directly from C#You write the tokenizer, pre- and post-processing and parity checks yourselfAll of that is included and checked against upstream
Use a GGUF chat runtimeUsually chat only, and quantizedEmbeddings, extraction, image and video as well as chat. The fp32 graphs are checked against the original weights.

Built for coding agents

Your agent already knows how to use it.

Starling ships files written for Claude Code, Copilot, Cursor and Codex:

  • llms.txt is a short map of what matters when writing code.
  • The starling-dotnet Agent Skill covers which package to use, verified code for every model, CUDA, and the pitfalls.
  • The starling-add-model skill walks an agent through contributing a new model as a pull request.
Paste into your agent
Read https://raw.githubusercontent.com/starlinghq/starling/main/llms.txt and
https://raw.githubusercontent.com/starlinghq/starling/main/skills/starling-dotnet/SKILL.md,
then use the Starlings.* NuGet packages to <what you want to build>.
Or install the skill for Claude Code
mkdir -p .claude/skills/starling-dotnet
curl -fsSL https://raw.githubusercontent.com/starlinghq/starling/main/skills/starling-dotnet/SKILL.md \
  -o .claude/skills/starling-dotnet/SKILL.md

Starlings.Tokenizers

Token for token, the same as Hugging Face.

The tokenizer inside every Starling model is also its own library. It reads Hugging Face tokenizer.json files in pure .NET and produces the same token ids, offsets and decoded text as huggingface/tokenizers, checked case by case.

Faithful

Every supported tokenizer has a profile: a pinned file with reference outputs for multilingual text, emoji, combining marks, pairs, truncation and padding. CI fails on any difference.

Explicit

A component it doesn't implement fails at load with UnsupportedTokenizerComponentException, naming the JSON path. Nothing is approximated.

Plain .NET

No native code, no package dependencies, net10.0. Instances are immutable and thread-safe. A 17 MB vocabulary loads in about 0.27 s.

ModelAlgorithmEvidence
intfloat/multilingual-e5-smallSentencePiece Unigram49/49 cases exact
sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2SentencePiece Unigram, WhitespaceSplit49/49 cases exact
fastino/gliner2-base-v1SentencePiece Unigram (DeBERTa-v3)per-word and 10 whole-text cases · offsets, pairs and decode untested
jaredpalmer/kev-0.8bbyte-level BPE (Qwen2/Qwen3 regex)137/137 cases exact · 0 mismatches in 1.15 M randomized encodes
Qwen/Qwen3.5-0.8Bbyte-level BPE137/137 cases exact · 0 mismatches in 1.15 M randomized encodes
google/embeddinggemma-2SentencePiece-style BPE49/49 cases exact · 0 mismatches in 1.7 M randomized encodes
Qwen/Qwen3-Embedding-0.6Bbyte-level BPE137/137 cases exact
Qwen/Qwen3-Reranker-0.6Bbyte-level BPE137/137 cases exact
LiquidAI/LFM3-1.7Bbyte-level BPE (Llama 3 regex)137/137 cases exact
Diffuspace/Clover-35B-A3B-Previewbyte-level BPE137/137 cases exact
microsoft/Phi-4-mini-instructbyte-level BPE (o200k regex)137/137 cases exact · 1,500 randomized strings with the Phi-4 tags exact
Alibaba-NLP/gte-reranker-modernbert-basebyte-level BPE (GPT-2 regex)137/137 cases exact

Starlings.Tokenizers is at 0.3.5. Every tokenizer profile is still experimental. Starling 0.5.0 pins 0.3.4 exactly, and the next release moves to 0.3.5. Other files may load and work, but without a profile, matching the reference is untested. Full list and details →

Built on Starling

The rest of the flock.

Two projects from StarlingHq run on Starling's published packages.

Corvid

A local-first .NET agent. Kev, GLiNER2 and the embedding models make the many small decisions in-process, and a swappable local or cloud model does the writing. Secrets stay in Corvid Vault, a separate process that runs no model.

v0.1.0 pre-release · Linux x86_64

Roost

A C# serving engine for NVIDIA GPUs, built to replace vLLM and serve the Starling model library behind one host. Each GPU has its own worker process, so a GPU fault never takes down the host, and there is no Python or libtorch at runtime.

In development · nothing released yet

Join the flock.

Starling is open source and in active development. Issues, model requests and pull requests are welcome.