Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
238 commits
Select commit Hold shift + click to select a range
c322218
Split the runtime out of the Python bindings extension
shoumikhin Aug 12, 2026
a7b7591
Ship a C++ SDK in the wheel
shoumikhin Aug 12, 2026
6be8a31
Ship the quantized kernels as their own library
shoumikhin Aug 12, 2026
0c1af98
Update
shoumikhin Aug 13, 2026
a9957d7
Update
shoumikhin Aug 13, 2026
98375e9
Update
shoumikhin Aug 13, 2026
c268e65
Update
shoumikhin Aug 13, 2026
cc0b424
Update
shoumikhin Aug 13, 2026
89193df
Update
shoumikhin Aug 13, 2026
df36289
Update
shoumikhin Aug 13, 2026
dbc9d9e
Update
shoumikhin Aug 13, 2026
a346f95
Update
shoumikhin Aug 13, 2026
a3d04f7
Update
shoumikhin Aug 13, 2026
5969792
Update
shoumikhin Aug 13, 2026
cf9946f
Update
shoumikhin Aug 13, 2026
3550f70
Update
shoumikhin Aug 13, 2026
34e9026
Update
shoumikhin Aug 13, 2026
b251a51
Update
shoumikhin Aug 13, 2026
6165ac3
Update
shoumikhin Aug 13, 2026
a33f57c
Update
shoumikhin Aug 13, 2026
c77ab69
Update
shoumikhin Aug 13, 2026
6924d32
Update
shoumikhin Aug 13, 2026
8511957
Update
shoumikhin Aug 13, 2026
1de0fe8
Update
shoumikhin Aug 13, 2026
6879c80
Update
shoumikhin Aug 13, 2026
7fcdfc0
Update
shoumikhin Aug 13, 2026
d2c2c23
Update
shoumikhin Aug 14, 2026
84659f0
Update
shoumikhin Aug 14, 2026
e612cb0
Update
shoumikhin Aug 14, 2026
b7d7d08
Update
shoumikhin Aug 14, 2026
3b4fa85
Update
shoumikhin Aug 14, 2026
3f00a6b
Update
shoumikhin Aug 14, 2026
3ee5d59
Update
shoumikhin Aug 14, 2026
ebb619f
Update
shoumikhin Aug 14, 2026
0014990
Update
shoumikhin Aug 14, 2026
9cdcde6
Update
shoumikhin Aug 14, 2026
7defeed
Update
shoumikhin Aug 14, 2026
1d34be9
Update
shoumikhin Aug 14, 2026
0bda668
Update
shoumikhin Aug 14, 2026
863d5d9
Update
shoumikhin Aug 14, 2026
26ed0ee
Update
shoumikhin Aug 14, 2026
791f3b5
Update
shoumikhin Aug 14, 2026
ab2da5d
Update
shoumikhin Aug 14, 2026
ed620a8
Update
shoumikhin Aug 14, 2026
dee94c7
Update
shoumikhin Aug 14, 2026
f9c6090
Update
shoumikhin Aug 14, 2026
ba9f1f9
Update
shoumikhin Aug 14, 2026
c931d2a
Update
shoumikhin Aug 14, 2026
b108767
Update
shoumikhin Aug 14, 2026
4da03a2
Update
shoumikhin Aug 14, 2026
0eaf4e3
Update
shoumikhin Aug 14, 2026
7bf349f
Update
shoumikhin Aug 14, 2026
dbddbb3
Update
shoumikhin Aug 14, 2026
a690369
Update
shoumikhin Aug 14, 2026
14836fe
Update
shoumikhin Aug 14, 2026
bc529d1
Update
shoumikhin Aug 14, 2026
f0cf7c5
Update
shoumikhin Aug 14, 2026
4bfde8e
Update
shoumikhin Aug 14, 2026
2f67cd2
Update
shoumikhin Aug 14, 2026
b74750f
Update
shoumikhin Aug 14, 2026
9537960
Update
shoumikhin Aug 14, 2026
e3f71d1
Update
shoumikhin Aug 14, 2026
797d682
Update
shoumikhin Aug 14, 2026
64debc5
Update
shoumikhin Aug 14, 2026
a73735c
Update
shoumikhin Aug 14, 2026
4bcd419
Update
shoumikhin Aug 15, 2026
55a13b9
Update
shoumikhin Aug 15, 2026
af15bef
Update
shoumikhin Aug 15, 2026
82b75d5
Update
shoumikhin Aug 15, 2026
93510b1
Update
shoumikhin Aug 15, 2026
707e35f
Update
shoumikhin Aug 15, 2026
6ac281e
Update
shoumikhin Aug 15, 2026
b793003
Update
shoumikhin Aug 15, 2026
5670d38
Update
shoumikhin Aug 15, 2026
10ddbe9
Update
shoumikhin Aug 15, 2026
ee69fa7
Update
shoumikhin Aug 15, 2026
80cc50e
Update
shoumikhin Aug 15, 2026
07abf92
Update
shoumikhin Aug 15, 2026
fe636b9
Update
shoumikhin Aug 15, 2026
0d87b12
Update
shoumikhin Aug 15, 2026
e5bd7ab
Update
shoumikhin Aug 15, 2026
7f20553
Update
shoumikhin Aug 15, 2026
4c5ff69
Update
shoumikhin Aug 15, 2026
107d3c0
Update
shoumikhin Aug 15, 2026
856980e
Update
shoumikhin Aug 15, 2026
875ad71
Update
shoumikhin Aug 15, 2026
7b0dc3e
Update
shoumikhin Aug 15, 2026
ca429df
Update
shoumikhin Aug 15, 2026
209c0bd
Update
shoumikhin Aug 15, 2026
f4849ab
Update
shoumikhin Aug 15, 2026
89d4c8c
Update
shoumikhin Aug 15, 2026
e7177db
Update
shoumikhin Aug 15, 2026
d6c543c
Update
shoumikhin Aug 15, 2026
5a7ce2d
Update
shoumikhin Aug 15, 2026
c8fb112
Update
shoumikhin Aug 15, 2026
8b07750
Update
shoumikhin Aug 15, 2026
a08fb9f
Update
shoumikhin Aug 15, 2026
4c049da
Update
shoumikhin Aug 15, 2026
faf9c0f
Update
shoumikhin Aug 15, 2026
207926a
Update
shoumikhin Aug 15, 2026
1edbb75
Update
shoumikhin Aug 15, 2026
90ad462
Update
shoumikhin Aug 16, 2026
deda54e
Update
shoumikhin Aug 16, 2026
69f23e9
Update
shoumikhin Aug 16, 2026
dcda51d
Update
shoumikhin Aug 16, 2026
4ad455a
Update
shoumikhin Aug 16, 2026
64a8c6f
Update
shoumikhin Aug 16, 2026
2f93e19
Update
shoumikhin Aug 16, 2026
6311d11
Update
shoumikhin Aug 16, 2026
b2e20c0
Update
shoumikhin Aug 16, 2026
9336d31
Update
shoumikhin Aug 16, 2026
95c838f
Update
shoumikhin Aug 16, 2026
ff5e447
Update
shoumikhin Aug 16, 2026
e38d2dc
Update
shoumikhin Aug 16, 2026
dba35de
Update
shoumikhin Aug 16, 2026
029785c
Update
shoumikhin Aug 16, 2026
4553641
Update
shoumikhin Aug 16, 2026
57a0b50
Update
shoumikhin Aug 16, 2026
9ef0082
Update
shoumikhin Aug 16, 2026
b877122
Update
shoumikhin Aug 16, 2026
12a62ba
Update
shoumikhin Aug 16, 2026
208d4d7
Update
shoumikhin Aug 16, 2026
71cc17d
Update
shoumikhin Aug 16, 2026
04c3e79
Update
shoumikhin Aug 16, 2026
53402ea
Update
shoumikhin Aug 16, 2026
1d62fbd
Update
shoumikhin Aug 16, 2026
013867d
Update
shoumikhin Aug 16, 2026
0926f33
Update
shoumikhin Aug 16, 2026
2ca2429
Update
shoumikhin Aug 16, 2026
e9d986d
Update
shoumikhin Aug 16, 2026
667c2a0
Update
shoumikhin Aug 16, 2026
1cf432a
Update
shoumikhin Aug 17, 2026
97136c3
Update
shoumikhin Aug 17, 2026
c06e924
Update
shoumikhin Aug 17, 2026
0958811
Update
shoumikhin Aug 17, 2026
3b683b2
Update
shoumikhin Aug 17, 2026
6086b9b
Update
shoumikhin Aug 17, 2026
2f483ba
Update
shoumikhin Aug 17, 2026
fafc231
Update
shoumikhin Aug 17, 2026
fad6f63
Update
shoumikhin Aug 17, 2026
fd874ca
Update
shoumikhin Aug 17, 2026
1ddfe57
Update
shoumikhin Aug 17, 2026
a74be37
Update
shoumikhin Aug 17, 2026
64683d3
Update
shoumikhin Aug 17, 2026
17d9f0b
Update
shoumikhin Aug 17, 2026
527a6f5
Update
shoumikhin Aug 17, 2026
7483dd9
Update
shoumikhin Aug 17, 2026
58a6f64
Update
shoumikhin Aug 17, 2026
c169bcc
Update
shoumikhin Aug 17, 2026
5a6389e
Update
shoumikhin Aug 17, 2026
455cb2c
Update
shoumikhin Aug 17, 2026
6aea4ec
Update
shoumikhin Aug 17, 2026
a54c84f
Update
shoumikhin Aug 17, 2026
70a1c5f
Update
shoumikhin Aug 17, 2026
e9a410f
Update
shoumikhin Aug 17, 2026
e90c122
Update
shoumikhin Aug 17, 2026
4a41acb
Update
shoumikhin Aug 17, 2026
0830b54
Update
shoumikhin Aug 17, 2026
5d760b0
Update
shoumikhin Aug 17, 2026
3b068e6
Update
shoumikhin Aug 17, 2026
8d5164b
Update
shoumikhin Aug 17, 2026
0458d41
Update
shoumikhin Aug 17, 2026
e0d5be0
Update
shoumikhin Aug 17, 2026
55785f7
Update
shoumikhin Aug 17, 2026
4017168
Update
shoumikhin Aug 17, 2026
76f35fd
Update
shoumikhin Aug 17, 2026
a87494b
Update
shoumikhin Aug 17, 2026
75c520f
Update
shoumikhin Aug 17, 2026
76ed9f8
Update
shoumikhin Aug 17, 2026
b87d66a
Update
shoumikhin Aug 17, 2026
d2e7fe8
Update
shoumikhin Aug 17, 2026
de531bf
Update
shoumikhin Aug 17, 2026
9c0e747
Update
shoumikhin Aug 17, 2026
56ca0d2
Update
shoumikhin Aug 17, 2026
dbf799a
Update
shoumikhin Aug 17, 2026
b7e7e9c
Update
shoumikhin Aug 17, 2026
55a00ff
Update
shoumikhin Aug 18, 2026
ce4b0ef
Update
shoumikhin Aug 18, 2026
e04d0e0
Update
shoumikhin Aug 18, 2026
1573e0b
Update
shoumikhin Aug 18, 2026
aa1bb51
Update
shoumikhin Aug 18, 2026
48c5e9b
Update
shoumikhin Aug 18, 2026
038e36c
Update
shoumikhin Aug 18, 2026
bb43581
Update
shoumikhin Aug 18, 2026
ac6a665
Update
shoumikhin Aug 18, 2026
65e3621
Update
shoumikhin Aug 18, 2026
cc4d013
Update
shoumikhin Aug 18, 2026
2af0c80
Update
shoumikhin Aug 18, 2026
d6ec909
Update
shoumikhin Aug 18, 2026
d2ddb04
Update
shoumikhin Aug 18, 2026
39e3a30
Update
shoumikhin Aug 18, 2026
1b58553
Update
shoumikhin Aug 19, 2026
1f90586
Update
shoumikhin Aug 19, 2026
50c329a
Update
shoumikhin Aug 19, 2026
34a39b0
Update
shoumikhin Aug 19, 2026
2b886e2
Update
shoumikhin Aug 19, 2026
2dd1a16
Update
shoumikhin Aug 19, 2026
636d433
Update
shoumikhin Aug 19, 2026
12e101b
Update
shoumikhin Aug 19, 2026
537d0e7
Update
shoumikhin Aug 19, 2026
b7b109a
Update
shoumikhin Aug 19, 2026
4af3062
Update
shoumikhin Aug 19, 2026
601ea70
Update
shoumikhin Aug 19, 2026
37058f6
Update
shoumikhin Aug 19, 2026
fdc827d
Update
shoumikhin Aug 19, 2026
fb42f27
Update
shoumikhin Aug 19, 2026
c686c3f
Update
shoumikhin Aug 19, 2026
b30dad3
Update
shoumikhin Aug 19, 2026
5ca10de
Update
shoumikhin Aug 19, 2026
d663a16
Update
shoumikhin Aug 19, 2026
946557b
Update
shoumikhin Aug 19, 2026
9502898
Update
shoumikhin Aug 19, 2026
7439020
Update
shoumikhin Aug 19, 2026
bbb264d
Update
shoumikhin Aug 19, 2026
04696ce
Update
shoumikhin Aug 19, 2026
1d06139
Update
shoumikhin Aug 19, 2026
2e09096
Update
shoumikhin Aug 19, 2026
ae991df
Update
shoumikhin Aug 19, 2026
d8b5901
Update
shoumikhin Aug 19, 2026
5b6fe07
Update
shoumikhin Aug 19, 2026
053b207
Update
shoumikhin Aug 19, 2026
8fedf97
Update
shoumikhin Aug 19, 2026
0e2f70e
Update
shoumikhin Aug 19, 2026
d64c0d0
Update
shoumikhin Aug 19, 2026
61824d2
Update
shoumikhin Aug 20, 2026
4b4e231
Update
shoumikhin Aug 20, 2026
78efab9
Update
shoumikhin Aug 20, 2026
f736168
Update
shoumikhin Aug 20, 2026
ed9732b
Update
shoumikhin Aug 20, 2026
e847aaf
Update
shoumikhin Aug 20, 2026
31b5943
Update
shoumikhin Aug 20, 2026
9756431
Update
shoumikhin Aug 20, 2026
c5f5746
Update
shoumikhin Aug 20, 2026
fd63222
Update
shoumikhin Aug 20, 2026
b6c3c8a
Update
shoumikhin Aug 20, 2026
9f02f69
Update
shoumikhin Aug 20, 2026
36db138
Update
shoumikhin Aug 20, 2026
1149045
Update
shoumikhin Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 165 additions & 4 deletions .ci/scripts/wheel/test_cpp_sdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,25 @@ def forward(self, x, image):
with torch.no_grad():
expected = model(*example)

if mode == "quantized":

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.

nice!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks! The quantized path needed its own mode here because the export step is different, so it seemed worth exercising rather than assuming it behaved like the plain one.

# Quantize with the same flow the documentation shows, so the exported program
# references the quantized operator set rather than the plain one.
# Importing this loads the ahead-of-time library, which is what registers the out
# variants of the quantized operators with torch. Without it the export fails with
# "Missing out variants: quantized_decomposed::quantize_per_tensor", because the
# lowering step has no out variant to select.
import executorch.kernels.quantized # noqa: F401
from executorch.backends.xnnpack.quantizer.xnnpack_quantizer import (
get_symmetric_quantization_config,
XNNPACKQuantizer,
)
from torchao.quantization.pt2e.quantize_pt2e import convert_pt2e, prepare_pt2e

quantizer = XNNPACKQuantizer().set_global(get_symmetric_quantization_config())
prepared = prepare_pt2e(torch.export.export(model, example).module(), quantizer)
prepared(*example)
model = convert_pt2e(prepared)

partitioners = []
if mode == "delegate":
from executorch.backends.xnnpack.partition.xnnpack_partitioner import (
Expand Down Expand Up @@ -85,6 +104,11 @@ def forward(self, x, image):
"expected": expected.flatten().tolist(),
"delegated": mode == "delegate",
"has_xnnpack": b"XnnpackBackend" in bytes(buffer),
# Whether the program actually carries quantized operators. The numeric comparison alone
# cannot tell: an unquantized export of the same model produces a closer match than the
# tolerance a quantized one needs, so it would pass while proving nothing about the
# quantized kernels.
"has_quantized": b"quantized_decomposed" in bytes(buffer),
}
)
)
Expand All @@ -102,6 +126,7 @@ def forward(self, x, image):

#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
#include <vector>
Expand Down Expand Up @@ -196,8 +221,14 @@ def forward(self, x, image):
}
worst = std::fmax(worst, diff);
}
if (worst > 1e-4) {
std::printf("output differs from eager PyTorch by %g\n", worst);
// Passed in rather than fixed, because the acceptable difference depends on the
// model. A float32 model should match to within rounding, while an int8 quantized one
// legitimately differs by about one quantization step, and using the looser number
// for both would stop the float path catching a real regression.
const double tolerance = argc > 7 ? std::atof(argv[7]) : 1e-4;
if (worst > tolerance) {
std::printf(
"output differs from eager PyTorch by %g, tolerance %g\n", worst, tolerance);
return 1;
}

Expand Down Expand Up @@ -335,8 +366,16 @@ def _build_consumer(work_dir: Path, name: str, components) -> Path:
return consumer


def _run_consumer(consumer: Path, model: Path, reference, work_dir: Path) -> str:
"""Run the application and require it to match eager PyTorch."""
def _run_consumer(
consumer: Path, model: Path, reference, work_dir: Path, tolerance: float = 1e-4
) -> str:
"""Run the application and require it to match eager PyTorch within `tolerance`.

The tolerance is a parameter because the acceptable difference depends on the model.
A float32 model should match to within rounding, while an int8 quantized one
legitimately differs by about one quantization step, and using the looser number for
both would stop the float path catching a real regression.
"""
inputs = reference["inputs"]
shape_a, data_a = _write_tensor(work_dir, "a", inputs[0])
shape_b, data_b = _write_tensor(work_dir, "b", inputs[1])
Expand All @@ -358,6 +397,7 @@ def _run_consumer(consumer: Path, model: Path, reference, work_dir: Path) -> str
str(shape_b),
str(data_b),
str(expected),
str(tolerance),
],
capture_output=True,
text=True,
Expand Down Expand Up @@ -1236,6 +1276,125 @@ def test_pre_3_28_route_builds_a_consumer_through_variables(work_dir: Path) -> N
)


def test_quantized_kernels_component_runs_a_model(work_dir: Path) -> None:
"""A C++ application can run a quantized model using the shipped quantized kernels.

Before the quantized kernels became their own library they existed only inside the
ahead-of-time extension beside the Python bindings, so a C++ application loading a
quantized program had nothing to link and failed at run time with the operators
reported missing.

A missing library is a failure rather than a skip. The preset that builds the wheel
always enables the quantized kernels, so their absence is a regression in packaging
or in the build, not a configuration this suite has to tolerate. Skipping there
reported the whole check as coverage while running none of it.
"""
package_dir = _installed_package_dir()
# Globbed for the same reason the profiler check is: the library carries a version suffix outside a
# wheel build, and an exact name would skip this silently there rather than running it.
shipped = sorted((package_dir / "lib").glob("libexecutorch_kernels_quantized.so*"))
assert shipped, (
"the wheel ships no quantized kernels library. The preset that builds it enables "
"them unconditionally, so this is a packaging or build regression rather than an "
"unsupported configuration."
)

model, reference = _export(work_dir, "quantized")
# The export has to have produced a quantized program, or the rest of this proves nothing about the
# quantized kernels. The numeric comparison cannot tell the difference: an unquantized export of the
# same model lands well inside the tolerance a quantized one needs, so it would pass while linking a
# library it never exercised.
assert reference["has_quantized"], (
"the quantized export produced a program with no quantized operators, so this check would "
"prove nothing about the quantized kernels"
)
consumer = _build_consumer(
work_dir,
"with-quantized",
["runtime", "kernels_optimized", "kernels_quantized"],
)
# One int8 quantization step over this model's output range is about 5e-3, so a
# float32 tolerance cannot be met by a correct quantized run.
output = _run_consumer(consumer, model, reference, work_dir, tolerance=2e-2)
print(
f"✓ a C++ app linking executorch::kernels_quantized runs a quantized model "
f"({output})"
)


def test_aggregate_variable_excludes_the_quantized_kernels(work_dir: Path) -> None:
"""`${EXECUTORCH_LIBRARIES}` must not drag in the quantized kernels.

The export-time plugin that `executorch.kernels.quantized` loads carries its own
copy of those kernels rather than depending on the shipped library, so a process
holding both registers the same operators twice and the runtime stops on the
second one. An application that links whatever the package offers by default
would inherit that, so the component is defined but held out of the aggregate and
a consumer that wants it names it.

Checked by reading the link line rather than by running, because the failure is a
process-wide abort that needs a Python interpreter in the same process to trigger.
What this owns is the packaging decision: is the library on the link line at all.
"""
package_dir = _installed_package_dir()
# Fatal for the same reason the check above is: the preset that builds the wheel
# always enables these kernels, so their absence is a regression rather than a
# configuration to tolerate, and skipping would report this as coverage.
assert sorted(
(package_dir / "lib").glob("libexecutorch_kernels_quantized.so*")
), "the wheel ships no quantized kernels library, so this check cannot run"

source_dir = work_dir / "aggregate-only"
source_dir.mkdir(parents=True, exist_ok=True)
(source_dir / "consumer.cpp").write_text(_CONSUMER_SOURCE)
# No COMPONENTS and no named target, which is the shape the older-CMake route
# forces and the documentation offers as the general case.
(source_dir / "CMakeLists.txt").write_text(
"cmake_minimum_required(VERSION 3.28)\n"
"project(consumer CXX)\n"
"find_package(executorch REQUIRED)\n"
"add_executable(consumer consumer.cpp)\n"
"target_link_libraries(consumer PRIVATE ${EXECUTORCH_LIBRARIES})\n"
)
build_dir = work_dir / "aggregate-only-build"
config = package_dir / "share" / "cmake" / "executorch-config.cmake"
for command in (
[
_tool("cmake"),
"-S",
str(source_dir),
"-B",
str(build_dir),
f"-DCMAKE_PREFIX_PATH={config.parent}",
],
[_tool("cmake"), "--build", str(build_dir)],
):
result = subprocess.run(command, capture_output=True, text=True, check=False)
assert result.returncode == 0, (
"an application linking only ${EXECUTORCH_LIBRARIES} could not be built:\n"
f"{result.stdout[-2000:]}\n{result.stderr[-2000:]}"
)

consumer = build_dir / "consumer"
dependencies = subprocess.run(
["readelf", "-d", str(consumer)], capture_output=True, text=True, check=False
).stdout
assert "libexecutorch_kernels_quantized" not in dependencies, (
"an application that linked only ${EXECUTORCH_LIBRARIES} depends on the "
"quantized kernels. That library collides with the export-time plugin, so it "
"has to be opted into by name rather than handed to every consumer."
)
# The rest of the aggregate still has to be there, or this would pass by shipping
# nothing at all.
assert "libexecutorch_kernels_optimized" in dependencies, (
"the aggregate no longer carries the CPU kernels, so an application linking it "
"would fail at run time with the operators reported missing"
)
print(
"✓ ${EXECUTORCH_LIBRARIES} carries the CPU kernels and not the quantized ones"
)


def run_tests(work_dir: Path) -> None:
test_find_package_honours_a_version_request(work_dir)
test_profiler_component_is_usable(work_dir)
Expand All @@ -1245,6 +1404,8 @@ def run_tests(work_dir: Path) -> None:
test_runtime_alone_links_but_cannot_compute(work_dir)
test_kernels_component_runs_a_model(work_dir)
test_pre_3_28_route_builds_a_consumer_through_variables(work_dir)
test_quantized_kernels_component_runs_a_model(work_dir)
test_aggregate_variable_excludes_the_quantized_kernels(work_dir)
test_delegated_model_needs_the_delegate_component(work_dir)
test_consumer_is_relocatable(work_dir)
test_one_registry_in_the_cpp_process(work_dir)
Expand Down
Loading
Loading