Skip to content
ryoji-infoPublic

About

ΨLM (PsiLM): a frozen language model and a frozen physics model running as one system, coupled through trainable latent bridges — two hemispheres, one output. No text at the interface. Runs on consumer Apple Silicon.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Latest commit

 

History

251 Commits

Folders and files

Repository files navigation

ΨLM — a frozen language model and a frozen physics model, coupled through latent bridges, producing one answer.

What PsiLM is

PsiLM takes two pretrained models that are never fine-tuned — a language model and a physics model (a neural operator solving a PDE) — and runs them as one system. A small trainable forward bridge reads the physics model's inputs out of the language model's hidden states; the physics model computes; a reverse channel returns the looked-up value as soft tokens injected into a later layer of the language model. No text crosses the interface. The framing is the brain's two hemispheres cooperating across the corpus callosum: two specialists, one output. Why it matters: the physics model's answer arrives inside the language model's reasoning — a 12B model that scores 6.7% on the field-value question below — and 0% if it is not forced to commit to an answer — answers it at 96.7% with the same weights — and a trained gate keeps the channel shut when physics is irrelevant, so on everything else the coupled model matches its backbone to within a question or two (GSM8K 89% either way, MMLU 61% against 60%).

flowchart LR
    P["prompt"] --> L1["frozen LLM<br/>layers 1 … l_fwd"]
    L1 --> FB["forward bridge<br/>reads a, φ, x0 out of the hidden states"]
    FB --> PM["frozen physics model<br/>(Burgers FNO / DPOT-Tiny)"]
    PM --> V["value u(x0)"]
    V --> RB["reverse channel<br/>Fourier features → 8 soft tokens"]
    RB -->|"gated cross-attention"| L2["frozen LLM<br/>layers l_rev … N"]
    L1 --> L2
    L2 --> A["answer"]
Loading

The name: Ψ is the wave function, and the coupling is a nod to complementarity — the language model sees the particle-like, verbalized reading of a process whose wave-like description lives in the physics model. The analogy is a motivation, not a claim.

What it does today

Task: "u(x,0) = a·sin(2πx+φ) evolves by Burgers' equation (ν = 0.02) until t = 0.5; what is u at x0?" — 60 held-out questions, correct within ±0.05. LLM alone is the backbone by itself, oracle is the backbone with the answer stated in the prompt (the ceiling the channel is measured against). Gate selective? says whether the gate was also trained to stay shut on non-physics prompts (the guard-rail below).

backbone LLM alone PsiLM oracle gate selective? result file
Qwen2.5-0.5B (fp16, torch) 8.3% 100% 100% not trained results/stage2/final_eval.json
Qwen3-1.7B (fp16, torch) 1.7% 93.3% 96.7% not trained results/stage2_qwen3-1.7b/final_eval.json
Qwen3-8B-4bit (MLX) 0%1 95.0% (98.3% before selectivity training) 100% yes results/stage2_mlx8b9/final_eval_summary.json (98.3%: results/stage2_mlx8b8/final_eval_summary.json)
Gemma 4 12B-4bit (MLX) 6.7%2 96.7% 98.3% yes results/stage2_gemma12b/final_eval.json
Gemma 4 12B-4bit, multi-mode ICs 20.8% 100% in-distribution3 100% yes results/stage2b_gemma12b_2b/final_eval.json
Gemma 4 12B-4bit, 2D Fisher-KPP (DPOT-Tiny) 10.0% 100% 96.7%4 yes results/stage2d_gemma12b_2d/final_eval.json
Qwen3.5 9B-NVFP4 (MLX; 24 of 32 layers recurrent) 3.3%5 100% 98.3%5 yes results/stage2_qwen35/final_eval.json

Guard-rail (n = 100 per dataset, results/bench/*_summary.json): with the selective gate the coupled model equals its backbone on GSM8K (Qwen3-8B 89% → 89%, Gemma 84% → 84%, Qwen3.5 83% → 83% item for item), MMLU (60% → 61%, 53% → 55%, 64% → 66%) and BoolQ (88% → 88%, 90% → 89%, 90% → 89%), with the gate open on 0% of non-physics prompts and 100% of physics prompts (guardrail_qwen35_guardrail_summary.json for the Qwen3.5 row); before selectivity training the 8B's gate was open everywhere and GSM8K fell from 89% to 34% (v8_8b_guardrail_summary.json).6

What it costs and what it buys

Measured end to end on one Apple M2 (24 GB) with the largest backbone in the table above.

component parameters on disk trained?
Gemma 4 12B-it, 4-bit MLX (language tower) 12.28B 6.3 GB frozen
PsiLM bridges, one (backbone, task) pair 25.5M 102 MB trained
Burgers FNO, the physics hemisphere 0.07M 0.55 MB frozen, pretrained
DPOT-Tiny, 2D task only 7.5M 30 MB frozen, fine-tuned

The trained part is 0.21% of the backbone's parameters and 1.6% of its checkpoint size. The 12.28B never move.

Gemma 4 12B, n = 100 per dataset alone + PsiLM injection zeroed gate σ
physics QA 0% 97% 10% 0.144
MMLU, 5 subjects 53% 55% 53% 0.008
GSM8K 84% 84% 84% 0.004
physics, seconds per question 77.0 (768 tokens) 3.14 (16.9 tokens) — —
GSM8K, seconds per question 25.07 25.06 — —

+0.21% parameters and +103 MB turn 0% into 97%, at 24× lower latency on the task, with nothing measurable lost elsewhere. The last column is why the middle rows do not move: the gate's σ is 0.14 on physics against 0.004–0.008 on everything else, so the channel is shut when physics is irrelevant. Zeroing the injection collapses physics to 10% — the accuracy arrives through the bridge, not through the prompt.

Sources: results/bench/gemma12b_guardrail_summary.json (accuracy, gate σ and seconds per question), results/stage2_gemma12b/final_eval.json (n=60 held-out: PsiLM 96.7%, oracle 98.3%). Parameter counts are read from the checkpoint headers, not from the model names. The backbone's 0% is its own text protocol: it spends the whole 768-token budget deriving and never commits to an answer line; forced to answer (n=60, final_eval_baseline_forced.json) it scores 6.7%, four near-constant guesses (0.41 / 0.51 / 0.54 / 0.11, sd 0.37 against truths spanning [-0.56, +0.60]) landing inside the tolerance; the best single constant would score 13.3%. The latency gap has the same cause — PsiLM answers in one line.

Products

Backbones and physics models are public models loaded separately; the bridges are what PsiLM adds (3.5M–28.4M parameters per backbone/task pair, and they do not transfer between backbones).

  • ryoji-info/PsiLM-bridges — bridge checkpoints (safetensors + config.json) for every backbone/task pair in the table above, plus the Stage-1 Bicameral reproduction.
  • ryoji-info/PsiLM-physics — the frozen physics hemispheres: fno_burgers_singlemode, fno_burgers_multimode (70K-param FNOs) and dpot_tiny_fisher2d_finetuned (DPOT-Tiny, 7.5M, from hzk17/DPOT).
  • ryoji-info/Gemma-4-12B-PsiLM — the standalone release: Gemma 4 12B-4bit bridges, the FNO and a one-file inference script, for running the coupled model without this repository.
  • ryoji-info/Qwen3.5-9B-PsiLM — the same for Qwen3.5 9B, and the only place its backbone lives: the NVFP4 text tower rebuilt from Ollama sits at the repo's root, the bridges, the FNO and the script beside it.
  • Paper — paper/psilm.pdf (27 pages; Section 9 covers scaling, the guard-rail, Gemma, the leaky-gate sweep with its content control, and the Qwen3.5 9B campaign).

The first three repositories are public; the Qwen3.5 release is private until its maintainer flips it.

Quickstart

Apple Silicon, 24 GB unified memory is the reference platform (everything here ran on one M2). Python ≥ 3.11.

git clone https://github.com/ryoji-info/PsiLM && cd PsiLM
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[stage1]"            # mlx, mlx-lm, numpy, torch, transformers (pyproject.toml)
pip install datasets huggingface_hub  # GSM8K/MMLU guard-rail benchmarks and Hub downloads

Tested with mlx 0.32.2, mlx-lm 0.31.3, transformers 5.16.1 and torch 2.13.0; the Gemma 4 and Qwen3.5 loaders go through mlx-lm internals (qwen3_5.py shipped in 0.31.3, and MLX 0.32 is the first release that reads NVFP4 natively), so pin mlx-lm==0.31.3 if a newer release breaks either.

The Qwen3.5 backbone is not a Hugging Face download. It is Ollama's qwen3.5:9b-mlx release (NVFP4, Apache-2.0) reassembled into a directory mlx-lm loads — every wide projection stays in NVFP4 at group size 16, the vision tower is dropped:

ollama pull qwen3.5:9b-mlx
python eval/ollama_to_mlx.py qwen3.5:9b-mlx --out ~/Documents/huggingface/qwen3.5-9b-mlx

Pass that directory as both --model and --hf-tokenizer wherever the commands below name a backbone. No Hub checkpoint is bit-compatible with it (the Hub's NVFP4 builds also quantize the embeddings, the output head and the recurrence's gate projections, which this one keeps in bf16), so bridges trained against it expect exactly this conversion — which is why the same weights are republished at the root of ryoji-info/Qwen3.5-9B-PsiLM, next to the bridges; that repo id loads through mlx-lm like any other.

Run inference with the standalone Gemma-4-12B-PsiLM release. Bridge weights are not in this repository — nothing large is (see License and the blanket rule in .gitignore) — so fetch them from the Hugging Face repo first; the backbone itself downloads on first use:

hf download ryoji-info/Gemma-4-12B-PsiLM --local-dir release/gemma-4-12b-psilm
cd release/gemma-4-12b-psilm
python psilm_infer.py --a 1.28 --phi 0.5 --x0 0.76

It prints three results: the PsiLM answer, the backbone-alone answer, and the physics model's own value of u(x0), so you can see the coupled model verbalize what the operator computed. The pip install -e above makes the psilm package importable; without it, set PSILM_REPO=/path/to/PsiLM. The same checkpoint evaluates on the 60 held-out questions from the repository root — this one reads results/stage2_gemma12b/bridges.npz, which is likewise not in the repository, so either train it with the recipe below or place the downloaded bridges.safetensors there converted to .npz:

python eval/mlx_stage2_eval.py --model mlx-community/gemma-4-12B-it-4bit \
  --hf-tokenizer mlx-community/gemma-4-12B-it-4bit --tag _gemma12b --n 60 --out final_eval_repro.json

(--out keeps the committed final_eval.json intact; the new file lands in results/stage2_gemma12b/).

Reproduce a training run (Gemma 4 12B, batch 4, 12 s/step at a 13 GB peak; the FNO results/stage2/fno.pt and the no-harm prompts data/noharm_gemma_all.json are committed):

python eval/mlx_stage2_train.py --model mlx-community/gemma-4-12B-it-4bit \
  --hf-tokenizer mlx-community/gemma-4-12B-it-4bit --tag _gemma12b --steps 500 --batch 4 --lr 3e-4 \
  --readout-only 2000 --readout-norm dim --calib-n 32 --channel value --inj-cap 0.2 --gate-bias 0.0 \
  --lam-x0 1.0 --clip module --detach-x0 --eval-n 48 --fresh

Repeat without --fresh until step 5,500 (11 chunks of 500 in all: 2,000 readout-only steps, then 3,500 coupled), then three chunks at --lr 1e-4 with --noharm-data data/noharm_gemma_all.json --noharm-every 2 --noharm-gate-only 1 --lam-gate 1.0 for the selective gate (step 7,000 is the committed checkpoint). The scripts that produced the released checkpoint are results/gemma12b/resume_recipe.sh (the coupled chunks, after run_recipe.sh opened the run) and noharm_recipe.sh (the selective gate); the Qwen3-8B and 0.5B commands are in the technical notes, and the Qwen3.5 9B campaign is results/qwen35/phaseA_recipe.sh (readout warm-up) followed by results/qwen35/coupled_recipe.sh (negatives, coupled phase, selective gate), with its coupling-depth probes in results/qwen35/probes.txt.

How it works

Forward bridge (language → physics). The prompt runs through the frozen backbone up to layer l_fwd. A learned query attention-pools the prompt's hidden states and a small head regresses (a, sin φ, cos φ) from the pooled vector; the queried position x0 is read separately, by averaging the hidden states over x0's token span — the span is supplied by the QA builder, not learned — and turning that vector into a value with a 100-bin classifier (softmax expectation). On Gemma and Qwen3.5 the hidden states are first standardized per dimension with statistics from a 32-prompt calibration pass, because Gemma's massive-activation dimensions otherwise swamp the digit signal (Qwen3.5 simply keeps the option on). The staged forward drives the backbone's own decoder layers one at a time, whatever their kind: on Qwen3.5, 24 of the 32 layers are Gated DeltaNet linear attention carrying a recurrent state rather than a KV cache, and the bridges neither know nor care — they pool a residual stream and add to one.

Physics model. A 70K-parameter Fourier neural operator, pretrained to solve 1D viscous Burgers to 0.28% relative error and then frozen, maps the reconstructed initial condition to the field at t = 0.5 (DPOT-Tiny plays the same role for 2D Fisher-KPP).

Reverse channel (physics → language). The field is sampled at x0, the single value is expanded through Fourier features into eight soft tokens, and a cross-attention block at layer l_rev adds them to the residual stream behind a sigmoid gate, with the injection capped at 20% of the stream.

Gate and the no-harm arm. The gate is an MLP on the residual stream, trained on physics questions (answer cross-entropy plus deep supervision on the readouts and on u(x0)) and on non-physics prompts — GSM8K and MMLU items paired with the backbone's own continuation — where only the gate receives gradient and the target is "change nothing". That second arm is what makes it selective.

Two design rules the 8B campaign taught, each after a failed run: the channel must be narrow — carry only the queried value; a channel carrying the whole latent field was shortcut to per-trajectory constants (17% → 98.3% after the swap) — and a gate is only a gate if it is trained on prompts where it must close; trained on physics alone it sat open on every input.

Is the answer really coming through the channel?

The obvious worry about a system like this is that the language model isn't reading the physics at all — that it has learned a reply template, or picked up a correlate from the prompt, and the channel is decoration. Two controls answer that, and the second one is decisive.

Zero the injection and run everything else — readout, FNO, value tokens, gate — and the physics result disappears (0% for Qwen3-8B; 10% for Gemma and for Qwen3.5, whose zeroed replies all give 0.5, a number the question contains, which lies within ±0.05 of the answer on 10 of the 100). So the injection is load-bearing.

Corrupt only the number. Feed the value encoder another question's answer, at matched magnitude, leaving the prompt, the readout, the gate, reply length and parsing untouched. The frozen model then reports the corrupted value:

the spoken answer sits distance within ±0.05
from the injected (wrong) value 0.0121 99 / 100
from the true answer 0.4098 9 / 100
gold -0.095 | readout computed -0.103 | injected -0.546 | model said -0.550
gold +0.328 | readout computed +0.286 | injected -0.478 | model said -0.490

Accuracy falls from 98% to 9% while the KL to the base model doesn't move (0.222 either way) — the output distribution travels exactly as far, just to a different number. The model is reading the channel.

The same control run on non-physics prompts changes nothing (GSM8K 0.88 both ways, MMLU 0.66 both ways, p = 1.00), which separates the two things the channel does: off-task its effects come from its presence, on-task from its content. Details and the full ε sweep behind it are in docs/technical-notes.md and §9.7 of the paper.

Read more

  • docs/technical-notes.md — Stage 0 → 2d narratives, the multi-mode generalization and loop-coupling studies, the 2D DPOT result, the eight-run 8B failure analysis, the Gemma calibration fix, the full guard-rail tables, the leaky-gate ε sweep on both backbones with its shuffled-value content control, the Qwen3.5 9B integration (Ollama conversion, the two adapter accommodations, the coupling-depth cliff), and every reproduce command.
  • paper/psilm.pdf — PsiLM: Coupling Frozen Language and Physics Models through Trainable Latent Bridges.
  • Nearest prior work: the Bicameral Model (arXiv:2605.11167), the Global Latent Workspace line (shimmer), CALM (arXiv:2401.02412).

Beyond physics: what would and would not transfer

PsiLM is a recipe for coupling a frozen language model to a frozen quantitative model, and nothing in the recipe is specific to PDEs. The language side reads a fixed set of quantities out of the text, the frozen model computes from them, and a selective gate returns one value into the language model's reasoning only on questions that model can answer. A calibrated market model, an event-probability model, or an agent-based simulator in the physics model's seat would be the same architecture, and the reason to want it is the same: a language model's forecast grounded in a model that can be validated separately, with a gate that stays shut when the model does not apply.

That is the extent of what this repository supports. Everything that made the physics results possible is absent in markets: every training example here has an exact oracle, the targets are deterministic, and the test distribution matches the training one. Prediction markets and asset prices have no oracle, stochastic targets, and constant distribution shift, so the training signal would be noisy, calibration would have to be earned by backtesting rather than assumed, and the guard-rail question ("does the gate open only when it should?") would become the central problem rather than a final check. No experiment in this repository touches financial data, and nothing here is evidence of forecasting performance or a basis for investment decisions. If someone builds a market model into PsiLM, the first result worth reporting is the equivalent of this repository's guard-rail table, not a return.

Roadmap

Stage What Status
0 Loop-level coupling (LLM extracts → simulator → result back in the prompt); eval harness and baseline arms done
1 Bicameral Model reproduction at 0.5B: two frozen twins, trainable gated interface done
2 PsiLM proper: frozen LLM ⇄ frozen FNO through latent bridges done — 100%
2b–2d Multi-mode ICs, held-out families, loop coupling, 2D physics via DPOT-Tiny done
3 MLX port, 4-bit backbones, Qwen3-1.7B and Qwen3-8B done
4 Guard-rail benchmarks (GSM8K/MMLU) and the selective gate done
5 Second model family, Gemma 4 12B, single-pass recipe transfer done — 96.7%
5b Gemma 4 12B on the multi-mode task (in-distribution 100%; generalization families open) done
5c Span readout with mode-shared heads, for the two generalization families validated at 0.5B, 12B run not started
5d Leaky-gate ε sweep on both backbones, and the shuffled-value content control done
5e Third family, Qwen3.5 9B: a mostly recurrent (Gated DeltaNet) decoder, NVFP4, rebuilt from an Ollama release; readout warm-up the best of any backbone (CE 0.45, 91% exact bins, on twice the warm-up samples) done — 100% held-out (oracle 98.3%); guard-rail: physics 99%, GSM8K 83% = backbone item for item, MMLU 66% vs 64%, BoolQ 89% vs 90%
6 Loop coupling at 8B; Mac app (27B feasibility measured: inference-only at 24 GB, 39 GB training peak) planned

Support

PsiLM is independent research, run on a single Apple M2. If it is useful to you, you can support the work at ko-fi.com/ryojifurui.

Citation

@misc{furui2026psilm,
  title  = {PsiLM: Coupling Frozen Language and Physics Models through Trainable Latent Bridges},
  author = {Furui, Ryoji},
  year   = {2026},
  month  = sep,
  url    = {https://github.com/ryoji-info/PsiLM},
  note   = {Research generated by Claude (Anthropic) under the author's direction}
}

License

Code, bridges and this repository's own data: Apache-2.0. DPOT-Tiny derives from hzk17/DPOT (Apache-2.0). Claude's constitution, the text the constitution partner model was fine-tuned on, is released by Anthropic under CC0 1.0. The evaluation items redistributed verbatim in data/ and in the task caches results/bench/tasks_*.json come from third-party datasets under their own licences — HH-RLHF (MIT), GSM8K (MIT), MMLU (MIT) and BoolQ (CC BY-SA 3.0, whose passages are not covered by this repository's Apache-2.0 grant) — each named with its source in DATA_LICENSES.md.

Two notes on the record. Every text field in results/bench/*.rows.jsonl and results/stage2c_*/*.jsonl is raw model output kept for scoring: the names, phone numbers, addresses and hotlines in it are frequently fabricated by the model, and none of it should be read as factual (results/bench/README.md). The chain scripts and run metadata under results/ are a historical record and carry the author's local paths as they ran.

AI generation disclosure: this research was designed, implemented, run and written up by Claude (Anthropic) under the direction and review of Ryoji Furui; see the paper's AI Generation Disclosure (Section 12).

Footnotes

  1. Forced-answer protocol (n=60, results/stage2_mlx8b9/final_eval_baseline_forced.json): 0/60, every item answered, a systematic overestimate (predictions mean +0.79 against truths mean −0.03; MAE 0.89). The 6.7% previously quoted was the parser reading numbers out of unfinished derivations. Retried under a deliberately generous protocol — four solved examples, 1,536 tokens, thinking enabled (final_eval_baseline_strong.json): 2/60 = 3.3%, with the answers now echoing the examples' values (−0.21, −0.47, −0.23 repeated); the best single constant would score 13.3%. ↩

  2. Forced-answer protocol (n=60, results/stage2_gemma12b/final_eval_baseline_forced.json): Gemma spends the whole 768-token budget deriving and never reaches an "Answer:" line on its own (0% in final_eval.json), so its continuation is started with "Answer:" and it commits to a number. It then scores 6.7% (MAE 0.42) — four near-constant guesses (0.41 / 0.51 / 0.54 / 0.11) landing inside the tolerance; the best single constant would score 13.3%. ↩

  3. Multi-mode families (n=48 each, results/stage2b_gemma12b_2b/final_eval.json): in-distribution 100% (MAE 0.009), the held-out mode combination 25.0% (MAE 0.123) and amplitude extrapolation 52.1% (MAE 0.086), against a backbone of 20.8% / 16.7% / 10.4% and an oracle of 100% on all three. The FNO is exact on every family (MAE 0.0008), so the two generalization gaps are the readout's: 19 of 48 combination answers match a single-mode field value, and on extrapolation the implied amplitude is below the true one for 71% of items (median ratio 0.68, inside mode 1's training range). A span readout with mode-shared heads was validated at 0.5B for exactly this (combination 0.61 → 0.99 in a teacher-forced probe, results/readout_transfer/); the 12B run has not been started. ↩

  4. PsiLM is above the oracle here, the only such row in this table. The oracle arm has to copy a number out of the prompt and occasionally mis-rounds it (MAE 0.021); the bridge reads the field value exactly and the answer never passes through text (MAE 0.0096). The 0.5B backbone reached 95.0% on the same task. ↩

  5. Both text arms were forced to answer after deriving for the whole 768-token budget on every item. The oracle's one miss is a forced reply whose parser took the phase out of the derivation (5.61 for a true −0.202), which is also why its MAE is 0.100; PsiLM's largest error on the sixty items is 0.047. Scored on the training numerics (the differentiable recurrent scan rather than the Metal kernel) PsiLM is again 100%, with 58 of the 60 answers identical (final_eval_ops.json). Full tables, per-arm protocols and the earlier 0.5B results (multi-mode generalization, loop coupling, 2D with DPOT-Tiny at 95.0%) are in docs/technical-notes.md. ↩ ↩2

  6. The same files carry MAE: PsiLM 0.014 / 0.022 / 0.021 / 0.017 / 0.015 for the five backbones, oracle 0.003 / 0.021 / 0.003 / 0.007 / 0.100. ↩

About

ΨLM (PsiLM): a frozen language model and a frozen physics model running as one system, coupled through trainable latent bridges — two hemispheres, one output. No text at the interface. Runs on consumer Apple Silicon.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages