Skip to content

[PyBindings] Pass float8 tensors through DLPack - #25098

Open
shoumikhin wants to merge 1 commit into
pytorch:mainfrom
shoumikhin:float8-dlpack
Open

shoumikhin wants to merge 1 commit into
pytorch:mainfrom
shoumikhin:float8-dlpack

Conversation

@shoumikhin

@shoumikhin shoumikhin commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

The pybindings' DLPack tables have no float8 types, so a float8 tensor cannot be passed to a method through DLPack, and a float8 output cannot be read through it. For example:

  • A float8 weight uploaded with ExecuTorchResult.from_host(bits, 0, dtype="float8_e4m3fn") cannot be passed to a method: the call fails with "ExecuTorch result dtype cannot be represented by DLPack".
  • torch.from_dlpack() refuses a float8 ExecuTorchResult output with the same error.
  • A raw DLPack capsule of a CPU torch float8 tensor, or a wrapper that only has __dlpack__, is refused with "Unsupported DLPack dtype".

A plain torch tensor is not affected, because the bindings read it without DLPack.

DLPack 1.1 added codes for the float8 formats. This change maps Float8_e4m3fn, Float8_e4m3fnuz, Float8_e5m2 and Float8_e5m2fnuz to and from them, with the same codes PyTorch uses. Because these codes are from DLPack 1.1, the capsules the bindings export now say version 1.1, and the bindings ask producers for up to version 1.1. Both sides still accept any 1.x capsule. The README now says that float8 results keep their dtype through DLPack, and how to pass a float8 input.

Tested:

  • A CPU test runs float8_e4m3fn and float8_e5m2 through an identity method, given as a wrapper that only has __dlpack__, as a legacy capsule and as a versioned capsule. It also passes the fnuz dtypes as versioned capsules and checks that they are read as themselves.
  • A CPU test with EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT=executorch reads float8 ExecuTorchResult outputs with torch.from_dlpack and passes them back into the method.
  • A CUDA test uploads the bits of all four float8 dtypes with from_host, checks the dtype torch reads, and runs float8_e4m3fn and float8_e5m2 through a method.
  • The no-ATen tests check that the bindings ask producers for up to version 1.1, and that the capsules they export say 1.1.

All of these fail without the change and pass with it. The existing CUDA DLPack tests still pass on an H100, and torch 2.14 and newer read the version 1.1 capsules.

The pybindings' DLPack tables had no float8 types. A float8 value handed
to a method through DLPack was refused. A float8 weight uploaded with
from_host(..., dtype="float8_e4m3fn") failed with "ExecuTorch result
dtype cannot be represented by DLPack". A raw DLPack capsule, or a
wrapper with only __dlpack__, of a CPU torch float8 tensor failed with
"Unsupported DLPack dtype". A float8 output could not be read through
DLPack either, so torch.from_dlpack() refused a float8 ExecuTorchResult.

DLPack 1.1 added type codes for the float8 formats. The pybindings now map
Float8_e4m3fn, Float8_e4m3fnuz, Float8_e5m2 and Float8_e5m2fnuz to and from
those codes, with 8 bits and 1 lane, the same codes PyTorch uses. Because
the codes are from DLPack 1.1, the capsules the pybindings export now say
version 1.1, and the pybindings ask producers for up to version 1.1. Both
sides still accept any 1.x version. The README now says float8 results
keep their dtype through DLPack, as bfloat16 results do, and how to pass
a float8 input.

Tests:
- test_float8_dlpack_input runs float8_e4m3fn and float8_e5m2 tensors
  through an identity method on the CPU, given as a wrapper with only
  __dlpack__, a legacy capsule and a versioned capsule. It also checks
  that the fnuz dtypes are read as themselves from a versioned capsule:
  the method refuses them with their own dtype names, since exir cannot
  export them.
- test_float8_result_round_trips_through_dlpack gets ExecuTorchResult
  outputs on the CPU (EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT=executorch),
  reads them with torch.from_dlpack, and passes them back into the method.
- test_float8_host_bits_run_through_a_method uploads bits of all four
  float8 dtypes to CUDA with from_host and checks the dtype torch reads.
  It runs float8_e4m3fn and float8_e5m2 through a method, since exir
  cannot export the fnuz dtypes.
- In test_pybindings_no_aten.py, the importer test now expects a request
  for up to version 1.1, and the DLPack export test checks that an
  exported capsule says version 1.1.
@shoumikhin shoumikhin added the release notes: api Changes to public facing apis (any interfaces, pybinded runtime methods, etc.) label Nov 14, 2026
Copilot AI balanced review requested due to automatic review settings November 14, 2026 22:41
@pytorch-bot

pytorch-bot Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

🔗 Helpful Links

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

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

✅ No Failures

As of commit 03e3acb with merge base b3b565b (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 Nov 14, 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 insufficient remaining quota. Try requesting a review at a lower effort level.

@shoumikhin

Copy link
Copy Markdown
Contributor Author

@claude review effort=max

@claude

claude Bot commented Nov 14, 2026 •

Copy link
Copy Markdown

Claude finished @shoumikhin's task in 18m 8s —— View job


Nothing blocks this PR, but there are four minor problems: CPU CI never runs the new float8 export entries, the fnuz codes are not tested anywhere, the DLPack capsules are still labelled 1.0 although they can now carry 1.1 dtype codes, and the README still describes DLPack dtype round-trips for bfloat16 only.

PR Review: #25098 (details)

Summary

The PR maps Float8_e4m3fn, Float8_e4m3fnuz, Float8_e5m2 and Float8_e5m2fnuz to and from the DLPack 1.1 codes 10–13. It does this in the importer (scalar_type_from_dlpack) and in the exporter (dl_dtype()). The mappings themselves match the DLPack 1.1 codes. The problems are:

  • the tests exercise few of the eight new entries;
  • the version the capsules report;
  • the README.

All five changed files were reviewed. I could not run torch here, so the remarks about how peer libraries treat the version come from the DLPack spec, not from a run.

API Design

  • Capsules are labelled DLPack 1.0 but can now carry 1.1 dtype codes.
    • extension/pybindings/pybindings_executorch_result.h:552 still sets versioned.version = {1, 0}. dl_dtype() can now put codes 10–13 into that capsule, and those codes only exist from DLPack 1.1, as the new comment at pybindings_dlpack.h:34 says.
    • DLPack raises the minor version when it adds enum values. So a consumer can read a 1.0 label to mean that only 1.0 codes appear.
    • The importer has the same mismatch: pybindings.cpp:1723 still asks producers for max_version=(1, 0) (see the comment at :1712), even though it now reads 1.1 codes.
    • Impact: low, because consumers generally compare only the major version.
    • Fix: set the version to {1, 1} and ask for (1, 1). The bindings' own checks look only at the major version (pybindings.cpp:1909, and wants_versioned_capsule at pybindings_executorch_result.h:578-584), so this still works with every 1.x peer.
    • Fix this →
  • The README documents DLPack dtype preservation for bfloat16 only.
    • extension/pybindings/README.md:208-210 says two things, both about bfloat16 only: DLPack consumers receive the original bfloat16 dtype, and a bfloat16 result passed back in is read through DLPack.
    • It mentions float8 only for its integer view through the buffer protocol (:205).
    • After this PR, float8 results do both. torch.from_dlpack(out) used to fail with "cannot be represented by DLPack", and so did passing out back into a method. This is the main change users will see, so the README should say so.
    • Fix this →

Testing

  • CPU CI never runs the new export entries (pybindings_executorch_result.h:657-664).
    • The CPU test: in the default torch output mode, CPU outputs are built with torch.frombuffer(..., dtype=...) (pybindings.cpp:2743-2757). So torch.from_dlpack(method(...)[0]) at test_pybindings.py:2313 only converts a torch tensor to a torch tensor. The test covers only scalar_type_from_dlpack, as does the complex32 test it is modeled on.
    • CPU CI: the only CPU job that sets EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT=executorch is test_pybindings_no_aten.py (pull.yml:208,219).
    • The CUDA test: float8 export is covered only by the CUDA test, only for e4m3fn, and only in the executorch pass of .ci/scripts/test-cuda-build.sh:92-96.
      • In the torch pass, CUDA outputs come back through a uint8 capsule plus .view(dtype) (pybindings.cpp:2728-2734), which already worked before this PR.
      • The uploaded input goes from the bindings' exporter straight to their importer, so a code that is wrong in both tables still passes.
      • The comment at test_pybindings_cuda_dlpack.py:1192-1193 claims output coverage that only one of the two passes provides.
    • Not tested on CPU: the two changes users will see. One is torch.from_dlpack on a float8 ExecuTorchResult. The other is passing that result back into a method, which prefer_dlpack_input routes through DLPack (pybindings.cpp:2345-2349).
    • Fix: add a subprocess case that sets EXECUTORCH_PYBINDINGS_TENSOR_OUTPUT=executorch, like test_pybindings.py:2165-2185. Assert the output type and torch.from_dlpack(output).dtype, and pass the output back in, as at :2153-2162.
    • Fix this →
  • The fnuz codes and the e5m2 export entry never run.
    • The CUDA test covers only float8_e4m3fn (test_pybindings_cuda_dlpack.py:1194-1201).
    • So no test would catch a wrong code in any fnuz entry (pybindings.cpp:1996-1999,2003-2006, pybindings_executorch_result.h:659-660,663-664) or in the e5m2 export entry (:661-662).
    • Adding the fnuz dtypes to the CPU tuple won't work: scalar_type_table (exir/tensor.py:348-370) has no fnuz entries, so exir cannot export such a program.
    • Fix: loop the CUDA from_host case over all four dtype names and assert torch.from_dlpack(uploaded).dtype. That checks every export code against torch. Run the method only for e4m3fn and e5m2.
    • Fix this →

Specific Comments

  • PR description: "a CPU torch float8 tensor [is refused] with 'Unsupported DLPack dtype'" is true only for a raw capsule or a wrapper that only has __dlpack__.
    • A plain torch tensor, subclasses included, goes through TorchTensorView (pybindings.cpp:3568-3583).
    • That view resolves the dtype by name (:1527), and the name table already listed float8 (pybindings_executorch_result.h:63-66). That is why the new test needs DLPackOnly.
    • The PR has the release notes: api label, so it is worth rewording; otherwise the notes suggest that plain float8 torch inputs were broken.

Recommendation

Approve. None of these blocks merging. The CPU executorch-mode test is the most useful addition, and the version change is one line; both are worth adding before merge.

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 insufficient remaining quota. Try requesting a review at a lower effort level.

@shoumikhin

Copy link
Copy Markdown
Contributor Author

@claude review effort=max

@claude

claude Bot commented Nov 15, 2026 •

Copy link
Copy Markdown

Claude finished @shoumikhin's task in 18m 27s —— View job


Nothing blocks this PR. The new float8 input test never reads a versioned capsule or a fnuz code, the README doesn't say how to pass a float8 input, and there are a few minor code-quality and wording points.

PR Review: #25098 (details)

Summary

The PR maps Float8_e4m3fn, Float8_e4m3fnuz, Float8_e5m2 and Float8_e5m2fnuz to and from the DLPack 1.1 codes 10–13. It also marks exported versioned capsules as version 1.1 and asks producers for up to 1.1. There are no correctness bugs. The problems are:

  • two gaps in the new input test;
  • a README gap for float8 inputs;
  • a few small code-quality and wording points.

All 7 changed files were reviewed. This job can't run Python, so every finding comes from reading the code.

API Design

  • The README doesn't say how to pass a float8 input (extension/pybindings/README.md:209-212).
    • The paragraph now treats float8 results like bfloat16 results, but the input advice still covers only bfloat16: "A bfloat16 input does not accept 16-bit integers …; pass the result itself or raw bytes."
    • A float8 input refuses integers of its width. Those integers are what the buffer protocol shows for a float8 result (:205). If such a view, for example np.asarray(out), is passed back in, the dtype check fails with "has dtype Byte but the method expects Float8_e4m3fn" (pybindings.cpp:1445-1450).
    • Raw bytes don't work for float8 either, because only bfloat16 has a raw-bytes path (pybindings.cpp:1019-1022).
    • Fix: extend the last sentence to say that a float8 input takes neither 8-bit integers nor raw bytes, so you pass the result itself or a float8 tensor.
    • Fix this →

Testing

  • test_float8_dlpack_input misses two of the new import paths (extension/pybindings/test/test_pybindings.py:2294).
    • Versioned capsules:
      • DLPackOnly.__dlpack__(self, stream=None) (:53) takes no max_version. The new (1, 1) request (pybindings.cpp:1724) therefore raises TypeError, and dlpack_capsule falls back to a legacy capsule (:1728-1735).
      • The other two float8 tests pass an ExecuTorchResult back in, and that is always read from a legacy capsule (:1792-1795).
      • As a result, no test reads float8 codes from a dltensor_versioned capsule. No test passes a raw capsule either, which is the third example in the PR description.
      • Both paths share the dtype table, so the risk is low. One more input in the loop covers both cases: method(value.__dlpack__(max_version=(1, 1))).
    • fnuz codes:
      • exir can export only e4m3fn and e5m2 (exir/tensor.py:366-367). The CUDA test checks fnuz only in the export direction (torch.from_dlpack(uploaded)).
      • So the fnuz branches of scalar_type_from_dlpack (pybindings.cpp:1997-2000, :2004-2007) never run, and a wrong code there would pass CI.
      • These branches can be tested without a fnuz program. Wrap each fnuz dtype in DLPackOnly, pass it to the e4m3fn identity method, and assert that the ValueError names it, e.g. "has dtype Float8_e4m3fnuz but the method expects Float8_e4m3fn" (pybindings.cpp:1445-1450, reached at :3642).
      • Before this change the same call failed with "Unsupported DLPack dtype", so the assertion pins the new branches.
    • Fix this →

Code Quality

  • The DLPack version is hard-coded twice, each time with its own comment: pybindings.cpp:1724 and pybindings_executorch_result.h:553.
    • Both stand for the DLPack ABI version the bindings implement. dlpack.h keeps that as one DLPACK_MAJOR_VERSION/DLPACK_MINOR_VERSION pair.
    • pybindings_dlpack.h already declares Version (:68-71) next to the 1.1 codes.
    • Fix: add a constexpr Version kVersion{1, 1}; there. That keeps the importer and exporter in step at the next bump, and one comment replaces the two.
    • Fix this →
  • The CUDA test's dtype guard can never skip (test_pybindings_cuda_dlpack.py:1201-1203).
    • The pinned torch (torch_pin.py:1, 2.14.0) has all four float8 dtypes, so getattr(torch, name, None) never returns None.
    • It is also the dynamic getattr pattern that CLAUDE.md asks to avoid.
    • Fix: loop over the dtypes and get the name with str(dtype).removeprefix("torch."), as the CPU test in this PR does (test_pybindings.py:2359).
    • Fix this →

Specific Comments

  • These places still call the versioned capsule "DLPack 1.0":

    • pybindings_executorch_result.h:361-362, in the user-facing BufferError text;
    • pybindings.cpp:1893;
    • pybindings_dlpack.h:73;
    • README.md:80.

    These capsules now report version 1.1. "Versioned DLPack capsule" is accurate for both versions.

  • pybindings.cpp:1712-1714: with two "which" clauses, "up to version 1.1" reads as part of the read-only clause. Suggested rewording: "Asks for a versioned DLPack capsule, which can mark memory read-only. The request allows up to version 1.1, for its float8 codes. Falls back to …"

  • test_pybindings_cuda_dlpack.py:1191-1193:

    • The test name says every dtype runs through a method, but the fnuz subtests stop before the method (:1217-1218).
    • The comment says the method's float8 output passes through DLPack. That is true only in the executorch pass of .ci/scripts/test-cuda-build.sh:92-95. In the torch pass, CUDA outputs come back as a byte capsule plus .view(dtype) (pybindings.cpp:2729-2735).

Recommendation

Approve. Nothing here blocks merging. The two test cases are the most useful additions, and each takes only a few lines.

Copilot AI balanced review requested due to automatic review settings November 15, 2026 08:31
@shoumikhin

Copy link
Copy Markdown
Contributor Author

@claude review effort=max

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 insufficient remaining quota. Try requesting a review at a lower effort level.

@claude

claude Bot commented Nov 15, 2026 •

Copy link
Copy Markdown

Claude finished @shoumikhin's task in 25m 49s —— View job


Nothing blocks this float8 DLPack change; three minor points remain: the no-ATen version check asks for 1.1, so it can't tell an unconditional 1.1 label from an echoed request; the new subprocess test brings back a dynamic getattr(torch, name); and two comments still describe the versioned capsule as 1.0.

PR Review: #25098

Summary

The PR maps the DLPack 1.1 float8 codes (10–13) to the four float8 dtypes on import and export. It also labels versioned capsules 1.1 and asks producers for max_version=(1, 1). No blocking issues were found, and all 7 changed files were reviewed. Python, torch and web access weren't available in this run, so the findings come from reading the code; the tests were not run.

Testing

  • The version check can't tell an unconditional 1.1 label from an echoed request (extension/pybindings/test/test_pybindings_no_aten.py:349).
    • The new ctypes check asks for max_version=(1, 1) and expects (1, 1). A producer that labeled capsules min(max_version, 1.1) would pass too.
    • The PR's choice only shows up on a (1, 0) request, like the one at :342. That consumer now gets a capsule labeled 1.1, but the test never reads that capsule's version.
    • Reading the version from the max_version=(1, 0) capsule pins the behavior that the comment at :344-345 describes ("Versioned capsules say 1.1").
    • Fix this →

Code Quality

  • Dynamic getattr in the new subprocess test (extension/pybindings/test/test_pybindings.py:2360).
    • dtype = getattr(torch, name) is a pattern that CLAUDE.md rules out ("no dynamic setattr/getattr patterns"). The previous round removed the same lookup from the CUDA test.
    • The script could loop over (torch.float8_e4m3fn, torch.float8_e5m2) itself, with the parent passing only the .pte paths in that order. That removes the name-to-dtype lookup.
    • Fix this →

Specific Comments

  • extension/pybindings/pybindings.cpp:1893 - stale comment: "legacy or DLPack 1.0". :1723-1724 now asks producers for up to 1.1, so capsule_ can hold a 1.1 capsule. "Legacy or versioned" would match the wording at :1712.
  • extension/pybindings/pybindings_dlpack.h:77 - "DLPack 1.0 capsules carry this struct" sits right under kVersion{1, 1} and makes the struct sound 1.0-only. "Versioned capsules (DLPack 1.0 and later)" avoids that.
  • Fix this →

Recommendation

Approve

Nothing blocks merging. The points above are minor fixes to tests and comments.

This branch has not been deployed

No deployments
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: api Changes to public facing apis (any interfaces, pybinded runtime methods, etc.)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants