Skip to content

Fix "Too many open files" in QNN DeepLabV3 example - #21874

Draft
mcollinswisc wants to merge 1 commit into
pytorch:mainfrom
mcollinswisc:qnn-deeplab-v3-dataset-fds
Draft

Fix "Too many open files" in QNN DeepLabV3 example#21874
mcollinswisc wants to merge 1 commit into
pytorch:mainfrom
mcollinswisc:qnn-deeplab-v3-dataset-fds

Conversation

@mcollinswisc

@mcollinswisc mcollinswisc commented Aug 16, 2026

Copy link
Copy Markdown

Summary

Fixes #21870

Avoids exhausting the process limit of file descriptors by sampling indices, and loading only the sample. This avoids materializing the full VOC val dataset with list(...).

Test plan

Since this is just editing an example Python script, we tested it against a built executorch installed from pip too. With
CWD in a working copy pointed to this branch:

python3 -m venv /tmp/dlv3-venv
. /tmp/dlv3-venv/bin/activate
pip install \
    --index-url https://download.pytorch.org/whl/cpu \
    --extra-index-url https://pypi.org/simple \
     executorch==1.4.1 torchvision py-cpuinfo transformers pydot

# ulimit -n 1024   # Only needed to repro the error in a shell that has a different default soft limit
unset QNN_SDK_ROOT
export QNN_SDK_ROOT="$(python backends/qualcomm/scripts/download_qnn_sdk.py --print-sdk-path)"
export LD_LIBRARY_PATH="$QNN_SDK_ROOT/lib/x86_64-linux-clang:$LD_LIBRARY_PATH"

# Runs the example python script from this working copy
python ./examples/qualcomm/scripts/deeplab_v3.py \
    --build_folder build-x86 --soc_model SM8550 \
    --artifact ./dlv3 --compile_only --download

Run at the main branch (or with python -m executorch.examples.qualcomm.scripts.deeplab_v3 ... this fails with:

...
  File "/tmp/dlv3-venv/lib/python3.14/site-packages/torchvision/datasets/voc.py", line 155, in __getitem__
    img = Image.open(self.images[index]).convert("RGB")
          ~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  File "/tmp/dlv3-venv/lib/python3.14/site-packages/PIL/Image.py", line 3639, in open
    fp = builtins.open(filename, "rb")
OSError: [Errno 24] Too many open files: './dlv3/voc_image/VOCdevkit/VOC2012/JPEGImages/2010_001448.jpg'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/mcollins/soft/executorch/./examples/qualcomm/scripts/deeplab_v3.py", line 200, in <module>
    raise Exception(e)
Exception: [Errno 24] Too many open files: './dlv3/voc_image/VOCdevkit/VOC2012/JPEGImages/2010_001448.jpg'

after this change it succeeds & writes the .pte file:

python ./examples/qualcomm/scripts/deeplab_v3.py     --build_folder build-x86 --soc_model SM8550     --artifact ./dlv3 --compile_only --download
[QNN] Using QNN SDK at /home/mcollins/.cache/executorch/qnn/sdk-2.37.0.250724 (from QNN_SDK_ROOT)
W0815 21:36:10.026000 308359 torch/utils/_pytree.py:630] <enum 'KernelPreference'> is an Enum subclass and is now natively supported by torch.compile as an opaque value type. Calling register_constant() on Enum subclasses is deprecated and will be an error in a future release.
[WARNING 2026-08-15 21:36:11,241 backend_opinfo_adapter.py:67] The backend_opinfo module couldn't be imported, so the abstract implementation will be used instead. This might be because $QNN_SDK_ROOT/lib/python isn't included in your PYTHONPATH, or the `BackendOpInfo` API isn't available in your QNN SDK version. Note that the `BackendOpInfo` API is supported starting from QNN SDK 2.41 and above.
[INFO 2026-08-15 21:36:11,786 export_utils.py:172] Using parser's config
[INFO 2026-08-15 21:36:43,104 model.py:41] loading deeplabv3_resnet101 model
[INFO 2026-08-15 21:36:43,636 model.py:45] loaded deeplabv3_resnet101 model
[WARNING 2026-08-15 21:36:44,895 pass_manager.py:57] PassManager is deprecated. Please use ExportedProgramPassManager instead.
No quant config is implemented for op, aten.dropout.default
No quant config is implemented for op, aten.dropout.default
/usr/lib/python3.14/copyreg.py:104: FutureWarning: `isinstance(treespec, LeafSpec)` is deprecated, use `isinstance(treespec, TreeSpec) and treespec.is_leaf()` instead.
  return cls.__new__(cls, *args)
Quantizing(PTQ) the model...
...
[INFO 2026-08-15 21:29:47,581 qnn_preprocess.py:78] Visiting: aten_permute_copy_default_2, aten.permute_copy.default
[INFO 2026-08-15 21:29:47,581 qnn_preprocess.py:78] Visiting: quantized_decomposed_dequantize_per_tensor_default_1, quantized_decomposed.dequantize_per_tensor.default

====== DDR bandwidth summary ======
spill_bytes=6553600
fill_bytes=6553600
write_total_bytes=14983168
read_total_bytes=68526080

[INFO] [Qnn ExecuTorch]: Destroy Qnn context
[WARNING 2026-08-15 21:29:48,878 _program.py:1029] Op aten.unbind.int was requested for preservation by partitioner.  This request is ignored because it aliases output.
[INFO] [Qnn ExecuTorch]: Destroy Qnn device
[INFO] [Qnn ExecuTorch]: Destroy Qnn backend

### Summary

`deeplab_v3.py --download` dies partway through calibration with
`OSError: [Errno 24] Too many open files` on any host whose soft
descriptor limit is the usual 1024 -- a stock Docker container, a systemd
service and an ordinary login shell all get that.

`get_dataset` materializes all 1449 VOC 2012 val samples with `list()` so
that `random.shuffle` can pick 100 of them. torchvision's
`VOCSegmentation.__getitem__` calls `.convert("RGB")` on the image, which
forces `load()` and lets Pillow close the file it opened, but it leaves
the mask as a lazy `Image.open`. The example sets `transform` and not
`target_transform`, so nothing ever reads the mask pixels and every
retained sample keeps a `SegmentationClass/*.png` descriptor open -- one
per sample, until the limit is reached and the run dies. The file named
in the error is a JPEG only because that is whichever `open()` happened
to tip it over.

Sampling the index range instead never holds more than the sample being
converted, and peak descriptors across `get_dataset` drop from 1454 to 6.
Setting `target_transform` would also stop the leak, but it treats the
symptom and keeps the waste, since `list()` still decodes, resizes,
normalizes and retains 1449 images in order to use 100. `random.sample`
over the index range and shuffle-then-take-100 draw from the same
distribution, and nothing under `examples/qualcomm/` seeds `random`, so
which 100 you get is exactly as reproducible as it was before. This also
brings the script in line with `get_imagenet_dataset` in
`examples/qualcomm/utils.py` and with the oss_scripts examples, none of
which materialize the dataset.

Fixes pytorch#21870

### Test plan

No device and no source build are needed. The 1.4.0 wheel ships
`examples/qualcomm/`, its copy of this script is byte-identical to the
file before this patch, and the backend downloads the pinned QNN SDK
itself on first import. Starting from nothing:

    python3 -m venv dlv3-venv
    . dlv3-venv/bin/activate
    pip install \
        --index-url https://download.pytorch.org/whl/cpu \
        --extra-index-url https://pypi.org/simple \
        executorch==1.4.0 torchvision==0.28.0 py-cpuinfo transformers pydot

    ulimit -n 1024   # the stock soft limit; some shells raise it already
    export QNN_SDK_ROOT=$HOME/.cache/executorch/qnn/sdk-2.37.0.250724
    export LD_LIBRARY_PATH=$QNN_SDK_ROOT/lib/x86_64-linux-clang:$LD_LIBRARY_PATH

    # before: the wheel's own copy
    python -m executorch.examples.qualcomm.scripts.deeplab_v3 \
        --build_folder build-x86 --soc_model SM8550 \
        --artifact ./dlv3 --compile_only --download

    # after: this checkout's copy, against the same installed wheel
    python <checkout>/examples/qualcomm/scripts/deeplab_v3.py \
        --build_folder build-x86 --soc_model SM8550 \
        --artifact ./dlv3 --compile_only --download

`py-cpuinfo`, `transformers` and `pydot` are imported on the way to the
example but are not dependencies of the wheel. `--compile_only` exits
after writing the .pte, so no phone is involved, and `--build_folder` is
asserted by `QnnConfig` but never used on that path. Running the second
command by path rather than with `-m` puts the script's own directory on
`sys.path` and not the checkout root, so the two runs differ only in this
file.

The first died in `get_dataset` with `Exception: [Errno 24] Too many open
files` -- the top-level handler re-raises bare, which hides the errno
class from anything reading only the tail of a log -- after 34 min,
nearly all of it the 2 GB VOCSegmentation download. The second reused
that download and completed in 2 min 12 s, exit 0, writing a
61,732,608-byte `dlv3_qnn.pte`. No two runs produce the same bytes,
patched or not, because the calibration draw is unseeded.

Descriptors were counted from /proc/self/fd around `get_dataset` against
a generated 1449-sample stand-in for VOC 2012 val: 8x8 images in the same
layout, since nothing in the mechanism depends on their contents. Peak
held was 1454 before (1449 masks plus the process's own five) and 6
after. At `ulimit -n 1024` the unpatched code stops at 1022 descriptors
and the patched code completes. Where both complete they agree, on 100
inputs of (1, 3, 224, 224) and 100 targets of (224, 224) uint8, and both
return all 50 when asked for 100 from a 50-sample set.

Measured with Python 3.14.4, executorch 1.4.0+cpu, torch 2.13.0+cpu,
torchvision 0.28.0+cpu and Pillow 12.3.0.

Authored with Claude Code (Claude Opus 5).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RyGcV8dicAd91uZPFUCLA9
@pytorch-bot

pytorch-bot Bot commented Aug 16, 2026

Copy link
Copy Markdown

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/21874

Note: Links to docs will display an error until the docs builds have been completed.

⚠️ 16 Awaiting Approval

As of commit ac4b743 with merge base 42ebbc3 (image):

AWAITING APPROVAL - The following workflows need approval before CI can run:

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla

meta-cla Bot commented Aug 16, 2026

Copy link
Copy Markdown

Hi @mcollinswisc!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@linux-foundation-easycla

linux-foundation-easycla Bot commented Aug 16, 2026

Copy link
Copy Markdown

CLA Signed
The committers listed above are authorized under a signed CLA.

  • ✅ Unknown (ac4b743)
  • ✅ login: mcollinswisc / name: Maxwell D Collins (ac4b743)

One or more co-authors of this pull request were not found. You must specify co-authors in commit message trailer via:

Co-authored-by: name <email>

Supported Co-authored-by: formats include:

  1. Anything <id+login@users.noreply.github.com> - it will locate your GitHub user by id part.
  2. Anything <login@users.noreply.github.com> - it will locate your GitHub user by login part.
  3. Anything <public-email> - it will locate your GitHub user by public-email part. Note that this email must be made public on Github.
  4. Anything <other-email> - it will locate your GitHub user by other-email part but only if that email was used before for any other CLA as a main commit author.
  5. login <any-valid-email> - it will locate your GitHub user by login part, note that login part must be at least 3 characters long.

Alternatively, if the co-author should not be included, remove the Co-authored-by: line from the commit message.

Please update your commit message(s) by doing git commit --amend and then git push [--force] and then request re-running CLA check via commenting on this pull request:

/easycla

@github-actions

Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

@mcollinswisc

Copy link
Copy Markdown
Author

/easycla

@meta-cla

meta-cla Bot commented Aug 16, 2026

Copy link
Copy Markdown

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

QNN DeepLabV3 example opens too many files

1 participant