ElBruno.LocalLLMs
0.19.1
dotnet add package ElBruno.LocalLLMs --version 0.19.1
NuGet\Install-Package ElBruno.LocalLLMs -Version 0.19.1
<PackageReference Include="ElBruno.LocalLLMs" Version="0.19.1" />
<PackageVersion Include="ElBruno.LocalLLMs" Version="0.19.1" />
<PackageReference Include="ElBruno.LocalLLMs" />
paket add ElBruno.LocalLLMs --version 0.19.1
#r "nuget: ElBruno.LocalLLMs, 0.19.1"
#:package ElBruno.LocalLLMs@0.19.1
#addin nuget:?package=ElBruno.LocalLLMs&version=0.19.1
#tool nuget:?package=ElBruno.LocalLLMs&version=0.19.1
ElBruno.LocalLLMs
Run local LLMs in .NET through IChatClient ๐ง
Run local LLMs in .NET through IChatClient โ the same interface you'd use for Azure OpenAI, Ollama, or any other provider. Powered by ONNX Runtime GenAI and BitNet.
What's New
- โ
Gemma 4 support path is now active (
E2B,E4B,12B Unified,26B-A4B,31B) via conversion workflows. - ๐ Gemma 4 status moved from pending to convert across the model tables and guides.
- โฌ๏ธ ONNX Runtime GenAI upgraded to
0.14.1across library, tests, samples, and benchmarks.
Features
- ๐
IChatClientimplementation โ seamless integration with Microsoft.Extensions.AI - ๐ฆ Automatic model download โ models are fetched from HuggingFace on first use
- ๐ Zero friction โ works out of the box with sensible defaults (Phi-3.5 mini)
- ๐ฅ๏ธ Multi-hardware โ CPU, CUDA, and DirectML execution providers
- ๐ DI-friendly โ register with
AddLocalLLMs()orAddBitNetChatClient()in ASP.NET Core - ๐ Streaming โ token-by-token streaming via
GetStreamingResponseAsync - ๐ Multi-model โ switch between Phi-3.5, Phi-4, Qwen2.5, Llama 3.2, and more
- ๐ฏ Fine-tuned models โ pre-trained Qwen2.5 variants for tool calling and RAG (guide)
- โก BitNet support โ run 1.58-bit ternary models via bitnet.cpp with extreme efficiency (guide)
- ๐ OpenTelemetry diagnostics โ lifecycle activities and metrics for queued, first-token, completion, cancellation, and failure (guide)
Packages
Installation
dotnet add package ElBruno.LocalLLMs
Then add one runtime package depending on your target hardware:
# ๐ฅ๏ธ CPU (works everywhere โ required for CPU-only apps):
dotnet add package Microsoft.ML.OnnxRuntimeGenAI
# ๐ข NVIDIA GPU (CUDA):
dotnet add package Microsoft.ML.OnnxRuntimeGenAI.Cuda
# ๐ต Any Windows GPU โ AMD, Intel, NVIDIA (DirectML):
dotnet add package Microsoft.ML.OnnxRuntimeGenAI.DirectML
โ ๏ธ Add exactly one runtime package. Do not reference both
Microsoft.ML.OnnxRuntimeGenAIandMicrosoft.ML.OnnxRuntimeGenAI.Cudasimultaneously โ the native binaries conflict and GPU support will silently fail.
๐ The library defaults to
ExecutionProvider.Autoโ it tries GPU first and falls back to CPU automatically. No code changes needed.
Quick Start
using ElBruno.LocalLLMs;
using Microsoft.Extensions.AI;
// Create a local chat client (downloads Phi-3.5 mini on first run)
using var client = await LocalChatClient.CreateAsync();
var response = await client.GetResponseAsync([
new(ChatRole.User, "What is the capital of France?")
]);
Console.WriteLine(response.Text);
First Run
The first time you create a LocalChatClient, the model is downloaded from HuggingFace to your local cache directory (~2-4 GB). This typically takes 30-60 seconds depending on your internet connection.
Track download progress:
using var client = await LocalChatClient.CreateAsync(
new LocalLLMsOptions { Model = KnownModels.Phi35MiniInstruct },
progress: new Progress<ModelDownloadProgress>(p =>
{
var percent = (p.BytesDownloaded * 100) / p.TotalBytes;
Console.WriteLine($"{p.FileName}: {percent:F1}%");
})
);
Subsequent runs load instantly from cache (%LOCALAPPDATA%/ElBruno/LocalLLMs/models).
Skip auto-download if using a pre-downloaded model:
var options = new LocalLLMsOptions
{
Model = KnownModels.Phi35MiniInstruct,
ModelPath = "/path/to/local/model",
EnsureModelDownloaded = false
};
using var client = await LocalChatClient.CreateAsync(options);
Streaming
using ElBruno.LocalLLMs;
using Microsoft.Extensions.AI;
using var client = await LocalChatClient.CreateAsync(new LocalLLMsOptions
{
Model = KnownModels.Phi35MiniInstruct
});
await foreach (var update in client.GetStreamingResponseAsync([
new(ChatRole.System, "You are a helpful assistant."),
new(ChatRole.User, "Explain quantum computing in simple terms.")
]))
{
Console.Write(update.Text);
}
GPU Acceleration
By default, ExecutionProvider.Auto tries GPU first (CUDA โ DirectML) and falls back to CPU automatically:
// Use explicit GPU provider (fails if CUDA not installed; use Auto to fallback to CPU)
var options = new LocalLLMsOptions
{
ExecutionProvider = ExecutionProvider.Cuda
};
// Multi-GPU systems: select device ID
var options2 = new LocalLLMsOptions
{
ExecutionProvider = ExecutionProvider.Cuda,
GpuDeviceId = 1 // Use second GPU
};
Auto fallback behavior:
- CUDA available โ uses NVIDIA GPU
- CUDA unavailable, DirectML available โ uses AMD/Intel Arc GPU
- GPU unavailable โ falls back to CPU (no errors, just slower)
See Troubleshooting: GPU Setup for debugging GPU issues.
Model Metadata
Inspect model capabilities at runtime โ context window size, model name, and vocabulary:
using var client = await LocalChatClient.CreateAsync();
var metadata = client.ModelInfo;
Console.WriteLine($"Model: {metadata?.ModelName}");
Console.WriteLine($"Context window: {metadata?.MaxSequenceLength}");
Console.WriteLine($"Vocab size: {metadata?.VocabSize}");
This is useful for prompt-length validation, adaptive chunking, and model selection logic.
Dependency Injection
builder.Services.AddLocalLLMs(options =>
{
options.Model = KnownModels.Phi35MiniInstruct;
options.ExecutionProvider = ExecutionProvider.DirectML;
});
// Inject IChatClient anywhere
public class MyService(IChatClient chatClient) { ... }
Error Handling
The library provides structured exception types for graceful error handling:
using ElBruno.LocalLLMs;
using Microsoft.Extensions.AI;
try
{
using var client = await LocalChatClient.CreateAsync();
var response = await client.GetResponseAsync([
new(ChatRole.User, "Your question here")
]);
}
catch (ExecutionProviderException ex)
{
// GPU/provider-specific error (no CUDA, DirectML not available, etc.)
Console.WriteLine($"Provider error: {ex.Message}");
}
catch (ModelCapacityExceededException ex)
{
// Prompt/response too long for model's context window
Console.WriteLine($"Capacity error: {ex.Message}");
// Solution: use a larger model or truncate the prompt
}
catch (InvalidOperationException ex)
{
// General operation error (model not found, download failed, etc.)
Console.WriteLine($"Operation error: {ex.Message}");
}
Observability
LocalChatClient emits generation lifecycle diagnostics through ActivitySource and Meter, both named ElBruno.LocalLLMs.
using ElBruno.LocalLLMs.Diagnostics;
builder.Services.AddOpenTelemetry()
.WithTracing(tracing => tracing.AddSource(LocalLLMsInstrumentation.ActivitySourceName))
.WithMetrics(metrics => metrics.AddMeter(LocalLLMsInstrumentation.MeterName));
By default, telemetry excludes prompt and completion text. Opt in only when you want content attached:
var options = new LocalLLMsOptions
{
CaptureTelemetryContent = true
};
See docs/observability.md for the lifecycle event contract, metric names, and Aspire wiring notes, and docs/cancellation.md for voice barge-in cancellation behavior.
Troubleshooting
GPU not working? Use ExecutionProvider.Cpu explicitly. See GPU Setup Validation.
Out of memory? Try a smaller model:
var options = new LocalLLMsOptions
{
Model = KnownModels.Qwen25_05BInstruct // 0.5B instead of 3.8B
};
Model download fails?
- Check your internet connection
- For private HuggingFace models, set the
HF_TOKENenvironment variable
For detailed troubleshooting, see docs/troubleshooting-guide.md.
Supported Models
| Tier | Model | Parameters | ONNX | ID |
|---|---|---|---|---|
| โช Tiny | TinyLlama-1.1B-Chat | 1.1B | โ Native | tinyllama-1.1b-chat |
| โช Tiny | SmolLM2-1.7B-Instruct | 1.7B | โ Native | smollm2-1.7b-instruct |
| โช Tiny | Qwen2.5-0.5B-Instruct | 0.5B | โ Native | qwen2.5-0.5b-instruct |
| โช Tiny | Qwen2.5-1.5B-Instruct | 1.5B | โ Native | qwen2.5-1.5b-instruct |
| โช Tiny | Gemma-2B-IT | 2B | โ Native | gemma-2b-it |
| โช Tiny | Gemma-4-E2B-IT | 5.1B (2B active) | ๐ Convert | gemma-4-e2b-it |
| โช Tiny | StableLM-2-1.6B-Chat | 1.6B | ๐ Convert | stablelm-2-1.6b-chat |
| ๐ข Small | Phi-3.5 mini instruct | 3.8B | โ Native | phi-3.5-mini-instruct |
| ๐ข Small | Qwen2.5-3B-Instruct | 3B | โ Native | qwen2.5-3b-instruct |
| ๐ข Small | Llama-3.2-3B-Instruct | 3B | โ Native | llama-3.2-3b-instruct |
| ๐ข Small | Gemma-2-2B-IT | 2B | โ Native | gemma-2-2b-it |
| ๐ข Small | Gemma-4-E4B-IT | 8B (4B active) | ๐ Convert | gemma-4-e4b-it |
| ๐ก Medium | Qwen2.5-7B-Instruct | 7B | โ Native | qwen2.5-7b-instruct |
| ๐ก Medium | Llama-3.1-8B-Instruct | 8B | โ Native | llama-3.1-8b-instruct |
| ๐ก Medium | Mistral-7B-Instruct-v0.3 | 7B | โ Native | mistral-7b-instruct-v0.3 |
| ๐ก Medium | Gemma-2-9B-IT | 9B | โ Native | gemma-2-9b-it |
| ๐ก Medium | Gemma-4-12B-IT | 12B | ๐ Convert | gemma-4-12b-it |
| ๐ก Medium | Phi-4 | 14B | โ Native | phi-4 |
| ๐ก Medium | DeepSeek-R1-Distill-Qwen-14B | 14B | โ Native | deepseek-r1-distill-qwen-14b |
| ๐ก Medium | Mistral-Small-24B-Instruct | 24B | โ Native | mistral-small-24b-instruct |
| ๐ด Large | Qwen2.5-14B-Instruct | 14B | โ Native | qwen2.5-14b-instruct |
| ๐ด Large | Qwen2.5-32B-Instruct | 32B | โ Native | qwen2.5-32b-instruct |
| ๐ด Large | Llama-3.3-70B-Instruct | 70B | โ ONNX | llama-3.3-70b-instruct |
| ๐ด Large | Mixtral-8x7B-Instruct-v0.1 | 8x7B | ๐ Convert | mixtral-8x7b-instruct-v0.1 |
| ๐ด Large | DeepSeek-R1-Distill-Llama-70B | 70B | ๐ Convert | deepseek-r1-distill-llama-70b |
| ๐ด Large | Command-R (35B) | 35B | ๐ Convert | command-r-35b |
| ๐ด Large | Gemma-4-26B-A4B-IT | 25.2B (3.8B active) | ๐ Convert | gemma-4-26b-a4b-it |
| ๐ด Large | Gemma-4-31B-IT | 30.7B | ๐ Convert | gemma-4-31b-it |
๐ Convert = Use the conversion scripts in
scripts/to export ONNX locally before running the model.
Fine-Tuned Models
Pre-trained variants optimized for specific tasks. A fine-tuned 0.5B model often matches or exceeds a base 1.5B on its specialized task.
| Model | Size | Task | HuggingFace ID |
|---|---|---|---|
| Qwen2.5-0.5B-ToolCalling | ~1 GB | Tool/function calling | elbruno/Qwen2.5-0.5B-LocalLLMs-ToolCalling |
| Qwen2.5-0.5B-RAG | ~1 GB | RAG with citations | elbruno/Qwen2.5-0.5B-LocalLLMs-RAG |
| Qwen2.5-0.5B-Instruct | ~1 GB | General-purpose | elbruno/Qwen2.5-0.5B-LocalLLMs-Instruct |
See the Supported Models Guide for detailed model cards, performance benchmarks, and selection guidance.
Samples
| Sample | Description |
|---|---|
| HelloChat | Minimal console chat |
| StreamingChat | Token-by-token streaming |
| MultiModelChat | Switch models at runtime |
| DependencyInjection | ASP.NET Core DI registration |
| ToolCallingAgent | Function calling and tool use |
| FineTunedToolCalling | Fine-tuned model for improved tool calling |
| RagChatbot | RAG pipeline with document retrieval |
| ZeroCloudRag | Zero-cloud RAG pipeline with real local embeddings and LLM inference |
| BitNetChat | BitNet 1.58-bit model chat completion |
| BitNetPerformance | Performance benchmark: BitNet vs ONNX models |
| ConsoleAppDemo | Interactive console application |
Requirements
- .NET 8.0 or .NET 10.0
- CPU (default), NVIDIA GPU (CUDA), or Windows GPU (DirectML)
- ~2-8 GB disk space per model (depending on size and quantization)
Building from Source
git clone https://github.com/elbruno/ElBruno.LocalLLMs.git
cd ElBruno.LocalLLMs
dotnet restore ElBruno.LocalLLMs.slnx
dotnet build ElBruno.LocalLLMs.slnx
dotnet test ElBruno.LocalLLMs.slnx --framework net8.0
Documentation
- Getting Started โ installation, first steps, configuration
- Supported Models โ full model reference with tiers, specs, decision tree
- BitNet Guide โ setup and usage of 1.58-bit BitNet models
- Cancellation Guide โ streaming cancellation semantics and voice barge-in usage
- Observability โ OpenTelemetry, lifecycle events, metrics, Aspire wiring
- Architecture โ design decisions and internal structure
- Samples Guide โ walkthrough of each sample application
- Benchmarks โ how to run and interpret performance benchmarks
- Fine-Tuning Guide โ using and training fine-tuned models
- ONNX Conversion โ converting HuggingFace models to ONNX format
- Publishing โ NuGet package publishing with OIDC
- Contributing โ how to contribute
- Changelog โ version history
๐ค Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
๐ License
This project is licensed under the MIT License โ see the LICENSE file for details.
๐ About the Author
Hi! I'm ElBruno ๐งก, a passionate developer and content creator exploring AI, .NET, and modern development practices.
Made with โค๏ธ by ElBruno
If you like this project, consider following my work across platforms:
- ๐ป Podcast: No Tienen Nombre โ Spanish-language episodes on AI, development, and tech culture
- ๐ป Blog: ElBruno.com โ Deep dives on embeddings, RAG, .NET, and local AI
- ๐บ YouTube: youtube.com/elbruno โ Demos, tutorials, and live coding
- ๐ LinkedIn: @elbruno โ Professional updates and insights
- ๐ Twitter: @elbruno โ Quick tips, releases, and tech news
๐ Acknowledgments
- ONNX Runtime GenAI โ inference engine
- BitNet / bitnet.cpp โ 1.58-bit ternary model inference
- Microsoft.Extensions.AI โ IChatClient interface
- Hugging Face โ model hosting and community
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- ElBruno.HuggingFace.Downloader (>= 0.6.0)
- Microsoft.Extensions.AI.Abstractions (>= 10.4.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.0)
- Microsoft.ML.OnnxRuntimeGenAI (>= 0.14.1)
-
net8.0
- ElBruno.HuggingFace.Downloader (>= 0.6.0)
- Microsoft.Extensions.AI.Abstractions (>= 10.4.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 9.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 9.0.0)
- Microsoft.ML.OnnxRuntimeGenAI (>= 0.14.1)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on ElBruno.LocalLLMs:
| Package | Downloads |
|---|---|
|
ElBruno.LocalLLMs.Rag
RAG (Retrieval-Augmented Generation) pipeline for ElBruno.LocalLLMs. Provides document chunking, embedding storage, and semantic search. |
|
|
ElBruno.ModelContextProtocol.MCPToolRouter
Semantic routing for Model Context Protocol (MCP) tool definitions using local embeddings. Indexes MCP tools and returns the most relevant tools for a given prompt via vector search. |
|
|
ElBruno.LocalLLMs.BitNet
BitNet 1.58-bit LLM inference using bitnet.cpp. IChatClient implementation for Microsoft.Extensions.AI. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.19.1 | 140 | 7/3/2026 |
| 0.19.0 | 140 | 7/3/2026 |
| 0.18.0 | 170 | 6/5/2026 |
| 0.17.0 | 142 | 6/3/2026 |
| 0.16.0 | 214 | 4/17/2026 |
| 0.15.0 | 151 | 4/16/2026 |
| 0.11.0 | 154 | 4/4/2026 |
| 0.9.0 | 126 | 4/4/2026 |
| 0.7.2 | 220 | 3/28/2026 |
| 0.7.1 | 138 | 3/28/2026 |
| 0.7.0 | 129 | 3/28/2026 |
| 0.6.1 | 134 | 3/28/2026 |
| 0.6.0 | 127 | 3/28/2026 |
| 0.5.0 | 169 | 3/28/2026 |
| 0.1.8 | 116 | 3/19/2026 |
| 0.1.7 | 115 | 3/18/2026 |
| 0.1.6 | 111 | 3/18/2026 |
| 0.1.0 | 115 | 3/18/2026 |