Start here: what is this, really?
Imagine two friends each keep a notebook about what causes what.
Ana writes:
stress causes bad sleep — I'm 90% sure.
Ben writes:
bad sleep causes stress — I'm 80% sure.
Notice that neither wrote "yes" or "no". They wrote how sure they are — a degree rather than a fact. That number between 0 and 1 is the whole point of this page. Knowledge graphs built by machines — scraping papers, extracting claims from text, merging databases — almost never produce certainty. They produce confidence, which is what fuzzy logic exists to reason with. So the interesting question is not "is this graph broken", it is "where do these two graphs disagree, and how badly?"
Put the notebooks side by side. Some contradictions are easy: they sit on the same line. Ana says the arrow points one way, Ben says it points the other, and both are confident. Finding those is just comparing numbers, line by line.
The hard contradictions hide behind a chain. Ana never wrote "stress causes mistakes" — but she wrote stress → bad sleep, bad sleep → poor focus, and poor focus → mistakes. Follow her arrows and she implies it. If Ben wrote "mistakes cause stress", the two notebooks disagree about which way the whole loop runs — and you cannot see that on any single line. You have to walk the chains — which is the transitive closure of everything said.
Walking every chain from every starting point is an enormous amount of extremely simple arithmetic. That is exactly what a graphics card is for: instead of one worker checking chains one at a time, you get thousands of tiny workers, each checking one.
Here's the catch, and it is the real result of this page. The workers live across the room. Before they can start, you must carry every notebook page over to them, and carry the answer back. If there are twenty pages, the walk takes longer than just checking them yourself. If there are twenty million, the walk is absolutely worth it.
That trade — the walk versus the work — is what everything below measures. It is also why we keep quoting two different numbers: 17× counts the walk and the work together, while 144× counts only the work. The gap between them is the walking.
What the data actually looks like
Nothing here is hand-waving about graphs in the abstract. A graph is a pile of subject → predicate → object statements, and in our case each statement carries a confidence. rete stores that with RDF-star, where a triple can itself be the subject of another triple:
# Ana's notebook — graph G1 << :stress :causes :poor_sleep >> :confidence 0.9 . << :poor_sleep :causes :low_focus >> :confidence 0.8 . << :low_focus :causes :mistakes >> :confidence 0.7 . # Ben's notebook — graph G2 << :poor_sleep :causes :stress >> :confidence 0.8 . << :mistakes :causes :stress >> :confidence 0.75 . << :alertness :causes :coffee >> :confidence 0.2 .
A GPU cannot read any of that. It has no idea what a URL is. So the last step before the GPU is to flatten the graph into three plain number arrays — which is easy, because rete already stores every term as an integer id internally rather than as a string:
// the same six statements, as the GPU sees them src [ 0, 1, 2 | 1, 3, 4 ] u32 — dictionary ids, never strings dst [ 1, 2, 3 | 0, 0, 5 ] u32 confidence [ .9, .8, .7 | .8, .75, .2 ] f32 — the degree of belief └── Ana ─┘ └─── Ben ───┘
Important honesty note. The six statements above are hand-written so you can follow them. The benchmark further down does not use them — it generates synthetic graphs with millions of random edges, because it is measuring speed, not discovering real facts about sleep. The small editable example in the next section is where you can actually see the reasoning happen.
The logical questions you can ask
Once every claim carries a source and a confidence, a surprising amount of bad reasoning becomes a shape in the graph — findable by arithmetic, with no language model reading anything. Four questions, and each one is either a same-line comparison or a walk along chains. That is not a coincidence: those are precisely the two kernels benchmarked further down.
Do they disagree about direction? chain
One source says the arrow runs this way, another implies the opposite — possibly only after several steps. A two-step round trip is a plain disagreement; a longer one is a circulus in probando, where a claim ends up supporting itself.
Does one cause it and another prevent it? same line
The bluntest contradiction there is: the same pair of things, the same direction, opposite sign. No chain-following needed — just compare two numbers on the same line.
Is the claim stronger than its support? chain
Someone asserts a big direct effect, but the only route between the two runs through a chain of weak steps. A chain is only as strong as its weakest link, so the confident conclusion outruns its evidence — a slippery slope.
Is something else causing both? chain
Two things move together and someone declares one caused the other — but a third thing causes them both. The classic cum hoc ergo propter hoc: mistaking a shared cause for a direct one.
Following arrows uses min along the path (weakest link) and keeps the max
over all paths (best available route). That is the max-min rule — the same thing as the
widest path problem — and it is exactly what the
GPU kernel computes — on millions of edges instead of a dozen. Three knobs decide what counts:
τ, how strong something must be before it is reported; k, how many arrows deep to
follow; and the t-norm, how fuzzy logic says "both at once" —
cautious min(a,b)
or harsher a × b.
Try it on a real argument
The default text is the budget debate from the fallacy-annotation experiment, rewritten as causal claims with who-said-what attached. That page finds fallacies by having an agent read the speech; this one finds them by computing over the claims. Edit anything below and every finding recomputes live.
One claim per line: Who: thing causes thing 0.8, or
prevents for the opposite. -> and -| work too.
Keep it
Your text is kept in this browser as you type. The button below builds a link with the whole argument in it — settings included — so you can send it to whoever you were arguing with.
Your argument, drawn
Every claim as an arrow, coloured by who said it. Solid means causes, dashed with a bar means prevents. Hover an arrow for the claim; click any finding below to light up exactly the arrows involved in it.
Watch belief spread — this is literally the GPU kernel
Pick something to believe completely, then press the same button the shader presses: each round, every node takes the best chain reaching it, one arrow longer than before. A chain is only as strong as its weakest link, so values can only fall as they travel. The benchmark runs exactly this loop k times over millions of arrows without the data ever leaving the card. Formally it is a sparse matrix multiply in the (max, min) semiring — a GraphBLAS primitive.
So who told the truth?
The honest answer is that this table cannot tell you, and it is worth being precise about why. Being implicated in a contradiction does not make a source wrong — it can equally mean that source was the only one willing to make a strong claim, and it is the set of claims that is inconsistent, not any single one. What the numbers below actually measure is who keeps turning up at the scene, and whose claims survive independently of them. Treat it as a place to start reading, not a verdict.
Use this on an argument of your own
The point of writing a disagreement down like this is not to win it. It is that once the claims are separated from the rhetoric, the structure becomes checkable — and quite often the thing people were actually disagreeing about turns out to be a single arrow.
- Write one claim per line, and name who made it. Not who you agree with — who said it. Attribution is what makes the last table possible, and what stops the exercise turning into your own opinions wearing a graph costume.
- Use nouns for the things, not sentences.
budget_cut, not "the decision we took in January to reduce the maintenance budget". If you cannot name it in one noun, it is probably two claims. - Pick
causesorprevents. If a claim is neither — "this is unfair", "we cannot afford it" — it is a value or a constraint, not a causal claim, and it belongs in the discussion rather than in the graph. - Put a number on how sure that person sounded. This is the step people resist, and it is the one that does the work. 0.9 is "they staked their credibility on it", 0.5 is "they floated it". Being forced to write 0.5 next to something is often the whole insight.
- Then move the knobs. If a finding disappears the moment you nudge τ, it was never solid. If a contradiction survives every setting you try, it is real and someone has to give ground.
What comes back is not a verdict, and the page never pretends otherwise. It tells you where to look: the loop nobody noticed they were arguing in, the leap that outran its evidence, the third factor that explains both things at once. Those are the places a conversation is actually stuck.
When it outgrows a text box
Download as RDF-star gives you the same claims as a real graph — each statement carrying its confidence and its speaker as annotations on the triple itself:
# the download, abbreviated :budget_cut :causes :breakdowns . << :budget_cut :causes :breakdowns >> :confidence 0.9 ; :statedBy :Bruno .
That file is ordinary Turtle, so rete build argument.ttl -o argument.rete turns it
into a queryable graph you can hit with SPARQL, federate against other sources, or hand to an
agent — see getting started and
SPARQL support. The toy in this page and the engine the rest of these
docs describe are the same data model; only the scale changes.
The algebra, one step at a time
Everything on this page rests on one operation, repeated. Here it is slowly, first as a walk through a graph, then as the matrix multiply it really is, and finally as the thing a GPU can do to every row at once. Press play, or step through it yourself.
Why this is hand-drawn and not Manim. Manim is Python: it wants FFmpeg, Cairo and LaTeX, and it renders a video. There are real browser answers now — a TypeScript port and the Manim-inspired Motion Canvas — but each is either a WebGL bundle or a build tool that emits a player. This page is a single hand-written file with no external scripts at all, which is why it still works offline, cannot be broken by someone else's CDN, and lets you step the animation rather than watch it. The equations above are native MathML — real typesetting, no library.
Now do it at scale — on your GPU
Six statements is a toy. The real question is what happens at millions of edges, where you would genuinely want to reach for a graphics card. The benchmark runs the same two questions as WebGPU compute shaders and races them against a single CPU core — which is the honest comparison, because rete's engine is single-threaded in the browser too.
Reference run: NVIDIA Blackwell, Chrome 150, versus a single-threaded JS baseline.
Your hardware
Detected on load. The last two rows are the reason people ask about VRAM in the first place.
- status
- detecting…
The crossover
Speedup over a single CPU core, against problem size. Both axes are logarithmic. Below the dashed line the GPU is losing — that is the walk costing more than the work. The two kernels run on the same hardware and the same data; the only difference is how much arithmetic each does per byte it moves.
Where the time actually goes
Each bar is one measurement, split into the three phases of a GPU round trip and normalised to 100%; the number on the right is the wall-clock total. This is the walking-versus-working picture: at the large sizes almost everything is upload, and the actual arithmetic is nearly free.
So what actually happened?
Every number
Median of repeated runs. agree is the correctness gate: the GPU answer is compared against the CPU answer on every configuration — identical counts for kernel A, exact float match for kernel B. A speedup with a failed gate would be meaningless, so it is reported alongside.
Kernel A — same-line contradictions
Kernel B — chain contradictions
The shader, for the curious
One closure step — "let every node take the best chain reaching it, one arrow longer than last time". It runs k times with the graph never leaving the GPU, which is why kernel B pays the walk once and then works for free.
@compute @workgroup_size(256) fn main(@builtin(global_invocation_id) gid: vec3<u32>) { var i: u32 = gid.x; loop { if (i >= params.n) { break; } var best: f32 = x[i]; // what I already believe let s = rowPtr[i]; let e = rowPtr[i + 1u]; for (var k: u32 = s; k < e; k = k + 1u) { // a chain is only as strong as its weakest link → min // and we keep the best chain of all of them → max best = max(best, min(val[k], x[colIdx[k]])); } y[i] = best; i = i + params.stride; // grid-stride: cover any size } }
What this means for rete
- Stage once, run many. Upload measured ~2.2–2.4 GB/s and dominates every large configuration. One-shot checks waste the GPU; a coherence session that uploads the edge slab once and then sweeps τ, k and several conflict rules against the resident copy approaches the compute-only numbers.
- Pick the operation, not the hardware. A 2× ceiling and a 17× win came from the same GPU on the same data. Arithmetic intensity decided it.
- This is not an argument for GPU SPARQL. Remote
.retequeries are bound by HTTP range round trips, and local ones finish in single-digit milliseconds — below the latency floor of a GPU round trip. Whole-graph coherence is the opposite shape: batch, seconds-scale, one small answer at the end. - On memory: this adapter caps a single buffer below the 4 GiB wasm32 heap, so any capacity win comes from using many buffers, not one large one — and rete's lazy tiles and chunked dictionary already keep 17 GB files queryable inside that heap.
Caveats — do not over-read these numbers
- The baseline is JavaScript, not wasm. Wasm would likely be 1.5–2× faster, so discount accordingly; kernel B still lands around 8–9× end-to-end.
- The benchmark graph is uniform random. Real causal and citation graphs are power-law, and one-thread-per-row will suffer load imbalance on them. This needs re-measuring on a skewed degree distribution before anyone trusts it on real data.
- The reference GPU is a high-end discrete card. Integrated GPUs will be far less dramatic — which is exactly what the run button is for.
- The cost of getting data out of a
.reteis not measured here. Decoding tiles and materialising the flat arrays is plausibly larger than both the CPU and GPU numbers on this page. That is the next thing to measure, and it could sink the whole idea. - Real causal systems genuinely contain feedback loops, so "both directions asserted" is not automatically an error — feedback is real. That is precisely why this reports a degree and a threshold rather than a verdict.
Where these ideas come from
Almost nothing on this page is new. It is a handful of well-studied ideas from three different fields that happen to meet: how to reason with uncertainty, how to do that reasoning as matrix algebra, and how to run matrix algebra on a graphics card in a browser tab.
Reasoning with degrees
- Fuzzy logic and fuzzy sets — truth as a degree in [0,1] rather than yes/no. The reason every claim here carries a number.
- T-norm — the family of "both at once" operators. The τ knob picks between the two most common ones: Gödel (min) and product.
- Composition of relations — chaining "a relates to b" with "b relates to c", which is what following arrows is.
- Defeasible reasoning — conclusions that can be withdrawn when better information arrives. Why this page reports a degree instead of a verdict.
The fallacies it looks for
- Circular reasoning and begging the question — the loop check, when the cycle is three steps or more.
- Slippery slope — a confident conclusion resting on a chain of weak steps.
- Correlation does not imply causation and confounding — the third-thing-causes-both check.
- Post hoc ergo propter hoc — the one this page cannot catch, because it has no notion of time.
- List of fallacies — most of the rest are rhetorical, not structural, which is what the annotation experiment handles instead (ad hominem, straw man).
- Argument mapping — the older, broader tradition of drawing arguments to inspect them.
Chains as algebra
- Widest path problem — the sharpest single reference here. "The strongest chain is only as strong as its weakest link" is exactly the bottleneck/maximum-capacity path, and max-min composition is how you solve it.
- Semiring — swap (+, ×) for (max, min) and the same matrix multiply answers a completely different question.
- Transitive closure and the Floyd–Warshall algorithm — what "follow the arrows k steps" converges to.
- GraphBLAS (overview) — the standard that says graph algorithms *are* sparse linear algebra. Kernel B is one of its primitives.
- Sparse matrices — the CSR layout the benchmark uploads.
Doing it on the GPU
- WebGPU (MDN, overview) and WGSL — the API and the shading language the kernels are written in.
- Chrome's buffer limits — where the 2 GiB-per-buffer ceiling on this page's hardware comes from.
- The
mapAsyncreadback cost — the long-standing complaint that reading results back is slow. It was cheaper than expected here, and the measurement is on this page. - Is Memory64 actually worth using? — the 4 GiB wasm ceiling, the 16 GB alternative, and its 10–100% bounds-checking cost.
GPUs and RDF, before this
- MAGiQ — SPARQL compiled to matrix algebra, run on GPUs. The clearest statement of the idea kernel B borrows.
- Wukong+G — GPU-assisted RDF querying, and an honest account of how much work goes into hiding the host-to-GPU transfer. The same transfer that dominates this page.
- TripleID-Q — brute-force scans over dictionary-encoded triples on a GPU.
- A survey of RDF stores & SPARQL engines — where all of the above sits in the wider landscape.
The rest of rete
- Fallacy graphs from a conversation — the companion experiment: an agent reads speech and annotates rhetorical fallacies. This page computes structural ones instead.
- Reasoning & coherence — the OWL RL / RDFS reasoner and the incoherence checks that motivated the benchmark.
- SPARQL support and
getting started — for when your exported
argument.ttlbecomes a real graph. - RDF 1.2 concepts — the standard
behind the
<< … >>syntax in the export. - Benchmarks and HTTP range requests — why remote queries are bound by round trips rather than compute.