Het implementeren van 'Latent Reasoning' als een volledig functioneel model

Waar de vorige editie stopte

In de vorige editie heb ik een CoLaR-head (Compressed Latent Reasoning) gekoppeld aan DeepSeek Flash v4. Dat werk had alle kenmerken van een demo: een head die het model in staat stelt te redeneren in de latente ruimte, een geleerde 'stop-head' die bepaalt wanneer er voldoende is nagedacht, en een raadsel dat aantoonde waarom eenvoudige autoregressieve generatie vaak gecachte antwoorden herhaalt in plaats van echt te redeneren.

Er was echter één wezenlijk probleem: de head was een adapter—iets dat aan de zijkant was vastgeprikt. Om het model te serveren, moest je handmatig het basismodel, de head, het stopcriterium en een aangepaste runtime samenvoegen, in de hoop dat de onderdelen pasten. De gewichten stonden op één plek, de inferentiemachinerie op een andere. Het proces om het model te draaien was een hindernisbaan.

Deze editie overbrugt dat gat. Het werk is nu een compleet, zelfstandig model. Elk gewicht dat nodig is voor de serving wordt in één repository geleverd. De backbone is gekwantiseerd naar NVFP4 zodat het op echte hardware past, en de latente loop wordt aangestuurd door een correcte, gebenchmarkte serving runtime.

Model: nmitchko/DeepSeek-V4-Flash-0731-Latent-Reasoning

De grote verandering: geen adapter meer

Het hoofddoel van deze editie is verpakking (packaging). Waar de oude release een head was die je moest bevestigen, is de nieuwe versie een volledig model. Alle benodigde gewichten bevinden zich nu in één HuggingFace-repo:

  • De DeepSeek-V4-Flash-0731 backbone: Gekwantiseerd naar NVFP4 (groepsgrootte 16) op de routed MoE-experts. Attention, shared experts, de LM-head en het draft-blok behouden een hogere precisie. Dit komt neer op ongeveer 79 GiB aan gewichten per GPU bij TP=2 (totaal 158–164 GiB).
  • Het DSpark draft-blok (3 lagen): Behouden uit de bron, zodat speculatieve decoding direct inbegrepen is.
  • De getrainde latent reasoning head: 35,7 miljoen parameters, geladen vanuit een enkel latentreasoninghead.safetensors bestand (~152 MB).

Omdat de gewichten compleet zijn, kan de modelkaart nu eindelijk echte benchmarks rapporteren in plaats van "binnenkort meer benchmarks".

Eerste evaluaties

BBH (BIG-Bench Hard), cot_zeroshot, 27 subtaken: aggregaat 0.94 ± 0.008. Gemeten met lm-evaluation-harness 0.4.12 tegen een OpenAI-compatibel eindpunt. Thinking ingeschakeld. 50 items per subtak, 1350 items in totaal.

SubtaskScoreSubtaskScore
trackingshuffledobjectsthreeobjects1.00date_understanding0.92
trackingshuffledobjectsfiveobjects1.00sports_understanding0.88
trackingshuffledobjectssevenobjects1.00logicaldeductionfive_objects0.88
penguinsina_table1.00weboflies0.86
formal_fallacies1.00snarks0.84
boolean_expressions1.00ruin_names0.84
word_sorting0.98movie_recommendation0.84
temporal_sequences0.98salienttranslationerror_detection0.76
object_counting0.98geometric_shapes0.74
navigate0.98causal_judgement0.66
logicaldeductionthree_objects0.98disambiguation_qa0.58
reasoningaboutcolored_objects0.96dyck_languages0.26
hyperbaton0.96multisteparithmetictwo0.94
logicaldeductionseven_objects0.94

Het patroon is precies wat je zou verwachten van een latent reasoning model. Het is het sterkst waar redeneren betekent dat er sprake is van multi-stap statusbijhouding (state tracking). trackingshuffledobjects, booleanexpressions, formalfallacies en penguinsinatable scoren allemaal 1.00. Het model is het zwakst bij mechanische, syntax-zware taken; dycklanguages (haakjesmatchen) scoort 0.26, wat een duidelijke uitschieter en een echt zwaktepunt is.

Twee belangrijke opmerkingen bij de tabel:

  1. Lees flexible-extract, niet strict-match. De strict-match regex van BBH zoekt naar de letterlijke frase "The answer is X". Dit model produceert die frase niet, omdat het redeneert in de latente ruimte. De bijna nulscore voor strict-match is dus een artefact van de opmaak, niet van een falen inHet redeneren.
  2. De waarden per subtak hebben een marge van ongeveer ±0.05–0.07 bij 50 items. Het aggregaat van 0.880 (gecorrigeerd) is het betrouwbare getal.

Waarom de head er nu anders uitziet

De architectuur bouwt voort op het oorspronkelijke CoLaR-idee, maar heeft nu een vaste plek in het model. Een kleine head leest de hidden state van laag 35 van de backbone, projecteert deze naar een 1024-d latent en decodeert dit terug naar de residual stream. Eén latente stap vervangt meerdere reasoning tokens (een geregistreerde compression_factor van 6). Een geleerde stop-head beëindigt de loop op een variabele diepte, afhankelijk van de inhoud.

Architectuur flow:

  • Input: layer 35 hidden (4096-d) → LayerNorm
  • ReasoningCompressionHead:
  • Linear 4096 → 2048 . SiLU
  • Linear 2048 → 2048 . SiLU
  • Linear 2048 → 2048 → [mu, log_sigma]
  • Stop-head: Linear 4096 → 1024 . SiLU → Linear 1024 → 1 (beëindiging redenering)
  • LatentDecoder:
  • mu (1024-d latent) → LayerNorm
  • Linear 1024 → 2048 . SiLU
  • Linear 2048 → 2048 . SiLU
  • Linear 2048 → 4096
  • Output: Teruggeschreven in de residual stream van de DeepSeek-V4-Flash-0731 backbone (bevroren, NVFP4).

Configuratiedetails:

ParameterWaarde
hidden_size4096
latent_dim1024
mlp_dim2048
source / target layer35 / 42
activatieSiLU
learned stop headja
head + decoder params35.7M (float32)
backbone layers43

De head is een variationele compressie die [mu, logsigma] voorspelt en logsigma klemmet. De decoder verdeelt de latent terug in de 4096-d stream. Omdat de geometrie in de metadata van het checkpoint staat, heeft de serving runtime geen configuratie nodig; deze leest de vorm van de head direct uit het bestand.

De serving runtime is nu een echt product

De oude runtime bestond uit omgevingsvariabelen en headers die aan een fork waren toegevoegd. Nu is de runtime als een zelfstandig onderdeel uitgebracht, verdeeld over twee stukken:

RepositoryBeschrijving
nickmitchko/ds4-reasoning-addonDe latent-reasoning addon: de closed-loop driver, serve-script, vaste requirements en GPU-sizing gids. Start hier.
nickmitchko/vllm-ds4-sm120De DS4 vLLM fork waarop het draait (branch ds4-sm120-preview-dev). Verplicht; upstream vLLM kan dit model niet serveren.

De addon blijft dormant tot VLLMDS4REASONING_CKPT is ingesteld. Dit is een bewuste veiligheidskeuze. De ondersteunde entrypoint is een script dat alle optimale standaardwaarden instelt:

# 1. de engine (een volledige build duurt even)
git clone https://github.com/nickmitchko/vllm-ds4-sm120.git && cd vllm-ds4-sm120
git checkout ds4-sm120-preview-dev
export CUDA_HOME=/usr/local/cuda-13.0 PATH=/usr/local/cuda-13.0/bin:$PATH
export TORCH_CUDA_ARCH_LIST="12.0"
pip install torch==2.11.0 --index-url https://download.pytorch.org/whl/cu130
pip install -e . --no-build-isolation

# 2. de addon + bijbehorende deps
git clone https://github.com/nickmitchko/ds4-reasoning-addon.git && cd ds4-reasoning-addon
pip install -e . --no-deps
pip install -r release/requirements-serve.txt \
--extra-index-url https://flashinfer.ai/whl/cu130/torch2.11

# 3. serveren: geen argumenten nodig, de head wordt herkend uit de model repo
release/serve_ds4_reasoning.sh

Waarom überhaupt een fork?

Er is een technische reden waarom standaard vLLM dit model niet kan serveren. DeepSeek-V4 routeert MoE-experts via een hash gebaseerd op inputids. In het native promptembeds pad van vLLM worden input_ids genegeerd wanneer embeddings worden meegeleverd, wat zou leiden tot een crash bij de opstart van DeepSeek V4.

In plaats daarvan overschrijft de addon de output van embedtokens op de doelposities met de gedecodeerde latent, terwijl de token-id's normaal blijven doorstromen zodat de hash-MoE routing blijft werken. Latente stappen gebruiken een gereserveerd pad-token-id voor de administratie. Omdat dit draait op de executemodel naad, blijft het op het cudagraph snelle pad staan, zonder dat enforce_eager nodig is en met batching over gelijktijdige verzoeken.

Speculatieve drafting wordt onderdrukt tijdens de latente fase (VLLMDS4SUPPRESSLATENTDRAFTS, standaard aan). Dit voorkomt dat een draft-slot de injectie steelt.

De instellingen, herzien

De splitsing tussen omgevingsvariabelen en headers is behouden, maar nu schoner.

Server-brede omgevingsvariabelen (standaard = optimaal)

VariabeleStandaardFunctie
MAX_LATENT256Veiligheidskap op latente stappen (voorkomt fouten van stop-head).
MIN_LATENT4Ondergrens; voorkomt dat het model nooit nadenkt.
USE_STOP1Gebruik geleerde stop; 0 gebruikt een vaste N-cap.
STOP_THRESHOLD0.5Drempelwaarde voor de stop-head.
MINOUTPUTTOKENS4096Ondergrens voor het token-budget van het antwoord.
RIDER10 serveert alleen de backbone (A/B baseline).
DEBUG01 print per-verzoek latente statistieken.
MAXMODELLEN262144Context window.
MAXNUMSEQS2Gelijktijdige sequenties (ruilt af tegen context).
GPU_UTIL0.95Smalle bandbreedte; 0.97 geeft vaak OOM.
TP2Tensor-parallel grootte.

Per-verzoek HTTP-headers

Deze hebben voorrang op de omgevingsvariabelen en werken op zowel /v1/chat/completions als /v1/messages.

HeaderEffect
x-ds4-thinkingActiveert nadenken zonder body-veld.
x-ds4-max-latentPer-verzoek maximum aan latente stappen.
x-ds4-min-latentPer-verzoek minimum aan latente stappen.
x-ds4-use-stopSchakelt de geleerde stop in/uit.
x-ds4-stop-thresholdPer-verzoek drempelwaarde voor stop.
x-ds4-min-output-tokensPer-verzoek ondergrens antwoord-budget.

Twee belangrijke instellingen toegelicht

  • MAX_LATENT: Dit is een veiligheidskap, geen knop om de diepte van het redeneren aan te passen. De geleerde stop beëindigt normaal gesproken de fase. Te hoog instellen (ver voorbij de K=256 uit de training) leidt tot onzin in plaats van dieper nadenken.
  • MINOUTPUTTOKENS: Bestaat omdat de latente fase en het antwoord één budget delen. Elke latente stap kost één administratief token. Zonder deze vloer kan een client met een laag maxtokens budget zijn hele budget verbruiken aan nadenken, waardoor er een leeg antwoord terugkomt (stopreason=length).

De canonieke client

Er zijn twee niet-voor-de-hand liggende vereisten voor clients die met de server communiceren:

  1. Je moet expliciet om 'thinking' vragen. De redeneerfase is afhankelijk van chattemplatekwargs={"thinking": true} (of de header x-ds4-thinking: 1). Zonder dit is de output onbruikbaar.
  2. Geef het antwoord voldoende token-ruimte. Omdat nadenken en antwoorden hetzelfde budget delen, kan een te krappe max_tokens volledig door de latente fase worden opgeslokt.

Voorbeeld in Python:

from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8001/v1", api_key="dummy")
resp = client.chat.completions.create(
    model="nmitchko/DeepSeek-V4-Flash-0731-Latent-Reasoning",
    messages=[{"role": "user", "content": "Write a Python LRU cache."}],
    extra_body={"chat_template_kwargs": {"thinking": True}},   # VERPLICHT
    max_tokens=4096,
    temperature=0.6,
)
print(resp.choices[0].message.content)

Twee zaken die als bugs lijken, maar dat niet zijn

  1. Degeneratieve herhaling bij opstart: De eerste één of twee verzoeken na het opstarten kunnen resulteren in herhalingen (bijv. "be be be"), zelfs als de stop-head normaal werkt. Dit trekt vanzelf recht. Stuur één testverzoek na opstart en behandel vroege foutieve antwoorden als 'niet opgewarmd' in plaats van kapot.
  2. Stille dormantie: Een actieve rider en een sluimerende rider produceren identieke serverlogs. Als VLLMDS4REASONINGCKPT niet is ingesteld of het verzoek vraagt niet om thinking, doet de plugin simpelweg niets zonder melding. Gebruik VLLMDS4REASONINGDEBUG=1 om te bevestigen dat injectie daadwerkelijk plaatsvindt.

Wat er nodig is om het te draaien

De backbone gebruikt NVFP4, wat betekent dat dit Blackwell-klasse hardware (sm120) of beter vereist. De sm120 sparse-MLA kernel path is specifiek voor deze fork; Hopper en oudere architecturen zijn niet getest.

VRAM vereisten:

VRAM (totaal)Verdict
$\ge 192$ GiB (bijv. $2\times 96$ of $4\times 48+$)Aanbevolen. Lange context (256k) met ruimte voor KV cache.
$160–192$ GiBWerkbaar. Gewichten passen; verlaag MAXMODELLEN naar 32k–64k.
$< 160$ GiBOnvoldoende voor NVFP4 gewichten bij elke context.

Geverifieerde configuratie: $2\times \text{RTX PRO 6000 Blackwell Max-Q}$ (96 GiB elk), TP=2, context 262.144, gpumemoryutilization 0.95. Doorvoersnelheid 11,1 ms/token inclusief head en DSpark spec decode (1.40×).

Aanvullende technische noten:

  • maxnumseqs concurreert met de context. Bij 256k past er slechts een paar volledige sequenties in de fp8 KV cache.
  • Bij opstartfouten met de KV-cache: verlaag eerst MAXMODELLEN, dan MAXNUMSEQS.
  • MoE backend: Gebruik enkel FLASHINFER_CUTLASS op sm120; andere opties zoals marlin of trtllm crashen of weigeren te starten.
  • Systeemgeheugen: Het laden van een ~164 GiB checkpoint vult de page cache. Op hosts met minder dan 256 GB RAM kan systemd-oomd het proces doden zonder traceback. Beperk de page cache om dit te voorkomen.

De gesloten latente loop, nogmaals

Dit is wat er gebeurt wanneer een verzoek de server bereikt: de backbone voert één keer de prefill van je prompt uit en gaat daarna over op autoregressieve latente decode-stappen. De input-embedding van elke stap is decoder(head(vorige layer-35 hidden)), teruggevoerd via een per-verzoek anchor store. Dit gaat door tot de geleerde stop-head zijn drempelwaarde overschrijdt, waarna het antwoord als gewone tokens wordt gedecodeerd.

Flow: hsrc (laag 35) --layernorm--> head -> mu (latent, 1024-d) mu --layer_norm--> decoder -> hidden vector (4096-d)

Dit proces draait gebatcht over gelijktijdige verzoeken op het cudagraph snelle pad en wordt gestreamd. Eén latente stap vervangt ongeveer zes reasoning tokens. Het model denkt gecomprimeerd, en spreekt daarna pas.

Beperkingen en toekomstig werk

Er zijn enkele kanttekeningen bij dit model:

  • Blackwell-afhankelijkheid: NVFP4 vereist sm120 native kernels. Zonder Blackwell-kaart krijg je de backbone, maar niet de latent reasoning.
  • Ondoorzichtigheid: De getoonde trace is niet de eigenlijke berekening. Omdat redeneren in de latente ruimte gebeurt, is de tekst die je ziet geen trouwe weergave van het denkproces. De latente ruimte blijft opaak.
  • Beperkte evaluatie: De huidige benchmarks zijn beperkt tot BBH met 50 items per subtak. Er zijn nog geen multi-task of long-context suites gerapporteerd.
  • Syntax-zwakte: De lage score op dyck_languages (0.26) laat zien dat mechanische, syntax-zware taken lastig zijn voor gecomprimeerde latente redenering.

Toekomstige plannen:

  • Uitbreiding van de benchmark suite naar multi-task en long-context scenario's.
  • Het interpreteerbaar maken van latent reasoning, zodat we kunnen zien wat het model denkt.
  • Verbeteren van de afhandeling van mechanische syntax-taken.
  • Verdere optimalisatie van de interactie met DSpark om de snelheid te verhogen.

De vorige editie bewees het idee: een model kan denken in gecomprimeerde latente ruimte en leren wanneer het moet stoppen. Deze editie bewijst dat dit in productie gebracht kan worden. Een NVFP4-gekwantiseerd, DSpark-drafted, gebenchmarkt en zelfstandig model met een runtime die met één commando start, transformeert een theoretisch idee naar een bruikbare tool.