§ 03 · Matrix math
Integers and quantization
Small whole numbers in, exact 32-bit sums out
On this page 5 sections
Pearl’s mining multiplies matrices of small whole numbers, between −64 and 64. Not fractions, not big numbers.
That choice does three jobs at once. Whole numbers make the arithmetic exact, so everyone agrees on every bit. Small ones fit in a single byte, even after noise is added, so GPUs can use their fastest circuits. And their sums fit comfortably in 32 bits, so nothing ever overflows.
Why whole numbers and not fractions?
Because a mining ticket must come out identical on every computer that checks it.
Computers store fractions as floating-point numbers, which round after every operation. Add the same numbers in a different order and the last digits can differ. A GPU, a CPU and a proof circuit would sometimes disagree, and the ticket, a hash, would change completely.
Integer arithmetic has no rounding. 3 × (−4) + 7 is −5 on every machine, in any order. So Pearl’s rules define the work in integers: small inputs that fit in a byte, 32-bit running sums. The format is named in every certificate: Int7 × Int7 → Int32.
What are the ranges, exactly?
| Numbers | Range | Stored as |
|---|---|---|
| Entries of A and B | [−64, 64], 129 values | one signed byte each |
| Noise factors (the “thin” part) | [−32, 31] | 6-bit random numbers |
| Noise added to A and B | [−63, 63] | one signed byte |
| Noisy entries A′ = A + E, B′ = B + F | [−127, 127] | one signed byte (int8) |
| Running sums of products | far inside ±2³¹ | 32-bit signed integer (int32) |
The first row is enforced twice. The plain check a pool runs on a winning share rejects any revealed entry outside [−64, 64], and the zero-knowledge circuit that every node verifies has the same range check built in. The noise ranges come from how the noise is built, on the rank and noise page.
Why can’t the sums overflow?
A 32-bit signed integer holds values up to 2,147,483,647. Check the worst case:
So the GPU can accumulate a whole dot product in 32-bit registers without ever checking for overflow. This is one reason k has an upper limit at all.
Make a guess
If entries could go all the way to ±127 before noise, what range would the noisy entries need?
Think of your answer first, then check it.
Answer ±190, which no longer fits in a byte.
127 + 63 = 190, beyond int8’s 127. The noisy matrices would need a wider format, and the GPU would lose its fast 8-bit integer mode. Capping entries at 64 keeps the noisy matrices in one byte.
What is quantization?
AI models are trained with fractional numbers, usually 16-bit floating point. QuantizationQuantization: Rounding numbers to a smaller set of values so they fit in fewer bits. Pearl's miner turns an AI layer's numbers into small whole numbers so the GPU's fast integer circuits can multiply them. squeezes them into a few integer levels, with a scale factor to convert back.
The common symmetric method: find the largest magnitude in a row, divide it into 63 steps, and round each number to the nearest step.
Pearl’s official miner does this inside vLLM. In mining mode, each token’s activations are quantized to 7 bits, [−63, 63], with one scale per token. The weights were quantized ahead of time, with one scale per output channel. After the integer multiplication and denoising, the result is multiplied by both scales and written back as 16-bit floating point, the layer’s normal output.
Pearl Research Labs describe this setup as W7A7 (7-bit weights and activations), prepared with the GPTQ and SmoothQuant methods. Their whitepaper reports Llama-3.3-70B accuracy scores (MMLU) of 0.8198 for the original 16-bit model and 0.8190 to 0.8198 for Pearl-certified runs: little or no loss on that test.
How does the ticket read a negative sum?
As raw bits. Every 128 steps the miner XORs a tile’s running sums together into one 32-bit word, and XOR works on bits, not on values. A negative sum is read in two’s complementTwo's complement: The standard way to store negative whole numbers in bytes: −1 is all ones (0xff in one byte), −128 is 0x80. Pearl’s matrix entries are stored this way, one signed byte each..
In Pearl’s toy example, a tile’s first four running sums are 6, 1, −66 and 10:
| Sum | 32-bit pattern (hex) |
|---|---|
| 6 | 00000006 |
| 1 | 00000001 |
| −66 | ffffffbe |
| 10 | 0000000a |
| XOR of all four | ffffffb3 |
That word is what goes into the ticket’s diary. See tickets and target.
Code: the range check and the casts
The plain check of a share, which a pool or gateway runs, rejects any revealed entry outside the range in
verify.rs (zk-pow/src/api/verify.rs:113-118 at v1.5.2, on GitHub) (the zero-knowledge circuit that nodes verify enforces the same range):
// Verify all strip values are in [-64, 64] (matching the ZK circuit's IRANGE7P1 range check)
for strip in private_params.s_a.iter().chain(private_params.s_b.iter()) {
for &val in strip {
ensure!((-64..=64).contains(&val), "Matrix value {} out of range [-64, 64]", val);
}
} In the tile loop, entries are widened to i32 before multiplying, and the sums are reinterpreted as u32 for the
XOR (x as u32), exactly the two’s-complement reading above.