I made an LLM think in latent space. The model never read a single thought

oli2661 pts0 comments

LatentReasoningNoDecode/BLOG.md at main · Oli-26/LatentReasoningNoDecode · GitHub

//blob/show" data-turbo-transient="true" />

Skip to content

Search or jump to...

Search code, repositories, users, issues, pull requests...

-->

Search

Clear

Search syntax tips

Provide feedback

--><br>We read every piece of feedback, and take your input very seriously.

Include my email address so I can be contacted

Cancel

Submit feedback

Saved searches

Use saved searches to filter your results more quickly

-->

Name

Query

To see all available qualifiers, see our documentation.

Cancel

Create saved search

Sign in

//blob/show;ref_cta:Sign up;ref_loc:header logged out"}"<br>Sign up

Appearance settings

Resetting focus

You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.

Dismiss alert

{{ message }}

Oli-26

LatentReasoningNoDecode

Public

Notifications<br>You must be signed in to change notification settings

Fork

Star

FilesExpand file tree

main

/BLOG.md

Copy path

Blame<br>More file actions

Blame<br>More file actions

Latest commit

History<br>History<br>History

58 lines (31 loc) · 7.77 KB

main

/BLOG.md

Copy path

Top

File metadata and controls<br>Preview

Code

Blame

58 lines (31 loc) · 7.77 KB

Raw<br>Copy raw file<br>Download raw file

OutlineEdit and raw actions

I tried to make an LLM think in latent space. The metrics said it worked. The model never read a single thought.

Standard LLM reasoning pays a strange tax. Every step of a chain of thought gets squeezed through the vocabulary: hidden state to logits, sample a token, embed the token, continue. Two lossy projections per step, and the whole trajectory of "thought" is constrained to hop between ~150k discrete points in a continuous space. Papers like Coconut (Meta, 2024) asked the obvious question: what if the model just kept thinking in hidden-state space and only decoded at the end?

I spent a couple of weeks and about 21 GPU-hours on a 12GB laptop GPU testing an aggressive version of this idea, and I want to write up what happened, because the interesting part isn't that it failed. It's how confidently the standard metrics told me it was working while it failed.

The architecture

The setup, on Qwen2.5-1.5B-Instruct: at moments of uncertainty during generation (next-token entropy spike), pause. Take the current hidden state, clone it into K parallel streams, add a different noise vector to each. Let each stream "think" by feeding its final-layer hidden state back in as the next input embedding, N steps, no decoding. Then compress the streams through a bottleneck, inject the result into the context as extra positions, and resume generating.

There's a whole story in the design about parallel subconscious processing and global-workspace bottlenecks, but mechanically that's it: recurrent latent rollouts, noise for diversity, inject, decode.

Day one: the frozen model has nothing to say in latent space

Before any training, I built two diagnostics that turned out to be the most valuable code in the project.

The first was a logit lens over latent states : at every latent step, project each stream's hidden state through the output head and log the top tokens. Not to generate, just to see what region of token space the state occupies.

The result was immediate. At step 0 the states decode to task-flavoured tokens ("Step", "answer", digits). Within 2 to 4 latent steps they drift to junk: whitespace, /router, Array, random CJK fragments. Task-token fraction drops from about 8% to about 1%. The trajectories cover a spectacular amount of hidden-space volume (about 1000x the convex-hull volume of a token-level CoT trajectory in top-5 PCA dims) but the volume is empty. Reach is not meaning.

Worse, the architecture had a built-in uncertainty story: streams disagreeing means the model is uncertain, streams converging means consensus, so use variance as a halt signal. The lens killed that too. Stream agreement does rise as variance falls, but they agree on junk tokens. What looks like consensus is all the streams falling into the same content-free attractor of the frozen recurrent map. If I had shipped variance-convergence as a feature without the lens, I'd have been measuring collapse and calling it agreement.

The training ladder

Fine, nobody expected the frozen model to do this. Coconut needed curriculum training. So: train it.

The second diagnostic mattered here. At every evaluation, I decoded each held-out problem three ways: (a) normally, (b) with the latent states zeroed before injection, (c) with latent states shuffled across problems, so each question gets another question's thoughts. If (a) isn't better than (b) and (c), the latent channel is decorative, whatever the other metrics say.

Then I climbed the ladder. Each rung is a thing that plausibly fixes the previous rung's...

latent space model file hidden state

Related Articles