You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

百万级嵌入矩阵两两余弦距离计算内存溢出问题求助

解决大规模嵌入矩阵余弦距离计算的内存问题

针对10万×100的嵌入矩阵计算两两余弦距离的内存溢出问题,给你几个可行的实操方案:

1. 计算后直接写入磁盘,不累积内存

你之前的分批计算崩溃,核心是把所有batch的结果都存在distances_matrix列表里,累计到11个batch时,已经占用了4.4GB内存,继续累加必然溢出。改成每算完一个batch就直接写入磁盘,用HDF5格式存储可以实现分块写入,内存里始终只保留当前batch的结果:

import h5py
import numpy as np
from sklearn.metrics.pairwise import cosine_distances
from tqdm import tqdm

embeddings = embeddings.astype(np.float32)
batch_size = 1000
df_size = len(embeddings)

# 创建HDF5文件并预分配存储空间
with h5py.File('cosine_distances.h5', 'w') as f:
    distances_dataset = f.create_dataset('distances', shape=(df_size, df_size), dtype=np.float32)
    
    for i in tqdm(range(0, df_size, batch_size)):
        end = min(i + batch_size, df_size)
        batch = embeddings[i:end]
        batch_distances = cosine_distances(batch, embeddings)
        # 将当前batch结果写入对应位置
        distances_dataset[i:end, :] = batch_distances

2. 手动基于归一化优化计算,减少内存开销

余弦距离公式为1 - (a·b)/(||a||*||b||),先对所有嵌入做L2归一化后,||a||=||b||=1,余弦距离可简化为1 - a·b,计算更快且能减少中间变量的内存占用:

import numpy as np
from tqdm import tqdm
import h5py

# 对嵌入矩阵做L2归一化
embeddings_norm = embeddings / np.linalg.norm(embeddings, axis=1, keepdims=True)
embeddings_norm = embeddings_norm.astype(np.float32)

batch_size = 1000
df_size = len(embeddings_norm)

with h5py.File('cosine_distances_norm.h5', 'w') as f:
    distances_dataset = f.create_dataset('distances', shape=(df_size, df_size), dtype=np.float32)
    
    for i in tqdm(range(0, df_size, batch_size)):
        end = min(i + batch_size, df_size)
        batch_norm = embeddings_norm[i:end]
        # 矩阵点积得到余弦相似度,1减后为余弦距离
        batch_distances = 1 - batch_norm @ embeddings_norm.T
        distances_dataset[i:end, :] = batch_distances

手动用矩阵乘法计算比调用sklearn函数更省内存,避免了库内部的额外内存开销。

3. 降低数据精度(场景允许时)

如果业务对精度要求不高,可以将数据从float32降到float16,直接减少一半的内存和磁盘占用:

embeddings_norm = embeddings_norm.astype(np.float16)

with h5py.File('cosine_distances_fp16.h5', 'w') as f:
    distances_dataset = f.create_dataset('distances', shape=(df_size, df_size), dtype=np.float16)
    # 后续计算逻辑同上,最终结果转float16写入

注意:float16精度有限,会存在微小误差,需先验证是否符合需求。

4. 用GPU加速计算(有GPU资源时)

如果有GPU,用PyTorch或TensorFlow计算,GPU显存能更高效处理大矩阵,且计算速度更快:

import torch
from tqdm import tqdm

# 将嵌入矩阵转成GPU张量
embeddings_tensor = torch.tensor(embeddings, dtype=torch.float32).cuda()
# L2归一化
embeddings_tensor = torch.nn.functional.normalize(embeddings_tensor, p=2, dim=1)

batch_size = 2000  # 可根据GPU显存大小调整
df_size = len(embeddings_tensor)

with h5py.File('cosine_distances_gpu.h5', 'w') as f:
    distances_dataset = f.create_dataset('distances', shape=(df_size, df_size), dtype=np.float32)
    
    for i in tqdm(range(0, df_size, batch_size)):
        end = min(i + batch_size, df_size)
        batch_tensor = embeddings_tensor[i:end]
        # 计算余弦距离
        batch_distances = 1 - torch.matmul(batch_tensor, embeddings_tensor.T)
        # 转成numpy写入磁盘,同时释放GPU显存
        distances_dataset[i:end, :] = batch_distances.cpu().numpy()
        del batch_distances
        torch.cuda.empty_cache()

5. 只计算必要部分(无需完整矩阵时)

如果最终目的不是获取完整距离矩阵,而是找每个样本的topK最近邻,直接用近似最近邻库(如faiss)检索,内存和计算量会大幅降低:

import faiss

# 归一化嵌入矩阵
embeddings_norm = embeddings / np.linalg.norm(embeddings, axis=1, keepdims=True)
embeddings_norm = embeddings_norm.astype(np.float32)

# 创建IVF_HNSW索引加速检索
index = faiss.IndexIVFHSW(faiss.IndexFlatIP(100), 100, 32)
index.train(embeddings_norm)
index.add(embeddings_norm)

# 查找每个样本的top10最近邻
k = 10
similarities, indices = index.search(embeddings_norm, k)
# 如需余弦距离,转换为:distances = 1 - similarities

内容的提问来源于stack exchange,提问作者EyalG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 04:45:05