Skip to content

Commit e8dcbc0

Browse files
authored
Merge branch 'main' into gasoonjia/drop-cuda-12.6
2 parents cb3db9f + bffccd1 commit e8dcbc0

59 files changed

Lines changed: 2548 additions & 414 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/lint.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
pip install -r requirements-lintrunner.txt
5959
USE_CPP=0 pip install --no-build-isolation third-party/ao
6060
# Match requirements-examples.txt for the model APIs checked by mypy.
61-
pip install pytest numpy parameterized huggingface_hub "transformers==5.0.0rc1" timm expecttest types-requests tosa-tools
61+
pip install pytest numpy parameterized huggingface_hub "transformers==5.11.0" timm expecttest types-requests tosa-tools
6262
6363
- name: Generate mypy stubs for C++ bindings
6464
run: |

‎backends/arm/BUCK‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ fbcode_target(
136136
":arm_compile_spec",
137137
"//caffe2:torch",
138138
"//executorch/backends/arm/_passes:passes",
139+
"//executorch/backends/arm/operator_support:operator_support",
139140
"//executorch/backends/arm/tosa/dialect:lib",
140141
"//executorch/backends/arm/tosa:specification",
141142
"//executorch/backends/arm/tosa:partitioner",

‎backends/arm/operator_support/tosa_supported_operators.py‎

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,22 +1021,54 @@ def is_node_supported(
10211021
class CheckResolvedTensorShapes(OperatorSupportBase):
10221022
"""Reject nodes with unresolved tensor input or output shapes."""
10231023

1024-
def __init__(self, reporter: WhyNoPartitionReporter):
1024+
def __init__(
1025+
self,
1026+
reporter: WhyNoPartitionReporter,
1027+
*,
1028+
targets: Optional[Sequence[object]] = None,
1029+
reject_symbolic_scalars: bool = False,
1030+
):
10251031
"""Initialize the check with a reporter.
10261032
10271033
Args:
10281034
reporter (WhyNoPartitionReporter): Reporter for rejection reasons.
1035+
targets (Optional[Sequence[object]]): Operators requiring resolved
1036+
tensor shapes. None checks all operators.
1037+
reject_symbolic_scalars (bool): Reject symbolic scalar inputs and
1038+
outputs on all call_function nodes, regardless of targets.
10291039
10301040
"""
10311041
self.reporter = reporter
1042+
self.targets = targets
1043+
self.reject_symbolic_scalars = reject_symbolic_scalars
10321044

10331045
def is_node_supported(
10341046
self, submodules: typing.Mapping[str, torch.nn.Module], node: fx.Node
10351047
) -> bool:
1036-
"""Return False when the node depends on unresolved tensor shapes."""
1048+
"""Return False for unresolved shapes or disallowed symbolic scalars."""
10371049
del submodules
10381050
if node.op in ("placeholder", "output"):
10391051
return True
1052+
1053+
if (
1054+
self.reject_symbolic_scalars
1055+
and node.op == "call_function"
1056+
and any(
1057+
isinstance(
1058+
value.meta.get("val"), (torch.SymInt, torch.SymFloat, torch.SymBool)
1059+
)
1060+
for value in (node, *node.all_input_nodes)
1061+
)
1062+
):
1063+
self.reporter.report_reject(
1064+
node, "Target does not support scalar symbolic inputs or outputs."
1065+
)
1066+
return False
1067+
1068+
# Require resolved tensor shapes only for the selected operators.
1069+
# Symbolic scalar restrictions above apply to all operators.
1070+
if self.targets is not None and node.target not in self.targets:
1071+
return True
10401072
if node.op == "call_function" and node.target in (*Q_OPS, *DQ_OPS):
10411073
return True
10421074

‎backends/arm/runtime/EthosUExecutionStats.cpp‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,17 @@ void copy_pmu_events(
6363
}
6464
} // namespace
6565

66+
// cppcheck-suppress unusedFunction
6667
EthosUStatsBuffer* set_ethosu_stats_buffer(EthosUStatsBuffer* buffer) noexcept {
6768
return capture_buffer.exchange(buffer, std::memory_order_acq_rel);
6869
}
6970

71+
// cppcheck-suppress unusedFunction
7072
bool ethosu_execution_metrics_enabled() noexcept {
7173
return true;
7274
}
7375

76+
// cppcheck-suppress unusedFunction
7477
executorch::runtime::Error set_ethosu55_65_pmu_events(
7578
executorch::runtime::ArrayRef<ethosu_pmu_event_type> events) noexcept {
7679
using executorch::runtime::Error;
@@ -81,6 +84,7 @@ executorch::runtime::Error set_ethosu55_65_pmu_events(
8184
return Error::Ok;
8285
}
8386

87+
// cppcheck-suppress unusedFunction
8488
executorch::runtime::Error set_ethosu85_pmu_events(
8589
executorch::runtime::ArrayRef<ethosu_pmu_event_type> events) noexcept {
8690
using executorch::runtime::Error;
@@ -173,6 +177,7 @@ void ethosu_inference_end(struct ethosu_driver* drv, void* user_arg) {
173177
record.success = drv->job.result == ETHOSU_JOB_RESULT_OK;
174178
record.stats.device_identifier = reinterpret_cast<uintptr_t>(drv->dev.reg);
175179
record.stats.product = drv->dev.caps.product;
180+
record.stats.macs_per_cycle = 1u << drv->dev.caps.log2_macs;
176181
record.stats.cycle_count = ETHOSU_PMU_Get_CCNTR(drv);
177182
record.stats.cycle_count_valid = true;
178183
const uint32_t overflow_status = ETHOSU_PMU_Get_CNTR_OVS(drv);
@@ -195,7 +200,7 @@ void ethosu_inference_end(struct ethosu_driver* drv, void* user_arg) {
195200
uint8_t metadata[executorch::backends::arm::kEthosUCycleMetadataMaxSize]{
196201
'E', 'U', 'C', '1'};
197202
write_le32(metadata + 4, record.stats.product);
198-
write_le32(metadata + 8, 1u << drv->dev.caps.log2_macs);
203+
write_le32(metadata + 8, record.stats.macs_per_cycle);
199204
write_le32(metadata + 12, static_cast<uint32_t>(record.stats.platform));
200205
const uint32_t flags =
201206
(record.stats.cycle_overflow ? 1u : 0u) | (record.success ? 2u : 0u);

‎backends/arm/runtime/EthosUExecutionStats.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ struct EthosUExecutionStats {
8080
uintptr_t device_identifier = 0;
8181
// Ethos-U product identifier for the NPU that produced this record.
8282
uint32_t product = 0;
83+
// Number of MACs per cycle supported by the NPU.
84+
uint32_t macs_per_cycle = 0;
8385
// Platform that produced this record.
8486
EthosUPlatform platform = EthosUPlatform::CoreDriver;
8587
// NPU PMU cycle count for this execution, when available.

‎backends/arm/scripts/setup-mlsdk-from-source.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ root_dir="${et_dir}/examples/arm/arm-scratch"
1515
setup_path_script=""
1616
mlsdk_manifest_dir="ml-sdk-for-vulkan-manifest"
1717
mlsdk_manifest_url="${MLSDK_MANIFEST_URL:-https://github.com/arm/ai-ml-sdk-manifest.git}"
18-
mlsdk_manifest_tag="${MLSDK_MANIFEST_TAG:-refs/tags/v2026.12.0.dev20261027}" # Keep this in sync with backends/arm/requirements-arm-vgf.txt and backends/arm/requirements-arm-vgf-runtime.txt
18+
mlsdk_manifest_tag="${MLSDK_MANIFEST_TAG:-refs/tags/v2026.09.0}" # Keep this in sync with backends/arm/requirements-arm-vgf.txt and backends/arm/requirements-arm-vgf-runtime.txt
1919

2020
enable_model_converter=0
2121
enable_vgf_lib=0

‎backends/arm/test/misc/test_compile_spec.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,27 @@ def test_compile_spec_vgf_no_quant():
108108
EthosUCompileSpec._from_list(spec_list)
109109

110110

111+
def test_compile_spec_vgf_default_enables_bf16():
112+
"""The default VGF spec advertises BF16 without dropping other
113+
extensions.
114+
"""
115+
spec = VgfCompileSpec()
116+
assert spec.tosa_spec.support_float()
117+
assert spec.tosa_spec.support_integer()
118+
assert spec.tosa_spec.support_extension("int4")
119+
assert spec.tosa_spec.support_extension("int16")
120+
assert spec.tosa_spec.support_extension("bf16")
121+
122+
roundtripped = VgfCompileSpec._from_list(spec._to_list())
123+
assert roundtripped.tosa_spec.support_extension("bf16")
124+
125+
126+
def test_compile_spec_vgf_explicit_tosa_spec_does_not_enable_bf16():
127+
"""Opting into a narrower TOSA spec must still remain possible."""
128+
spec = VgfCompileSpec("TOSA-1.0+FP+INT+int4+int16")
129+
assert not spec.tosa_spec.support_extension("bf16")
130+
131+
111132
def test_compile_spec_vgf_defaults_leaky_relu_to_decompose():
112133
compile_spec = VgfCompileSpec()
113134
pipeline_config = compile_spec._get_pass_pipeline_config()

‎backends/arm/test/misc/test_tosa_operator_support.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@
1919
)
2020
from executorch.backends.arm.operator_support.where_support import WhereSupported
2121
from executorch.backends.arm.tosa import TosaSpecification
22+
from executorch.backends.arm.tosa.compile_spec import TosaCompileSpec
23+
from executorch.backends.arm.tosa.partitioner import TOSAPartitioner
24+
from executorch.exir import to_edge
2225
from executorch.exir.backend.utils import WhyNoPartitionReporter
2326
from executorch.exir.dialects._ops import ops as exir_ops
2427
from torch._subclasses.fake_tensor import FakeTensorMode
2528
from torch.fx.experimental.symbolic_shapes import ShapeEnv
29+
from torch.fx.passes.operator_support import OpSupports
2630

2731

2832
def _fake_tensor(shape: tuple[int, ...], dtype: torch.dtype = torch.float32):
@@ -347,3 +351,36 @@ def test_int64_operand_check_widths(dtype, agree) -> None:
347351
node = graph.call_function(exir_ops.edge.aten.bitwise_or.Tensor, (x, y))
348352

349353
assert _int64_operands_agree(node) == agree
354+
355+
356+
@pytest.mark.parametrize("boundary", ["input", "output", "internal"])
357+
def test_tosa_symbolic_scalar_boundaries(boundary, caplog) -> None:
358+
class Reshape(torch.nn.Module):
359+
def forward(self, x):
360+
result = x.reshape(x.shape[0], -1).relu()
361+
return (result, x.shape[0]) if boundary == "output" else result
362+
363+
program = to_edge(
364+
torch.export.export(
365+
Reshape(),
366+
(torch.randn(2, 2, 3),),
367+
dynamic_shapes=({0: torch.export.Dim("batch", min=1, max=4)},),
368+
)
369+
).exported_program()
370+
sym_size = program.graph.find_nodes(
371+
op="call_function", target=torch.ops.aten.sym_size.int
372+
)[0]
373+
checks = (
374+
[OpSupports.decline_if_node_in_names({sym_size.name})]
375+
if boundary == "input"
376+
else []
377+
)
378+
partitioner = TOSAPartitioner(
379+
TosaCompileSpec("TOSA-1.1+FP+shape"), additional_checks=checks
380+
)
381+
with caplog.at_level("INFO"):
382+
result = partitioner.partition(program)
383+
384+
assert bool(result.partition_tags) is (boundary == "internal")
385+
if boundary != "internal":
386+
assert "Partition has unsupported scalar" in caplog.text

‎backends/arm/test/misc/test_vgf_backend.py‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from unittest import mock
1111

1212
import pytest
13+
import torch
1314

1415
from executorch.backends.arm._passes import EnsureUniqueOutputNodesPass, RewriteConvPass
1516
from executorch.backends.arm._passes.arm_pass_manager import (
@@ -18,6 +19,9 @@
1819
PassInsertions,
1920
)
2021

22+
from executorch.backends.arm.operator_support.tosa_supported_operators import (
23+
CheckResolvedTensorShapes,
24+
)
2125
from executorch.backends.arm.vgf import backend, backend as vgf_backend, VgfCompileSpec
2226
from executorch.backends.arm.vgf._passes import GroupIndependentCustomShadersPass
2327
from executorch.backends.arm.vgf.backend import (
@@ -29,9 +33,13 @@
2933
vgf_compile,
3034
)
3135
from executorch.exir.backend.backend_details import PreprocessResult
36+
from executorch.exir.backend.utils import WhyNoPartitionReporter
37+
from executorch.exir.dialects._ops import ops as exir_ops
3238
from executorch.exir.pass_base import ExportPass
39+
from torch._subclasses.fake_tensor import FakeTensorMode
3340
from torch.export.exported_program import ExportedProgram
3441
from torch.fx import GraphModule
42+
from torch.fx.experimental.symbolic_shapes import ShapeEnv
3543
from torch.fx.passes.infra.pass_base import PassResult
3644

3745

@@ -540,3 +548,38 @@ def fake_converter_run(command, **kwargs):
540548
assert not (artifact_path / "output.tosa.vgf").exists()
541549

542550
assert mock_run.call_count == 2
551+
552+
553+
@pytest.mark.parametrize("dynamic", [False, True])
554+
@pytest.mark.parametrize(
555+
"target,args",
556+
[
557+
(exir_ops.edge.aten.view_copy.default, ([-1],)),
558+
(exir_ops.edge.aten.permute_copy.default, ([1, 0],)),
559+
(torch.ops.aten.sym_size.int, (0,)),
560+
(exir_ops.edge.aten.relu.default, ()),
561+
],
562+
)
563+
def test_vgf_dynamic_shape_support(target, args, dynamic) -> None:
564+
shape_env = ShapeEnv()
565+
inputs = torch.empty((2, 3))
566+
graph = torch.fx.Graph()
567+
x = graph.placeholder("x")
568+
with FakeTensorMode(shape_env=shape_env) as mode:
569+
x.meta["val"] = mode.from_tensor(inputs, static_shapes=not dynamic)
570+
node = graph.call_function(target, (x, *args))
571+
node.meta["val"] = target(x.meta["val"], *args)
572+
573+
checker = CheckResolvedTensorShapes(
574+
WhyNoPartitionReporter(),
575+
targets=(
576+
exir_ops.edge.aten.view_copy.default,
577+
exir_ops.edge.aten.permute_copy.default,
578+
),
579+
reject_symbolic_scalars=True,
580+
)
581+
expected_support = not dynamic or target == exir_ops.edge.aten.relu.default
582+
assert checker.is_node_supported({}, node) is expected_support
583+
584+
default_checker = CheckResolvedTensorShapes(WhyNoPartitionReporter())
585+
assert default_checker.is_node_supported({}, node) is (not dynamic)

‎backends/arm/test/misc/tosa_dialect/test_tosa_shape_support.py‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,14 @@ def forward(self, x: torch.Tensor) -> torch.Tensor:
128128
return x.reshape(x.shape[0], 6)
129129

130130

131-
class ReturnSymSizeArithmetic(torch.nn.Module):
131+
class FullWithSymSizeArithmetic(torch.nn.Module):
132132
def __init__(self, operation) -> None:
133133
super().__init__()
134134
self.operation = operation
135135

136-
def forward(self, x: torch.Tensor) -> tuple[torch.Tensor, int]:
137-
return x, self.operation(x.shape[0], 2)
136+
def forward(self, x: torch.Tensor) -> torch.Tensor:
137+
size = self.operation(x.shape[0], 2) + 3
138+
return torch.full((size,), 1.0)
138139

139140

140141
class Index(torch.nn.Module):
@@ -583,10 +584,10 @@ def test_shape_extension_rejects_sym_size_from_int64_without_int64_extension():
583584

584585

585586
def test_shape_extension_partitions_sym_size_int():
586-
inputs = (torch.randn(2, 3),)
587+
inputs = (torch.randn(2, 2, 3),)
587588
batch = Dim("batch", min=1, max=4)
588589
exported_program = _exported_program(
589-
ReturnSymSize(),
590+
ReshapeWithSymSize(),
590591
inputs,
591592
dynamic_shapes=({0: batch},),
592593
)
@@ -609,7 +610,7 @@ def test_shape_extension_partitions_symint_arithmetic(operation):
609610
inputs = (torch.randn(2, 3),)
610611
batch = Dim("batch", min=1, max=4)
611612
exported_program = _exported_program(
612-
ReturnSymSizeArithmetic(operation),
613+
FullWithSymSizeArithmetic(operation),
613614
inputs,
614615
dynamic_shapes=({0: batch},),
615616
)

0 commit comments

Comments
 (0)