Skip to content

Share the CUDA device guard through the extension so other backends can use it - #22970

Merged
shoumikhin merged 1 commit into
pytorch:mainfrom
shoumikhin:device-guard-extension-cuda
Sep 22, 2026
Merged

shoumikhin merged 1 commit into
pytorch:mainfrom
shoumikhin:device-guard-extension-cuda

Conversation

@shoumikhin

@shoumikhin shoumikhin commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

The problem

Selecting a CUDA device and putting the caller's device back is written out by hand in two places in the CUDA
backend. One is a struct inside a source file, private to that file. The other is the same save and restore
spelled inline in a destructor. A comment on a third copy says it follows the first. The TensorRT delegate,
which lives in another repository and loads into the same process, had written a fourth.

The change

This tree already had the right guard, sitting in a backend directory. It restores on destruction, it is
built through a factory that returns a result so a failure cannot be ignored by accident, it logs a failed
restore, it leaves a moved-from copy looking already restored so only one of the two puts the device back,
and it has tests. What it lacked was a home another backend could reach.

So it moves to the CUDA extension, beside the caller-stream guard, for the same reason that one is there. One
program can run several backends whose engines sit on different devices, so each has to leave the device as
it found it, or the next one inherits a selection it never made. Kernel launches follow the stream they are
given, but allocation follows the current device, so a backend that allocates has to select one even when the
caller chose the stream.

Only the guard moves. The per-device stream helpers stay where their callers are, because the extension
already answers the stream question a different way and two mechanisms side by side would leave a reader
guessing which to use. Their header keeps the name in scope, which it needs regardless, since its own stream
guard holds one.

Two details were carried across rather than rewritten. The destructor decides whether to restore from what
the caller asked for, not from whether the switch reported success, because a failed selection can still move
the calling thread once an error is pending on the device. And a negative index is refused rather than
ignored, so the one call site that can reach one asks first, exactly as it did before.

The device index is a plain int here. The old signature used an alias from a backend compatibility layer, and
that alias was the only thing tying this guard to that backend.

Testing

Measured on four machines: two with several cards each, and two with a single card, on both processor
architectures. A selection that needs a switch restores the caller's device, one that does not changes
nothing, a negative index and an absent device are both refused and leave the device alone, and moving a
guard restores once rather than twice. The single-card machines take the path where the multi-device cases
are skipped, which is the same path the new test takes there.

A new test pins the move. It takes the borrow over, lets the taker go out of scope, moves the device
somewhere a second restore would be visible, and requires the moved-from guard to leave it alone. Deleting
the one line that marks a moved-from guard as already restored turns it red, which matters because a
defaulted move would have restored in both scopes.

The moved header and the file it came from both compile against the real runtime headers on Arm.

Every place that reaches the header is wired: a target of its own in the extension, a dependency from the
guard's former target, and one from each backend target whose sources include it. No public job builds that
graph, so this part was checked by reading rather than by a green tick.

The guard's tests, and the stream guard's tests beside them, had a target in one build system and none in
the other, so the build a pull request runs never compiled either file. Both have a target now, and the job
that runs the other CUDA runtime tests builds and runs them, so twenty nine cases that compiled nowhere
now compile and run.

Copilot AI lite review requested due to automatic review settings September 21, 2026 16:23
@shoumikhin shoumikhin added the release notes: runtime Changes related to the core runtime which loads the program methods, initializes delegates, and runs label Sep 21, 2026
@pytorch-bot

pytorch-bot Bot commented Sep 21, 2026

Copy link
Copy Markdown

🔗 Helpful Links

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

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

❗ 1 Active SEVs

There are 1 currently active SEVs. If your PR is affected, please view them below:

⏳ No Failures, 5 Pending

As of commit 087bf0d with merge base bb2683b (image):
💚 Looks good so far! There are no failures yet. 💚

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

@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 Sep 21, 2026

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from f4f076d to 41589d7 Compare September 21, 2026 16:31
Copilot AI review requested due to automatic review settings September 21, 2026 16:31

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings September 21, 2026 21:13
@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from 41589d7 to 8ff9309 Compare September 21, 2026 21:13

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin shoumikhin changed the title Share one CUDA device guard instead of writing it out in each backend Use the device guard this tree already has instead of hand-rolling it again Sep 21, 2026
Copilot AI review requested due to automatic review settings September 21, 2026 21:14
@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from 8ff9309 to 4c67d1c Compare September 21, 2026 21:14

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from 4c67d1c to 9e75639 Compare September 21, 2026 21:33
Copilot AI review requested due to automatic review settings September 21, 2026 21:33

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin shoumikhin changed the title Use the device guard this tree already has instead of hand-rolling it again Share the CUDA device guard through the extension so other backends can use it Sep 21, 2026
Copilot AI review requested due to automatic review settings September 21, 2026 21:48
@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from 9e75639 to 4f4c6f7 Compare September 21, 2026 21:48

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings September 21, 2026 23:31
@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from 4f4c6f7 to 7ba1311 Compare September 21, 2026 23:31

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from 7ba1311 to f4b38d5 Compare September 21, 2026 23:41
Copilot AI review requested due to automatic review settings September 21, 2026 23:41
Copilot AI review requested due to automatic review settings September 22, 2026 02:39
@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from a155b86 to dcfdfa5 Compare September 22, 2026 02:39

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings September 22, 2026 03:11
@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from dcfdfa5 to f8e43b3 Compare September 22, 2026 03:11

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings September 22, 2026 03:39
@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from f8e43b3 to 7335df3 Compare September 22, 2026 03:39

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings September 22, 2026 04:01
@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from 7335df3 to 3e337c4 Compare September 22, 2026 04:01

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from 3e337c4 to 19e3200 Compare September 22, 2026 04:01
Copilot AI review requested due to automatic review settings September 22, 2026 04:30
@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from 19e3200 to a273ed9 Compare September 22, 2026 04:30

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from a273ed9 to 6cc920f Compare September 22, 2026 04:35
Copilot AI review requested due to automatic review settings September 22, 2026 04:35

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings September 22, 2026 04:37
@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from 6cc920f to 21103b1 Compare September 22, 2026 04:37

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from 21103b1 to b8f964a Compare September 22, 2026 05:32
Copilot AI review requested due to automatic review settings September 22, 2026 05:32

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…an use it

Selecting a CUDA device and putting the caller's back is written out by hand in two places in the CUDA
backend: a struct private to one source file, and the same save and restore spelled inline in a destructor.
A comment on a third copy says it follows the first. The TensorRT delegate, which lives in another
repository and loads into the same process, had written a fourth.

The tree already had the right guard, in a backend directory. It restores on destruction, is built through
a factory returning a Result so a failure cannot be ignored by accident, logs a failed restore, neuters a
moved-from copy so only one of the two restores, and carries tests. What it lacked was a home any other
backend could reach.

So it moves to the CUDA extension, beside the caller-stream guard, for the same reason that one is there:
one program can run several backends whose engines sit on different devices, so each has to leave the
device as it found it or the next inherits a selection it never made. Kernel launches follow the stream
they are given, but allocation follows the current device, so a backend that allocates has to select one
even when the caller chose the stream.

Only the guard moves. The per-device stream helpers stay where their callers are, because the extension
already answers the stream question differently and two mechanisms side by side would leave a reader
guessing. Their header keeps the name in scope, which it needs anyway: its own stream guard holds one.

Two details carried across rather than rewritten. The destructor decides whether to restore from what the
caller asked for, not from whether the switch reported success, because a failed selection can still move
the calling thread once an error is pending on the device. And a negative index is refused rather than
ignored, so the one call site that can reach one asks first, the way it did before.

The class is marked for export the same way the stream guard beside it is, so its symbols leave the
shared library on a platform that requires saying so. The build that runs here does not hide symbols, so
the omission would only have shown up for a consumer on another platform.

The header reaches for no CUDA header at all, which is what lets a wheel ship it. Nothing in the
interface names a CUDA type, so the bodies live in a source file beside it and only a consumer that
actually uses the guard links that. A wheel check compiles every shipped header against the wheel and
nothing else, deliberately without a toolkit include path, and a header that only compiles with one
fails in a consumer project rather than in that check.

The device index is a plain int here. The old signature used an alias from the backend's compatibility
layer, and that alias was the only thing tying this guard to that backend.

Test plan:

Measured on a two card machine: a selection that needs a switch restores the caller's device, one that does
not changes nothing, a negative index and an absent device are both refused, and moving a guard restores
once rather than twice. That last one matters because a defaulted move would have restored in both scopes.

The guard's tests, and the stream guard's tests beside them, had a target in one build system and none in
the other, so the build a pull request runs never compiled either file. Both have a target now, and the job
that runs the other CUDA runtime tests builds and runs them. Twenty nine cases that compiled nowhere now
compile and run.

The guard's existing tests still exercise it through the header that re-exports it, including the cases for
a negative index and for move assignment being unavailable.

The moved header and the file it came from both compile against the real runtime headers on Arm.

The definitions are compiled into the library this directory already ships, rather than into one of
their own. A separate archive would have had to be shipped and declared as its own component for a
consumer to link what the header declares, and it would have needed position independent code of its
own, because shared objects link it and the global setting for that covers only one build
configuration. Folding it in avoids all of that. The library is only built for an accelerator, so it
can call the runtime, which the stream helper beside it deliberately does not. It also takes the runtime now, because the guard
logs and the logging lives there, and before this source existed that library called nothing at all. It
takes it through the helper the tree provides rather than as an ordinary dependency, because the ordinary
form puts the static core ahead of the shared runtime on the link line and leaves the library with its own
copy of the registries. It also declares the runtime path every shipped library beside it declares.

The CUDA delegate takes the runtime through the same helper. It asked for the shared runtime as an
ordinary dependency, which its comment says was the intent, but that form loses to the ordering the
helper exists to correct, and the delegate ended up carrying its own copy of the backend registry. It
went unnoticed because a library that no longer exists used to sit on that link line and happened to
order it correctly. It takes those from the shared runtime
where one is built, not from the static core, because this library ships in the wheel and pulling the
static core into it would give a process two platform layers. The CUDA platform library beside it
chooses the same way, and privately, so that a library linking this one does not inherit the runtime and
end up carrying a second copy of the registries.

Every place that reaches the header is wired: a target of its own in the extension, a dependency from the
guard's old target, and one from each backend target whose sources include it. The Buck graph is not built
by any public job, so this was checked by reading rather than by a green tick.
Copilot AI review requested due to automatic review settings September 22, 2026 06:34
@shoumikhin
shoumikhin force-pushed the device-guard-extension-cuda branch from b8f964a to 087bf0d Compare September 22, 2026 06:34

Copilot AI left a comment

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@shoumikhin
shoumikhin merged commit 8b1e07b into pytorch:main Sep 22, 2026
265 checks passed
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. release notes: runtime Changes related to the core runtime which loads the program methods, initializes delegates, and runs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants