§ 03 · Matrix math
Matrices
Grids of numbers, and the two that every Pearl miner multiplies
On this page 6 sections
A matrixMatrix: A grid of numbers arranged in rows and columns. AI models store most of what they have learned as huge matrices. is a grid of numbers, arranged in rows and columns. That’s all.
Matrices matter to Pearl because they matter to AI. A language model stores what it has learned as huge matrices of numbers, and running it means multiplying matrices, billions of times. Pearl’s mining is exactly that multiplication. This group of pages explains the math behind it, starting with the grids themselves.
What does a matrix look like?
Here is a small one: the toy matrix A from Pearl’s worked mining example. It has 4 rows and 16 columns, filled with whole numbers from −4 to 4. Darker cells hold bigger numbers, positive or negative.
Its shape is 4 × 16: rows first, then columns. It has 4 × 16 = 64 entries. The entry in row i and column j is written Aij, so A0,2 = 4. (Programmers count from 0.)
A matrix with one row or one column is called a vector. A matrix with as many rows as columns is square. Pearl’s are usually rectangular.
What are Pearl’s matrices A and B?
A Pearl miner multiplies two matrices of its own choosing:
In Pearl’s official miner, the multiplication is a layer of a language model running inside the vLLM serving software:
- A holds the model’s activations: one row per token (a word piece), one column per feature. Mining only kicks in when a batch has at least 1,024 tokens.
- B holds the layer’s weights: k inputs by n outputs.
- C = A·B is the layer’s output, which the model needs anyway.
In practice the GPU kernel keeps the weights as an n × k grid and computes A times its transpose. The transpose Bᵀ is B flipped over its diagonal, rows becoming columns. That turns out to be exactly the layout Pearl fingerprints, as you’ll see below.
Where are A and B inside a language model?
In the official miner’s mining mode, any large linear layer can mine: the attention projections (query, key, value and output) and the feed-forward projections (gate, up and down). Each multiplies a batch’s activations by a weight matrix.
Only big multiplications mine: at least 1,024 tokens, n of at least 256 and k of at least 1,024. Smaller ones run as a plain multiplication. That suggests, though Pearl doesn’t say so, that mining happens mostly while a model reads long prompts or large batches, not while it writes one token at a time. Pearl Research Labs publish versions of Llama 3.3 70B and Llama 3.1 8B prepared for the miner.
What rules do the matrices have to follow?
The certificate declares the sizes, and every node checks them:
| Rule | Value |
|---|---|
| Entries of A and B | whole numbers in [−64, 64] |
| Shared size k | 1,024 to 65,536 (2¹⁶), a multiple of 64 |
| k against the noise rank r | 16·r ≤ k ≤ 4·r², so with r = 128: 2,048 to 65,536 |
| Rows m and columns n | at most 2²⁴ = 16,777,216 each |
The entries’ range is narrow on purpose: the integers page explains why. The link between k and r comes from the noise.
Make a guess
The certificate of block 130,825 says m = 131,072, k = 8,192 and n = 262,144. If each entry is one byte, how big are A and B?
Think of your answer first, then check it.
Answer About 1 GiB and 2 GiB.
A has 131,072 × 8,192 = 1,073,741,824 entries, exactly 1 GiB at one byte each. B has 8,192 × 262,144 = 2,147,483,648, exactly 2 GiB. Those are the sizes the miner declared and committed to; no node ever downloads them.
How is a matrix stored as bytes?
A computer’s memory is one long line of bytes, so a grid has to be flattened. There are two natural ways:
- Row-majorRow-major order: Storing a matrix as one long list, row after row. Column-major stores it column after column. Pearl commits to matrix A row by row and to B column by column.: row 0 left to right, then row 1, and so on.
- Column-major: column 0 top to bottom, then column 1, and so on.
Each entry is one signed byte (−3 is fd, see bytes). So the first row of the toy A, −3, 1, 4, 4, …, becomes the bytes fd 01 04 04 ….
How does Pearl fingerprint a matrix?
Before mining, the miner commits to both matrices. It takes the bytes of A row by row, and of B column by column, pads each with zero bytes to a multiple of 1,024, and hashes them with keyed BLAKE3, using the job key:
BLAKE3 is a Merkle tree over 1 KiB chunks, so this fingerprint is a Merkle root. Later, the miner can prove that the two rows and few columns of its winning tile belong to the committed matrices, with short paths of sibling hashes.
That’s why B is stored by columns. A tile needs whole columns of B, and column-major storage makes each column a contiguous run of chunks.
Block 130,825’s certificate carries both fingerprints (raw bytes, as stored):
| Field | Value |
|---|---|
| hash_A | 8e17f731a0c6f8e09a7a29f19732fc29f65932b63886013274b920b057e6930f |
| hash_B | c2e4cb51cff3f95861c45248b6369f5c2dbd623895a3146be39134d060837c8d |
These fingerprints feed the noise seeds: change one entry of A and all of A’s noise changes; change one entry of B and the noise of both matrices changes, because B’s seed feeds into A’s.
Code: flattening and fingerprinting in JavaScript
This is the same procedure the codex’s toy miner uses, mirroring Pearl’s reference miner:
import { blake3 } from '@noble/hashes/blake3.js';
const rowMajor = (M) => Int8Array.from(M.flat()); // A, row by row
const colMajor = (M) => Int8Array.from(M[0].flatMap((_, j) => M.map((row) => row[j]))); // B, column by column
const pad1024 = (b) => { const out = new Uint8Array(Math.ceil(b.length / 1024) * 1024); out.set(new Uint8Array(b.buffer)); return out; };
const hashA = blake3(pad1024(rowMajor(A)), { key: jobKey });
const hashB = blake3(pad1024(colMajor(B)), { key: jobKey }); Pearl’s reference CPU miner draws A and B at random and commits to them in
ffi/mine.rs (zk-pow/src/ffi/mine.rs:15-52 at v1.5.2, on GitHub).