Spacy: Memory leak issues in v2.3.2 using beam_parse function

Created on 5 Nov 2020  ·  13Comments  ·  Source: explosion/spaCy

(Posting new issue as #6269 has been closed)

Hi, I'm experiencing the same memory leak issues in v2.3.2 using beam_parse function to extract confidence scores. I ran the exact same script as the one provided in #4432 but am still witnessing a memory leak. The code again for your convenience:

```# Imports for SpaCy
import spacy
from spacy.tokens import Doc
from spacy.pipeline import EntityRecognizer

Imports for debug

import psutil
import os
import time

Imports for drawing

import matplotlib.pyplot as plt

Miscellaneous

from collections import defaultdict

nlp = spacy.load("fr_core_news_md")

Process a "long" text

texts = ["De deux choses l'une, ou le puits était vraiment bien profond, ou elle tombait bien doucement ; car elle eut tout le loisir, dans sa chute, de regarder autour d'elle et de se demander avec étonnement ce qu'elle allait devenir. D'abord elle regarda dans le fond du trou pour savoir où elle allait ; mais il y faisait bien trop sombre pour y rien voir. Ensuite elle porta les yeux sur les parois du puits, et s'aperçut qu'elles étaient garnies d'armoires et d'étagères ; çà et là, elle vit pendues à des clous des cartes géographiques et des images. En passant elle prit sur un rayon un pot de confiture portant cette étiquette, « MARMELADE D'ORANGES. » Mais, à son grand regret, le pot était vide : elle n'osait le laisser tomber dans la crainte de tuer quelqu'un ; aussi s'arrangea-t-elle de manière à le déposer en passant dans une des armoires. « Certes, » dit Alice, « après une chute pareille je ne me moquerai pas mal de dégringoler l'escalier ! Comme ils vont me trouver brave chez nous ! Je tomberais du haut des toits que je ne ferais pas entendre une plainte. » (Ce qui était bien probable.) Tombe, tombe, tombe ! « Cette chute n'en finira donc pas ! Je suis curieuse de savoir combien de milles j'ai déjà faits, » dit-elle tout haut. « Je dois être bien près du centre de la terre. Voyons donc, cela serait à quatre mille milles de profondeur, il me semble. » (Comme vous voyez, Alice avait appris pas mal de choses dans ses leçons ; et bien que ce ne fût pas là une très-bonne occasion de faire parade de son savoir, vu qu'il n'y avait point d'auditeur, cependant c'était un bon exercice que de répéter sa leçon.) « Oui, c'est bien à peu près cela ; mais alors à quel degré de latitude ou de longitude est-ce que je me trouve ? » (Alice n'avait pas la moindre idée de ce que voulait dire latitude ou longitude, mais ces grands mots lui paraissaient beaux et sonores.) Bientôt elle reprit : « Si j'allais traverser complétement la terre ? Comme ça serait drôle de se trouver au milieu de gens qui marchent la tête en bas. Aux Antipathies, je crois. » (Elle n'était pas fâchée cette fois qu'il n'y eût personne là pour l'entendre, car ce mot ne lui faisait pas l'effet d'être bien juste.) « Eh mais, j'aurai à leur demander le nom du pays. — Pardon, Madame, est-ce ici la Nouvelle-Zemble ou l'Australie ? » — En même temps elle essaya de faire la révérence. (Quelle idée ! Faire la révérence en l'air ! Dites-moi un peu, comment vous y prendriez-vous ?) « Quelle petite ignorante ! pensera la dame quand je lui ferai cette question. Non, il ne faut pas demander cela ; peut-être le verrai-je écrit quelque part. » Tombe, tombe, tombe ! — Donc Alice, faute d'avoir rien de mieux à faire, se remit à se parler : « Dinah remarquera mon absence ce soir, bien sûr. » (Dinah c'était son chat.) « Pourvu qu'on n'oublie pas de lui donner sa jatte de lait à l'heure du thé. Dinah, ma minette, que n'es-tu ici avec moi ? Il n'y a pas de souris dans les airs, j'en ai bien peur ; mais tu pourrais attraper une chauve-souris, et cela ressemble beaucoup à une souris, tu sais. Mais les chats mangent-ils les chauves-souris ? » Ici le sommeil commença à gagner Alice. Elle répétait, à moitié endormie : « Les chats mangent-ils les chauves-souris ? Les chats mangent-ils les chauves-souris ? » Et quelquefois : « Les chauves-souris mangent-elles les chats ? » Car vous comprenez bien que, puisqu'elle ne pouvait répondre ni à l'une ni à l'autre de ces questions, peu importait la manière de les poser. Elle s'assoupissait et commençait à rêver qu'elle se promenait tenant Dinah par la main, lui disant très-sérieusement : « Voyons, Dinah, dis-moi la vérité, as-tu jamais mangé des chauves-souris ? » Quand tout à coup, pouf ! la voilà étendue sur un tas de fagots et de feuilles sèches, — et elle a fini de tomber. Alice ne s'était pas fait le moindre mal. Vite elle se remet sur ses pieds et regarde en l'air ; mais tout est noir là-haut. Elle voit devant elle un long passage et le Lapin Blanc qui court à toutes jambes. Il n'y a pas un instant à perdre ; Alice part comme le vent et arrive tout juste à temps pour entendre le Lapin dire, tandis qu'il tourne le coin : « Par ma moustache et mes oreilles, comme il se fait tard ! » Elle n'en était plus qu'à deux pas : mais le coin tourné, le Lapin avait disparu. Elle se trouva alors dans une salle longue et basse, éclairée par une rangée de lampes pendues au plafond. Il y avait des portes tout autour de la salle : ces portes étaient toutes fermées, et, après avoir vainement tenté d'ouvrir celles du côté droit, puis celles du côté gauche, Alice se promena tristement au beau milieu de cette salle, se demandant comment elle en sortirait. Tout à coup elle rencontra sur son passage une petite table à trois pieds, en verre massif, et rien dessus qu'une toute petite clef d'or. Alice pensa aussitôt que ce pouvait être celle d'une des portes ; mais hélas ! soit que les serrures fussent trop grandes, soit que la clef fût trop petite, elle ne put toujours en ouvrir aucune. Cependant, ayant fait un second tour, elle aperçut un rideau placé très-bas et qu'elle n'avait pas vu d'abord ; par derrière se trouvait encore une petite porte à peu près quinze pouces de haut ; elle essaya la petite clef d'or à la serrure, et, à sa grande joie, il se trouva qu'elle y allait à merveille. Alice ouvrit la porte, et vit qu'elle conduisait dans un étroit passage à peine plus large qu'un trou à rat. Elle s'agenouilla, et, jetant les yeux le long du passage, découvrit le plus ravissant jardin du monde. Oh ! Qu'il lui tardait de sortir de cette salle ténébreuse et d'errer au milieu de ces carrés de fleurs brillantes, de ces fraîches fontaines ! Mais sa tête ne pouvait même pas passer par la porte. « Et quand même ma tête y passerait, » pensait Alice, « à quoi cela servirait-il sans mes épaules ? Oh ! que je voudrais donc avoir la faculté de me fermer comme un télescope ! Ça se pourrait peut-être, si je savais comment m'y prendre. » Il lui était déjà arrivé tant de choses extraordinaires, qu'Alice commençait à croire qu'il n'y en avait guère d'impossibles. Comme cela n'avançait à rien de passer son temps à attendre à la petite porte, elle retourna vers la table, espérant presque y trouver une autre clef, ou tout au moins quelque grimoire donnant les règles à suivre pour se fermer comme un télescope. Cette fois elle trouva sur la table une petite bouteille (qui certes n'était pas là tout à l'heure). Au cou de cette petite bouteille était attachée une étiquette en papier, avec ces mots « BUVEZ-MOI » admirablement imprimés en grosses lettres."]

Beam configuration

beam_width = 16
beam_density = 0.0001

For plots

x = []
y = []

Treat the document multiple times

for i in range(0, 30):
x.append(i)
process = psutil.Process(os.getpid())
y.append(process.memory_info().rss / 1000000)

with nlp.disable_pipes('ner'):
    docs = list(nlp.pipe(texts))
beams = nlp.entity.beam_parse(docs, beam_width=beam_width, beam_density=beam_density)

for doc, beam in zip(docs, beams):
    entity_scores = defaultdict(float)
    for score, ents in nlp.entity.moves.get_beam_parses(beam):
        for start, end, label in ents:
            entity_scores[(start, end, label)] += score

Give time for gc to do its work if needed

time.sleep(30)
x.append(x[-1]+1)
process = psutil.Process(os.getpid())
y.append(process.memory_info().rss / 1000000)

Display memory usage evolution

plt.plot(x, y)
plt.show()
```

Here is the resulting memory plot:
memory_leak

Your Environment

Operating System: macOS Catalina Version 10.15.7
Python Version Used: 3.7.7
spaCy Version Used: 2.3.2
Environment Information: Running on mac terminal using conda a virtual environment

_Originally posted by @FrogFeather in https://github.com/explosion/spaCy/issues/6269#issuecomment-722070042_

bug perf / memory

All 13 comments

Hmm, I can't reproduce this in linux. Can you post the output of spacy info --markdown, conda list, and pip freeze for your venv? (If it gets too long, attaching as a .txt file is fine.)

Sure, here are the details:

spacy info --markdown:

Info about spaCy

  • spaCy version: 2.3.2
  • Platform: Darwin-19.6.0-x86_64-i386-64bit
  • Python version: 3.7.7

pip freeze and conda list attached as .txt files
pip_freeze.txt
conda_list.txt

Hmm, that all looks fine. Can you try installing in a clean venv with only pip (entirely without conda if that's possible) to see if you see the same behavior? (If you do have to use a conda env, install numpy from conda, but then install the rest with pip.)

I am seeing the same leak behavior as well using the above code in both Spacy v2.2.1 and v2.3.2

download (2)

Info about spaCy

  • spaCy version: 2.3.2
  • Platform: Linux-4.9.217-0.1.ac.205.84.332.metal1.x86_64-x86_64-with-redhat-5.3-Tikanga
  • Python version: 3.7.7

Just ran the script as you suggested with a clean pip virtualenv and installed spacy. Here is the memory plot:

memory_leak_clean_pip

@karankishinani : v2.2.4 was the first release with the fix for the beam state memory leak, so in v2.2.1 it won't be fixed.

It's kind of hard to track this down when I can't reproduce it locally, but we can try to look into it.

Would someone be willing to run valgrind on their end to get a better idea of where the memory leak is coming from? I have a tutorial I can post with the details on how to run it.

Sure, if you could please post that tutorial I would greatly appreciate it. I'll go ahead and run that when it's up, thanks!

Okay, here are the basics: https://adrianeboyd.github.io/using-valgrind-with-cython/

You can just go through step 5 and send me the logs. The logs get pretty large, so as a dropbox/drive/whatever link to adriane AT explosion DOT ai would be fine. Running valgrind will be slow, so use an example where it goes through the beam parse processing for one short document 1x and then 10x.

Hi @adrianeboyd,

I went ahead and followed the instructions to produce the log files on the beam_parse script. I shared a folder with you that includes the log files for 1x and 10x, the script I ran it on, and the .supp file in case you wanted to see it. Here's the link to the folder as well (shared with you only) https://drive.google.com/drive/folders/1t4x0UygCgVVAMQmrcQjG4CPhYx23QrW0?usp=sharing

Let me know if you need more info.

Ah, it is the same memory leak and I can reproduce this on my end. We'll have to track down why the destructor isn't being called.

But because the destructor is implemented, there is at least a workaround, which is to explicitly delete the beam states like this:

    for doc, beam in zip(docs, beams):
        entity_scores = defaultdict(float)
        for score, ents in nlp.entity.moves.get_beam_parses(beam):
            for start, end, label in ents:
                entity_scores[(start, end, label)] += score
    for beam in beams:
        beam.__del__()

del beam doesn't work, you do have to call beam.__del__().

Great! Looks like with this change to explicitly call the destructor, the memory leak is gone. I'm happy with using this workaround for the time being, thanks again for helping to look into this.

Okay, I think this will be fixed by https://github.com/explosion/thinc/pull/421 in the next release of thinc (should be v7.4.3).

I think we can close this as there's a workaround and a fix? Thanks for the detailed report and all the help, @FrogFeather!

Was this page helpful?
0 / 5 - 0 ratings