Table of Contents

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:

  1. 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.
  2. Capability-driven task routing. When your application handles vision questions, text chat, embeddings, and speech, routing each task to a model with the right ModelCapabilities flag 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

Share