Runtime
The Feasibility of Pure C\ Local LLM Runtimes for GGUF/LLaMA Architectures
Report summary
The proliferation of localized Large Language Model (LLM) inference has historically been governed by a rigid technical orthodoxy: peak performance necessitates unmanaged, low-level systems programming languages. Frameworks such as llama.cpp and its underlying ggml tensor library have entrenched the
Key topics
- Runtime
- AI
- .NET
- C#
- Python
- Rust
- GGUF
- Semantic Systems
Research provenance
For citation, use the report title and canonical URL. Archival presence does not establish authorship or promote report statements into portfolio evidence.
This page renders the archived Markdown as safe, formatted HTML. It is background research and does not become a portfolio claim without evidence review.
Full report
On this page
Introduction to the Managed Runtime Paradigm Shift
The proliferation of localized Large Language Model (LLM) inference has historically been governed by a rigid technical orthodoxy: peak performance necessitates unmanaged, low-level systems programming languages. Frameworks such as llama.cpp and its underlying ggml tensor library have entrenched the notion that C and C++ are absolute prerequisites for executing multi-billion-parameter transformer models on constrained consumer hardware.1 These legacy frameworks capitalize on manual memory allocation, explicit hardware vectorization, and highly tuned, assembly-level microkernels to maximize memory bandwidth and computational throughput. Conversely, managed runtimes—most notably the Common Language Runtime (CLR) of.NET and C\#—have traditionally been viewed as structurally unsuited for the rigorous demands of LLM inference. The inherent overhead of non-deterministic Garbage Collection (GC) pauses, array bounds-checking, pointer aliasing opacity, and the unpredictability of Just-In-Time (JIT) compiler register allocation seemingly precluded C\# from competing in the high-performance computing domain. However, recent architectural advancements within the.NET ecosystem—specifically targeting.NET 8 through.NET 10—have drastically shifted this paradigm. The feasibility of constructing a "pure C\#" local LLM inference engine, completely devoid of Foreign Function Interface (FFI) calls to Python, llama.cpp, or native shared libraries, is no longer merely theoretical. Emerging inference engines such as dotLLM and TensorSharp unequivocally demonstrate that by systematically bypassing the managed heap and leveraging modern C\# hardware intrinsics, developers can achieve an entirely native C\# execution pipeline that rivals the performance of established C++ frameworks.1 This encompasses the entirety of the inference lifecycle: GGUF file parsing, memory-mapped tensor loading, Byte-Pair Encoding (BPE) tokenization, Grouped-Query Attention (GQA) execution, composable stochastic sampling, and highly optimized CPU tensor computations.1 This exhaustive research report investigates the technical mechanics required to execute real LLM prompts using LLaMA-style architectures strictly within a pure C\# environment. The analysis evaluates structural memory management paradigms, the bit-level processing of tensor quantization, the limits of Single Instruction, Multiple Data (SIMD) parallelization under RyuJIT constraints, the implementation of complex memory constructs like Paged KV-Caches, and the empirical performance expectations of executing multi-gigabyte models entirely within the CLR. Furthermore, the report establishes the rigorous test fixtures required to guarantee numerical accuracy against canonical reference implementations, outlining the strategic gaps and realistic milestones necessary for the full maturation of pure C\# AI inference.
Structural Memory Management and the Zero-Allocation Hot Path
The foundational requirement for any high-performance LLM runtime operating within a managed language is the total elimination of managed memory allocations during the inference hot path. In C\#, any instantiation of an object, array, or reference type on the managed heap risks triggering the generational garbage collector. During autoregressive text generation, where models must sequentially predict tokens step-by-step, a GC pause of even a few milliseconds introduces unacceptable latency spikes—commonly referred to as jitter—into the output stream. To achieve parity with C++, the runtime must adopt a zero-allocation architecture. To circumvent the garbage collector entirely, pure C\# runtimes utilize an unmanaged memory architecture for all tensor data and transient operational buffers. By utilizing the NativeMemory.AlignedAlloc API, execution engines allocate memory blocks directly from the operating system, bypassing the CLR's heap management system.1 Furthermore, these allocations are strictly aligned to specific byte boundaries—typically 64-byte alignment.1 The enforcement of 64-byte alignment serves two critical hardware-level purposes. First, it perfectly matches the standard 64-byte cache line size of modern x86-64 and ARM processors, ensuring that when the CPU fetches a vector of weights from main memory, it does not incur the latency penalty of straddling two separate cache lines. Second, exact alignment is a strict requirement for utilizing AVX-512 (512-bit) vector instructions, which will fault if provided with unaligned memory pointers.1 Consequently, the entire inference hot path operates over contiguous spans of unmanaged memory, utilizing C\#'s Span\<T\> and unsafe pointers to navigate the data. By pinning these memory addresses and avoiding object creation, the architecture ensures that the entire forward pass—from the initial embedding lookup, through the successive transformer blocks, to the final logit softmax calculation—generates absolute zero garbage.1 This zero-GC characteristic is not merely an optimization; it is a strict structural necessity for executing billion-parameter models within a managed runtime without catastrophic latency degradation.
GGUF Tensor Loading and Virtual Memory Mapping
The GGUF (GGML Universal File) format has become the de facto standard mechanism for distributing quantized transformer models. It is a highly structured binary format engineered for rapid loading, ease of machine reading, and seamless extensibility.7 Constructing a native C\# GGUF parser requires precise handling of binary offsets, variable-length key-value metadata, and hardware-aligned tensor payloads without relying on external Python or C parsing libraries.
The Structural Layout of the GGUF Format
A GGUF file is sequentially structured into three primary segments: the global header, the metadata key-value store, and the massive tensor data blocks.9 The C\# runtime must first ingest the header, which contains a magic number (GGUF encoded as bytes), the format version (most commonly GGUF version 3, which introduced big-endian support), the total count of tensors contained in the file, and the count of metadata key-value pairs.7 The metadata section is a flexible, dynamically typed store utilized to define the model's precise architectural characteristics. The C\# parser iterates through this metadata block sequentially, extracting configurations critical to the execution pipeline.10 Because strings in the GGUF format lack a terminating NULL character, the C\# parser must explicitly read the length prefix of each string, allocate a suitably sized buffer, and extract the characters directly to prevent memory access violations.11
| Metadata Key Example | Data Type | Operational Description |
|---|---|---|
| general.architecture | String | Defines the model family (e.g., llama, qwen, phi), determining which C\# transformer block architecture is instantiated.10 |
| llama.context\_length | UInt32 | Defines the maximum sequence length, dictating the maximum allocation size for the KV cache.10 |
| tokenizer.ggml.tokens | Array of Strings | The discrete vocabulary tokens utilized to instantiate the C\# tokenizer.10 |
| llama.attention.head\_count\_kv | UInt32 | Defines the number of Key/Value heads, instructing the runtime whether to use Multi-Head, Multi-Query, or Grouped-Query Attention.12 |
| general.alignment | UInt32 | Defines the byte alignment boundary for the subsequent tensor payload segment.9 |
Memory-Mapped OS Demand-Paging
Multi-gigabyte LLMs cannot be efficiently loaded into physical RAM via traditional file I/O streams. Reading a 30-gigabyte model byte-by-byte into memory arrays would incur massive startup latency and duplicate the memory footprint. Pure C\# runtimes completely bypass standard file streaming, utilizing the MemoryMappedFile API provided by the.NET standard library.1 This mechanism maps the GGUF tensor data directly into the virtual address space of the application. Rather than copying the tensor weights from the solid-state drive to RAM, the operating system's virtual memory manager handles the data via demand-paging.1 As the C\# inference engine requests specific tensor blocks during the mathematical forward pass, the operating system intercepts the page fault and pages the data into physical memory autonomously. This architecture allows multi-gigabyte models to technically "load" in mere milliseconds, shifting the actual disk I/O cost to the execution phase while delegating memory management entirely to the operating system.1
Tensor Alignment and Offset Computation Mechanics
A critical, and notoriously error-prone, element of native C\# GGUF parsing is resolving the byte alignment of the tensor data. The GGUF specification dictates that all tensor data blocks must begin on a multiple of a specified alignment boundary, governed by the general.alignment metadata key (which defaults to 32 bytes).9 The C\# parser must calculate the appropriate padding to locate the absolute beginning of the tensor data blob using modulo arithmetic. The mathematical formulation applied in the C\# parser is: [Figure omitted from source export] Where [Figure omitted from source export] is the absolute byte offset accumulated after parsing the variable-length metadata, and [Figure omitted from source export] is the global alignment boundary.7 Parsing failures in pure C\# implementations, such as metadata offset miscalculations encountered with GGUF version 3 files in early engine versions, are almost exclusively traced back to improper handling of this mathematical padding during the transition from the variable-length metadata segment to the statically aligned tensor segment.14 Flawless offset calculation is required to ensure that the unmanaged pointers map exactly to the beginning of the quantized weight structs.
Text Tokenization: Native BPE, SentencePiece, and Accuracy Dynamics
Before inference can occur, raw text strings must be converted into discrete numerical identifiers (tokens). Relying on native implementations for this task is critical; crossing a managed-to-unmanaged boundary to invoke a Python tokenizer introduces unnecessary latency and shatters the standalone deployment model. Pure C\# runtimes natively implement both Byte-Pair Encoding (BPE) and SentencePiece (Unigram) algorithms from scratch.4
Algorithmic Complexity: From Tries to Aho-Corasick Automata
Vocabulary sizes for modern language models are immense, ranging from 32,000 tokens for LLaMA-2 to over 150,000 tokens for multilingual models like Qwen. The C\# tokenizer initialization phase extracts the tokenizer.ggml.tokens (the vocabulary) and tokenizer.ggml.merges (the priority rules) arrays directly from the GGUF metadata to construct its internal search structures.12 Early or naive implementations of C\# tokenizers often rely on a Trie (prefix tree) data structure. A Trie permits the algorithm to scan an incoming character stream and traverse branches to find the longest matching sequence present in the vocabulary.1 However, scanning for multi-character sequences—especially complex special tokens like \<|im\_start|\> or \<|end\_of\_text|\>—using a Trie results in a worst-case algorithmic complexity of [Figure omitted from source export], where [Figure omitted from source export] is the length of the input sequence and [Figure omitted from source export] is the maximum token length.1 This linear scanning bottleneck becomes highly evident during large prompt prefilling, causing noticeable CPU stalling before the first tensor operation even begins. To achieve production-grade performance and ensure tokenizer accuracy, the C\# runtime must transition the structural representation from a nested node dictionary to a flattened array structure, or ideally, implement an Aho-Corasick automaton.18 The Aho-Corasick algorithm builds deterministic failure links between nodes during initialization. This allows the C\# tokenizer to process string prefixes in strictly linear time relative to the input length, irrespective of the vocabulary size or the length of individual tokens, drastically accelerating the preprocessing phase and preventing catastrophic backtracking delays.18
Chat Templates, Jinja2 Execution, and Tokenizer Accuracy
Modern instruct-tuned and chat-tuned models require highly specific prompt structuring. Rather than hardcoding these formats, the C\# runtime extracts the tokenizer.chat\_template string from the GGUF metadata, which is predominantly written in the Jinja2 templating language.4 To maintain the pure C\# architecture, the runtime must parse and execute the Jinja2 Abstract Syntax Tree (AST) natively, dynamically mapping arbitrary user input structures to the specific formatting expectations of the model prior to BPE processing.4 Ensuring absolute tokenizer accuracy in a pure C\# environment requires intense validation. A single misaligned byte, a mishandled Unicode surrogate pair, or an incorrect fallback to byte-level encoding can drastically alter the semantic meaning of the token stream, resulting in catastrophic model hallucinations. Because the C\# runtime operates entirely independently of the canonical Hugging Face Python libraries, developers must construct rigorous test fixtures that pump thousands of edge-case strings through both the Python reference implementation and the C\# implementation, ensuring that the resulting arrays of integer token IDs are identical down to the byte level.15
CPU Inference, Tensor Computations, and SIMD Vectorization limits
The computational heart of an LLM lies in the billions of matrix multiplications executed within the feed-forward networks (FFN) and attention projections. In a pure C\# environment—lacking implicit access to BLAS (Basic Linear Algebra Subprograms) libraries like OpenBLAS, Intel MKL, or cuBLAS—all mathematical operations must be explicitly vectorized and managed by the developer. The performance of the engine is entirely dependent on how effectively C\# can orchestrate the underlying CPU architecture.
The Strategic Application of System.Runtime.Intrinsics
The.NET CLR exposes low-level CPU capabilities directly to the developer via the System.Runtime.Intrinsics namespace. This namespace allows C\# code to map directly to architecture-specific assembly instructions, such as AVX2 (Advanced Vector Extensions) and AVX-512 on x86/x64 hardware, or ARM NEON on Apple Silicon platforms.1 Basic tensor operations occasionally rely on System.Numerics.Tensors.TensorPrimitives, a framework-provided library that automatically vectorizes standard math loops.1 However, TensorPrimitives is severely insufficient for the complex, fused operations required in LLM inference. Consequently, custom, hand-tuned microkernels are written using Vector128\<T\>, Vector256\<T\>, and Vector512\<T\> structures to process 4, 8, or 16 floating-point values in a single CPU clock cycle.1 These custom SIMD kernels cover critical operational paths including Root Mean Square Normalization (RMSNorm), Rotary Position Embeddings (RoPE), and various activation functions like SiLU.1
The Fast Math Approximation: Circumventing Transcendental Bottlenecks
A highly instructive example of C\# systems optimization occurs within the Softmax operation of the attention mechanism. Softmax requires computing the transcendental exponential function, [Figure omitted from source export], for thousands of matrix elements. Standard library calls to MathF.Exp are strictly scalar; they process one value at a time, creating a severe bottleneck that halts the vector processing pipeline.21 To optimize this exclusively in C\#, developers implement an IEEE-754 bit-manipulation fast exponential approximation, heavily influenced by QuAKE-style algorithmic math.21 By deeply exploiting the structural memory layout of a 32-bit floating-point number in memory, the exponential can be approximated using a single vectorized multiply-add instruction coupled with a bitwise reinterpretation, avoiding the complex polynomial expansions typically required for exponentiation. The mathematical formulation utilized in these C\# kernels is: [Figure omitted from source export] This formulation allows a single Vector256\<float\> instruction to process 8 exponentials simultaneously using hardware vfmadd (fused multiply-add) and vcvttps2dq (convert with truncation) instructions.21 While this bit-hacking methodology introduces a minute 1-2 Unit in the Last Place (ULP) error, the Softmax output quality remains entirely unaffected for the purposes of attention scaling. Implementing this fast math approach results in up to a 35% speedup in end-to-end inference without degrading the model's linguistic coherence in any measurable capacity.21
The RyuJIT Register Pressure Wall and Prefill Bottlenecks
A critical, systemic insight into the limitations of pure C\# inference emerges when analyzing matrix multiplication behavior during the prefill phase. Prefilling a prompt requires Batched Matrix-Matrix Multiplication (GEMM), which is heavily compute-bound.4 Optimal GEMM algorithms utilize an outer-product tiled approach. In this methodology, a small tile of weights is loaded into CPU registers and multiplied against multiple input vectors simultaneously, maximizing data reuse and minimizing slow L1 cache fetches. For AVX2 instruction sets, an optimal mathematical tile size is [Figure omitted from source export], which requires 12 YMM registers strictly for accumulators, plus additional registers to hold the incoming weights and activations.4 This creates a total requirement of roughly 23 unique 256-bit registers.4 However, the physical x86 AVX2 architecture only possesses 16 YMM registers.4 When C++ is compiled via GCC or Clang, the compiler's register allocator goes to extreme lengths to heavily optimize this constraint via instruction reordering and aggressive loop unrolling. In contrast, the.NET RyuJIT compiler—architected primarily for fast compilation times rather than maximum theoretical compute efficiency—fails to reconcile this immense register pressure. To cope, RyuJIT is forced to "spill" variables, meaning it temporarily writes register data to the CPU stack memory and reads it back milliseconds later.4 This continuous stack spilling entirely neutralizes the memory-reuse benefits of the tiled GEMM strategy, thrashing the cache.4 As a result, dotLLM and similar C\# engines find their prefill throughput limited to roughly 20-50% of the performance achieved by the highly mature llama.cpp.4 Overcoming this limitation in a pure C\# environment either requires advancing to AVX-512 capable hardware (which physically provides 32 ZMM registers, naturally alleviating the pressure) or rewriting the microkernel in native C, which entirely defeats the objective of a pure managed runtime.4
Quantization Modalities and Bit-Level Dequantization Kernels
Running modern LLMs locally relies entirely on post-training quantization (PTQ) to reduce memory bandwidth requirements. Storing billions of 32-bit floats is impossible on consumer hardware. A pure C\# runtime must interpret block-quantized tensors directly from the unmanaged memory map without instantiating intermediate uncompressed arrays, performing the dequantization dynamically on the fly during the matrix multiplication.
GGUF Block Layouts and C# Struct Mapping
Quantized tensors in GGUF files are divided into highly granular blocks, typically consisting of 32 weights per block.23 The C\# runtime must map standard struct definitions identically to the binary layout of these blocks using the \\ attribute to ensure memory alignment perfectly matches the GGUF spec.25 For example, the Q4\_0 format utilizes 4-bit absolute quantization. A block\_q4\_0 struct contains a 16-bit float scale (the delta) and an array of 16 bytes representing 32 quantized 4-bit nibbles:
C\# public struct BlockQ4\_0 { public Half Delta; public fixed byte Quants; // Contains 32 4-bit nibbles }
Similarly, the Q8\_0 format pairs a 16-bit float delta with 32 8-bit integers.24 Understanding these precise byte boundaries is what allows the C\# unsafe pointer logic to traverse the multi-gigabyte memory map without corrupting the read cycle.
Emulating Half and Fused Operations
A significant architectural friction point in C\# is the handling of the 16-bit Half float used for the delta scales. While modern C\# explicitly supports the Half type, older consumer hardware architectures lack native SIMD instructions for 16-bit floating-point math unless they feature specific AVX-512 FP16 extensions. If the C\# runtime attempts to emulate Half math using standard scalar operations, performance degrades by catastrophic factors of 10x to 100x compared to native hardware execution.27 To bypass this critical limitation, advanced C\# SIMD kernels perform aggressive bitwise reinterpretation. They "smuggle" the Half values into short or ushort vectors, utilizing specific integer bit-shifts to carefully extract the sign, exponent, and mantissa. They then natively convert these extracted integers to standard 32-bit floats strictly within the vector lanes prior to the matrix multiplication.28 Furthermore, to maintain competitive memory bandwidth during decoding, the runtime must implement fused dequantize-and-accumulate kernels.17 Instead of executing a two-pass operation over the memory—converting Q4\_0 to an intermediate FP32 buffer and then executing a standard float dot-product—the C\# kernel unpacks the 4-bit nibbles, calculates the integer dot-product directly against a Q8\_0 quantized activation buffer, applies the smuggled delta scaling factor, and adds the final result to the accumulator all within a single, uninterrupted SIMD instruction pipeline.1 This fused operation is absolutely critical; failing to implement it results in memory bus saturation and total performance collapse during standard autoregressive decoding.17 Fallbacks to scalar code are only utilized as correctness oracles or for highly obscure quantization formats (like Q5\_0) where optimized AVX2 paths have not yet been written.18
Paged Attention and Advanced KV Cache Management
Transformer context windows require caching the Key (K) and Value (V) tensors for all historical tokens at every layer to prevent redundant recomputation. In naive, early-generation implementations, a massive contiguous block of memory \\ is statically allocated per sequence. For high-concurrency environments or complex multi-turn chats, this continuous allocation creates massive internal memory fragmentation, severely limiting the runtime's batch size and wasting vast amounts of RAM.32
The Virtual Memory Approach: Paged KV Cache Architecture
To resolve this severe fragmentation, advanced C\# engines implement a "Paged KV Cache," a sophisticated memory management paradigm heavily inspired by operating system virtual memory paging and the Python-based vLLM project.4 The unmanaged memory pool dedicated to the cache is partitioned into fixed-size physical "blocks" (e.g., capable of holding exactly 16 or 32 tokens each).32 Each active sequence (a user request) is assigned a "Logical Block Table," which maps logical token sequence positions to disparate, non-contiguous physical blocks stored dynamically in the shared pool.33
- Demand Allocation: As a sequence generates tokens autoregressively, physical blocks are checked out of the free pool strictly on-demand. When a block fills up, a new one is allocated, meaning a sequence only consumes exactly the memory it requires at that specific millisecond.32
- Prefix Sharing: Multiple distinct sequences responding to the exact same system prompt can point their individual logical block tables to the exact same physical prefix blocks in memory, drastically reducing redundant memory footprints across the server.33
- Reference Counting: Each physical block maintains an atomic reference counter. If the counter is greater than 1, the engine knows the block is currently shared across multiple sequences.
- Copy-On-Write (COW): If a sequence needs to mutate a shared block (e.g., appending a new, unique token during a divergent branch of generation), the engine executes a Copy-On-Write maneuver. It clones the shared physical block to a new exclusive block, decrements the original reference count, and safely isolates the sequence's state without disturbing the shared prefix.30
True Paged Attention Kernels and Cache Quantization
The existence of a non-contiguous paged cache requires highly specialized SIMD attention kernels. Rather than performing a standard memory gather into a contiguous buffer—which is extremely cache-unfriendly—the native C\# PagedAttention kernel computes attention scores by iterating over the logical block table. It performs indirect pointer lookups to access the physical blocks and executes the dot-products sequentially, skipping the intermediate gather phase entirely.17 This eliminates the massive [Figure omitted from source export] staging buffer copy previously required per transformer layer, yielding a tangible and highly significant throughput increase for extremely long sequence contexts.17 Furthermore, pure C\# systems enhance this by supporting a Quantized KV Cache.30 Instead of caching full FP16 arrays, the runtime dynamically quantizes the cached Keys and Values downward to Q8\_0 or Q4\_0 formats.38 Combining a quantized cache with a mixed-precision window extends the effective context window size available on the host machine by a factor of 2 to 4 without exceeding available RAM limits, making long-context models suddenly viable on consumer laptops.30
Composable Sampling, Structured Constraints, and Speculative Decoding
Once the final transformer layer outputs the raw logit scores for the entire vocabulary, the runtime must deterministically or stochastically sample the next token.
Composable Sampling Pipelines
The C\# engine constructs a dynamic pipeline of composable ISamplerStep objects that directly mutate a Span\<float\> representing the logits. The pipeline executes in a strict, configurable order to manipulate the probability mass function:
- Repetition Penalty: Mutates the logits of recently generated tokens (maintained in a lookback window) using multiplicative or additive penalties to discourage looping behavior.35
- Temperature: Divides logits by a temperature coefficient to either flatten (increase randomness) or sharpen (increase determinism) the probability distribution.35
- Top-K: Masks out all but the [Figure omitted from source export] highest-probability tokens by forcing all other values to [Figure omitted from source export].35
- Top-P (Nucleus): Sorts logits descending and retains the smallest subset of tokens whose cumulative probability exceeds the threshold [Figure omitted from source export].35
- Min-P: Filters out tokens whose probability is less than a specific fraction of the absolute highest-probability token, stabilizing sampling across highly variable distributions better than Top-P alone.35
- Categorical Selection: Applies the final Softmax function to convert logits to probabilities, generating a random number to select the token based on the resulting distribution.1
Grammar and Structured Output Constraints
A significant advantage of building the inference engine natively in C\# is the ability to deeply integrate constrained decoding without paying cross-boundary invocation penalties to Python. When applications require strict JSON, regular expressions (Regex), or specific formal grammars (GBNF), the C\# runtime constructs a Finite State Machine (FSM) or Pushdown Automaton (PDA).1 At every decoding step, strictly before the Softmax is applied, the IDecodingConstraint module evaluates the FSM against the entire vocabulary. Any token that would violate the syntax is masked out (set to [Figure omitted from source export]).1 Because this occurs directly within the C\# logit array in unmanaged memory, the process mathematically guarantees syntactically valid output without retries, model prompting hacks, or expensive post-processing, making it highly efficient for structured data extraction tasks.30 During Beam Search, these constraint states are simply cloned alongside the Copy-On-Write KV cache forks to maintain independent validation states.17
Speculative Decoding and Multi-Token Prediction (MTP)
To accelerate the memory-bound decode phase, advanced C\# runtimes implement Speculative Decoding architectures. Because memory bandwidth restricts processing to roughly one token at a time during standard decode, the GPU/CPU is often starved for compute.4 Speculative Decoding utilizes a fast "Draft and Verify" loop.17
- Drafting: A small, highly efficient draft model (or embedded draft heads in newer architectures like Qwen 3.6 NextN) autoregressively hallucinates several potential future tokens.37
- Verification: The primary, massive trunk model takes these drafted tokens and executes a single batched forward pass (a prefill-style operation, which is compute-bound rather than memory-bound) to verify all of them simultaneously.37
- Acceptance: Based on complex rejection sampling formulas, the trunk model accepts the tokens that perfectly align with its own probability distribution. Any rejected token immediately halts the chain, and the KV cache rolls back to the divergence point using the Paged Cache COW mechanics.17
In pure C\# execution, this mechanism proves highly profitable because it forces the hardware to leverage the faster GEMM matrix algorithms over the slower, memory-bound GEMV matrix-vector algorithms, resulting in up to a 2–3x net speedup in tokens generated per second.17
Quantitative Performance Expectations and Bottlenecks
Evaluating a pure C\# engine requires strictly differentiating between Prefill (processing the input prompt) and Decode (autoregressively generating the output). The performance limits are entirely dictated by the underlying hardware bottlenecks and the efficiency of the managed CLR's interaction with the memory bus.
Decode Phase: Memory Bandwidth Saturation
During the decode phase, the runtime performs a Vector-Matrix multiplication (GEMV). The CPU must fetch the entirety of the model's weights from RAM into the CPU cache for every single token generated. Because the actual mathematical computation is minimal compared to the sheer volume of data moving physically across the motherboard traces, decode is strictly memory-bandwidth-bound.4 Because memory fetching is the absolute bottleneck, the C\# execution and runtime overhead is almost entirely masked. Modern C\# engines with SIMD intrinsics are highly capable of saturating the hardware memory bus.4 Empirical benchmarks utilizing an AMD Ryzen 9 5950X demonstrate that a pure C\# engine like dotLLM achieves between 66% and 88% of the throughput of the highly optimized C++ llama.cpp.4
| Model Architecture | Quantization Format | C\# Engine Throughput | llama.cpp Throughput | C\# Efficiency Ratio |
|---|---|---|---|---|
| SmolLM-135M | Q4\_K\_M | 279.1 tok/s | 334.7 tok/s | 0.83x |
| SmolLM-135M | Q8\_0 | 197.7 tok/s | 255.9 tok/s | 0.77x |
| Llama 3.2 1B | Q4\_K\_M | 32.4 tok/s | 48.9 tok/s | 0.66x |
| Llama 3.2 1B | Q8\_0 | 25.0 tok/s | 31.0 tok/s | 0.81x |
| Llama 3.2 3B | Q4\_K\_M | 15.4 tok/s | 19.6 tok/s | 0.79x |
| Llama 3.2 3B | Q8\_0 | 9.9 tok/s | 11.2 tok/s | 0.88x |
| (Data derived from comparative analysis of native dotLLM Phase 6 execution).4 |
This data confirms that for pure generative tasks on consumer CPUs, the CLR is not an insurmountable bottleneck; it is entirely feasible to achieve near-native interactive chat rates purely in C\#.
Prefill Phase: The Compute Bound Wall
Conversely, prompt prefilling processes all user tokens simultaneously. This shifts the mathematical operation from a memory-bound GEMV to a dense Matrix-Matrix multiplication (GEMM).17 Fetching weights from RAM happens only once for a large batch of inputs, making this phase completely compute-bound.4 It is here that the pure C\# implementation suffers severely. As previously analyzed, the RyuJIT compiler's inability to gracefully handle the heavy register pressure of an outer-product AVX2 tile causes extreme stack spilling.4 Without the hand-tuned assembly microkernels present in llama.cpp, the C\# engine's prefill throughput is currently observed to be roughly 2x to 5x slower across the board.4 While still highly functional, processing a massive 4,000-token input prompt will incur a noticeable latency penalty in a C\# runtime compared to native C/C++, representing the primary performance gap in the architecture.
Test Fixtures, Numerical Accuracy, and Correctness Oracles
Ensuring mathematical parity with PyTorch or llama.cpp requires extensive continuous integration. Because C\# developers are reimplementing massive tensor operations from scratch, small errors in precision, bit-shifting, or accumulator overflows can introduce severe numerical drift. If left unchecked, this drift causes gradients to explode during inference, resulting in a complete collapse of linguistic coherency. To combat this, C\# projects implement sweeping test fixtures. For instance, the TensorSharp.TestMatrix sweeps supported hosts across models, backends, features, and environment variables, comparing outputs against pre-computed per-host baselines.37 Crucially, every SIMD-optimized tensor operation in pure C\# is paired with a mathematically pure, unoptimized scalar reference implementation.6 This scalar implementation acts as the absolute "correctness oracle." During unit testing, the test fixtures execute both the highly optimized AVX2/AVX-512 path and the slow scalar path on identical, edge-case synthetic data (including denormals, large values, and specific block boundaries). The outputs are compared, and the SIMD path is only considered valid if its output matches the scalar oracle within a strictly defined epsilon tolerance.6 Furthermore, complex integration tests validate the fused dequantize-and-accumulate logic. A Q8\_0 GEMV output is tested directly against a full dequantized FP32 GEMV on real tensor data to ensure that the rapid integer dot-products are not losing critical floating-point precision.6 This level of rigorous testing is the only mechanism that allows a pure C\# engine to reliably execute complex architectures like LLaMA without silently corrupting the output space.
Strategic Gaps and Realistic Milestones
The current ecosystem of pure C\# LLM inference engines has demonstrated extraordinary capability, rapidly migrating from experimental wrappers to ground-up, end-to-end architectures capable of executing complex models natively.1 However, several strategic gaps remain before these engines reach total technological parity with established native C/C++ ecosystems.
- AVX-512 Specialization and RyuJIT Avoidance: The register-spilling bottleneck in RyuJIT is predominantly an AVX2 constraint. Widespread implementation of Vector512\<T\> pathways utilizing Avx512F and Avx512BW instructions will provide 32 ZMM registers, naturally resolving the JIT limitations without relying on native compilation.31 Developing these discrete hardware paths is a primary milestone for improving prefill throughput.
- NativeAOT Maturation: While zero-allocation architectures successfully bypass GC pauses, startup times and initial JIT compilation delays can be completely eradicated using.NET's NativeAOT (Ahead-Of-Time compilation) functionality. Experimental NativeAOT support exists in modern C\# engines 1, but full maturity—where the entire C\# engine compiles to a standalone, minimal-footprint, GC-free native binary without requiring the.NET runtime to be installed on the host machine—is strictly necessary for edge deployments and embedded systems.
- Mixture of Experts (MoE) SSD Routing: As state-of-the-art models shift heavily toward sparse architectures like Mixture of Experts (MoE), pure C\# runtimes must implement efficient expert routing algorithms. The milestone is ensuring that only the specific parameters required for a specific token are dynamically paged from SSD memory and evaluated, capitalizing on the memory-mapped virtual architecture.4
- Advanced Attention Mechanisms: Expanding support beyond standard Grouped-Query Attention to include more complex implementations like DeepSeek's Multi-Head Latent Attention (MLA) represents the next frontier for architectural compatibility.4
The construction of a local LLM inference engine executed entirely within a pure C\# architecture is fundamentally, provably feasible. By adhering to strict unmanaged memory principles, actively avoiding the managed heap to prevent garbage collection interruptions, and aggressively utilizing hardware-intrinsic SIMD operations, managed languages can pierce the veil of high-performance computing that was traditionally reserved exclusively for low-level systems programming. While certain compiler-level constraints temporarily degrade the efficiency of compute-bound prefill operations, the memory-bound nature of the decoding phase allows a native C\# pipeline to achieve near parity with industry-standard C++ implementations, solidifying the viability of C\# as a premier ecosystem for modern AI engineering.
Works cited
- kkokosa/dotLLM: LLM inference engine written in .NET \- GitHub, accessed June 29, 2026, https://github.com/kkokosa/dotLLM
- TensorSharp: Open Source Local LLM Inference Engine \- Hacker News, accessed June 29, 2026, https://news.ycombinator.com/item?id=48519136
- Where can I learn more about SIMD, CPU intrinsics and the like in the context of Rust? \- Reddit, accessed June 29, 2026, https://www.reddit.com/r/rust/comments/11uw3fi/where\_can\_i\_learn\_more\_about\_simd\_cpu\_intrinsics/
- Introducing dotLLM \- Building an LLM Inference Engine in C\# | Konrad 'Dev Nerd' Kokosa, accessed June 29, 2026, https://kokosa.dev/blog/2026/dotllm/
- Scaling Large-scale GNN Training to Thousands of Processors on CPU-based Supercomputers \- arXiv, accessed June 29, 2026, https://arxiv.org/pdf/2411.16025
- Implement CPU tensor operations (MatMul, RMSNorm, SiLU, Softmax) \#4 \- GitHub, accessed June 29, 2026, https://github.com/kkokosa/dotLLM/issues/4
- ggml/docs/gguf.md at master · ggml-org/ggml \- GitHub, accessed June 29, 2026, https://github.com/ggml-org/ggml/blob/master/docs/gguf.md
- GGUF · Hugging Face, accessed June 29, 2026, https://huggingface.co/docs/hub/en/gguf
- LLM GGUF Guide: File Format, Structure, and How It Works \- ApX Machine Learning, accessed June 29, 2026, https://apxml.com/posts/gguf-explained-llm-file-format
- GGUF Format: A Complete Guide to Local LLM Inference \- DataCamp, accessed June 29, 2026, https://www.datacamp.com/tutorial/gguf-format-a-complete-guide
- GGUF File Format \- Chair of Computer Architecture, accessed June 29, 2026, https://cca.informatik.uni-freiburg.de/debugging/ws23/FORMAT.html
- dotLLM/docs/GGUF\_FORMAT.md at main \- GitHub, accessed June 29, 2026, https://github.com/kkokosa/dotLLM/blob/main/docs/GGUF\_FORMAT.md
- GGUF file format \- ggml, accessed June 29, 2026, https://ggml-org-ggml.mintlify.app/formats/gguf
- Support for GGUF v3 files · Issue \#131 · kkokosa/dotLLM \- GitHub, accessed June 29, 2026, https://github.com/kkokosa/dotLLM/issues/131
- google/sentencepiece: Unsupervised text tokenizer for Neural Network-based text generation. \- GitHub, accessed June 29, 2026, https://github.com/google/sentencepiece
- blingfire2 \- PyPI, accessed June 29, 2026, https://pypi.org/project/blingfire2/
- dotLLM/docs/ROADMAP.md at main \- GitHub, accessed June 29, 2026, https://github.com/kkokosa/dotLLM/blob/main/docs/ROADMAP.md
- Wave 7: CPU performance — partial sort, schema cache, AVX2, accessed June 29, 2026, https://github.com/kkokosa/dotLLM/issues/109
- Labels · kkokosa/dotLLM · GitHub, accessed June 29, 2026, https://github.com/kkokosa/dotLLM/labels/enhancement
- Efficient Dart: Part 2, Breaking Bad \- DEV Community, accessed June 29, 2026, https://dev.to/maximsaplin/efficient-dart-part-2-going-competitive-307c
- Step 28: Fast approximate exp/softmax — IEEE-754 bit-manipulation vectorized exp · Issue \#55 · kkokosa/dotLLM \- GitHub, accessed June 29, 2026, https://github.com/kkokosa/dotLLM/issues/55
- Vector256 Class (System.Runtime.Intrinsics) \- Microsoft Learn, accessed June 29, 2026, https://learn.microsoft.com/en-us/dotnet/api/system.runtime.intrinsics.vector256?view=net-10.0
- Quantize Llama models with GGUF and llama.cpp \- Medium, accessed June 29, 2026, https://medium.com/data-science/quantize-llama-models-with-ggml-and-llama-cpp-3612dfbcc172
- ggml-quants.h · natasa365/whisper.cpp at 51d2d34e66448b51035bf414b82ce0fddcc272f9, accessed June 29, 2026, https://huggingface.co/spaces/natasa365/whisper.cpp/blob/51d2d34e66448b51035bf414b82ce0fddcc272f9/ggml-quants.h
- Custom Vector class with System.Runtime.Intrinsics : r/csharp \- Reddit, accessed June 29, 2026, https://www.reddit.com/r/csharp/comments/k2blu7/custom\_vector\_class\_with\_systemruntimeintrinsics/
- fp8 GGUF and GPU accelerated inference support · ggml-org llama.cpp · Discussion \#8780, accessed June 29, 2026, https://github.com/ggml-org/llama.cpp/discussions/8780
- Support Half-precision floating-point numbers · Issue \#8243 · dotnet/runtime \- GitHub, accessed June 29, 2026, https://github.com/dotnet/runtime/issues/8243
- Performance Improvements in .NET 10 \- Microsoft Developer Blogs, accessed June 29, 2026, https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-10/
- Improving Performance With SIMD intrinsics \- Emin's Notes, accessed June 29, 2026, https://notes.xeome.dev/notes/SIMD-Intrinsics
- dotLLM — Native .NET LLM Inference, accessed June 29, 2026, https://dotllm.dev/
- Step 10: SIMD kernel tuning — benchmark-driven Q8\_0/Q4\_K GEMV optimization · Issue \#26 · kkokosa/dotLLM \- GitHub, accessed June 29, 2026, https://github.com/kkokosa/dotLLM/issues/26
- Paged KV cache and scheduler: Phase 1 (design feedback requested) · ggml-org llama.cpp · Discussion \#21961 \- GitHub, accessed June 29, 2026, https://github.com/ggml-org/llama.cpp/discussions/21961
- Paged Attention: Turning the Page on Transformer Memory | by Sai Saketh \- Towards AI, accessed June 29, 2026, https://pub.towardsai.net/paged-attention-turning-the-page-on-transformer-memory-f394ca74e230
- Paged Attention | AI Engineering | CrackingWalnuts | CrackingWalnuts, accessed June 29, 2026, https://crackingwalnuts.com/ai-engineering/paged-attention
- dotLLM/docs/SAMPLING.md at main \- GitHub, accessed June 29, 2026, https://github.com/kkokosa/dotLLM/blob/main/docs/SAMPLING.md
- HERALD: High-Throughput Block Diffusion LLM Serving via CPU-GPU Cooperative KV Cache Retrieval \- arXiv, accessed June 29, 2026, https://arxiv.org/html/2606.21633v1
- A native .NET LLM inference engine for GGUF models. TensorSharp provides a console application, a web-based chatbot interface, and Ollama/OpenAI-compatible HTTP APIs for programmatic access. It supports Windows/MacOS/Linux with full GPU capability · GitHub, accessed June 29, 2026, https://github.com/zhongkaifu/TensorSharp
- Step 33: KV-cache quantization — Q8\_0/Q4\_0 compression, mixed-precision window · Issue \#74 · kkokosa/dotLLM \- GitHub, accessed June 29, 2026, https://github.com/kkokosa/dotLLM/issues/74
- Step 43: Speculative decoding — draft-verify-accept loop with KV, accessed June 29, 2026, https://github.com/kkokosa/dotLLM/issues/98
- SciSharp/TensorSharp2: A C\# inference engine for running large language models (LLMs) locally using GGUF model files. TensorSharp provides both a console application and a web-based chatbot interface for multi-turn conversations with multimodal models. · GitHub, accessed June 29, 2026, https://github.com/SciSharp/TensorSharp2