Skip to content

Pytorch backend - #540

Merged
TApplencourt merged 14 commits into
argonne-lcf:develfrom
DonAurelio:pytorch-backend
Sep 14, 2026
Merged

TApplencourt merged 14 commits into
argonne-lcf:develfrom
DonAurelio:pytorch-backend

Conversation

@DonAurelio

@DonAurelio DonAurelio commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Adds a pytorch backend to THAPI that traces PyTorch RecordFunction (forward + backward) ops. Enable it explicitly:

iprof --backends pytorch --no-analysis -- python model.py

Pending follow-up work (not in this PR):

  • Default backend. Currently must be explicitly requested with --backends pytorch, and requires --no-analysis since there's no analysis-side decoder for this backend yet. Making it a default backend depends on adding that.
  • Second calling convention via environment variables. iprof -- python3 model.py (active torch env) works; iprof -- venv/bin/python3 module.py (non-active env) doesn't yet — needs env vars to let the target interpreter be specified explicitly.
  • ABI version validation. The tracer hand-copies a private PyTorch struct layout for the version it was built against; a version mismatch would silently corrupt tracing rather than fail loudly. Planned fix is a version allow-list checked at load time.

Build: suppress two clang-only warnings in the pytorch tracer

tracer_pytorch.cpp is the only backend translation unit that calls LTTng's tracepoint() macro from C++ instead of C (every other backend does so from a .c file). That exposes two warnings that only clang raises, both false positives.

Before:

libTracerPytorch_la_CXXFLAGS = -std=c++17 -Wall -Wextra -Wno-unused-parameter $(WERROR) $(LTTNG_UST_CFLAGS)

After:

libTracerPytorch_la_CXXFLAGS = -std=c++17 -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unused-private-field $(WERROR) $(LTTNG_UST_CFLAGS)
  • -Wno-missing-field-initializers — LTTng's tracepoint.h initializes some of its internal structs with a designated initializer that only names one field (e.g. .struct_size = ...), leaving the rest to their default zero value — a standard, safe C idiom. C accepts this silently; C++ (under clang) does not, and treats it as a missing-initializer warning. Every other backend includes this same header from a .c file compiled as C, so they never trigger it; tracer_pytorch.cpp is the only backend source that pulls it into a C++ translation unit.
  • -Wno-unused-private-fieldRecordFunctionCallback in our hand-written record_function.h declares private fields (start_, end_, sampling_prob_, etc.) that our own code never reads. That's intentional: this struct is passed by value into PyTorch's real addGlobalCallback, so its memory layout must match PyTorch's real class byte-for-byte — the fields are read by libtorch on the other side of that call, not by us. Clang can't see that and flags them as dead; gcc doesn't have this check at all.

Aurelio Vivas and others added 14 commits September 9, 2026 21:15
Copies the transitive header closure of ATen/record_function.h (605
files) from Aurora's `module load frameworks` PyTorch install
(2.10.0a0), following the same vendoring pattern hip/ze/mpi use for
their real API headers. Verified standalone compile against these
headers with the POC tracer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pytorch_events.yaml declares op_entry/op_exit (cxi-style hand-written
events, no API model needed since we only trace two RecordFunction
callbacks). tracer_pytorch.cpp adapts the POC tracer to fire these as
LTTng-UST tracepoints instead of printing to stdout, registering via
at::addGlobalCallback at library load time so it works under
LD_PRELOAD with no application changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Generates the lttng_ust_pytorch tracepoint provider from
pytorch_events.yaml (cxi-style, via gen_custom_probes.rb), builds it
into libpytorchtracepoints.la, and links libTracerPytorch.la against
it plus two throwaway dummy libtorch_cpu.so/libc10.so (built with
`-x c++ /dev/null`, matching the POC's build.sh) so the linker can
resolve -ltorch_cpu/-lc10 without a real torch install at THAPI
build time. The dummy libs are removed right after each link
(all-local, install-exec-hook) and again by clean-local, using
explicit file removal + rmdir rather than a recursive delete.

Validated against the real THAPI Autotools build (autogen.sh,
configure, make, make install, make clean) using the
thapi-tracer-environment-config/-build module set on Aurora, and
confirmed end-to-end: the installed libTracerPytorch.so, LD_PRELOADed
onto the real `frameworks` module's PyTorch, records a genuine LTTng
session that babeltrace2 reads back as well-formed op_entry/op_exit
events.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds backends/pytorch to SUBDIRS and its Makefile/tracer script to
AC_CONFIG_FILES, matching every other backend. Also adds
tracer_pytorch.sh.in, the standalone manual-usage wrapper (omp/itt
style): it creates its own LTTng session, enables lttng_ust_pytorch,
locates the active python3's torch lib directory to satisfy
libTracerPytorch.so's runtime dependency on libtorch_cpu.so/libc10.so,
and LD_PRELOADs it.

Validated with a full `autogen.sh && configure && make -j install` of
the whole tree (all existing backends plus pytorch) on Aurora, then
ran tracer_pytorch.sh directly against the `frameworks` module's real
PyTorch: the model runs forward+backward and babeltrace2 reads back
1708 well-formed op_entry/op_exit events from the recorded trace.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds enable_events_pytorch (enables lttng_ust_pytorch:*, mirroring
itt/hip) and an all_env_tracers block that, when `pytorch` is
requested via --backends, probes `python3 -c "import torch..."` for
the active interpreter's torch lib directory and sets
LD_LIBRARY_PATH/LD_PRELOAD accordingly so libTracerPytorch.so resolves
its libtorch_cpu.so/libc10.so dependency at runtime. When torch isn't
importable, it warns and leaves the backend disabled rather than
failing iprof.

This completes the goal for this stage: `iprof --backends pytorch
--no-analysis -- python model.py` now records a trace, and babeltrace2
reads it back as well-formed op_entry/op_exit events (verified: 1708
events from the POC model's forward+backward pass, on Aurora's real
`frameworks` PyTorch). The default --backends path (pytorch not
requested) is unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
tracer_pytorch.cpp only touches five names from <ATen/record_function.h>:
RecordScope, ObserverContext, RecordFunction::name(),
RecordFunctionCallback, and addGlobalCallback. Everything else in the
previously-vendored 605-file/4.3MB closure (ATen/core, ATen/ops, c10/*,
torch/headeronly) was pulled in transitively only because the real
record_function.h needs it to typecheck, not because our tracer calls
into it. Replaces backends/pytorch/include/ with a single ~90-line
ATen/record_function.h declaring only those five names.

Advantages:
  - 605 files / 4.3MB -> 1 file / ~2.5KB: nothing to keep in sync with
    an upstream torch checkout, no per-release re-extraction step.
  - Self-documents the tracer's actual API surface: the whole contract
    with libtorch is visible in one small file instead of implied by
    a large, mostly-unused header tree.
  - Verified equivalent at compile, link, and runtime: builds clean
    under -Wall -Wextra -Werror, links against the same build-time
    dummy libtorch_cpu.so/libc10.so stubs, and produces byte-identical
    output when LD_PRELOADed onto the real `frameworks` module's
    PyTorch -- babeltrace2 reads back the same 1708 op_entry/op_exit
    events from the POC model's forward+backward pass as the
    real-header build did.

Risks, and why they're judged acceptable here:
  - RecordFunction is declared as a bare method signature with no
    fields: safe, because we only ever receive `const RecordFunction&`
    from the real library and call .name() against its real
    out-of-line symbol -- we never construct or lay out this type
    ourselves.
  - RecordFunctionCallback is NOT safe by the same argument: we
    construct it ourselves and pass it BY VALUE into addGlobalCallback,
    so its four private fields (start_, end_, sampling_prob_, the
    scopes_ bool array sized by RecordScope::NUM_SCOPES, three trailing
    bools) must match upstream's layout exactly, field for field. They
    were copied verbatim from the real header for this PyTorch version.
  - If a future PyTorch release reorders, adds, or removes a field on
    RecordFunctionCallback, or changes RecordScope's member count, this
    header will still compile without any warning or error -- the
    mismatch would silently corrupt which scopes are enabled or which
    function pointers are read, not fail the build. This is a real
    regression in safety compared to vendoring real headers, which
    can't drift from whatever torch they were extracted from.
  - Mitigation: this layout must be re-verified against the real
    ATen/record_function.h (diff the RecordFunctionCallback class and
    RecordScope enum) whenever the target PyTorch version changes, and
    the change re-validated end-to-end (this commit's babeltrace2
    event-count check) rather than trusted on compile success alone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
at::addGlobalCallback and at::RecordFunction::name, the only two ATen
symbols tracer_pytorch.cpp calls, are both defined in libtorch_cpu.so.
Verified with nm against the real installed libs and confirmed the
resulting libTracerPytorch.so still LD_PRELOADs correctly and produces
an identical RecordFunction trace against the POC workload.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Shorten the dummy-lib comment to just state which symbols come from
libtorch_cpu.so. Also drop the all-local target: no other backend
Makefile.am uses one, and clean-local/install-exec-hook already remove
the dummy libtorch_cpu.so.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
clang++ -Werror flags two things gcc doesn't:
- -Wmissing-field-initializers on LTTng's own tracepoint.h aggregate
  inits (not our code)
- -Wunused-private-field on RecordFunctionCallback's fields, which
  exist only to match PyTorch's real class layout for addGlobalCallback
  and are read by libtorch, never by us

Reproduced both errors locally against the CI's clang toolchain and
confirmed these two -Wno- flags clear them without touching gcc builds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# include/ATen/record_function.h) -- the real libtorch reads them, we
# never do, by design.
libTracerPytorch_la_CXXFLAGS = -std=c++17 -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unused-private-field $(WERROR) $(LTTNG_UST_CFLAGS)
libTracerPytorch_la_LDFLAGS = $(LTTNG_UST_LIBS) -Ldummy_libs -ltorch_cpu -avoid-version -module

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why libtorch_cpu and not just libtorch? libtorch
We should verify that it work with the GPU backend of pytorch too if not done already

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

LibTracerPytorch.so calls at::addGlobalCallback() and RecordFunction::name(), both are implemeted in libtorch_cpu.so

@TApplencourt

Copy link
Copy Markdown
Collaborator

Thanks. So next step interval, tally and timeline.
Also potentialy the pretty print, so that iprof --backends pytorch --trace -- python model.py work

@TApplencourt
TApplencourt merged commit bae4503 into argonne-lcf:devel Sep 14, 2026
14 checks passed
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