~/satyajit

Ripple-65B Preview: a dense 65B from scratch, read from its headers and run on a DGX Spark budget

mdjsonmcp

2026-11-09 · 16 min · attention · kv-cache · speculative-decoding · agentic-coding · quantization · inference · tokenization

Why read this

Notabletop 60%

Ripple-65B counted from its headers, its value-residual weights read from the checkpoint, and the DGX Spark claim turned into bytes per token.

  • Original analysis
  • Widely used
  • Concrete numbers to act on

LLM architectureNeeds a workstation GPUApache-2.0Practitioner model

How this was scored
Is it new?
1 of 3: An incremental tweak
Can I trust it?
2 of 3: Measures key facts from files, code or configs
Can I run it?
2 of 3: Open code or weights with real limits
Will I understand it?
2 of 3: Mechanism from first principles with figures
Can I act on it?
2 of 3: A concrete recipe, numbers or comparison
Will it last?
1 of 3: Relevant for months
Does it affect many?
2 of 3: A widely used model, tool or lab release
Only here?
2 of 3: A teardown or measurement few others did

Score 62 of 100, ranked 369 of 701 rated articles. Each question is answered 0–3 by hand, and a 3 is rare. How articles are scored

On 4 November Ricursive Intelligence posted: "Today we release Ripple-65B Preview, our 65B dense LLM we pretrained from scratch." The same post claims "a new state-of-the-art for U.S. open-weight models in agentic coding tasks, surpassing significantly larger models including Muse Spark Open-816B, Beam-501B, Inkling-975B, and Nemotron 3 Ultra-550B." An hour later a widely shared repost added that it "even runs locally on my DGX Spark".

Every MoE I have covered this autumn was a statement about sparsity. This one is the opposite bet: every parameter runs for every token. That makes the hardware question simple arithmetic, and the architecture is unusual enough to be worth reading out of the files. Three things first:

Throughout, measured means I computed it from a file or a run. Reported means the publisher's figure, not re-run. Reasoned means my arithmetic on the other two.

ricursiveAI/Ripple-65B-Preview@da94cc0 · snapshot 2026-11-10
announced
65B dense
measured
64,782,689,360
parameters
64.78B
repo size
129.57 GB
architecture
RicursiveForCausalLM
license
apache-2.0
safetensors
16 shards
largest file
8.42 GB
files
31
downloads
5.8K
likes
463
parameters by dtype
BF1664.78B

BF16 instruction-tuned checkpoint. Architecture read from the 16 safetensors headers, config.json and modeling_ricursive.py at da94cc0. Licence: Apache 2.0.

repo last modified 2026-11-06

What is public

Four repositories under ricursiveAI, plus a launch blog with no paper (measured, file sizes from the Hugging Face API on 10 November):

RepositoryWhatSize
Ripple-65B-Previewinstruction-tuned, BF16, 16 shards129.57 GB
Ripple-65B-Preview-NVFP4same model, MLPs in NVFP4, attention in BF1657.21 GB
Ripple-65B-Base-Previewpretrain-only checkpoint, BF16129.57 GB
Ripple-65B-Preview-DFlash24.04B speculative drafter, BF168.08 GB

The BF16 repository also carries modeling_ricursive.py (a transformers implementation, trust_remote_code), an SGLang port under inference/server/, and chat_template.jinja. The NVFP4 repository adds inference/local/spark.py and a notebook titled "Ripple on DGX Spark". The LICENSE file is the stock Apache 2.0 text. There is no training code and no GitHub repository.

Within six days the community had posted more than fifty derivatives. The most downloaded is a llama.cpp GGUF set, which says Ripple support was merged into llama.cpp as release b12167. I use two of its files below for Q8 and Q4 sizes.

The architecture, read from the headers

A safetensors file opens with an 8-byte length and a JSON header naming every tensor's dtype, shape and offset. Two range requests per shard give the whole model without downloading 130 GB. The script is twenty lines of standard-library Python.

Here is where the 64.78B parameters sit (measured, sums of tensor shapes over 80 layers):

GroupShape per layerParametersShare
MLP gate, up, down3 x 5,120 x 40,96050,331,648,00077.7%
attention q, o and output gate3 x 5,120 x 10,24012,582,912,00019.4%
attention k, v2 x 5,120 x 1,024838,860,8001.3%
input embedding and output head2 x 100,352 x 5,1201,027,604,4801.6%
norms, Q/K norms, 80 scalars1,664,080under 0.01%

Read against config.json and the modeling code, that is:

Four sliding layers, then one global

layer_types is four sliding_attention layers followed by one full_attention, repeated sixteen times. The sliding layers see the last 1,024 tokens and use RoPE with theta 10,000. The 16 global layers see everything and have no positional encoding at all: the code builds no rotary embedding for them. max_position_embeddings is 2,097,152.

This is the same trick as Muse Spark Open, with a harder ratio (4:1 instead of 3:1) and a shorter window (1,024 instead of 2,048). For the cache it means (measured from the config, reasoned into bytes):

So the claimed 1M-token context costs 69 GB of BF16 cache, more than the NVFP4 weights. On a Spark the official recipe caps it at 131,072 (--context-length 131072 in spark.py).

Value residuals, read out of the checkpoint

The last addition in the blog's list is "value residuals". In the code it is three lines. Every layer has one learned scalar, value_res_lambda, and computes the input to its V projection as a blend:

vin=(1−s) e+s x,s=σ(λ)v_{\text{in}} = (1 - s)\, e + s\, x, \qquad s = \sigma(\lambda)

Here xx is the layer's normed input and ee is the token's own embedding from layer 0, scaled by 71.554. Q and K still read xx. So each layer can pull its values straight from the token identity, skipping 79 layers of mixing.

Those 80 scalars are in the checkpoint, 2 bytes each, so I range-read them (measured). One layer stands out: layer 6 has λ=−11.94\lambda = -11.94, so ss is zero to four decimals and its values come entirely from the embedding. Layers 14 to 39 mostly sit above λ=5\lambda = 5, where s>0.99s > 0.99, and ignore the embedding.

The raw weight understates the embedding's pull, because the two inputs are not the same size. The scaled embedding has an RMS of 3.08 (median over 48 sampled vocabulary rows, measured). The normed input has roughly the RMS of its norm weight, 0.14 to 0.46 by layer (measured). Weighting each side by its size gives an estimate of the embedding's share of the V input (reasoned: it ignores how the two vectors correlate).

The pattern is a U. The first ten layers take most of their values from the token embedding. The middle third ignores it. From layer 40 on, the embedding comes back: it carries more than half the V input in 34 of the 80 layers, and under a tenth in 28. Ricursive does not discuss this. My reading, which I have not tested, is that the late layers want a clean copy of "which token is this" for predicting what comes next, and the value path is a cheap place to get it.

Running the tokenizer and the chat template

The tokenizer is a byte-level BPE with 100,000 merges and 100,278 entries, the same cl100k-style vocabulary Olmo 3 uses, with ChatML markers and fill-in-the-middle tokens added (measured, tokenizers 0.23.3). The embedding has 100,352 rows, so 74 are padding. One of them is used: the DFlash2 drafter's mask_token_id is 100,278, the first spare row.

On my samples (measured): modeling_ricursive.py encodes at 4.44 characters per token, the model card's Markdown at 2.86, the Apache licence at 5.00. All five tokenizer probe strings from ripple_format.py, including digits, contractions, whitespace runs and mixed scripts, round-trip exactly.

I rendered chat_template.jinja with jinja2 3.1.6 and encoded the result:

Four things show up only when you run it (measured):

clear_thinking=True drops earlier reasoning from history; the default keeps it. The template has no thinking switch and no effort levels.

Does it fit a DGX Spark?

A DGX Spark has 128 GB of unified LPDDR5x and 273 GB/s of memory bandwidth. Single-stream decoding on a dense model reads every weight once per token, so the bandwidth is the speed limit. The batching piece covers why that limit moves with concurrency; here I stay at one stream.

The fit, first (measured file sizes, reasoned fit):

Then the speed. The bytes read per token are every weight except the embedding table, from which one row is gathered (measured for the official files, estimated for the GGUFs). Divide 273 GB/s by that and you have a ceiling no engine can beat without speculation (reasoned):

WeightsRead per tokenCeiling at 8K contextat 128K
BF16128.54 GB2.11 tok/s1.99 tok/s
Q8_0 GGUF68.29 GB3.95 tok/s3.54 tok/s
NVFP4 MLP + BF16 attention56.19 GB4.79 tok/s4.20 tok/s
Q4_K_M GGUF38.49 GB6.95 tok/s5.77 tok/s

The cache is part of the read: at 128K it adds 8.86 GB to every pass, which is why the right-hand column drops.

What speculation has to deliver

Neither Ricursive's card nor the launch posts give a Spark speed. Ricursive's Spark recipe turns on DFlash2 with 32 draft tokens, one request at a time. DFlash 2 drafts a whole block in one pass and has the target verify it in one pass, so each cycle reads the target, the 8.08 GB drafter and the 1.03 GB output head the drafter borrows: about 65.3 GB, or 4.13 cycles a second at 8K (reasoned).

The only measured Spark numbers I found are from a third-party server kit, rlaope/Ripple-65B-Spark-Server, on one GB10 at temperature 0 (reported): code 20 to 36 tok/s, average 26.5 over three prompts; prose about 9; and "plain decoding near ~5 tok/s". The plain figure sits right on my 4.8 ceiling. Its authors also found that cutting draft tokens from 32 to 16 helped, because "most steps accept far fewer than 32".

Divide their numbers by my cycle rate and you get a floor on how many tokens each verify pass keeps (reasoned). 26.5 tok/s needs at least 6.4 tokens a pass; 36 needs 8.7; prose at 9 needs 2.2. Those are floors, since no engine hits the bandwidth ceiling. So the Spark story is really a drafter story: code, which is repetitive and predictable, runs five times faster than the dense bandwidth allows, and prose barely doubles.

The datacenter number

The card also says the model reaches "250 tok/s on 2xB200 batch=8 prior to quantization or custom kernels" (reported). Two B200s give 16 TB/s, so BF16 weights cap one stream at 124.5 tok/s without speculation (reasoned). If 250 is per stream, it comes from the DFlash2 drafter that serve.py turns on by default; if it is the batch total, each stream gets about 31. The card does not say which.

The training claims

The model card's stage table (reported):

StageTokensStepsSequenceBatch
Pretrain12T877,5008K1408 to 3072
Midtrain200B8,00064K384
Long context6.5B3892M8
SFT183B7,274128K192
Long-context SFT13.5B8022M8
RLn/a700128K2048

Some of the rest is checkable and some is not:

The benchmark table

The card compares Ripple with four US open-weight models on 22 rows. Two caveats are printed in it: other labs' missing scores were filled in with "our own in-house eval flow" (in italics), and "Replicating benchmark scores has been challenging, particularly for modern agentic evals".

Counting the table (measured, from the card):

What survives is the size claim. A 65B dense model is in the same band as 500-975B MoEs on agentic coding, at 23B to 55B active parameters for them against 65B for Ripple. On active parameters per token the comparison is close to even; on memory it is not, and memory is what decides what fits on a desk.

What I ran, and what I did not

Ran (measured):

Did not run:

Should you run it?

If you have a 128 GB unified-memory box and your work is agentic coding, yes: it is Apache 2.0, the official NVFP4 recipe is one notebook, and code is exactly the workload where the drafter pays. Without the drafter, budget 4 to 5 tok/s. With it, expect about 9 on prose and 20 to 30 on code, per the only measurements so far. If you have a 64 GB machine, the 4-bit GGUFs fit with modest context; the ceiling there is set by your memory bandwidth, and the table above gives the bytes.

If what you want is the most capable open model per byte of memory, this is the first dense entrant in a while that makes the argument. If you want the most capable open model, the card's own table says Muse Spark Open, by 12 rows to 3.

Cite this article

For attribution, please use the following reference or BibTeX:

Satyajit Ghana, "Ripple-65B Preview: a dense 65B from scratch, read from its headers and run on a DGX Spark budget", ai.satyajit.si, November 2026.

bibtex
@misc{ghana2026ripple65b,
  author = {Satyajit Ghana},
  title  = {Ripple-65B Preview: a dense 65B from scratch, read from its headers and run on a DGX Spark budget},
  url    = {https://ai.satyajit.si/articles/ripple-65b},
  year   = {2026}
}
share