Advanced Features

The systems that make TensorSharp fast and scalable: continuous batching with a paged KV cache, speculative decoding, text diffusion, and the kernel- and memory-level optimizations underneath.

Continuous batching & paged KV cache

The server's InferenceEngine is a vLLM-style continuous-batching engine, on by default. Instead of running one request at a time, it interleaves many at the granularity of a single decode step.

Models that have not implemented the batched path still run on the engine's isolated per-sequence KV-swap fallback. Tune it with the TS_SCHED_* variables, or disable it entirely with --no-continuous-batching.

Native paged attention

The native kernel TSGgml_PagedAttentionForward (and a WithSinks variant for GPT OSS) gathers K/V from the paged buffer in C++, builds a small GGML graph per sequence, and dispatches ggml_flash_attn_ext — the same fused Metal/CUDA flash-attention kernel the single-sequence path uses. On a long-context Ministral-3-14B workload (4×~800 tokens) it runs ~21% faster than the legacy per-sequence GGML path.

Batched forward passes (Mistral 3, Gemma 4, GPT OSS, Qwen 3.5/3.6 with a GatedDeltaNet recurrent-state pool, and Nemotron-H with a Mamba2 recurrent-state pool) pack N sequences into one ForwardBatch call with one batched linear-projection matmul per layer and paged K/V scatter. Gemma 4 reaches ~1.5–1.6× legacy throughput; Nemotron-H Mamba2 batched reaches ~3.95× at batch=3 on an Apple M4 Pro.

📖

Full deep dive: docs/PAGED_ATTENTION_AND_CONTINUOUS_BATCHING.md in the repository.

MTP / NextN speculative decoding

Some architectures ship a multi-token-prediction (MTP / NextN) draft head that lets the server run lossless speculative decoding for solo (non-concurrent) sequences. The draft proposes several future tokens cheaply, the trunk verifies all of them in one batched forward, and accepted tokens are committed in a single step.

🎯

Because the request's own sampler — temperature, top-k/p, and all penalties — drives both the draft and the verify, the output is identical to standard decode. Speculation only changes how many forward passes it takes to produce the same tokens.

It is off by default. Enable it on the server with --mtp-spec (env TS_MTP_SPEC=1):

# Qwen 3.6 — the NextN block is embedded in the trunk GGUF, no extra file needed
# (use a GGUF from unsloth/Qwen3.6-35B-A3B-MTP-GGUF; base-repo exports strip the NextN block)
dotnet run --project TensorSharp.Server -c Release --no-build -- --model Qwen3.6-35B-A3B-UD-IQ2_XXS.gguf --backend ggml_cuda \
    --mtp-spec --mtp-draft 8 --mtp-pmin 0.75

# Gemma 4 — load the separate gemma4-assistant draft GGUF that matches the target
dotnet run --project TensorSharp.Server -c Release --no-build -- --model gemma-4-12B-it-qat-UD-Q4_K_XL.gguf --backend ggml_cuda \
    --mtp-spec --mtp-draft-model mtp-gemma-4-12B-it.gguf

Two draft-head shapes

Where it's profitable

BackendQwen 3.6Gemma 4
GGML CUDA / GGML Metal✅ fused verify + draft kernels✅ fused verify + draft kernels
Direct CUDA (cuda, pure C#)✅ GPU-resident per-op verify/draft✅ GPU-resident per-op verify/draft
CPU / GGML CPU / MLXstandard decodestandard decode

Tuning: --mtp-draft (default 8) bounds tokens drafted per step; --mtp-pmin is the minimum draft confidence to keep a token — its default depends on the drafter kind (0.75 for a per-token draft head, 0.35 for a block drafter, where the gate is the cumulative prefix probability and so the same number is far stricter). Gemma 4 A/B switches are the TS_GMTP_* env vars.

DSpark block speculative decoding (DeepSeek V4)

DeepSeek V4 ships DSpark ("Confidence-Scheduled Speculative Decoding with Semi-Autoregressive Generation") as a support module in the checkpoint: three DSV4 blocks that read the trunk's hidden states and propose a whole block of tokens per step instead of one, a Markov head that conditions each block position on the token before it, and a confidence head that predicts each position's acceptance probability. The trunk then verifies the block in one batched forward and keeps only the prefix its own sampler would have produced.

The drafter is a separate GGUF loaded with --draft-model — every GGUF conversion of the trunk drops the mtp.* tensors. Pre-built drafters are listed in the repository's MODEL_DOWNLOADS.md, or you can convert one from the upstream safetensors checkpoint with eng/dsv4-dspark-to-gguf.py (only the three shards holding mtp.* are downloaded).

# CLI — every single-sequence path (--input, --multi-turn-jsonl, --interactive) uses it
dotnet TensorSharp.Cli/bin/TensorSharp.Cli.dll --model DeepSeek-V4-Flash-...-00001-of-00005.gguf \
    --backend ggml_cuda --draft-model DSpark-drafter-Q2K-Q8-0731.gguf \
    --input prompt.txt --max-tokens 200 --temperature 0

# Server — same drafter, plus --mtp-spec
dotnet TensorSharp.Server/bin/TensorSharp.Server.dll --model DeepSeek-V4-Flash-...-00001-of-00005.gguf \
    --backend ggml_cuda --tp 4 --mtp-spec --draft-model DSpark-drafter-Q2K-Q8-0731.gguf

It runs on both GPU engines--backend cuda (direct CUDA) and --backend ggml_cuda (native ggml); ggml_vulkan and cpu have no speculative path for this architecture and log a warning if a drafter is configured. On ggml the drafter is three extra graph layers whose key ring the trunk graph commits itself, so speculation costs no host round-trips. On the CLI it needs a pure-argmax sampler (any temperature, top-k/p, or repetition penalty turns it off); on the server every verify row is drawn with the request's own sampler, so it composes with any sampling settings. Speculation serves solo sequences only — as soon as a second request is in flight, DSV4's per-sequence slots serve the batch at normal decode speed.

Measured (4×A40, DeepSeek-V4-Flash-0731 UD-Q8_K_XL, greedy)

Metriccuda baselinecuda + DSparkggml_cuda baselineggml_cuda + DSpark
Decode (200-token generation)26.0 tok/s34.0 tok/s (1.31×)26.4 tok/s37.1 tok/s (1.41×)
Prefill (15K prompt)962 tok/s955 tok/s952 tok/s954 tok/s
Acceptance69%69%

Multi-turn chat benefits most — a turn that continues an established context is exactly where the drafter is confident. On a 5-turn interactive session the same box measured 1.50× to 2.02× per turn (66–87% acceptance), with prefill at parity. Greedy output was byte-identical to the non-speculative baseline on both the 200-token and the 15K-context runs.

Why 1.3× and not more: the trunk is 6-of-256 sparse, so each extra token in the verify batch pulls its own set of routed experts through VRAM — a verify row costs roughly a quarter of a full decode step no matter how cheap the draft was. --spec-draft-conf-min (default 0.35, the minimum cumulative acceptance probability for a drafted position) is the knob that keeps that trade positive; --spec-draft-n-max caps tokens per block.

DiffusionGemma text diffusion

DiffusionGemma is fundamentally different from autoregressive models: it does not call Forward() one token at a time. Instead it uses block-wise EntropyBound denoising over fixed-length canvases on a Gemma-4-derived MoE backbone — the whole answer is refined iteratively rather than written left to right.

Performance optimizations

A cross-architecture summary; each per-model card in docs/models/ walks through the same kernels with the exact GGML graph dispatched.

Verified Gemma 4 E4B Q8_0 fast path: repository benchmarks verify the native-GGML E4B Q8_0 family and execution path on GPU backends. Use ggml-org/gemma-4-E4B-it-GGUF as the recommended public artifact.

Memory optimizations