Skip to content

[DO NOT MERGE] Resolve CI: torchaudio, dynamo FutureWarning, torch 2.13 named tensors - #2832

Open
bhimrazy wants to merge 39 commits into
Lightning-AI:mainfrom
bhimrazy:resolve-ci
Open

bhimrazy wants to merge 39 commits into
Lightning-AI:mainfrom
bhimrazy:resolve-ci

Conversation

@bhimrazy

@bhimrazy bhimrazy commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Combines three separate fixes into one branch so CI can be evaluated together, ahead of splitting back into #2829, #2830, #2831 individually:

  • Drop unused torchaudio from requirements/devel.txt — the pin (adjust-torch-versions.py tracking torch) is unsatisfiable at torch 2.14 (torchaudio stopped releasing in lockstep after 2.11), and nothing in the repo imports it.
  • Ignore the deprecated torch._dynamo.config.inline_inbuilt_nn_modules FutureWarning, which fires at import time from torch/testing/_internal/common_utils.py and breaks collection of all distributed tests.
  • Fix import thunder on PyTorch 2.13: named tensors were removed upstream, so Tensor.align_as, align_to, has_names, refine_names and rename are gone. Gates those five behind a new _TORCH_GREATER_EQUAL_2_13 constant.
Why combined

Each fix individually only exposes the next failure in the chain, making it hard to tell from CI alone whether a given fix works. This branch merges all three so we can see the full picture, then will be split back into the standalone PRs once confirmed.

Changes in this PR are currently in raw form. Glad that the CI is finally passing now.

torch 2.13 hard-removed the named tensor feature (pytorch/pytorch#173895), so
Tensor.align_as, align_to, refine_names and rename no longer exist. The auto
registration table in default_torch_ops.py dereferences them while building a
module-level dict literal, which makes `import thunder` fail outright there with
`AttributeError: type object 'Tensor' has no attribute 'align_as'`.

Gate those four entries on a new `_TORCH_GREATER_EQUAL_2_13` constant, and add
thunder/constants.py as a home for such checks, following litgpt/constants.py.
torch/testing/_internal/common_utils.py reads the deprecated
torch._dynamo.config.inline_inbuilt_nn_modules as a default argument, so the
FutureWarning fires when the module is imported. With `error::FutureWarning`
set, that fails collection of all the distributed tests.

Ignore that one message; every other FutureWarning stays fatal.
torchaudio is not imported anywhere in the repo, but CI's adjust-torch-versions.py
pins it in lockstep with torch. torchaudio stopped tracking torch releases after
2.11 ('compatible with future versions of torch'), so on torch 2.14 CI resolves
torchaudio==2.14.0, which does not exist, and every job fails at dependency
install before running a single test.
has_names went away in the same named-tensor removal as align_as/align_to/
refine_names/rename, so import still failed on torch 2.13 without it.
MANIFEST.in's `exclude *.toml` dropped pyproject.toml from the sdist. uv builds
the wheel from the sdist, so setuptools fell back to legacy setup.py-only
metadata there: the wheel came out named `thunder` (an unrelated PyPI project)
instead of `lightning-thunder`, and lost its summary, author, license and
dependencies. uv now rejects this outright:

  The source distribution declares name lightning-thunder, but the wheel
  declares name thunder

The published 0.2.6 sdist has the same gap, so installing it from source builds
a mis-named, dependency-less wheel.
torch 2.13 reimplemented Function.apply in C, so `Function.apply.__func__` no
longer exists and `import thunder` died at module scope.

Swapping in the bound `Function.apply` would not work: it is a
builtin_function_or_method, and `MyFunc.apply` is a fresh object that is neither
equal nor hash-equal to it, so the identity-keyed lookaside map would never match
a user's subclass and every autograd.Function would silently stop being traced.
The classmethod_descriptor on Function is the one object all subclasses share, so
that is the key.

The interpreter also has to reach it: its builtin-method unwrapping skipped the
case where __self__ is a class, leaving dispatch on the unusable bound object.
Handle builtin classmethods there by walking the class's own mro, which also
keeps the real provenance of __self__.
torch replaced args_tensor_mask with a required saved_for_backward_idx (plus an
optional dirty_idx) on the autograd_function_apply higher-order op, so
test_autograd_function_apply failed with KeyError: 'saved_for_backward_idx' on
torch >= 2.12.

The test helper now supplies it, and thunder's own symbol accepts both new kwargs
so the plain-callable path keeps working. Neither index changes what thunder
traces: they only drive ctx.mark_dirty/mark_non_differentiable, which thunder
does not model -- same as the non_differentiable_idx it already ignores.
thunder's forward-mode AD imports torch._decomp.decompositions_for_jvp, which
torch.jit.scripts its decompositions on import. With error::FutureWarning that
sank 133 tests in the grads suite on nightly.

The filter matches on a prefix so it covers both wordings torch emits: the plain
deprecation and the 'not supported in Python 3.14+' variant.
torch 2.14 rejects group_norm inputs with an empty channel or spatial extent
("Expected number of channels to be greater than 0", "Expected HxW to be greater
than 0"); 2.13 and earlier accepted them. The sample generator builds shapes from
(0, 2) in every dim, so it fed the torch reference inputs it now refuses. An empty
batch is still accepted, so only channels and the spatial dims are filtered.

Checked against 2.12.1, 2.13.0, 2.14.0 and 2.15.dev: the boundary is 2.14, not
2.13, hence the new constant.
The notebooks job installs with -U, which wants to replace PyJWT 2.7.0. That copy
came from the image's debian packages, so it has no RECORD file and uv refuses:

  error: uninstall-no-record-file
  Cannot uninstall PyJWT 2.7.0

--ignore-installed leaves it in place. The image shipping PyJWT via apt rather
than pip is the underlying issue.
@bhimrazy bhimrazy changed the title Resolve CI: torchaudio, dynamo FutureWarning, torch 2.13 named tensors [DO NOT MERGE] Resolve CI: torchaudio, dynamo FutureWarning, torch 2.13 named tensors Sep 15, 2026
--ignore-installed is global and takes no argument, so on the -U line it made pip
ignore every installed package, not just PyJWT. It pulled a stock torch 2.14.0
over the image's 2.12.0a0+git044db10 build, and nvfuser (compiled against that
torch, and version-stamped nvfuser_cu128_torch28) then failed to load:

  AssertionError: nvFuser is missing!
  xentropy_cuda ... undefined symbol: _ZN3c104cuda29c10_cuda_check_implementation...

Installing PyJWT on its own line gives it a RECORD, so the -U install that follows
can manage it normally and leaves the image's matched torch/nvfuser set alone.
The install resolved to TE 2.19, whose source build needs
torch/csrc/distributed/c10d/symm_mem/nccl_dev_cap.hpp -- a header torch 2.8.0
does not ship -- so the wheel build failed.

Building from source is unavoidable here: TE publishes torch2.8.0 wheels only for
cu129 and this image is cu128, so the URL it guesses always 404s. That makes the
source build the thing that has to work, and ep.cpp first includes that header in
2.17, so 2.16 is the last version that compiles against torch 2.8.
Three gaps that each mask the next, so all three are needed before any test moves:

- MappingKeysView had no __len__, while torch fx does len(user.users.keys())
  (torch/fx/graph.py). Added alongside isdisjoint as a plain interpreted method;
  __iter__/__reversed__ stay lookasides because they build wrapped iterators.

- wrap_args_from_list indexed and len()'d its argument, but the CALL_FUNCTION_EX
  handler two lines above only asserts Iterable -- f(*gen) is legal Python. It now
  falls back to interpreted iter/next for non-Sequences, the same way
  UNPACK_SEQUENCE already handles 'an Iterable, not necessarily a sequence'. That
  path can raise, so the caller propagates EXCEPTION_RAISED.

- Sequence.insert was a NotImplementedError stub. Implemented following append,
  with a LIST_INSERT provenance instruction beside LIST_APPEND/LIST_EXTEND.

Verified against eager: f(*gen), f(*d.keys()), f(*list), f(*tuple), and insert at
middle/front/past-end/negative indices.
Naming PyJWT explicitly only moved the failure along: with it handled, the -U
install hit the next apt-installed package the same way.

  error: uninstall-no-record-file
  Cannot uninstall cryptography 41.0.7

The image installs a set of Python packages through apt; none has a RECORD file,
so pip cannot uninstall any of them to make way for an upgrade. Rather than name
them one at a time, find every distribution with no RECORD and install a
pip-managed copy at the same version, so the install that follows can manage them
normally and versions do not otherwise change.

Custom builds are skipped by their local version segment (torch
2.12.0a0+git044db10, nvfuser_cu128_torch28): they are not on PyPI, and replacing
the image's torch is what broke nvfuser earlier.
torch.fx.GraphModule.recompile() writes `_code`/`_lineno_map` (and
`_in_spec`/`_out_spec` for pytree codegen) straight into the module's
__dict__. Thunder's attribute-modification tracking mistook these
codegen-cache internals for real module state and tried to pack them
into the epilogue trace, asserting when the value was a plain str/dict
instead of a Proxy. Fixes test_higher_order_inplace_alias_update
failing intermittently in CI depending on test order/dynamo cache
state.
Taking every RECORD-less distribution was too broad. The image's apt layer
also ships C extensions with no PyPI wheel, so pip tried to build them from
source and died on missing system headers:

  Preparing metadata (pyproject.toml) did not run successfully.
  meson.build:107:11: ERROR: Dependency "dbus-1" not found (tried pkg-config)

Only the packages the upgrade wants to replace need a pip-managed copy, so ask
pip for its plan up front with --dry-run --report and intersect that with the
RECORD-less set. That covers cryptography and PyJWT, the two that actually
failed, and leaves dbus-python and PyGObject alone.

Names are compared canonicalised, since the report and the installed metadata
disagree on case and separators (PyJWT vs pyjwt).
Both show up as test_higher_order_inplace_alias_update, which fails on every
core job -- macOS, Ubuntu and Windows, on torch latest and nightly alike.

The first is a crash. The object.__setattr__ lookaside makes an AnyProxy during
interpretation and parks it on provenance.proxy, which is the same slot the
prologue unpacker uses for its own cache. The unpacker short-circuits on it and
so emits no bsym and registers no param_ordering entry, and unpacking a guard
that reaches through a GraphModule then dies:

  KeyError: 5023200512   (jit_ext.py, from_load_attr)
  NotImplementedError: Exception occurred unpacking object from ProvenanceRecord(
    i5 = LOAD_ATTR(i4, 'nodes')
    i6 = LOAD_ATTR(i5, 'direction')
  )

Mark those proxies at the point they are made and unpack them properly. Keying
off "missing from param_ordering" instead looks equivalent but is not: it also
catches legitimately cached proxies and regresses test_aliased_input.

The second is a wrong answer the crash was hiding. Since torch 2.14 the fwd
subgraph returns its outputs as a tuple:

  return ((y,), (l_x_,))

so the caller does autograd_function_apply[0]. python_return(*sequencify(output))
unpacks a one-element tuple back to a bare tensor, which turns that subscript
into an index into the tensor -- slice to [1], squeeze to [], and thunderfx
quietly returns a scalar where eager and torch.compile both give f32[2]. Return
the structure the graph produced. Nothing changes for bare or multi-element
outputs, since return y1, y2 and return (y1, y2) are the same thing.
The dynamo splitter flattens a node's example_value to see whether anything in
it requires grad. On torch nightly that value can now be a FakeScriptObject,
the fake-mode stand-in for a ScriptObject, which tree_flatten rejects:

  TypeError: tree_flatten of type
    <class 'torch._library.fake_class_registry.FakeScriptObject'> is not supported.

taking out four DTensor and MoE tests in the distributed job. It is an opaque
object rather than a container, so list it alongside FunctionCtx: it flattens
to itself, and get_requires_grad then correctly finds no tensor in it.
bitsandbytes' cuda backend still calls torch._check_is_size, which torch
nightly now deprecates, and filterwarnings promotes it to an error:

  FutureWarning: _check_is_size will be removed in a future PyTorch release
  along with guard_size_oblivious. Use _check(i >= 0) instead.

That fails test_nvfuser_cse through thunder's quantization transform. Nothing
on our side calls it, so ignore it the way we already ignore torch.jit.script's.
TE 2.9 moved global_amax_buffer, global_scale_buffer and
global_amax_history_buffer off FP8GlobalStateManager onto a FP8GlobalState
dataclass held at .quantization_state, with no alias left behind:

  AttributeError: type object 'FP8GlobalStateManager' has no attribute
  'global_amax_buffer'

The CI pin now resolves TE 2.16, so the test hit it. Reach through
quantization_state when it is there and fall back to the class otherwise, so
this keeps working either side of 2.9.
The splitter resolves a call_method node against torch.Tensor and asserts if it
finds nothing. DTensor methods are not on torch.Tensor, and newer torch emits
them as plain call_method nodes rather than decomposing them into the
call_function prims the code below still describes, so the whole compile dies:

  AssertionError: Failed to find method redistribute

Fall back to DTensor, which puts redistribute and to_local back in reach.
Thunder already has symbols for both, so they resolve as supported rather than
merely stopping the crash. Unknown methods still trip the assert, and the
lookup stays safe when torch.distributed is unavailable, since DTensor is bound
to NoneType there.
cross_entropy under nvfuser is the slowest test in the suite and has now timed
out twice on the same dtype:

  240.24s call  test_core_vs_torch_consistency_cross_entropy_nvfuser_cuda_..float16
  185.17s call  ..float64

Sitting 0.24s over the limit with the next dtype 55s behind is a cap set too
low, not a flaky test: ops was the only job here capped at 240 while the rest
use 360, and it is the one running the slowest test with 9 workers sharing a
single GPU.
The wrapper expanded both operands before handing them to the prim. On a DTensor
that puts an aten.expand in front of the op, and sharding propagation either
rejects it outright on a sharded singleton dim:

  Sharding propagation failed for aten.expand.default(Spec(f32[4, 1](...)))

or resolves it to a placement the op's own rule would not have chosen, which is
what test_dtensor_opinfo_add_torch caught:

  AssertionError: DTensor placements do not match: (Shard(dim=0),) != (Shard(dim=1),)

Nothing needed the explicit broadcast: these ops broadcast natively in the meta,
through torch.add/torch.mul on fake tensors, and in execution through the same
functions. Sweeping add and mul over shape and placement combinations on a
single-rank CPU mesh, 114 cases in total:

  before   86 correct, 4 placement mismatches, 24 spurious failures
  after   114 correct, 0 placement mismatches, 0 spurious failures
torch 2.12 reimplemented the DTensor placements in C++, so Shard, Replicate and
Partial now expose __init__ as a builtin bound to a pybind11 function_record
rather than to the instance being built. _type_call_lookaside took it for an
ordinary bound method, overwrote its __self__ and called it, which lands in the
record's own tp_init. That slot throws a bare C++ exception rather than raising
through Python, so it never reaches the except clause and takes the process out:

  terminate called after throwing an instance of 'std::runtime_error'
    what():  UNEXPECTED CALL OF function_record_PyTypeObject_methods::tp_init_impl
  SIGABRT(6)

Build these in a single opaque call, the way the interpreter already handles
opaque callables, keeping the OPAQUE provenance record.

The test that caught this, test_dtensor_from_local_symbolic_values, reproduces
on a single-rank CPU mesh, and afterwards reports the one cache miss and one
cache hit it asserts.

Ordinary types keep the existing path: only a pybind11 __init__ is a
builtin_function_or_method, where dict, list, torch.device, torch.Size,
torch.Tensor and plain Python classes all give a wrapper_descriptor or function.
The DTensor tests build their reference with plain torch.compile, and on this
image inductor's static Triton launcher dies there:

  File "torch/_inductor/runtime/static_triton_launcher.py", line 291, in run
    self.C_impl._launch_kernel(
  RuntimeError: CUDA driver error: invalid argument

The stack is entirely torch, from torch.compile through aot_autograd into
inductor, so thunder is never reached; when a rank goes down this way its peer
is left waiting on a collective until the 300s timeout. Fall back to triton's
own launcher until the image's torch and triton pairing is fixed.
torch._library.opaque_object keeps its custom-class registry in a
WeakKeyDictionary and the DTensor placements are registered in it, so
torch's FX codegen looking one up puts a weakref into the provenance
chain. should_register_for_prologue assumed every callable constant was
a function and died on .__name__.

Such a callable is neither __getitem__ nor GetSetDescriptorType.__get__,
which are the only two a prologue can unpack, so a missing __name__ just
takes the existing reject branch.
Switching to triton's own launcher only changed the message: the same
three DTensor mul tests fail at the same point, now with "Pointer
argument (at 0) cannot be accessed from Triton (cpu tensor?)" instead of
"CUDA driver error: invalid argument". The launcher was not the cause,
so drop the override rather than carry an env var that claims a fix.
Newer torch puts a DeviceMesh into the graph as a FakeScriptObject
rather than the mesh itself. Three places in the splitter rejected it:
make_input_proxy raised, so every node taking a mesh became a split
reason, and the placeholder round-trip raised outright.

Treat it as the opaque value it is. It is its own metadata, and the
object it wraps is what the ops and the proxy check want, so hand that
over when an input is needed.
Accepting a FakeScriptObject as its own example-input metadata let a graph
taking a DeviceMesh reach the reproducer writer, where arg_like raised
TypeError: it only knew how to write tensors, numbers and sequences. A
DeviceMesh has no source form to write down -- it needs a live process
group -- so there is nothing arg_like could emit that would run.

Write None for it, and for an input that could not be inferred, which is
what the warning above the input list already promises. The script is then
one the reader can finish rather than one that cannot be generated at all.

That warning was being built and then dropped: the non-serialized branch
assigned over input_str instead of appending to it, so the holes arrived
unexplained. Append, and raise the warning for a FakeScriptObject too.
test_dtensor_basic_op built its reference with torch.compile, which put
an inductor Triton launch in front of a test that is about thunder's
lowering of the op over a DTensor. On the nightly image that launch
fails:

  torch/_inductor/runtime/static_triton_launcher.py:291
  RuntimeError: CUDA driver error: invalid argument

The stack is entirely torch and thunder is never reached. The three
failures are exactly the three entries of functions_to_test, and they
are the only DTensor tests that compile their reference; the opinfo
tests compare against eager and pass.

Eager is the better oracle here in any case. On a two-rank gloo mesh
the compiled and eager references agree on values, gradients,
placements and local strides for all three functions, so what is given
up is coverage of inductor, not of the op.
set_device ran after init_process_group, so the group was created
before the rank owned a device. NCCL then infers the rank to GPU
mapping from the first collective and warns that the device "is
currently unknown", noting that the guess can cause a hang.

Set the device first and name it with device_id. The parameter is
documented as far back as torch 2.7, comfortably inside the 2.7.1
floor in requirements/base.txt.
A rank stuck in NCCL takes its test down with "Process N terminated or
timed out" and no retrievable traceback, which says little about the
code under test. One such hang, in
test_reduce_scatter_executor_torch_dim0_inplace_False, failed an
otherwise green run.

Retry only that signature. --only-rerun matches against the exception
text, so an assertion failure still fails on its first attempt.

The retry is best effort rather than a clean second attempt:
_join_processes sends SIGTERM and raises without joining, so a rank
stuck in an uninterruptible CUDA call may still hold its GPU when the
next attempt starts on the same device.
Six tests crash their xdist worker on windows-latest, intermittently
across runs but always the same six:

  Windows fatal exception: code 0xc000001d
  [gw1] node down: Not properly terminated

0xc000001d is an illegal instruction, not a stack overflow; faulthandler
prints "stack overflow" by name for that one and printed the raw code
here. Nothing in the interpreter can raise it either, since it walks
existing code objects rather than building new ones, so the fault is in
native code.

torch's Windows CPU wheels carry AVX-512 instructions inside kernels
compiled for the AVX2 target, and they fault on a runner whose CPU stops
at AVX2, which is why this comes and goes with the machine the job lands
on. The reported fault sits in the bfloat16 vector helpers, and that
matches what fails here exactly: test_nanogpt and test_litgpt run their
model in bfloat16, while test_nanogpt_complete, a larger model on the
same ops in float32, passes.

Ask for the unvectorized kernels on Windows. Measured on a bfloat16
matmul and gelu, the capability makes no appreciable difference, since
matmul goes to MKL rather than these kernels.

Also print the capability, so a green run can be told apart from one
that happened to land on a CPU the bad kernels do not fault on.

Ref: pytorch/pytorch#145702
Six tests intermittently kill their xdist worker on windows-latest:

  Windows fatal exception: code 0xc000001d
  [gw1] node down: Not properly terminated

Always the same six, on about half the runs, on released and nightly
torch alike. The branch has also passed with the same torch on other
runs, so this follows the machine the job lands on rather than anything
in the tree.

The fault is in eager torch, not in our code. For test_litgpt the
innermost frames are torch's Linear.forward, reached from the plain
reference call before the jitted function is ever invoked; the
interpreter variants fault in the same place through the opaque-call
path. What separates these six from the tests that pass is bfloat16:
test_litgpt_variants builds the same gpt-neox-like model in float32 and
passes in the same runs that crash test_litgpt.

The previous attempt set ATEN_CPU_CAPABILITY=default on Windows, on the
theory that the wheels carry AVX-512 instructions inside kernels
compiled for the AVX2 target. A diagnostic step confirmed the capability
applied and all six still crashed, so the fault is not in ATen's
ISA-dispatched kernels. That does not clear the wider codegen issue in
pytorch/pytorch#145702: bfloat16 matmuls go to oneDNN or MKL, which pick
their own instruction set and ignore that variable. Those paths are
untested, so the workflow change is reverted rather than extended.

Skip the six on Windows until this is understood. Nothing in process can
retry them: the worker dies outright and pytest only sees a dead
channel, so rerunfailures and xfail both have nothing to act on.
test_native_fsdp timed out after 900s on the pt_2.8.0-dev job, with both
ranks reporting:

  using GPU 0 as device used by this process is currently unknown. This
  can potentially cause a hang if this rank to GPU mapping is incorrect.
  You can specify device_id in init_process_group() to force use of a
  particular device.

That is the warning an earlier commit here removed from
DistributedParallelTestCase._run, but these tests do not go through it.
test_native_ddp and test_native_fsdp spawn their ranks through a
multiprocessing pool and set up their own group in
init_per_process_distributed, which called init_process_group with no
device bound and no device_id. One caller sets the device afterwards,
which is already too late; the rest never set it.

Bind the device and pass device_id there as well, so both spawn paths
agree. The CPU backend keeps device_id=None.

This does not prove the timeout was that mapping, and the test can still
hang for another reason. It does remove the condition torch names as a
cause of exactly this.
Comment on lines +75 to +77
# cross_entropy under nvfuser is the slowest test here and landed right on the old 240s cap
# (240.24s, vs 185s for its next-slowest dtype) with 9 workers sharing the one GPU. 360s
# matches every other job in this file.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can put this in the PR description, not needed here

Suggested change
# cross_entropy under nvfuser is the slowest test here and landed right on the old 240s cap
# (240.24s, vs 185s for its next-slowest dtype) with 9 workers sharing the one GPU. 360s
# matches every other job in this file.

Comment on lines +2486 to +2488
def __len__(self):
return len(self.mapping)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this code used anywhere? i don't see

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants