标量量化 SQ:一维均匀量化加速向量比较
什么是标量量化
标量量化(Scalar Quantization,SQ) 是一种将高精度浮点数向量压缩为低比特整数的技术。它的核心思想是对向量中的每一个维度独立进行一维均匀量化,从而大幅降低存储开销,并通过整数运算加速向量的相似度比较(如内积、欧氏距离)。
在你面对百万甚至亿级向量检索任务时,SQ 能让内存占用降至原来的 1/4 甚至更少,同时几乎不损失检索精度。
为什么需要量化来加速向量比较
向量相似度计算(点积、余弦相似度、L2 距离)是高维浮点运算的密集操作。一个拥有 1 亿条 768 维向量的数据库:
- 存储:
1亿 × 768 × 4字节 ≈ 307 GB,仅存储原始向量就已非常庞大。 - 计算:每次检索都需要对候选向量执行完整的浮点乘法与加法,极耗 CPU 和内存带宽。
标量量化将每个 32 位浮点数映射到一个 8 位(甚至 4 位)整数,使得:
- 向量大小直接变为原来的 1/4(8-bit)或 1/8(4-bit)。
- 距离计算可以从浮点运算转换为快速的整数运算(或查表加速),利用现代 CPU 的 SIMD 指令可一次处理更多数据。
一维均匀量化的原理
标量量化是独立在每个维度上进行的,因此又被称为一维量化。均匀量化意味着划分的区间等宽,实现最简单、速度最快。
量化过程(编码)
对于一个向量维度 $j$,我们从该维度的数值分布中确定该维度的最小值 $min_j$ 和最大值 $max_j$。然后将区间 $[min_j, max_j]$ 均匀划分成 $2^b - 1$ 段,得到量化步长:
$$\Delta_j = \frac{max_j - min_j}{2^b - 1}$$
将一个浮点数值 $x_j$ 映射为 $b$ 比特整数 $q_j$ 的公式:
$$q_j = \left\lfloor \frac{x_j - min_j}{\Delta_j} + 0.5 \right\rfloor$$
之后将 $q_j$ 约束在 $[0, 2^b-1]$ 范围内(clip)。
反量化(解码)
从整数 $q_j$ 恢复近似浮点值 $\tilde{x}_j$:
$$\tilde{x}_j = min_j + q_j \cdot \Delta_j$$
这种恢复是有损的,量化误差 $|x_j - \tilde{x}_j| \le \frac{\Delta_j}{2}$。
如何使用 SQ 加速向量距离计算
假设两个向量 $\mathbf{x}$ 和 $\mathbf{y}$ 都已量化为 $b$ 比特整数向量 $\mathbf{q}^{(x)}$ 和 $\mathbf{q}^{(y)}$,并各自保存了 $min$、$max$ 数组(或步长 $\Delta$)。常见的加速方法有两种。
非对称量化距离计算(实践常用)
在实际检索场景中,查询向量不量化,仍保留为浮点数;数据库向量已量化为整数并存储步长信息。距离计算过程如下:
内积(点积)近似:
原始内积 $\langle \mathbf{x}, \mathbf{y} \rangle \approx \langle \mathbf{x}, \tilde{\mathbf{y}} \rangle = \langle \mathbf{x}, \mathbf{min}_y + \mathbf{q}^{(y)} \odot \boldsymbol{\Delta}_y \rangle$
可展开为:
$$\langle \mathbf{x}, \tilde{\mathbf{y}} \rangle = \sum_j x_j \cdot min_{y,j} + \sum_j x_j \cdot q_j^{(y)} \cdot \Delta_{y,j}$$
其中 $\sum_j x_j \cdot min_{y,j}$ 对于同一个数据库向量 $\mathbf{y}$ 是常数(与查询无关,但需每次查询计算),而第二项的 $x_j \cdot \Delta_{y,j}$ 可预先计算。更高效的做法是建立基于查询的查找表,避免逐维度乘法。
L2 距离:
$$||\mathbf{x} - \tilde{\mathbf{y}}||^2 = ||\mathbf{x}||^2 - 2\langle \mathbf{x}, \tilde{\mathbf{y}} \rangle + ||\tilde{\mathbf{y}}||^2$$
$||\mathbf{x}||^2$ 查询时计算一次,$||\tilde{\mathbf{y}}||^2$ 可预计算并存储,内积部分用上述方法加速。
乘积量化加速的基石:在实际系统中,通常进一步将向量维度分组,利用多组 SQ 构建乘积量化(PQ),此时标量量化就是 PQ 的一种特例(每组只有一个维度)。
对称量化距离计算
查询向量也做量化,两个量化向量间的距离可通过整数运算实现。例如内积:
$$\langle \tilde{\mathbf{x}}, \tilde{\mathbf{y}} \rangle = \sum_j (min_{x,j} + q_j^{(x)}\Delta_{x,j})(min_{y,j} + q_j^{(y)}\Delta_{y,j})$$
完全展开后可使用整数点积加一些浮点修正快速完成。但因其会引入双重量化误差,通常不如非对称量化效果好。
动手实现:8-bit 标量量化
以下 Python 示例演示如何对一个向量集执行 8-bit 均匀量化,并计算非对称量化后的近似点积。
import numpy as np
class ScalarQuantizer8b:
def __init__(self):
self.min_vals = None
self.max_vals = None
self.delta = None
self.codebook = None # 量化后的整数向量
def fit(self, vectors):
"""根据训练向量确定各维度的 min/max"""
self.min_vals = vectors.min(axis=0)
self.max_vals = vectors.max(axis=0)
# 避免步长为零
range_vals = self.max_vals - self.min_vals
range_vals[range_vals == 0] = 1.0
self.delta = range_vals / 255.0 # 8-bit
def encode(self, vectors):
"""将浮点向量量化为 0~255 整数"""
quantized = (vectors - self.min_vals) / self.delta
quantized = np.clip(np.round(quantized), 0, 255).astype(np.uint8)
return quantized
def decode(self, codes):
"""反量化恢复近似向量"""
return self.min_vals + codes.astype(np.float32) * self.delta
def asymmetric_dot_product(self, query, codes, norms=None):
"""非对称量化点积:query 是浮点向量,codes 是整数量化向量"""
# 预计算 (query * delta) 项
weighted_delta = query * self.delta
dot_product = np.dot(codes.astype(np.float32), weighted_delta)
# 加上 query 与 min_vals 的点积
dot_product += np.dot(query, self.min_vals)
return dot_product
使用示例
# 生成随机向量 (10000 个, 128 维)
data = np.random.randn(10000, 128).astype(np.float32)
query = np.random.randn(128).astype(np.float32)
# 创建量化器并拟合
sq = ScalarQuantizer8b()
sq.fit(data)
# 编码数据库向量
codes = sq.encode(data)
# 计算近似点积并排序,取 top-5
approx_sim = sq.asymmetric_dot_product(query, codes)
top5_idx = np.argpartition(-approx_sim, 5)[:5]
print("Top 5 indices:", top5_idx)
实际上,为了加速 asymmetric_dot_product 中的 np.dot(codes, weighted_delta),可以进一步利用整数 SIMD 指令,但在纯 Python 中体现不出优势;在生产环境(如 Faiss、ScaNN)中,这一步会被极致优化。
参数选择与精度权衡
- 量化比特数 $b$:常用 8-bit 或 4-bit。8-bit 几乎不损失检索精度(误差 < 0.5%),4-bit 压缩率更高但需要更精细的校准或使用非均匀量化。
- Min/Max 校准:直接用训练集的极值易受离群点影响,导致步长过大、分辨率浪费。实践中常采用 截断量化:选择一个分位数(如 99%)代替
max,将极端值钳制在该值,以降低整体量化误差。 - 各维度独立量化:假设维度之间分布不同,这样做是合理的。如果某些维度的方差极小,可将其共用一组量化参数,但我们依旧独立处理不会引入额外开销。
与其他量化方法的对比
| 方法 | 量化单元 | 压缩率(vs f32) | 距离计算 | 检索精度保持 |
|---|---|---|---|---|
| 标量量化 (SQ) | 每个维度独立 | 4× (8-bit) | 浮点+整数混合或纯整数 | 极高(8-bit 几乎无损) |
| 乘积量化 (PQ) | 子向量分组 | 典型 8~16× | 查表+ ADR | 较好(需合理分组) |
| 二值量化 | 整个向量 | 32× | 汉明距离 | 严重损失,仅适用于特定任务 |
SQ 经常作为更复杂量化方法的第一级粗糙量化或乘积量化内部的子量化器。
小结
标量量化是最直观、最易实现的向量压缩加速技术。它通过对每个维度独立进行均匀量化,将浮点向量转换为整数表示,在几乎不损失精度的情况下实现 4 倍内存节省和显著的相似度计算加速。学习 SQ 不仅为你打开了向量检索性能优化的大门,更是理解乘积量化、倒排索引压缩等高级方法的基石。
现在,你可以在自己的向量数据集上尝试用 8-bit SQ 替换原始浮点存储,观察内存占用的明显下降以及近乎不变检索质量的体验。