使用 Flask 和 Gunicorn 在生产中加载预训练手套
Loading pretrained glove on production with flask and Gunicorn
我有一个模型需要使用斯坦福的 Glove 进行一些预处理。根据我的经验,此代码至少需要 20-30 秒才能加载手套:
glove_pd = pd.read_csv(embed_path+'/glove.6B.300d.txt', sep=" ", quoting=3, header=None, index_col=0)
glove = {key: val.values for key, val in glove_pd.T.items()}
我的问题是在生产应用程序中处理此问题的最佳做法是什么?据我所知,每次重新启动服务器时,我都需要等待 30 秒,直到端点准备就绪。
另外,I have read在使用Gunicorn时,建议运行与workers>1
,类似这样:
ExecStart=/path/to/gunicorn --workers 3 --bind unix:app.sock -m 007 wsgi:app
这是否意味着每个gunicorn 实例都需要将相同的手套加载到内存中?这意味着服务器资源将非常大,请告诉我这里是否正确。
最重要的是,我的问题是在生产服务器上托管需要预训练嵌入 (glove/word2vec/fasttext) 的模型的推荐方法是什么
在某种程度上,如果您需要它在内存中,这就是将 gigabyte-plus 从磁盘读取到有用的 RAM 结构所花费的时间,那么是的 - 这就是进程准备就绪所花费的时间使用该数据。但是还有优化的空间!
例如,将其作为第一个 Pandas 数据帧读取,然后将其转换为 Python 字典,比其他选项涉及更多步骤和更多 RAM。 (在瞬间的高峰期,当 glove_pd
和 glove
都被完全构造和引用时,你将在内存中有两个完整的副本——而且都没有理想中的紧凑,这可能会引发其他减速,特别是如果使用任何 virtual-memory 触发膨胀。)
正如您担心的那样,如果 3 个 gunicorn
工作人员每个 运行 相同的加载代码,将加载相同数据的 3 个单独副本——但下面有一种方法可以避免这种情况。
我建议首先将向量加载到实用程序 class 中以访问 word-vectors,例如 Gensim 库中的 KeyedVectors
接口。它将所有向量存储在一个紧凑的 numpy
矩阵中,具有一个 dict-like 接口,每个单独的向量仍然 returns 一个 numpy
ndarray
。
例如,您可以将 GLoVe text-format 向量转换为 slightly-different 交换格式(带有额外的 header 行,Gensim 在使用后调用 word2vec_format
原始 Google word2vec.c
代码)。在 gensim-3.8.3
(截至 2020 年 8 月的当前版本)中,您可以:
from gensim.scripts.glove2word2vec import glove2word2vec
glove2word2vec('glove.6B.300d.txt', 'glove.6B.300d.w2vtxt')
然后,utility-class KeyedVectors
可以像这样加载它们:
from gensim.models import KeyedVectors
glove_kv = KeyedVectors.load_word2vec_format('glove.6B.300d.w2vtxt', binary=False)
(从未来的 gensim-4.0.0
版本开始,应该可以跳过转换 & 只需使用新的 no_header
参数直接读取 GLoVe 文本文件:glove_kv = KeyedVectors.load_word2vec_format('glove.6B.300d.w2vtxt', binary=False, no_header=True)
。但是这个 headerless-format 会慢一点,因为它需要遍历文件两次 - 第一次了解完整大小。)
在 KeyedVectors
中加载一次应该已经比原来的通用 two-step 过程更快并且 more-compact。而且,与您在先前的字典中所做的类似的查找将在 glove_kv
实例上可用。 (此外,还有许多其他方便的操作,例如排名 .most_similar()
查找,它们利用高效的数组库函数来提高速度。)
不过,您可以采取其他措施,以最大限度地减少 parsing-on-load,并推迟加载不需要的整组矢量范围,并在进程之间自动重用原始数组数据。
那个额外的步骤是 re-save 使用 Gensim 实例的 .save()
函数的向量,这会将原始向量转储到一个单独的密集文件中,该文件适合 memory-mapping 在下一个加载。那么首先:
glove_kv.save('glove.6B.300d.gs')
这将创建 多个文件,如果搬迁,这些文件必须保存在一起 – 但保存的 .npy
文件将是准备好的最小格式memory-mapping.
然后,以后需要的时候加载为:
glove_kv = KeyedVectors.load('glove.6B.300d.gs', mmap='r')
mmap
参数使用底层 OS 机制简单地将相关矩阵 address-space 映射到磁盘上的 (read-only) 个文件,以便initial 'load' 实际上是即时的,但任何访问矩阵范围的尝试都将使用 virtual-memory 到 page-in 文件的正确范围。因此,它消除了任何 scanning-for-delimiters 并推迟 IO 直到绝对需要。 (如果有任何范围你永远不会访问?它们永远不会被加载。)
memory-mapping 的另一大好处是,如果多个进程每个 read-only memory-map 相同的 on-disk 文件,OS 足够智能让他们共享任何共同的 paged-in 范围。因此,比方说,3 totally-separate OS 个进程,每个进程映射相同的文件,您将节省 3 倍的 RAM。
(如果在所有这些更改之后,重启服务器进程的延迟仍然是一个问题——可能是因为服务器进程崩溃或需要经常重启——你甚至可以考虑使用一些 other long-lived,稳定的过程来初始 mmap 向量。然后,即使所有服务器进程崩溃也不会导致 OS 丢失文件的任何 paged-in 范围,并且重新启动服务器进程可能会发现 RAM 中已经存在部分或全部相关数据。但是一旦其他优化到位,这个额外角色的复杂性可能是多余的。)
一个额外的警告:如果你开始使用像 .most_similar()
这样的 KeyedVectors
方法,它可以(直到 gensim-3.8.3
)触发 full-size 缓存的创建 unit-length-normalized word-vectors,你可能会失去 mmap 的好处,除非你采取一些额外的步骤来 short-circuit 该过程。在先前的答案中查看更多详细信息:How to speed up Gensim Word2vec model load time?
我有一个模型需要使用斯坦福的 Glove 进行一些预处理。根据我的经验,此代码至少需要 20-30 秒才能加载手套:
glove_pd = pd.read_csv(embed_path+'/glove.6B.300d.txt', sep=" ", quoting=3, header=None, index_col=0)
glove = {key: val.values for key, val in glove_pd.T.items()}
我的问题是在生产应用程序中处理此问题的最佳做法是什么?据我所知,每次重新启动服务器时,我都需要等待 30 秒,直到端点准备就绪。
另外,I have read在使用Gunicorn时,建议运行与workers>1
,类似这样:
ExecStart=/path/to/gunicorn --workers 3 --bind unix:app.sock -m 007 wsgi:app
这是否意味着每个gunicorn 实例都需要将相同的手套加载到内存中?这意味着服务器资源将非常大,请告诉我这里是否正确。
最重要的是,我的问题是在生产服务器上托管需要预训练嵌入 (glove/word2vec/fasttext) 的模型的推荐方法是什么
在某种程度上,如果您需要它在内存中,这就是将 gigabyte-plus 从磁盘读取到有用的 RAM 结构所花费的时间,那么是的 - 这就是进程准备就绪所花费的时间使用该数据。但是还有优化的空间!
例如,将其作为第一个 Pandas 数据帧读取,然后将其转换为 Python 字典,比其他选项涉及更多步骤和更多 RAM。 (在瞬间的高峰期,当 glove_pd
和 glove
都被完全构造和引用时,你将在内存中有两个完整的副本——而且都没有理想中的紧凑,这可能会引发其他减速,特别是如果使用任何 virtual-memory 触发膨胀。)
正如您担心的那样,如果 3 个 gunicorn
工作人员每个 运行 相同的加载代码,将加载相同数据的 3 个单独副本——但下面有一种方法可以避免这种情况。
我建议首先将向量加载到实用程序 class 中以访问 word-vectors,例如 Gensim 库中的 KeyedVectors
接口。它将所有向量存储在一个紧凑的 numpy
矩阵中,具有一个 dict-like 接口,每个单独的向量仍然 returns 一个 numpy
ndarray
。
例如,您可以将 GLoVe text-format 向量转换为 slightly-different 交换格式(带有额外的 header 行,Gensim 在使用后调用 word2vec_format
原始 Google word2vec.c
代码)。在 gensim-3.8.3
(截至 2020 年 8 月的当前版本)中,您可以:
from gensim.scripts.glove2word2vec import glove2word2vec
glove2word2vec('glove.6B.300d.txt', 'glove.6B.300d.w2vtxt')
然后,utility-class KeyedVectors
可以像这样加载它们:
from gensim.models import KeyedVectors
glove_kv = KeyedVectors.load_word2vec_format('glove.6B.300d.w2vtxt', binary=False)
(从未来的 gensim-4.0.0
版本开始,应该可以跳过转换 & 只需使用新的 no_header
参数直接读取 GLoVe 文本文件:glove_kv = KeyedVectors.load_word2vec_format('glove.6B.300d.w2vtxt', binary=False, no_header=True)
。但是这个 headerless-format 会慢一点,因为它需要遍历文件两次 - 第一次了解完整大小。)
在 KeyedVectors
中加载一次应该已经比原来的通用 two-step 过程更快并且 more-compact。而且,与您在先前的字典中所做的类似的查找将在 glove_kv
实例上可用。 (此外,还有许多其他方便的操作,例如排名 .most_similar()
查找,它们利用高效的数组库函数来提高速度。)
不过,您可以采取其他措施,以最大限度地减少 parsing-on-load,并推迟加载不需要的整组矢量范围,并在进程之间自动重用原始数组数据。
那个额外的步骤是 re-save 使用 Gensim 实例的 .save()
函数的向量,这会将原始向量转储到一个单独的密集文件中,该文件适合 memory-mapping 在下一个加载。那么首先:
glove_kv.save('glove.6B.300d.gs')
这将创建 多个文件,如果搬迁,这些文件必须保存在一起 – 但保存的 .npy
文件将是准备好的最小格式memory-mapping.
然后,以后需要的时候加载为:
glove_kv = KeyedVectors.load('glove.6B.300d.gs', mmap='r')
mmap
参数使用底层 OS 机制简单地将相关矩阵 address-space 映射到磁盘上的 (read-only) 个文件,以便initial 'load' 实际上是即时的,但任何访问矩阵范围的尝试都将使用 virtual-memory 到 page-in 文件的正确范围。因此,它消除了任何 scanning-for-delimiters 并推迟 IO 直到绝对需要。 (如果有任何范围你永远不会访问?它们永远不会被加载。)
memory-mapping 的另一大好处是,如果多个进程每个 read-only memory-map 相同的 on-disk 文件,OS 足够智能让他们共享任何共同的 paged-in 范围。因此,比方说,3 totally-separate OS 个进程,每个进程映射相同的文件,您将节省 3 倍的 RAM。
(如果在所有这些更改之后,重启服务器进程的延迟仍然是一个问题——可能是因为服务器进程崩溃或需要经常重启——你甚至可以考虑使用一些 other long-lived,稳定的过程来初始 mmap 向量。然后,即使所有服务器进程崩溃也不会导致 OS 丢失文件的任何 paged-in 范围,并且重新启动服务器进程可能会发现 RAM 中已经存在部分或全部相关数据。但是一旦其他优化到位,这个额外角色的复杂性可能是多余的。)
一个额外的警告:如果你开始使用像 .most_similar()
这样的 KeyedVectors
方法,它可以(直到 gensim-3.8.3
)触发 full-size 缓存的创建 unit-length-normalized word-vectors,你可能会失去 mmap 的好处,除非你采取一些额外的步骤来 short-circuit 该过程。在先前的答案中查看更多详细信息:How to speed up Gensim Word2vec model load time?