【发布时间】:2021-05-31 16:38:55
【问题描述】:
我正在运行一个 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 似乎也是一个合理的根本原因。
我的主要问题是:我可以在哪里以及如何调试它?
类似的discussion about an old SpaCy issue 建议偶尔重新加载nlp 对象可能是一种解决方法。我不确定这是否仍然适用于最新的 SpaCy 版本,以及应该如何解决。
另一方面,是否有 FastAPI 或 Uvicorn 选项可以负责释放线程的内存?
【问题讨论】:
-
不确定它是否与您的内存问题有关,但如果您只想使用 spaCy 作为分词器,您应该使用
spacy.blank("en")而不是您正在使用的spacy.load。 -
只是一个更新:该更改似乎确实减少了总内存消耗,但并没有改变它不断增加的事实。因此,在 MemoryErrors 开始发生之前只需要更长的时间。