[PyTorch] TE 2.17 Userbuffers initialization crashes in CommOverlapP2P with a GIL/refcount error
Describe the bug
Upgrading only Transformer Engine from 2.16 to 2.17 causes the public PyTorch initialize_ub() path to abort while constructing its first CommOverlapP2P communicator. The failure occurs with the legacy Userbuffers backend (with_cublasmp=False) before training starts:
!!! [UBP2P] UBuf 1
pybind11::handle::inc_ref() is being called while the GIL is either not held or invalid.
The failing pybind11::handle::inc_ref() call was triggered on a tuple object.
munmap_chunk(): invalid pointer
Fatal Python error: Aborted
The second process subsequently reports:
Segfault encountered
cfree
pybind11::class_<CommOverlapP2P, std::shared_ptr<CommOverlapP2P>, ...>
::dealloc_without_manipulating_gil(...)
This is deterministic in two CI attempts with TE 2.17. The same test initializes all Userbuffers communicators and passes with TE 2.16.
Steps/Code to reproduce bug
The regression is reproduced by Megatron-Bridge PR #4696, which changes only the TE source pin and lockfile:
From that PR checkout, run the two-rank recipe test:
uv run python -m torch.distributed.run --nproc_per_node=2 --nnodes=1 \
-m pytest -v -s -x \
tests/functional_tests/test_groups/recipes/test_nemotronh_recipes_pretrain.py
The first two TP=1 cases pass. The Nemotron 3 Nano case sets tensor_model_parallel_size=2, sequence_parallel=True, and tp_comm_overlap=True, then aborts in:
transformer_engine/pytorch/module/base.py:448 in add_ub
transformer_engine/pytorch/module/base.py:538 in initialize_ub
CI evidence:
The failure uses the documented default with_cublasmp=False; it is not a cuBLASMp execution path.
Expected behavior
initialize_ub() should construct the TP-overlap communicators without touching Python reference counts while the GIL is released. Behavior should match TE 2.16, where all Userbuffers communicators initialize and training proceeds.
Environment overview (please complete the following information)
- Environment location: Docker on AWS GitHub Actions GPU runners (
nemo-ci-aws-gpu-x2)
- Transformer Engine install: Git source pin resolved by
uv
- Test launch: two local processes / two visible GPUs
- Userbuffers backend:
with_cublasmp=False, bootstrapped through torch.distributed
Environment details
- OS: Linux container
- Python: 3.12.3
- PyTorch: 2.12.1
- Passing Transformer Engine:
2.16.0+d64bc14d
- Failing Transformer Engine:
2.17.0+2e559f06
- The full container and runner metadata is in the linked jobs.
Device details
- Two-GPU AWS CI runner; the launcher selects the H100 BF16 recipe configuration.
Additional context
The likely source-level regression is 815bf369e60f5d56ef20badd36b6aa5e359adb48 / PR #2443. That change replaced the direct py::init<...> bindings for both CommOverlap and CommOverlapP2P with factory lambdas returning std::shared_ptr, while retaining the outer py::call_guard<py::gil_scoped_release>():
The CI failure is directly observed in CommOverlapP2P. CommOverlap has the same new factory/guard pattern, so it may have the same lifetime hazard even though this run reaches the P2P factory first.
My source-level suspicion is that the outer call guard leaves the GIL released while pybind11 converts/wraps the factory return. A candidate fix would retain the GIL across the factory return boundary and, if construction needs to run without it, use a narrower py::gil_scoped_release only around std::make_shared, allowing the GIL to be reacquired before returning to pybind11. This hypothesis still needs confirmation with a patched TE build and a two-rank regression test.
[PyTorch] TE 2.17 Userbuffers initialization crashes in
CommOverlapP2Pwith a GIL/refcount errorDescribe the bug
Upgrading only Transformer Engine from 2.16 to 2.17 causes the public PyTorch
initialize_ub()path to abort while constructing its firstCommOverlapP2Pcommunicator. The failure occurs with the legacy Userbuffers backend (with_cublasmp=False) before training starts:The second process subsequently reports:
This is deterministic in two CI attempts with TE 2.17. The same test initializes all Userbuffers communicators and passes with TE 2.16.
Steps/Code to reproduce bug
The regression is reproduced by Megatron-Bridge PR #4696, which changes only the TE source pin and lockfile:
2.16.0+d64bc14datd64bc14dc87eb658ab98839e4b7687595ee53e2d2.17.0+2e559f06at2e559f062497bef768dfbe9d7e45548fadeca80aFrom that PR checkout, run the two-rank recipe test:
The first two TP=1 cases pass. The Nemotron 3 Nano case sets
tensor_model_parallel_size=2,sequence_parallel=True, andtp_comm_overlap=True, then aborts in:CI evidence:
CommOverlapP2Pdeallocation stackThe failure uses the documented default
with_cublasmp=False; it is not a cuBLASMp execution path.Expected behavior
initialize_ub()should construct the TP-overlap communicators without touching Python reference counts while the GIL is released. Behavior should match TE 2.16, where all Userbuffers communicators initialize and training proceeds.Environment overview (please complete the following information)
nemo-ci-aws-gpu-x2)uvwith_cublasmp=False, bootstrapped throughtorch.distributedEnvironment details
2.16.0+d64bc14d2.17.0+2e559f06Device details
Additional context
The likely source-level regression is
815bf369e60f5d56ef20badd36b6aa5e359adb48/ PR #2443. That change replaced the directpy::init<...>bindings for bothCommOverlapandCommOverlapP2Pwith factory lambdas returningstd::shared_ptr, while retaining the outerpy::call_guard<py::gil_scoped_release>():CommOverlapfactory and call guardCommOverlapP2Pfactory and call guardThe CI failure is directly observed in
CommOverlapP2P.CommOverlaphas the same new factory/guard pattern, so it may have the same lifetime hazard even though this run reaches the P2P factory first.My source-level suspicion is that the outer call guard leaves the GIL released while pybind11 converts/wraps the factory return. A candidate fix would retain the GIL across the factory return boundary and, if construction needs to run without it, use a narrower
py::gil_scoped_releaseonly aroundstd::make_shared, allowing the GIL to be reacquired before returning to pybind11. This hypothesis still needs confirmation with a patched TE build and a two-rank regression test.