OpenPay (1 crate, 2487 LOC, 33 tests)
op-fraud-graph Graph-based fraud detection. EntityKind (card/email/
device/IP/address/phone/BIN+last4/account) with
canonicalization + hashed EntityKey + billing↔shipping
mismatch detection. Adjacency-list FraudGraph with
idempotent upsert + edge-merge. Double-hashing Bloom
filter (capacity+FPR-sized). Union-find connected
components. PageRank with dangling-sink handling.
Louvain community detection. Shared-instrument hub
rings. Sliding ring-buffer velocity counters. Synthetic
identity heuristics (freshness + heavy-use + mail-drop).
Structuring / smurfing / mule-chain DFS. EntityRisk +
TransactionRisk + RiskBand combiner.
Smart Byte (1 crate, ~1844 LOC, 44 tests)
smart-byte-ilp Interledger Protocol v4. OER codec (length-determinant,
var-octet-string, BE u32/u64). Prepare/Fulfill/Reject
packet roundtrip with all 19 IL-RFC-27 reject codes.
SHA-256 Condition + Fulfillment with constant-time
verify. ILP address validator (7 schemes, 1-128 char
segments, 1023-byte cap, segment-aligned prefix match).
STREAM (15 frame types). BTP. SPSP payment-pointer
resolver. Open Payments API. Longest-prefix routing
Connector with fixed-point quote conversion + balance +
rate-limit checks.
EOC (1 crate, 1921 LOC, 36 tests)
eoc-quantize Quantization primitives. int8 (symmetric/asymmetric
with zero-inclusion fix), int4 (GPTQ + AWQ group quant),
int2 (BitNet), fp8 (E4M3/E5M2), nf4 (QLoRA). GGUF v3
header parse. safetensors read sketch. Calibration
datasets + per-channel scales. KV-cache quant. Energy
tradeoff curves (joules-per-token across schemes).
Co-Authored-By: Claude Opus 4.7 <[email protected]>
173 lines
4.8 KiB
Rust
173 lines
4.8 KiB
Rust
//! NormalFloat4 (NF4) — QLoRA (Dettmers et al., 2023).
|
|
//!
|
|
//! NF4 is a 4-bit code with 16 levels chosen so that each level is
|
|
//! approximately equally probable under a standard normal weight
|
|
//! distribution. The published levels are:
|
|
//!
|
|
//! ```text
|
|
//! [-1.0, -0.6961928, -0.5250730, -0.3949175,
|
|
//! -0.2844689595, -0.1848996, -0.0916872, 0.0,
|
|
//! 0.07958029, 0.16093843, 0.24611230, 0.33791524,
|
|
//! 0.44070983, 0.5626170, 0.7229568, 1.0]
|
|
//! ```
|
|
//!
|
|
//! Quantization picks the nearest level, then we store the index in 4
|
|
//! bits along with a per-block scale (block size typically 64).
|
|
|
|
use crate::error::QuantError;
|
|
use crate::scheme::{Numeric, QuantizationScheme};
|
|
|
|
/// The 16 NF4 levels (from the QLoRA paper).
|
|
pub const NF4_LEVELS: [f32; 16] = [
|
|
-1.0,
|
|
-0.696_192_8,
|
|
-0.525_073,
|
|
-0.394_917_5,
|
|
-0.284_468_96,
|
|
-0.184_899_6,
|
|
-0.091_687_2,
|
|
0.0,
|
|
0.079_580_29,
|
|
0.160_938_43,
|
|
0.246_112_3,
|
|
0.337_915_24,
|
|
0.440_709_83,
|
|
0.562_617,
|
|
0.722_956_8,
|
|
1.0,
|
|
];
|
|
|
|
/// Encoded NF4 tensor.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Nf4Encoded {
|
|
/// Block size used.
|
|
pub block_size: usize,
|
|
/// Per-block absmax used as the scale.
|
|
pub scales: Vec<f32>,
|
|
/// Packed 4-bit indices into `NF4_LEVELS`.
|
|
pub packed: Vec<u8>,
|
|
/// Original length.
|
|
pub elem_count: usize,
|
|
}
|
|
|
|
/// NF4 quantizer.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Nf4Quantizer {
|
|
/// Block size (QLoRA default = 64).
|
|
pub block_size: usize,
|
|
}
|
|
|
|
impl Nf4Quantizer {
|
|
/// Construct.
|
|
pub fn new(block_size: usize) -> Self {
|
|
Self { block_size }
|
|
}
|
|
|
|
fn nearest_level(v: f32) -> u8 {
|
|
let mut best = 0u8;
|
|
let mut best_err = f32::INFINITY;
|
|
for (i, &l) in NF4_LEVELS.iter().enumerate() {
|
|
let e = (v - l).abs();
|
|
if e < best_err {
|
|
best_err = e;
|
|
best = i as u8;
|
|
}
|
|
}
|
|
best
|
|
}
|
|
|
|
/// Quantize with explicit errors.
|
|
pub fn try_quantize(&self, x: &[f32]) -> Result<Nf4Encoded, QuantError> {
|
|
if x.is_empty() {
|
|
return Err(QuantError::EmptyInput);
|
|
}
|
|
if !x.len().is_multiple_of(self.block_size) {
|
|
return Err(QuantError::BadGroupSize {
|
|
len: x.len(),
|
|
group: self.block_size,
|
|
});
|
|
}
|
|
let n_blocks = x.len() / self.block_size;
|
|
let mut scales = Vec::with_capacity(n_blocks);
|
|
let mut codes = Vec::with_capacity(x.len());
|
|
for b in 0..n_blocks {
|
|
let start = b * self.block_size;
|
|
let end = start + self.block_size;
|
|
let absmax = x[start..end]
|
|
.iter()
|
|
.copied()
|
|
.fold(0.0_f32, |a, v| a.max(v.abs()))
|
|
.max(f32::EPSILON);
|
|
scales.push(absmax);
|
|
for &v in &x[start..end] {
|
|
let n = (v / absmax).clamp(-1.0, 1.0);
|
|
codes.push(Self::nearest_level(n));
|
|
}
|
|
}
|
|
Ok(Nf4Encoded {
|
|
block_size: self.block_size,
|
|
scales,
|
|
packed: crate::int4::pack_nibbles(&codes),
|
|
elem_count: x.len(),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl QuantizationScheme for Nf4Quantizer {
|
|
type Encoded = Nf4Encoded;
|
|
|
|
fn numeric(&self) -> Numeric {
|
|
Numeric::Nf4
|
|
}
|
|
|
|
fn quantize(&self, weights: &[f32]) -> Self::Encoded {
|
|
self.try_quantize(weights).unwrap_or(Nf4Encoded {
|
|
block_size: self.block_size,
|
|
scales: Vec::new(),
|
|
packed: Vec::new(),
|
|
elem_count: 0,
|
|
})
|
|
}
|
|
|
|
fn dequantize(&self, encoded: &Self::Encoded) -> Vec<f32> {
|
|
let codes = crate::int4::unpack_nibbles(&encoded.packed, encoded.elem_count);
|
|
let mut out = Vec::with_capacity(encoded.elem_count);
|
|
for (i, &c) in codes.iter().enumerate() {
|
|
let b = i / encoded.block_size;
|
|
let scale = encoded.scales.get(b).copied().unwrap_or(0.0);
|
|
let level = NF4_LEVELS.get(c as usize).copied().unwrap_or(0.0);
|
|
out.push(level * scale);
|
|
}
|
|
out
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn nf4_levels_are_sorted_and_unique() {
|
|
let mut prev = f32::NEG_INFINITY;
|
|
for &l in &NF4_LEVELS {
|
|
assert!(l > prev, "NF4 levels must be strictly increasing");
|
|
prev = l;
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn nf4_roundtrip_within_block_tolerance() {
|
|
let x: Vec<f32> = (0..64)
|
|
.map(|i| ((i as f32 - 32.0) / 32.0).clamp(-1.0, 1.0))
|
|
.collect();
|
|
let q = Nf4Quantizer::new(64);
|
|
let enc = q.quantize(&x);
|
|
let xr = q.dequantize(&enc);
|
|
let max_err = x
|
|
.iter()
|
|
.zip(xr.iter())
|
|
.map(|(a, b)| (a - b).abs())
|
|
.fold(0.0_f32, f32::max);
|
|
assert!(max_err < 0.2, "nf4 max err {max_err}");
|
|
}
|
|
}
|