5. Model Compression

Weight Quantization

midai-mlalgorithmic-optimization

A model's weights are normally 32-bit floats. Quantizing them to 8-bit integers shrinks memory (and often speeds up inference) by roughly 4x, at the cost of a small, mathematically bounded amount of precision -- this is exactly what llama.cpp/GGML's quantized model formats (Q8_0 and friends) do.

Implement:

  • quantize(values) -> (quantized, scale) -- find scale = max(abs(values)) / 127 (so the largest-magnitude value maps to exactly -127 or 127). Each value maps to round(value / scale), clamped to [-127, 127].
  • dequantize(quantized, scale) -> floats -- the inverse: each quantized[i] maps back to quantized[i] * scale. This recovers an APPROXIMATION of the original value, not an exact one -- that's the tradeoff.

Handle the all-zero input case in quantize by returning ([0] * len(values), 1.0) -- there's nothing meaningful to scale.

Example

quantize([1.0, -1.0, 0.5])   # -> ([127, -127, 64], 1/127 approx.)

The missing link: one scale for a whole tensor is a real liability

A single global scale is derived from the single LARGEST-magnitude value in values. That's fine when values are all similar in size -- but real model weights aren't: one rare outlier weight can be 100x the size of everything else around it. With a single global scale, that one outlier forces a coarse scale onto the ENTIRE tensor, quietly destroying precision for every other value that had nothing to do with the outlier.

The fix real formats use (GGML's Q8_0 and friends): split values into fixed- size blocks and give each block its OWN independent scale. An outlier in one block no longer affects any other block's precision at all.

Implement quantize_blocked(values, block_size) -- split values into consecutive chunks of block_size (the last chunk may be shorter), call your own quantize() on each chunk independently, and return (all_quantized_values_concatenated_in_order, [scale_per_block, ...]). Then dequantize_blocked(quantized, scales, block_size) -- the inverse, using scales[i] to dequantize() the i-th block of quantized.

Constraints

  • The bound that makes this useful: for every value, |value - dequantize(quantize(value))| <= scale / 2 -- rounding to the nearest integer can never be off by more than half a step. The same bound applies per-block for the blocked versions, using that block's own scale.
  • Quantized values must always land in [-127, 127].
  • quantize_blocked with block_size >= len(values) (one block covering everything) must produce identical output to plain quantize.

Real-world grounding

  • GGML's quantization formats, including the actual per-block layout (32 values per block for Q8_0): src/ggml-quants.c

Real quantization schemes get even more sophisticated on top of this (4-bit formats with even tighter packing, calibration against real activation distributions rather than just the raw weight values) -- but "confine an outlier's damage to its own block" is the core idea underneath block-wise quantization specifically.

hints
quantization.pysign in to run
Loading...
test result

This is a preview. Sign in to run this exercise against the real tests.