nlp - FastApi 和 SpaCy 的 MemoryError
问题描述
我正在运行一个FastAPI (v0.63.0) Web 应用程序,它使用SpaCy (v3.0.5) 对输入文本进行标记。Web 服务运行一段时间后,总内存使用量增长过大,SpaCy 抛出 MemoryErrors,导致 Web 服务出现 500 错误。
XXX.internal web[17552]: MemoryError:
XXX.internal web[17552]: INFO: xxx - "POST /page HTTP/1.1" 500 Internal Server Error
XXX.internal web[17552]: ERROR: Exception in ASGI application
[...]
XXX.internal web[17552]: Traceback (most recent call last):
XXX.internal web[17552]: File "spacy/tokens/token.pyx", line 263, in spacy.tokens.token.Token.text.__get__
XXX.internal web[17552]: File "spacy/tokens/token.pyx", line 806, in spacy.tokens.token.Token.orth_.__get__
XXX.internal web[17552]: File "spacy/strings.pyx", line 132, in spacy.strings.StringStore.__getitem__
XXX.internal web[17552]: KeyError: "[E018] Can't retrieve string for hash '10429668501569482890'. This usually refers to an issue with the `Vocab` or `StringStore`."
这是我的相关部分main.py
:
@app.post(f"/page", response_model=PageResponse)
async def classify(request: PageRequest):
try:
preprocessed = await preprocessor.preprocess(request.text)
[...]
该preprocessor
对象是一个类的实例,它的preprocess
方法调用 SpaCy 标记器:
class SpacyTokenizer(Tokenizer):
def __init__(self, nlp: spacy.Language):
self._nlp = spacy.load("en_core_web_sm")
for component in self._nlp.pipe_names:
# we only need tokenization
self._nlp.remove_pipe(component)
def tokenize(self, text: str) -> Iterable[str]:
if len(text) >= self._nlp.max_length:
raise ValueError(f"Text too long: {len(text)} characters.")
try:
doc = self._nlp(text)
return islice(
(token.text for token in doc), settings.SPACY_MAX_TOKENS
)
except MemoryError:
raise ValueError(f"Text too long: {len(text)} characters.")
正如您在代码中看到的那样,我试图通过限制生成的令牌数量并捕获MemoryError
. 不过,两者似乎都没有任何效果(我确实知道从MemoryError
概念上捕获通常不会起作用)。
我观察到服务器机器上的工作进程随着时间的推移不断使用更多内存:
17552 webapp 20 0 2173336 1,6g 7984 S 4,7 79,9 33:29.04 uvicorn
当该过程开始时,该uvicorn
过程需要〜700MB而不是1,6g。
从错误消息中,我想很明显 SpaCy 标记器是罪魁祸首。但是,我希望它在处理请求时释放一个工作线程以释放其内存,因此 FastAPI 或 Uvicorn 似乎也是一个合理的根本原因。
我的主要问题是:我在哪里以及如何调试它?
关于旧 SpaCy 问题的类似讨论表明,nlp
偶尔重新加载对象可能是一种解决方法。我不确定这是否仍然适用于更新的 SpaCy 版本,以及应该如何解决。
另一方面,是否有 FastAPI 或 Uvicorn 选项可以处理释放其线程的内存?
解决方案
SpaCy 标记器似乎在内部将每个标记缓存在地图中。因此,每个新标记都会增加该映射的大小。随着时间的推移,不可避免地会出现越来越多的新代币(尽管速度会降低,遵循 Zipf 定律)。在某些时候,在处理了大量文本之后,令牌映射将因此超出可用内存。有了大量可用内存,这当然可以延迟很长时间。
我选择的解决方案是将 SpaCy 模型存储在 TTLCache 中并每小时重新加载一次,清空令牌映射。这为重新加载 SpaCy 模型增加了一些额外的计算成本,但这几乎可以忽略不计。
推荐阅读
- assembly - 在汇编中使用外部变量的值
- c# - 如何使用带有 htmlagilitypack 的样式信息来抓取内部文本
- java - stringbuffer/stringbuilder 如何在内存中工作?stringbuilder.delete 函数是如何工作的?
- docusignapi - Docusign API 使用生产帐户失败
- r - 删除一列上具有相同多个值的行
- python - 将文件从 GCP 磁盘而不是存储桶加载到 googlle collab
- flutter - 在 Flutter 的 GridView 中显示图像?
- postgresql - 加速 PostgreSQL 上的 SUM Billion 记录
- jquery - Ajax jQuery 在 HostGator 网络托管站点中不起作用
- c# - C# 方法返回 int-by 值或引用