【问题标题】:Sharing memory for gensim's KeyedVectors objects between docker containers在 docker 容器之间共享 gensim 的 KeyedVectors 对象的内存
【发布时间】:2023-12-06 06:55:01
【问题描述】:

related question solution 之后,我创建了 docker 容器,它在 docker 容器中加载 GoogleNews-vectors-negative300 KeyedVector 并将其全部加载到内存中

KeyedVectors.load(model_path, mmap='r')
word_vectors.most_similar('stuff')

我还有另一个 Docker 容器,它提供了 REST API,它用

加载这个模型
KeyedVectors.load(model_path, mmap='r')

我观察到满载的容器占用超过 5GB 的内存,每个 gunicorn worker 占用 1.7GB 的内存。

CONTAINER ID        NAME                        CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
acbfd080ab50        vectorizer_model_loader_1   0.00%               5.141GiB / 15.55GiB   33.07%              24.9kB / 0B         32.9MB / 0B         15
1a9ad3dfdb8d        vectorizer_vectorizer_1     0.94%               1.771GiB / 15.55GiB   11.39%              26.6kB / 0B         277MB / 0B          17

但是,我希望所有这些进程为 KeyedVector 共享相同的内存,因此所有容器之间只需要共享 5.4 GB。

有人尝试过实现这一目标并取得成功吗?

编辑: 我尝试了以下代码 sn-p,它确实在不同的容器之间共享相同的内存。

import mmap
from threading import Semaphore

with open("data/GoogleNews-vectors-negative300.bin", "rb") as f:
    # memory-map the file, size 0 means whole file
    fileno = f.fileno()
    mm = mmap.mmap(fileno, 0, access=mmap.ACCESS_READ)
    # read whole content
    mm.read()
    Semaphore(0).acquire()
    # close the map
    mm.close()

所以KeyedVectors.load(model_path, mmap='r')不共享内存的问题

编辑2: 研究gensim的源代码我看到np.load(subname(fname, attrib), mmap_mode=mmap)被调用来打开memmaped文件。以下代码示例跨多个容器共享内存。

from threading import Semaphore

import numpy as np

data = np.load('data/native_format.bin.vectors.npy', mmap_mode='r')
print(data.shape)
# load whole file to memory
print(data.mean())
Semaphore(0).acquire()

【问题讨论】:

    标签: python mmap gensim word2vec


    【解决方案1】:

    经过大量调试后,我发现 mmap 对 KeyedVectors 对象中的 numpy 数组按预期工作。

    但是,KeyedVectors 具有其他属性,例如 self.vocabself.index2wordself.index2entity,它们不共享,每个对象消耗约 1.7 GB 的内存。

    【讨论】:

    • 如您链接的相关答案和下面我的兄弟答案中所述,生成的vectors_norm 只有在您采取额外步骤以防止它被重新生成(不是尚未显示在您的代码中)。 .vectors.vectors_norm 数组是大多数单词向量集的大部分内存使用 - 但字典/列表(如 gensim-3.8 中的 .vocab.index2entity)不能是 mmap-共享,因此会重复消耗内存。从GoogleNews 数据来看,1.7GB 的大小听起来有点高,但这是可能的——这在gensim-4.0.0 中应该会有所改善。
    【解决方案2】:

    我不确定容器化是否允许容器共享相同的内存映射文件 - 但即使允许,您用于测量每个容器内存使用的任何实用程序都有可能对内存进行两次计数,即使它是共享。您使用什么工具来监控内存使用情况,您确定它会指示真正的共享吗? (如果在 gensim 之外,您尝试使用 Python 的 mmap.mmap() 在两个容器中打开相同的巨型文件会发生什么情况?您看到与 gensim 的情况相同、更多还是更少的内存使用情况?)

    而且:为了做一个most_similar()KeyedVectors 将在属性vectors_norm 中创建一个second 字向量数组,标准化为单位长度。 (这在第一次需要时执行一次。)这个规范数组保存,因为它总是可以重新计算。因此,为了您的使用,每个容器都将创建自己的、非共享的 vectors_norm 数组 - 取消共享内存映射文件中任何可能的内存节省。

    您可以通过以下方式解决此问题:

    • 在加载模型之后但在触发自动归一化之前,用一个特殊的参数明确地强制它自己来破坏原始原始向量。然后保存这个预先规范的版本:

      word_vectors = KeyedVectors.load(model_path)
      word_vectors.init_sims(replace=True)
      word_vectors.save(normed_model_path)
      
    • 稍后以内存映射方式重新加载模型时,手动将vectors_norm 属性设置为与vectors 相同,以防止冗余重新创建规范数组:

      word_vectors = KeyedVectors.load(normed_model_path, mmap='r')
      word_vectors.vectors_norm = word_vectors.vectors
      

    如果是规范导致您无法看到预期的内存节省,那么这种方法可能会有所帮助。

    【讨论】:

    • 很明显,模型不会共享内存,因为一旦我创建 4 个容器实例并在 oom-kill 命令后恢复运行,我的工作站就会挂起
    最近更新 更多