Quantized Inference
Hurray treats quantization as first-class metadata, orthogonal to the storage type: a tensor is quantized if and only if it carries a quantization descriptor. Five schemes are normative — per-tensor affine, per-channel affine, per-block affine, NF4 (QLoRA), and MXFP (OCP Microscaling) — and the quantization parameters (scales, zero-points) live in separate buffer-table entries, never interleaved with the data, so both stay zero-copy.
This recipe builds a per-block-affine int4 weight tensor in Rust, round-trips it, and
reads the scheme back.
use hurray_core::{
BufferHandle, DeviceTag, ElementType, LayoutDescriptor, PerBlockAffine,
QuantizationDescriptor, Shape, SyncMode, TensorDescriptor, MIN_BUFFER_ALIGNMENT,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// A [256, 256] int4 weight matrix, per-block affine along axis 1, block_size 64.
let shape = Shape::new(vec![256u64, 256])?;
// Buffer 0 — packed int4 data: 256 × 256 values, 2 per byte = 32,768 bytes.
let data = BufferHandle::new(
32_768, MIN_BUFFER_ALIGNMENT, DeviceTag::Cpu, SyncMode::ProducerSynced,
)?;
// Buffer 1 — float32 scales, one per block: 256 rows × (256 / 64) = 1024 scales.
let scales = BufferHandle::new(
1024 * 4, MIN_BUFFER_ALIGNMENT, DeviceTag::Cpu, SyncMode::ProducerSynced,
)?;
// Per-block affine (symmetric): axis 1, block_size 64, scales in buffer index 1,
// scale element type float32.
let quant = QuantizationDescriptor::PerBlockAffine(
PerBlockAffine::new_symmetric(1, 64, 1, ElementType::Float32)?,
)
.encode_to_vec();
let desc = TensorDescriptor::new(
1, 0,
ElementType::Int4, // storage type is orthogonal to the scheme
shape,
0,
LayoutDescriptor::RowMajor,
vec![data, scales], // data + scale buffers
Some(quant), // HAS_QUANTIZATION
None, None, None, // no shard / statistics / extension-type
)?;
// Round-trip the descriptor.
let bytes = desc.encode()?;
let decoded = TensorDescriptor::decode(&bytes)?;
assert_eq!(decoded, desc);
// Read the scheme back from the (raw) quantization section.
let (q, _) = QuantizationDescriptor::decode(decoded.quantization.as_ref().unwrap())?;
println!("scheme tag = 0x{:02X}", q.scheme_tag().tag()); // 0x03 = per-block-affine
if let QuantizationDescriptor::PerBlockAffine(pb) = q {
println!("axis = {}, block_size = {}", pb.axis(), pb.block_size());
}
Ok(())
}
Dequantization
The scheme's dequantization formula is normative (see
Layer 2: Quantization Descriptors and
docs/spec/quantization.md). For per-block affine, each element uses the scale (and, for
asymmetric, the zero-point) of the block it belongs to:
value = (q_code - zero_point) * scale[block_index]
For a symmetric descriptor the zero-point is implicitly 0, so value = q_code * scale[block]. The block index is derived from the element's coordinate on the quantized
axis and block_size.
Other schemes
- NF4 (QLoRA) uses a fixed 16-level lookup table —
hurray_core::NF4_LUT— plus a per-block scale:QuantizationDescriptor::Nf4(Nf4::new(axis, block_size, scale_buffer)). - MXFP (OCP Microscaling) pairs an 8-bit shared exponent per block with the element
micro-floats:
QuantizationDescriptor::Mxfp(Mxfp::new(axis, block_size, scale_buffer)). - Per-tensor / per-channel affine cover the classic INT8 cases
(
PerTensorAffine::new,PerChannelAffine::new_symmetric/new_asymmetric).
hurray_core::validate_buffer_placement checks that the scale/zero-point buffer indices a
descriptor references actually exist in the buffer table.
Reading quantized tensors elsewhere
Python authors and reads every scheme — per-tensor, per-channel, per-block, NF4 and MXFP — and the multi-buffer descriptors the last four need. See Authoring Quantized Tensors for the constructors and Python: Layouts for how a tensor carries its parameter buffers.
import struct
import hurray
weight_bytes = bytes(1024 * 512) # your quantizer's output
scale_bytes = struct.pack("1024f", *[0.02] * 1024) # one float32 scale per row
weights = hurray.Tensor(
weight_bytes,
hurray.dtype.int8,
[1024, 512],
aux_buffers=[scale_bytes],
quantization=hurray.PerChannelAffine.symmetric(axis=0, scale_buffer_index=1),
)
assert weights.quantization.axis == 0
What Python does not do is dequantize: applying the formula above is the consuming framework's job, not the codec's.
To inspect a quantized descriptor byte by byte (scheme, axis, block size, buffer indices), use the CLI:
hurray-inspect weights.hrry
See hurray-inspect CLI; it decodes every quantization scheme and formats Tier 2 / sub-byte element values.