向量库容量测算台

向量维度翻一倍,内存为什么涨两倍不是四倍:float32、int8 与二值量化的字节账

换模型时,维度输入的变化会牵动整批向量,因为每条向量都包含固定数量的分量。维度不只是影响一条向量,而是会乘上条数进入总容量。

这里先把“内存”收窄为裸向量载荷的容量下限,不把索引附加结构、实现开销和可能保留的原始向量混进来。在条数、精度和打包方式不变时,维度翻一倍,裸向量字节就翻一倍,不是四倍。

一条向量的裸字节是怎么来的

固定宽度格式的裸容量可以写成:

总字节数 = 维度 × 每分量字节 × 条数

这里有三个彼此独立的量:

  • 维度决定每条向量有多少个分量。
  • 每分量字节决定一个分量占多少存储宽度。
  • 条数决定需要保存多少条向量。

因此,一条向量的裸字节是:

维度 × 每分量字节

整批向量的裸字节则是:

维度 × 每分量字节 × 条数

以 float32 为例,每分量占 4 字节,一条 d 维向量占 4 × d 字节,N 条向量占 4 × d × N 字节。

这个公式默认只计算连续存放的分量载荷,不计算索引参数、附加结构和实现级开销,所以算出的仍然是容量下限。

float32 为什么是每个分量 4 字节

NumPy 官方文档把 numpy.float32 描述为单精度浮点类型,典型结构包括符号位、指数位和尾数位:

  • 符号位:1 bit
  • 指数位:8 bit
  • 尾数位:23 bit

合起来是:

1 + 8 + 23 = 32 bit

一个字节包含 8 bit,所以:

32 bit ÷ 8 bit = 4 byte

因此,float32 的每分量字节数是 4。这里的 32 指二进制存储宽度,不是有效数字的个数。

Faiss 官方 wiki 的 Bytes/vector 列也给出了相同口径:IndexFlatL2 和 IndexFlatIP 的单精度向量存储为 4*d。

精度分档:改变的是每分量字节

固定宽度格式可以直接代入容量公式。

  • float32:每分量 4 字节,B = 4 × d × N。
  • float16:每分量 2 字节,B = 2 × d × N。
  • int8 字节口径:每分量 1 字节,B = d × N。

8 位浮点同样是每分量 1 字节。FP8 格式论文定义了 E4M3 和 E5M2 两种编码;它们与 int8 的存储宽度相同,但数值语义并不相同。容量测算只读取存储宽度,不从字节数推断检索质量。

二值量化不是每分量 1 字节,而是每维 1 bit。一条 d 维二值向量共有 d bit,按字节打包后是:

ceil(d / 8) 字节

对应的批量公式是:

B = N × ceil(d / 8)

ceil 表示向上取整。如果维度不是整字节,最后一个字节会有一部分不被使用。Faiss 官方 wiki 对二值 LSH 给出的公式是 ceil(nbits/8);当每维使用 1 bit 时,便有 nbits = d。

因此,精度档位改变的是公式里的每分量字节,或者二值格式里的每维比特数。它不改变维度,也不改变条数。

“维度翻一倍涨四倍”是错的

先看固定宽度格式。原来的容量是:

B = d × b × N

只把维度翻一倍,精度和条数保持不变,新容量为:

B' = (2d) × b × N = 2B

所以,裸向量载荷从 B 变成 2B。

这里之所以是线性增长,是因为维度 d 在容量公式中只作为一次乘法因子出现。公式里没有“再把维度乘一次”这一项,也就不存在从存储公式中凭空长出的 d×d。

“四倍”也可能出现,但对应的是两个独立变量同时翻倍。例如,维度和条数都翻一倍:

B' = (2d) × b × (2N) = 4B

这个四倍来自“维度乘条数”,不是维度自身的平方。

对 float32、float16 和 int8 这类固定宽度格式,只要条数和精度不变,维度翻一倍,逐向量字节就会精确翻一倍。二值格式还存在按字节向上取整,只有在维度与打包边界匹配时,逐向量字节才保持精确的倍数关系。

同样,这句话只针对裸向量载荷。若索引还保存图连接、向量标识或其他结构,总内存不能只凭维度翻倍就断言精确翻倍。

换精度改的是公式哪一段

维度 d 和条数 N 固定后,float32 切换到 int8,公式从:

4 × d × N

变成:

d × N

容量变为原来的四分之一,也就是按 4 倍口径缩减。

float32 切换到每维 1 bit 的二值量化,公式从:

4 × d × N

变成:

N × ceil(d / 8)

当 d 可以整字节打包时,二值载荷相对 float32 缩小 32 倍。不能在任意维度下机械使用这个倍数,因为 ceil(d/8) 会带来整字节取整。

Meilisearch 官方文档给出了一条 1536 维向量的存储对照:

  • 未使用二值量化:每维存为 32-bit float,整条向量为 6 KB。
  • 使用二值量化:每维存为 1 bit,整条向量为 192 字节。

按二值打包公式现场计算:

ceil(1536 / 8) = 192 字节

官方对照中的 6 KB 与 192 字节相差 32 倍,对应每个分量从 32 bit 变为 1 bit。这里保留官方文档的 6 KB 单位写法,不另行替换成其他 KB 口径。

同样的对照推广到相同条数时,两边都再乘条数。但它只证明向量表示的载荷变化,不等于整个进程的内存变化。如果实现同时保留原始 float32 向量,原始载荷仍然存在,不能因为增加了 int8 或二值索引,就把原始向量字节视为已经消失。

哪些字节是公式确定的

公式确定的是已限定口径后的纯算术部分。

固定宽度格式使用:

d × b × N

二值格式使用:

N × ceil(d / 8)

只要维度、精度、条数和打包规则明确,这些结果可以精确到字节。这个“精确”只针对被公式计入的载荷,不代表完整系统占用。

哪些结构可以从公开算法推出

Faiss 官方 wiki 的公开公式可以说明不同索引结构如何增加额外项:

  • IndexFlatL2 与 IndexFlatIP:4*d,即 float32 裸向量部分。
  • IndexScalarQuantizer 的 SQ8:d,即每分量 1 字节。
  • IndexIVFFlat:4*d + 8,其中额外的 8 字节用于向量标识。
  • IndexHNSWFlat:4*d + x*M*2*4。其中 M 是图中使用的邻居数量,剩余项属于图的附加存储,不改变裸向量部分的 4*d 线性关系。
  • IndexLSH:ceil(nbits/8)。
  • IndexPQ:ceil(M*nbits/8),容量还取决于该索引自己的参数。

这些都是每向量口径。计算整批索引时,还要乘以条数。

这些公式能确定结构,但不能在缺少 x、M、nbits 等参数时给出完整字节数。参数缺失时,只能保留公式,不能擅自代入一个默认值。

本站算不准的部分

随实现或索引参数变化的部分,本站不给通用倍数。

仅凭维度、条数、精度和索引类型,无法确定所有附加结构,也无法判断实现是否同时保留原始向量、量化副本或其他载荷。公开公式没有覆盖的实现级开销,也不会被虚构为固定余量。

因此,测算台输出的是下限估算:公式覆盖的字节可以精确计算,未覆盖的真实占用不会因此减少。

回到测算台:每个输入对应什么

  • 维度对应公式中的 d。它进入每一条向量,维度翻倍会重算整批载荷。
  • 条数对应公式中的 N。它与逐向量字节相乘,不能和维度混为一谈。
  • 精度对应固定宽度的 b,或二值格式的每维比特数。float32、float16、int8 和每维 1 bit 应走不同的容量公式。
  • 索引类型决定裸向量之后还能识别哪些公开附加项。它不能把线性维度公式擅自改成平方公式。

实际输入若只采用固定宽度格式,核心就是:

维度 × 每分量字节 × 条数

若采用每维 1 bit 的二值格式,核心则是:

条数 × ceil(维度 / 8)

输入字段与舍入口径见 测算规则,任务场景中的使用边界见 文章,本站的方法范围见 关于。

回到标题:维度翻一倍时,float32、float16 和 int8 的裸向量字节翻一倍,不是四倍;量化改变的是每分量宽度。所有结果都是公式覆盖部分的下限,真实占用只会更高,不会低于这个下限。

本文为容量测算方法说明,不构成选型建议。