You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
distributed.nanny.environ.MALLOC_TRIM_THRESHOLD_ is ineffective #5971
The MALLOC_TRIM_THRESHOLD_ env variable seems to be effective at making memory deallocation more reactive.
However, the config variable that sets it doesn't seem to do anything - which indicates that the variable is being set after the worker process is started, whereas it should be set before spawning it.
importdask.arrayimportdistributedclient=distributed.Client(n_workers=1, memory_limit="2 GiB")
N=7_000S=160*1024a=dask.array.random.random(N*S//8, chunks=S//8)
a=a.persist()
distributed.wait(a)
dela
Result:
Managed: 0
Unmanaged: 1.16 GiB
importosimportdask.arrayimportdask.configimportdistributedos.environ["MALLOC_TRIM_THRESHOLD_"] =str(dask.config.get("distributed.nanny.environ.MALLOC_TRIM_THRESHOLD_"))
client=distributed.Client(n_workers=1, memory_limit="2 GiB")
N=7_000S=160*1024a=dask.array.random.random(N*S//8, chunks=S//8)
a=a.persist()
distributed.wait(a)
dela
Result:
Managed: 0
Unmanaged: 151 MiB
Production Workaround
Set the env variable on the shell, before starting dask-worker:
I'm uncertain about how to solve this. The simple solution, to change os.environ in Nanny.__init__ instead of passing them down to Worker, would also mean poisoning the whole process of the nanny. This is annoying for unit tests but I'm not sure if anybody cares in production?
The alternative is to have an intermediate process which sets the variables and then invokes python again, which however is very expensive.
This issue also impacts the other two variables set by the config:
OMP_NUM_THREADS: 1
MKL_NUM_THREADS: 1
AFAIK, if for any reason numpy is imported on the worker process before the config, these two variables will not be picked up.
The simple solution, to change os.environ in Nanny.__init__ instead of passing them down to Worker, would also mean poisoning the whole process of the nanny
To be fair, all the environment variables we're currently talking about setting (malloc_trim and num_threads) basically don't have an impact unless they're set before the interpreter starts. So for these specifically, setting them in the Nanny shouldn't actually change anything in practice. I still dislike the uncleanliness of setting them in the Nanny process, though.
The alternative is to have an intermediate process which sets the variables and then invokes python again
Could also have a process-wide lock for Nanny, and set os.environ in the parent process while holding that lock, then reset it before releasing. You'd want to be clever and still allow subprocesses with the same env to be spawned in parallel, so dask-worker --nworkers=100 is still performant. Still not perfectly clean, but maybe an acceptable tradeoff between cleanliness and performance.
I meant having them set in the Nanny process won't really affect things for the Nanny itself. Guido and I don't like the poor hygiene of leaving them set on the Nanny, but I'm just noting that that poor hygiene shouldn't affect anything on the Nanny in practice because the particular variables we're setting only have an effect at interpreter startup/NumPy import time.
I was worried about potential user-defined variables, not the three we set. But I'm leaning towards not over-engineering this just to cover purely hypothetical use cases.
Reacted by Florian Jetter, Gabe Joseph and Riyad Parvez
I'm running a Jupyter Lab notebook in Ubuntu 22.04.2 LTS where unmanaged memory isn't being released. Dask is running using Coiled. After about 30 seconds of running my notebook, unmanaged memory appears and stays high for my long-running tasks. I have dask and distributed versions 2023.11.0 through conda-forge. I'm trying to follow the workaround here but having trouble with it.
When I include os.environ["MALLOC_TRIM_THRESHOLD_"] = str(dask.config.get("distributed.nanny.environ.MALLOC_TRIM_THRESHOLD_")) in my notebook import cell, I get KeyError: 'MALLOC_TRIM_THRESHOLD_'. How do I "Set the env variable on the shell, before starting dask-worker:" as mentioned above with
This seems to set the MALLOC_TRIM_THRESHOLD_ variable correction; after I create my client, I check the variable with client.run(os.getenv, "MALLOC_TRIM_THRESHOLD_")
@dagibbs22 the workarounds described above are very old. This issue was resolved in July 2022.
If you're unhappy with the default that dask sets you can change it through dask config:
This said, I'd be honestly surprised if tampering with the setting were to fix your issue, and if your unmanaged memory does disappear I would love to see your code.
Thanks, @crusaderky . I could tell the issue had been resolved but couldn't tell what it was...
Adding dask.config.set({"distributed.nanny.pre-spawn-environ.MALLOC_TRIM_THRESHOLD_: 1}) didn't reduce my unmanaged memory but did keep the model running despite high unmanaged memory. That's not great, either, but at least it demonstrates that I need to look somewhere else to trim my unmanaged memory. Why did you think (correctly) that changing MALLOC_TRIM_THRESHOLD_ wouldn't make my unmanaged memory disappear? Do you have other suggestions for how to keep unmanaged memory from accumulating?
Is unmanaged memory persisting while there are no tasks running? If it goes away, it's heap memory and you have to reduce the size of your chunks/partitions.
@crusaderky The old unmanaged memory gets up to about 6 GB in each worker when I run my notebook but drops to 2 GB per worker after the notebook finishes. Does 2 GB/worker count as "memory persisting while there are no tasks running"? Why would memory persist like that? Thanks.
Activity
crusaderky commentedon Mar 22, 2022
I'm uncertain about how to solve this. The simple solution, to change
os.environinNanny.__init__instead of passing them down to Worker, would also mean poisoning the whole process of the nanny. This is annoying for unit tests but I'm not sure if anybody cares in production?The alternative is to have an intermediate process which sets the variables and then invokes python again, which however is very expensive.
This issue also impacts the other two variables set by the config:
AFAIK, if for any reason numpy is imported on the worker process before the config, these two variables will not be picked up.
gjoseph92 commentedon Mar 22, 2022
Quite possible: #5729
To be fair, all the environment variables we're currently talking about setting (malloc_trim and num_threads) basically don't have an impact unless they're set before the interpreter starts. So for these specifically, setting them in the Nanny shouldn't actually change anything in practice. I still dislike the uncleanliness of setting them in the Nanny process, though.
Could also have a process-wide lock for
Nanny, and setos.environin the parent process while holding that lock, then reset it before releasing. You'd want to be clever and still allow subprocesses with the sameenvto be spawned in parallel, sodask-worker --nworkers=100is still performant. Still not perfectly clean, but maybe an acceptable tradeoff between cleanliness and performance.fjetter commentedon Mar 23, 2022
They'll be set before the worker process starts. the worker process is where it matters
gjoseph92 commentedon Mar 24, 2022
I meant having them set in the Nanny process won't really affect things for the Nanny itself. Guido and I don't like the poor hygiene of leaving them set on the Nanny, but I'm just noting that that poor hygiene shouldn't affect anything on the Nanny in practice because the particular variables we're setting only have an effect at interpreter startup/NumPy import time.
crusaderky commentedon Mar 25, 2022
I was worried about potential user-defined variables, not the three we set. But I'm leaning towards not over-engineering this just to cover purely hypothetical use cases.
dagibbs22 commentedon Dec 6, 2023
I'm running a Jupyter Lab notebook in Ubuntu 22.04.2 LTS where unmanaged memory isn't being released. Dask is running using Coiled. After about 30 seconds of running my notebook, unmanaged memory appears and stays high for my long-running tasks. I have
daskanddistributedversions 2023.11.0 through conda-forge. I'm trying to follow the workaround here but having trouble with it.When I include
os.environ["MALLOC_TRIM_THRESHOLD_"] = str(dask.config.get("distributed.nanny.environ.MALLOC_TRIM_THRESHOLD_"))in my notebook import cell, I getKeyError: 'MALLOC_TRIM_THRESHOLD_'. How do I "Set the env variable on the shell, before starting dask-worker:" as mentioned above withMy imports are currently:
This seems to set the MALLOC_TRIM_THRESHOLD_ variable correction; after I create my client, I check the variable with
client.run(os.getenv, "MALLOC_TRIM_THRESHOLD_")And get
But unmanaged memory still increases after about 30 seconds and stays high. That eventually causes my model to fail.
How do I trim the unmanaged memory? Thanks very much.
crusaderky commentedon Dec 8, 2023
@dagibbs22 the workarounds described above are very old. This issue was resolved in July 2022.
If you're unhappy with the default that dask sets you can change it through dask config:
This said, I'd be honestly surprised if tampering with the setting were to fix your issue, and if your unmanaged memory does disappear I would love to see your code.
dagibbs22 commentedon Dec 8, 2023
Thanks, @crusaderky . I could tell the issue had been resolved but couldn't tell what it was...
Adding
dask.config.set({"distributed.nanny.pre-spawn-environ.MALLOC_TRIM_THRESHOLD_: 1})didn't reduce my unmanaged memory but did keep the model running despite high unmanaged memory. That's not great, either, but at least it demonstrates that I need to look somewhere else to trim my unmanaged memory. Why did you think (correctly) that changingMALLOC_TRIM_THRESHOLD_wouldn't make my unmanaged memory disappear? Do you have other suggestions for how to keep unmanaged memory from accumulating?Ran test 50N_010E to completion on 10 years (2012-2021) but it failed…
crusaderky commentedon Dec 9, 2023
@dagibbs22 there are many causes for unmanaged memory, listed here: https://distributed.dask.org/en/stable/worker-memory.html#using-the-dashboard-to-monitor-memory-usage
Is unmanaged memory persisting while there are no tasks running? If it goes away, it's heap memory and you have to reduce the size of your chunks/partitions.
dagibbs22 commentedon Dec 15, 2023
@crusaderky The old unmanaged memory gets up to about 6 GB in each worker when I run my notebook but drops to 2 GB per worker after the notebook finishes. Does 2 GB/worker count as "memory persisting while there are no tasks running"? Why would memory persist like that? Thanks.
crusaderky commentedon Dec 18, 2023
Some of it will be logs. dask workers store log information in deques for forensic analysis. You can shorten them through the dask config: