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.
The last 5 notable additions to the library. Updated with each NuGet release.
ElBruno.LocalLLMs.BlazorComponents β new Razor Class Library with 7 ready-to-use Blazor components: ModelStatusCard (download progress bar + actions), ModelGallery (filterable grid), ModelSelector (two-way-bindable dropdown), ChatBox (streaming token display), EnvironmentDashboard (CPU/CUDA/DirectML badges), LocalLLMHealthBadge (nav-bar status dot), and RagPlayground. Call services.AddLocalLLMsBlazorComponents() to register. See the Blazor Components Guide and the BlazorDemo sample.onnxruntime/gpt-oss-20b-onnx artifacts. Adds the Harmony prompt format, channel-aware output filtering (chain-of-thought is stripped, never shown to users), Harmony tool calling, and a ReasoningEffort option. Two model IDs: gpt-oss-20b (CPU INT4) and gpt-oss-20b-cuda. See the GptOssChat sample. Also fixes a token-duplication bug that repeated the final token of every generation.v0.21.0 β Clean re-publish after v0.20.12 failed to propagate on NuGet.org; carries forward the issue #49 assembly-version fix and issue #51 vision-probe hardening.v0.20.12 β Corrects sibling-package assembly versions, hardens vision token probing against model context limits, and verifies Fara smart image resizing for screenshot workflows.v0.20.9 β Upgraded onnxruntime-genai to 0.15.1 and Microsoft.Extensions.AI.Abstractions to 10.8.3 across all projects. No API changes.ModelStatusCard, ChatBox, ModelGallery, ModelSelector, EnvironmentDashboard, LocalLLMHealthBadge, RagPlayground via ElBruno.LocalLLMs.BlazorComponents (guide)IChatClient implementation β seamless integration with Microsoft.Extensions.AIAddLocalLLMs() or AddBitNetChatClient() in ASP.NET CoreGetStreamingResponseAsyncLocalVisionChatClientdotnet add package ElBruno.LocalLLMs
For CPU scenarios, no extra package is required β the transitive buildTransitive shim copies onnxruntime-genai.dll automatically on Windows.
Add a runtime package only when you want a specific GPU provider:
# π’ 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 at most one GPU runtime package. Do not reference both
Microsoft.ML.OnnxRuntimeGenAI.CudaandMicrosoft.ML.OnnxRuntimeGenAI.DirectMLsimultaneously.If you use a GPU runtime package and want to disable the transitive CPU copy shim, set:
<ElBrunoLocalLLMsDisableCpuNativeCopy>true</ElBrunoLocalLLMsDisableCpuNativeCopy>in your application.csproj.
π The library defaults to
ExecutionProvider.Autoβ on Windows it tries DirectML β CUDA β CPU, and on Linux it tries CUDA β CPU. No code changes needed.
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);
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);
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);
}
By default, ExecutionProvider.Auto tries GPU first 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 note: ONNX Runtime GenAI
0.15.xexpects CUDA13.*, cuDNN9.*, and the latest Microsoft Visual C++ 2015-2022 runtime. When those native libraries are missing, provider diagnostics now surface the exact DLL mismatch or missing dependency instead of entering the failing native path.
See Troubleshooting: GPU Setup for debugging GPU issues.
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.
builder.Services.AddLocalLLMs(options =>
{
options.Model = KnownModels.Phi35MiniInstruct;
options.ExecutionProvider = ExecutionProvider.DirectML;
});
// Inject IChatClient anywhere
public class MyService(IChatClient chatClient) { ... }
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}");
}
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.
Inspect and manage the local model cache programmatically:
// Remove a model from the cache (no-op if not cached)
await LocalChatClient.DeleteModelFromCacheAsync(KnownModels.Phi35MiniInstruct);
// Or use a custom cache directory
await LocalChatClient.DeleteModelFromCacheAsync(
KnownModels.Phi35MiniInstruct,
cacheDirectory: @"D:\my-models");
// Get cached size in bytes for one model (0 if not downloaded)
long bytes = LocalChatClient.GetModelCacheSize(KnownModels.Phi35MiniInstruct);
Console.WriteLine($"Cached: {bytes / 1024 / 1024:N0} MB");
// List all cached models with size and last-modified date
var cached = LocalChatClient.ListCachedModels();
foreach (var repo in cached)
Console.WriteLine($"{repo.LocalDirectory} {repo.TotalSizeBytes / 1024 / 1024:N0} MB {repo.LastModified:yyyy-MM-dd}");
// Same APIs available on LocalVisionChatClient for vision models
await LocalVisionChatClient.DeleteModelFromCacheAsync(KnownModels.Fara15_9B);
long visionBytes = LocalVisionChatClient.GetModelCacheSize(KnownModels.Fara15_9B);
The default cache directory is %LOCALAPPDATA%/ElBruno/LocalLLMs/models (Windows) or ~/.local/share/ElBruno/LocalLLMs/models (Linux/macOS).
These operations delegate to ElBruno.HuggingFace.Downloader which provides the underlying DeleteCachedFilesAsync, GetCachedSize, and ListCachedRepos implementation.
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?
HF_TOKEN environment variableFor detailed troubleshooting, see docs/troubleshooting-guide.md.
| 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 | β Native | 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 | Qwen2.5-Coder-7B-Instruct | 7B | β Native | qwen2.5-coder-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 | β Native | mixtral-8x7b-instruct-v0.1 |
| π΄ Large | DeepSeek-R1-Distill-Llama-70B | 70B | β Native | deepseek-r1-distill-llama-70b |
| π΄ Large | Command-R (35B) | 35B | β Native | 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 |
| π£ Next-Gen | Qwen3-14B-Instruct | 14.77B | β Native | qwen3-14b-instruct |
| π§ GPT-OSS | GPT-OSS 20B (CPU INT4) | 21B (3.6B active, MoE) | β Native | gpt-oss-20b |
| π§ GPT-OSS | GPT-OSS 20B (CUDA INT4) | 21B (3.6B active, MoE) | β Native | gpt-oss-20b-cuda |
| π€ Agentic | MagenticBrain | ~14.77B | β Native | magentic-brain |
| ποΈ VLM | Fara 1.5-9B | ~9.4B | β Native | fara-1.5-9b |
π Convert = Use the conversion scripts in
scripts/to export ONNX locally before running the model.ΒΉ MagenticBrain ONNX: Native ONNX hosted at
elbruno/MagenticBrain-onnx(INT4 quantized). Auto-downloads whenEnsureModelDownloaded=true.Β² Fara 1.5-9B ONNX:
elbruno/Fara1.5-9B-onnxnow includes the validated multimodal package (qwen3vl-vision.onnx,qwen3vl-embedding.onnx, patchedgenai_config.json, and ORT-compatibleprocessor_config.json). See ONNX Conversion β Fara.Β³ GPT-OSS 20B: Apache-2.0, from the official
onnxruntime/gpt-oss-20b-onnxrepository. The CPU INT4 variant is a ~12 GB download, and because GPT-OSS is a mixture-of-experts model, CPU inference is slow β prefergpt-oss-20b-cudawithMicrosoft.ML.OnnxRuntimeGenAI.Cudawhen a GPU is available. GPT-OSS reasons before answering; that chain-of-thought is filtered out and never surfaced, per the model card. Reasoning depth is controlled byLocalLLMsOptions.ReasoningEffort.
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.
| 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 |
| MagenticBrainAgent | Multi-agent orchestration loop using Qwen3/MagenticBrain |
| FaraVisionAgent | Vision-language model (Fara 1.5-9B) image+text inference |
| GptOssChat | GPT-OSS 20B chat, streaming, reasoning effort, and tool calling |
| MagenticUIServer | ASP.NET Core + SignalR multi-agent server (FileSurfer, WebFetcher, Coder) |
| ConsoleAppDemo | Interactive console application |
π Reference App: ElBruno.MagenticUI β full Blazor Server port of microsoft/magentic-ui running locally with this library.
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
Run integration tests (downloads real models β requires internet):
RUN_INTEGRATION_TESTS=true dotnet test ElBruno.LocalLLMs.slnx --framework net8.0
Integration tests validate the full lifecycle (download β infer β cache hit β delete) for all 35 supported models. See docs/tests/README.md for details.
Contributions are welcome! Please:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)This project is licensed under the MIT License β see the LICENSE file for details.
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:
153 commits
C#
72.0%
Python
12.9%
Jupyter Notebook
5.4%
PowerShell
3.8%
HTML
2.9%
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.
The last 5 notable additions to the library. Updated with each NuGet release.
ElBruno.LocalLLMs.BlazorComponents β new Razor Class Library with 7 ready-to-use Blazor components: ModelStatusCard (download progress bar + actions), ModelGallery (filterable grid), ModelSelector (two-way-bindable dropdown), ChatBox (streaming token display), EnvironmentDashboard (CPU/CUDA/DirectML badges), LocalLLMHealthBadge (nav-bar status dot), and RagPlayground. Call services.AddLocalLLMsBlazorComponents() to register. See the Blazor Components Guide and the BlazorDemo sample.onnxruntime/gpt-oss-20b-onnx artifacts. Adds the Harmony prompt format, channel-aware output filtering (chain-of-thought is stripped, never shown to users), Harmony tool calling, and a ReasoningEffort option. Two model IDs: gpt-oss-20b (CPU INT4) and gpt-oss-20b-cuda. See the GptOssChat sample. Also fixes a token-duplication bug that repeated the final token of every generation.v0.21.0 β Clean re-publish after v0.20.12 failed to propagate on NuGet.org; carries forward the issue #49 assembly-version fix and issue #51 vision-probe hardening.v0.20.12 β Corrects sibling-package assembly versions, hardens vision token probing against model context limits, and verifies Fara smart image resizing for screenshot workflows.v0.20.9 β Upgraded onnxruntime-genai to 0.15.1 and Microsoft.Extensions.AI.Abstractions to 10.8.3 across all projects. No API changes.ModelStatusCard, ChatBox, ModelGallery, ModelSelector, EnvironmentDashboard, LocalLLMHealthBadge, RagPlayground via ElBruno.LocalLLMs.BlazorComponents (guide)IChatClient implementation β seamless integration with Microsoft.Extensions.AIAddLocalLLMs() or AddBitNetChatClient() in ASP.NET CoreGetStreamingResponseAsyncLocalVisionChatClientdotnet add package ElBruno.LocalLLMs
For CPU scenarios, no extra package is required β the transitive buildTransitive shim copies onnxruntime-genai.dll automatically on Windows.
Add a runtime package only when you want a specific GPU provider:
# π’ 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 at most one GPU runtime package. Do not reference both
Microsoft.ML.OnnxRuntimeGenAI.CudaandMicrosoft.ML.OnnxRuntimeGenAI.DirectMLsimultaneously.If you use a GPU runtime package and want to disable the transitive CPU copy shim, set:
<ElBrunoLocalLLMsDisableCpuNativeCopy>true</ElBrunoLocalLLMsDisableCpuNativeCopy>in your application.csproj.
π The library defaults to
ExecutionProvider.Autoβ on Windows it tries DirectML β CUDA β CPU, and on Linux it tries CUDA β CPU. No code changes needed.
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);
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);
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);
}
By default, ExecutionProvider.Auto tries GPU first 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 note: ONNX Runtime GenAI
0.15.xexpects CUDA13.*, cuDNN9.*, and the latest Microsoft Visual C++ 2015-2022 runtime. When those native libraries are missing, provider diagnostics now surface the exact DLL mismatch or missing dependency instead of entering the failing native path.
See Troubleshooting: GPU Setup for debugging GPU issues.
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.
builder.Services.AddLocalLLMs(options =>
{
options.Model = KnownModels.Phi35MiniInstruct;
options.ExecutionProvider = ExecutionProvider.DirectML;
});
// Inject IChatClient anywhere
public class MyService(IChatClient chatClient) { ... }
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}");
}
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.
Inspect and manage the local model cache programmatically:
// Remove a model from the cache (no-op if not cached)
await LocalChatClient.DeleteModelFromCacheAsync(KnownModels.Phi35MiniInstruct);
// Or use a custom cache directory
await LocalChatClient.DeleteModelFromCacheAsync(
KnownModels.Phi35MiniInstruct,
cacheDirectory: @"D:\my-models");
// Get cached size in bytes for one model (0 if not downloaded)
long bytes = LocalChatClient.GetModelCacheSize(KnownModels.Phi35MiniInstruct);
Console.WriteLine($"Cached: {bytes / 1024 / 1024:N0} MB");
// List all cached models with size and last-modified date
var cached = LocalChatClient.ListCachedModels();
foreach (var repo in cached)
Console.WriteLine($"{repo.LocalDirectory} {repo.TotalSizeBytes / 1024 / 1024:N0} MB {repo.LastModified:yyyy-MM-dd}");
// Same APIs available on LocalVisionChatClient for vision models
await LocalVisionChatClient.DeleteModelFromCacheAsync(KnownModels.Fara15_9B);
long visionBytes = LocalVisionChatClient.GetModelCacheSize(KnownModels.Fara15_9B);
The default cache directory is %LOCALAPPDATA%/ElBruno/LocalLLMs/models (Windows) or ~/.local/share/ElBruno/LocalLLMs/models (Linux/macOS).
These operations delegate to ElBruno.HuggingFace.Downloader which provides the underlying DeleteCachedFilesAsync, GetCachedSize, and ListCachedRepos implementation.
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?
HF_TOKEN environment variableFor detailed troubleshooting, see docs/troubleshooting-guide.md.
| 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 | β Native | 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 | Qwen2.5-Coder-7B-Instruct | 7B | β Native | qwen2.5-coder-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 | β Native | mixtral-8x7b-instruct-v0.1 |
| π΄ Large | DeepSeek-R1-Distill-Llama-70B | 70B | β Native | deepseek-r1-distill-llama-70b |
| π΄ Large | Command-R (35B) | 35B | β Native | 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 |
| π£ Next-Gen | Qwen3-14B-Instruct | 14.77B | β Native | qwen3-14b-instruct |
| π§ GPT-OSS | GPT-OSS 20B (CPU INT4) | 21B (3.6B active, MoE) | β Native | gpt-oss-20b |
| π§ GPT-OSS | GPT-OSS 20B (CUDA INT4) | 21B (3.6B active, MoE) | β Native | gpt-oss-20b-cuda |
| π€ Agentic | MagenticBrain | ~14.77B | β Native | magentic-brain |
| ποΈ VLM | Fara 1.5-9B | ~9.4B | β Native | fara-1.5-9b |
π Convert = Use the conversion scripts in
scripts/to export ONNX locally before running the model.ΒΉ MagenticBrain ONNX: Native ONNX hosted at
elbruno/MagenticBrain-onnx(INT4 quantized). Auto-downloads whenEnsureModelDownloaded=true.Β² Fara 1.5-9B ONNX:
elbruno/Fara1.5-9B-onnxnow includes the validated multimodal package (qwen3vl-vision.onnx,qwen3vl-embedding.onnx, patchedgenai_config.json, and ORT-compatibleprocessor_config.json). See ONNX Conversion β Fara.Β³ GPT-OSS 20B: Apache-2.0, from the official
onnxruntime/gpt-oss-20b-onnxrepository. The CPU INT4 variant is a ~12 GB download, and because GPT-OSS is a mixture-of-experts model, CPU inference is slow β prefergpt-oss-20b-cudawithMicrosoft.ML.OnnxRuntimeGenAI.Cudawhen a GPU is available. GPT-OSS reasons before answering; that chain-of-thought is filtered out and never surfaced, per the model card. Reasoning depth is controlled byLocalLLMsOptions.ReasoningEffort.
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.
| 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 |
| MagenticBrainAgent | Multi-agent orchestration loop using Qwen3/MagenticBrain |
| FaraVisionAgent | Vision-language model (Fara 1.5-9B) image+text inference |
| GptOssChat | GPT-OSS 20B chat, streaming, reasoning effort, and tool calling |
| MagenticUIServer | ASP.NET Core + SignalR multi-agent server (FileSurfer, WebFetcher, Coder) |
| ConsoleAppDemo | Interactive console application |
π Reference App: ElBruno.MagenticUI β full Blazor Server port of microsoft/magentic-ui running locally with this library.
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
Run integration tests (downloads real models β requires internet):
RUN_INTEGRATION_TESTS=true dotnet test ElBruno.LocalLLMs.slnx --framework net8.0
Integration tests validate the full lifecycle (download β infer β cache hit β delete) for all 35 supported models. See docs/tests/README.md for details.
Contributions are welcome! Please:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)This project is licensed under the MIT License β see the LICENSE file for details.
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:
153 commits
C#
72.0%
Python
12.9%
Jupyter Notebook
5.4%
PowerShell
3.8%
HTML
2.9%