Skip to content

[PyTorch] TE 2.17 Userbuffers initialization crashes in CommOverlapP2P with a GIL/refcount error #3199

Description

@cuichenx

[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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions