Browse and Select Models Programmatically
Choosing the right model for a task is one of the most impactful decisions in an LLM application. LM-Kit.NET ships with a curated catalog of predefined models accessible through the ModelCard class. You can enumerate all available models, filter by capabilities (chat, vision, embeddings, tool calling), check hardware compatibility, and download models on demand. This tutorial builds a model browser and an automated model selection system.
Why Programmatic Model Selection Matters
Two real-world problems that programmatic model selection solves:
- Adaptive model routing based on hardware. Different deployment targets (edge devices, workstations, cloud servers) have different VRAM budgets. A model selector that checks available VRAM and picks the largest model that fits ensures optimal quality without out-of-memory crashes.
- Capability-driven task routing. When your application handles vision questions, text chat, embeddings, and speech, routing each task to a model with the right
ModelCapabilitiesflag avoids loading unnecessary models and reduces memory footprint.
Prerequisites
| Requirement | Minimum |
|---|---|
| .NET SDK | 8.0+ |
| Internet | Required for model downloads (catalog browsing is offline) |
Step 1: Create the Project
dotnet new console -n ModelBrowser
cd ModelBrowser
dotnet add package LM-Kit.NET
Step 2: Understand the Model Catalog
┌──────────────────────────────────┐
│ ModelCard Catalog │
│ (predefined models + metadata) │
└───────────────┬──────────────────┘
│
┌───────────┴───────────┐
│ Filter / Sort / Pick │
└───────────┬───────────┘
│
┌───────────┴───────────┐
▼ ▼
┌───────────┐ ┌───────────────┐
│ Check │ │ Download & │
│ hardware │ │ Load (LM) │
│ score │ │ │
└───────────┘ └───────────────┘
| Class / Member | Purpose |
|---|---|
ModelCard.GetPredefinedModelCards() |
List all models in the catalog |
ModelCard.GetPredefinedModelCardByModelID() |
Look up a model by its ID |
ModelCard.CreateFromFile() |
Create a card from a local GGUF file |
ModelCapabilities |
Flags enum: Chat, Vision, ToolsCall, TextEmbeddings, etc. |
DeviceConfiguration.GetPerformanceScore() |
Check if a model fits your GPU |
LM.LoadFromModelID() |
Download and load a model by ID |
Step 3: Write the Model Browser
using System.Text;
using LMKit.Model;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
// ──────────────────────────────────────
// 1. Browse the full catalog
// ──────────────────────────────────────
List<ModelCard> catalog = ModelCard.GetPredefinedModelCards();
Console.WriteLine($"LM-Kit Model Catalog: {catalog.Count} models\n");
Console.WriteLine($"{"Model ID",-30} {"Params",-10} {"Context",-10} {"Quant",-8} {"Capabilities"}");
Console.WriteLine(new string('─', 100));
foreach (ModelCard card in catalog.OrderBy(c => c.ParameterCount))
{
string paramStr = card.ParameterCount > 0
? $"{card.ParameterCount / 1_000_000_000.0:F1}B"
: "N/A";
Console.WriteLine($"{card.ModelID,-30} {paramStr,-10} {card.ContextLength,-10} " +
$"{card.QuantizationPrecision,-8:F1} {card.Capabilities}");
}
// ──────────────────────────────────────
// 2. Filter by capabilities
// ──────────────────────────────────────
Console.WriteLine("\n\n=== Chat Models with Tool Calling ===\n");
var toolCallingModels = catalog
.Where(c => c.Capabilities.HasFlag(ModelCapabilities.Chat) &&
c.Capabilities.HasFlag(ModelCapabilities.ToolsCall))
.OrderBy(c => c.ParameterCount)
.ToList();
foreach (ModelCard card in toolCallingModels)
{
Console.WriteLine($" {card.ModelID,-30} {card.ParameterCount / 1e9:F1}B params, " +
$"ctx={card.ContextLength}");
}
Console.WriteLine($"\n Found {toolCallingModels.Count} tool-calling chat models.\n");
// ──────────────────────────────────────
// 3. Filter by category
// ──────────────────────────────────────
Console.WriteLine("=== Vision Models ===\n");
var visionModels = catalog
.Where(c => c.Capabilities.HasFlag(ModelCapabilities.Vision))
.ToList();
foreach (ModelCard card in visionModels)
Console.WriteLine($" {card.ModelID,-30} {card.ParameterCount / 1e9:F1}B");
Console.WriteLine($"\n=== Embedding Models ===\n");
var embeddingModels = catalog
.Where(c => c.Capabilities.HasFlag(ModelCapabilities.TextEmbeddings))
.ToList();
foreach (ModelCard card in embeddingModels)
Console.WriteLine($" {card.ModelID,-30} {card.ParameterCount / 1e9:F1}B");
Console.WriteLine($"\n=== Speech-to-Text Models ===\n");
var sttModels = catalog
.Where(c => c.Capabilities.HasFlag(ModelCapabilities.SpeechToText))
.ToList();
foreach (ModelCard card in sttModels)
Console.WriteLine($" {card.ModelID,-30} {card.ParameterCount / 1e9:F1}B");
// ──────────────────────────────────────
// 4. Check hardware compatibility
// ──────────────────────────────────────
Console.WriteLine("\n\n=== Hardware Compatibility Check ===\n");
foreach (ModelCard card in toolCallingModels.Take(5))
{
float score = LMKit.Hardware.DeviceConfiguration.GetPerformanceScore(card);
string status = score switch
{
>= 0.8f => "Excellent",
>= 0.5f => "Good",
>= 0.2f => "Tight",
_ => "Won't fit"
};
Console.ForegroundColor = score >= 0.5f ? ConsoleColor.Green : ConsoleColor.Red;
Console.WriteLine($" {card.ModelID,-30} Score: {score:F2} ({status})");
Console.ResetColor();
}
int optimalContext = LMKit.Hardware.DeviceConfiguration.GetOptimalContextSize();
Console.WriteLine($"\n Optimal context size for this hardware: {optimalContext} tokens");
// ──────────────────────────────────────
// 5. Smart model selection: pick the best model for a task
// ──────────────────────────────────────
Console.WriteLine("\n=== Smart Model Selection ===\n");
ModelCard bestChat = SelectBestModel(catalog, ModelCapabilities.Chat | ModelCapabilities.ToolsCall);
if (bestChat != null)
{
Console.WriteLine($" Best tool-calling chat model: {bestChat.ModelID}");
Console.WriteLine($" Parameters: {bestChat.ParameterCount / 1e9:F1}B");
Console.WriteLine($" Context: {bestChat.ContextLength}");
}
ModelCard bestVision = SelectBestModel(catalog, ModelCapabilities.Vision);
if (bestVision != null)
Console.WriteLine($" Best vision model: {bestVision.ModelID}");
ModelCard bestEmbedding = SelectBestModel(catalog, ModelCapabilities.TextEmbeddings);
if (bestEmbedding != null)
Console.WriteLine($" Best embedding model: {bestEmbedding.ModelID}");
// ──────────────────────────────────────
// 6. Load the selected model
// ──────────────────────────────────────
if (bestChat != null)
{
Console.WriteLine($"\nLoading {bestChat.ModelID}...");
using LM model = LM.LoadFromModelID(bestChat.ModelID,
downloadingProgress: (path, contentLength, bytesRead) =>
{
if (contentLength.HasValue)
Console.Write($"\r Downloading: {(double)bytesRead / contentLength.Value * 100:F1}% ");
return true;
},
loadingProgress: p =>
{
Console.Write($"\r Loading: {p * 100:F0}% ");
return true;
});
Console.WriteLine($"\n Loaded: {model.Name}");
Console.WriteLine($" Context length: {model.ContextLength}");
Console.WriteLine($" GPU layers: {model.GpuLayerCount}");
}
// ──────────────────────────────────────
// Helper: Select the largest model that fits in VRAM
// ──────────────────────────────────────
static ModelCard SelectBestModel(
List<ModelCard> catalog,
ModelCapabilities requiredCapabilities,
float minPerformanceScore = 0.4f)
{
return catalog
.Where(c => (c.Capabilities & requiredCapabilities) == requiredCapabilities)
.Select(c => new
{
Card = c,
Score = LMKit.Hardware.DeviceConfiguration.GetPerformanceScore(c)
})
.Where(x => x.Score >= minPerformanceScore)
.OrderByDescending(x => x.Card.ParameterCount)
.Select(x => x.Card)
.FirstOrDefault();
}
Step 4: Run the Browser
dotnet run
ModelCapabilities Flags Reference
| Flag | Value | Description |
|---|---|---|
TextEmbeddings |
1 | Vector embedding generation |
TextGeneration |
2 | Text completion |
Chat |
4 | Multi-turn conversational |
CodeCompletion |
8 | Code generation |
SentimentAnalysis |
16 | Sentiment classification |
Math |
32 | Mathematical reasoning |
Vision |
64 | Image understanding |
ImageEmbeddings |
128 | Image vector embeddings |
TextReranking |
256 | Search result reranking |
SpeechToText |
512 | Audio transcription |
Reasoning |
4096 | Chain-of-thought reasoning |
ToolsCall |
8192 | Function/tool calling |
Combine flags with bitwise OR: ModelCapabilities.Chat | ModelCapabilities.ToolsCall
Working with Local Models
For models not in the predefined catalog, create a ModelCard from a local file:
using System.Text;
using LMKit.Model;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
// ──────────────────────────────────────
// 1. Browse the full catalog
// ──────────────────────────────────────
List<ModelCard> catalog = ModelCard.GetPredefinedModelCards();
Console.WriteLine($"LM-Kit Model Catalog: {catalog.Count} models\n");
Console.WriteLine($"{"Model ID",-30} {"Params",-10} {"Context",-10} {"Quant",-8} {"Capabilities"}");
Console.WriteLine(new string('─', 100));
foreach (ModelCard card in catalog.OrderBy(c => c.ParameterCount))
{
string paramStr = card.ParameterCount > 0
? $"{card.ParameterCount / 1_000_000_000.0:F1}B"
: "N/A";
Console.WriteLine($"{card.ModelID,-30} {paramStr,-10} {card.ContextLength,-10} " +
$"{card.QuantizationPrecision,-8:F1} {card.Capabilities}");
}
// ──────────────────────────────────────
// 2. Filter by capabilities
// ──────────────────────────────────────
Console.WriteLine("\n\n=== Chat Models with Tool Calling ===\n");
var toolCallingModels = catalog
.Where(c => c.Capabilities.HasFlag(ModelCapabilities.Chat) &&
c.Capabilities.HasFlag(ModelCapabilities.ToolsCall))
.OrderBy(c => c.ParameterCount)
.ToList();
foreach (ModelCard card in toolCallingModels)
{
Console.WriteLine($" {card.ModelID,-30} {card.ParameterCount / 1e9:F1}B params, " +
$"ctx={card.ContextLength}");
}
Console.WriteLine($"\n Found {toolCallingModels.Count} tool-calling chat models.\n");
// ──────────────────────────────────────
// 3. Filter by category
// ──────────────────────────────────────
Console.WriteLine("=== Vision Models ===\n");
var visionModels = catalog
.Where(c => c.Capabilities.HasFlag(ModelCapabilities.Vision))
.ToList();
foreach (ModelCard card in visionModels)
Console.WriteLine($" {card.ModelID,-30} {card.ParameterCount / 1e9:F1}B");
Console.WriteLine($"\n=== Embedding Models ===\n");
var embeddingModels = catalog
.Where(c => c.Capabilities.HasFlag(ModelCapabilities.TextEmbeddings))
.ToList();
foreach (ModelCard card in embeddingModels)
Console.WriteLine($" {card.ModelID,-30} {card.ParameterCount / 1e9:F1}B");
Console.WriteLine($"\n=== Speech-to-Text Models ===\n");
var sttModels = catalog
.Where(c => c.Capabilities.HasFlag(ModelCapabilities.SpeechToText))
.ToList();
foreach (ModelCard card in sttModels)
Console.WriteLine($" {card.ModelID,-30} {card.ParameterCount / 1e9:F1}B");
// ──────────────────────────────────────
// 4. Check hardware compatibility
// ──────────────────────────────────────
Console.WriteLine("\n\n=== Hardware Compatibility Check ===\n");
foreach (ModelCard card in toolCallingModels.Take(5))
{
float score = LMKit.Hardware.DeviceConfiguration.GetPerformanceScore(card);
string status = score switch
{
>= 0.8f => "Excellent",
>= 0.5f => "Good",
>= 0.2f => "Tight",
_ => "Won't fit"
};
Console.ForegroundColor = score >= 0.5f ? ConsoleColor.Green : ConsoleColor.Red;
Console.WriteLine($" {card.ModelID,-30} Score: {score:F2} ({status})");
Console.ResetColor();
}
int optimalContext = LMKit.Hardware.DeviceConfiguration.GetOptimalContextSize();
Console.WriteLine($"\n Optimal context size for this hardware: {optimalContext} tokens");
// ──────────────────────────────────────
// 5. Smart model selection: pick the best model for a task
// ──────────────────────────────────────
Console.WriteLine("\n=== Smart Model Selection ===\n");
ModelCard bestChat = SelectBestModel(catalog, ModelCapabilities.Chat | ModelCapabilities.ToolsCall);
if (bestChat != null)
{
Console.WriteLine($" Best tool-calling chat model: {bestChat.ModelID}");
Console.WriteLine($" Parameters: {bestChat.ParameterCount / 1e9:F1}B");
Console.WriteLine($" Context: {bestChat.ContextLength}");
}
ModelCard bestVision = SelectBestModel(catalog, ModelCapabilities.Vision);
if (bestVision != null)
Console.WriteLine($" Best vision model: {bestVision.ModelID}");
ModelCard bestEmbedding = SelectBestModel(catalog, ModelCapabilities.TextEmbeddings);
if (bestEmbedding != null)
Console.WriteLine($" Best embedding model: {bestEmbedding.ModelID}");
// ──────────────────────────────────────
// 6. Load the selected model
// ──────────────────────────────────────
if (bestChat != null)
{
Console.WriteLine($"\nLoading {bestChat.ModelID}...");
using LM model = LM.LoadFromModelID(bestChat.ModelID,
downloadingProgress: (path, contentLength, bytesRead) =>
{
if (contentLength.HasValue)
Console.Write($"\r Downloading: {(double)bytesRead / contentLength.Value * 100:F1}% ");
return true;
},
loadingProgress: p =>
{
Console.Write($"\r Loading: {p * 100:F0}% ");
return true;
});
Console.WriteLine($"\n Loaded: {model.Name}");
}
ModelCard localCard = ModelCard.CreateFromFile("path/to/model.gguf");
Console.WriteLine($"Local model: {localCard.Name}");
Console.WriteLine($"Architecture: {localCard.Architecture}");
Console.WriteLine($"Context: {localCard.ContextLength}");
Or look up a specific model by ID:
using System.Text;
using LMKit.Model;
LMKit.Licensing.LicenseManager.SetLicenseKey("");
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = Encoding.UTF8;
// ──────────────────────────────────────
// 1. Browse the full catalog
// ──────────────────────────────────────
List<ModelCard> catalog = ModelCard.GetPredefinedModelCards();
Console.WriteLine($"LM-Kit Model Catalog: {catalog.Count} models\n");
Console.WriteLine($"{"Model ID",-30} {"Params",-10} {"Context",-10} {"Quant",-8} {"Capabilities"}");
Console.WriteLine(new string('─', 100));
foreach (ModelCard card in catalog.OrderBy(c => c.ParameterCount))
{
string paramStr = card.ParameterCount > 0
? $"{card.ParameterCount / 1_000_000_000.0:F1}B"
: "N/A";
Console.WriteLine($"{card.ModelID,-30} {paramStr,-10} {card.ContextLength,-10} " +
$"{card.QuantizationPrecision,-8:F1} {card.Capabilities}");
}
// ──────────────────────────────────────
// 2. Filter by capabilities
// ──────────────────────────────────────
Console.WriteLine("\n\n=== Chat Models with Tool Calling ===\n");
var toolCallingModels = catalog
.Where(c => c.Capabilities.HasFlag(ModelCapabilities.Chat) &&
c.Capabilities.HasFlag(ModelCapabilities.ToolsCall))
.OrderBy(c => c.ParameterCount)
.ToList();
foreach (ModelCard card in toolCallingModels)
{
Console.WriteLine($" {card.ModelID,-30} {card.ParameterCount / 1e9:F1}B params, " +
$"ctx={card.ContextLength}");
}
Console.WriteLine($"\n Found {toolCallingModels.Count} tool-calling chat models.\n");
// ──────────────────────────────────────
// 3. Filter by category
// ──────────────────────────────────────
Console.WriteLine("=== Vision Models ===\n");
var visionModels = catalog
.Where(c => c.Capabilities.HasFlag(ModelCapabilities.Vision))
.ToList();
foreach (ModelCard card in visionModels)
Console.WriteLine($" {card.ModelID,-30} {card.ParameterCount / 1e9:F1}B");
Console.WriteLine($"\n=== Embedding Models ===\n");
var embeddingModels = catalog
.Where(c => c.Capabilities.HasFlag(ModelCapabilities.TextEmbeddings))
.ToList();
foreach (ModelCard card in embeddingModels)
Console.WriteLine($" {card.ModelID,-30} {card.ParameterCount / 1e9:F1}B");
Console.WriteLine($"\n=== Speech-to-Text Models ===\n");
var sttModels = catalog
.Where(c => c.Capabilities.HasFlag(ModelCapabilities.SpeechToText))
.ToList();
foreach (ModelCard card in sttModels)
Console.WriteLine($" {card.ModelID,-30} {card.ParameterCount / 1e9:F1}B");
// ──────────────────────────────────────
// 4. Check hardware compatibility
// ──────────────────────────────────────
Console.WriteLine("\n\n=== Hardware Compatibility Check ===\n");
foreach (ModelCard card in toolCallingModels.Take(5))
{
float score = LMKit.Hardware.DeviceConfiguration.GetPerformanceScore(card);
string status = score switch
{
>= 0.8f => "Excellent",
>= 0.5f => "Good",
>= 0.2f => "Tight",
_ => "Won't fit"
};
Console.ForegroundColor = score >= 0.5f ? ConsoleColor.Green : ConsoleColor.Red;
Console.WriteLine($" {card.ModelID,-30} Score: {score:F2} ({status})");
Console.ResetColor();
}
int optimalContext = LMKit.Hardware.DeviceConfiguration.GetOptimalContextSize();
Console.WriteLine($"\n Optimal context size for this hardware: {optimalContext} tokens");
// ──────────────────────────────────────
// 5. Smart model selection: pick the best model for a task
// ──────────────────────────────────────
Console.WriteLine("\n=== Smart Model Selection ===\n");
ModelCard bestChat = SelectBestModel(catalog, ModelCapabilities.Chat | ModelCapabilities.ToolsCall);
if (bestChat != null)
{
Console.WriteLine($" Best tool-calling chat model: {bestChat.ModelID}");
Console.WriteLine($" Parameters: {bestChat.ParameterCount / 1e9:F1}B");
Console.WriteLine($" Context: {bestChat.ContextLength}");
}
ModelCard bestVision = SelectBestModel(catalog, ModelCapabilities.Vision);
if (bestVision != null)
Console.WriteLine($" Best vision model: {bestVision.ModelID}");
ModelCard bestEmbedding = SelectBestModel(catalog, ModelCapabilities.TextEmbeddings);
if (bestEmbedding != null)
Console.WriteLine($" Best embedding model: {bestEmbedding.ModelID}");
// ──────────────────────────────────────
// 6. Load the selected model
// ──────────────────────────────────────
if (bestChat != null)
{
Console.WriteLine($"\nLoading {bestChat.ModelID}...");
using LM model = LM.LoadFromModelID(bestChat.ModelID,
downloadingProgress: (path, contentLength, bytesRead) =>
{
if (contentLength.HasValue)
Console.Write($"\r Downloading: {(double)bytesRead / contentLength.Value * 100:F1}% ");
return true;
},
loadingProgress: p =>
{
Console.Write($"\r Loading: {p * 100:F0}% ");
return true;
});
Console.WriteLine($"\n Loaded: {model.Name}");
}
ModelCard card = ModelCard.GetPredefinedModelCardByModelID("gemma3:12b");
if (card != null)
{
Console.WriteLine($"Found: {card.ModelID}");
Console.WriteLine($"Available locally: {card.IsLocallyAvailable}");
if (card.IsLocallyAvailable)
Console.WriteLine($"Path: {card.LocalPath}");
}
Common Issues
| Problem | Cause | Fix |
|---|---|---|
| Empty catalog | LM-Kit.NET not properly referenced | Verify the NuGet package is installed |
GetPerformanceScore returns 0 |
No GPU detected | Check GPU backend installation (CUDA/Vulkan) |
| Model not found by ID | Typo or deprecated model | Use GetPredefinedModelCards() to list valid IDs |
| Download fails | Network issue or invalid URI | Check internet connection; verify ModelUri is accessible |
Next Steps
- Load a Model and Generate Your First Response: model loading basics.
- Distribute Large Models Across Multiple GPUs: split large models across GPUs.
- Route Prompts Across Models with RouterOrchestrator: automatic task routing.