§ 03

§ 03 · Matrix math

Matrices

Grids of numbers, and the two that every Pearl miner multiplies

A matrix 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.

−3144−3−142433−1−4−3−30−33−431−1201124−31−344003−20−41132−3243−32432402344−4−1−2−44−3row 0row 1row 2row 305101516 columns: k, the shared dimensionA, 4 × 16
The toy matrix A, 4 × 16. In an AI model each row would be one token and each column one feature. Toy numbers from the research worked example.

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:

A(m×k)×B(k×n)=C(m×n)A \;(m \times k) \;\times\; B \;(k \times n) \;=\; C \;(m \times n)
The inner sizes must match: A has k columns, B has k rows. k is called the shared or common dimension.

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-major: row 0 left to right, then row 1, and so on.
  • Column-major: column 0 top to bottom, then column 1, and so on.
A: row-major01234567891011B: column-major03691471025811
The same 3 × 4 grid, numbered in storage order. Pearl stores A row-major and B column-major.

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:

hashA=BLAKE3job key(pad1024(A row by row))\text{hash}_A = \mathrm{BLAKE3}_{\text{job key}}\big(\text{pad}_{1024}(A \text{ row by row})\big)

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:

JavaScript
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).

The codex

Suggested

Up and down arrows move through the results, Enter opens one, Escape closes. Anywhere on the site, slash or Command K opens this search, and the square brackets [ and ] go to the previous and next page.