Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .ci/docker/ci_commit_pins/pytorch.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
release/2.13
69985f4c2d95401063ec5739934ba09d9aae3792
2 changes: 2 additions & 0 deletions backends/vulkan/test/op_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ function(vulkan_op_test test_name test_src)
set(extra_deps ${ARGN})

add_executable(${test_name} ${test_src})
target_compile_features(${test_name} PRIVATE cxx_std_20)
target_include_directories(${test_name} PRIVATE ${COMMON_INCLUDES})
target_link_libraries(
${test_name}
Expand All @@ -89,6 +90,7 @@ endfunction()

if(TARGET vulkan_backend AND LIB_TORCH)
add_library(test_utils ${CMAKE_CURRENT_SOURCE_DIR}/test_utils.cpp)
target_compile_features(test_utils PRIVATE cxx_std_20)
target_include_directories(test_utils PRIVATE ${COMMON_INCLUDES})
target_link_libraries(
test_utils PRIVATE vulkan_backend ${LIB_TORCH} ${LIB_TORCH_CPU}
Expand Down
20 changes: 20 additions & 0 deletions backends/xnnpack/operators/quant_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,26 @@ def __str__(self) -> str:
f")"
)

def matches(self, other: QuantParams) -> bool:
def values_match(lhs, rhs) -> bool:
if isinstance(lhs, torch.Tensor) and isinstance(rhs, torch.Tensor):
return torch.equal(lhs, rhs)
return bool(lhs == rhs)

return (
self.per_channel == other.per_channel
and self.per_channel_group == other.per_channel_group
and values_match(self.scale, other.scale)
and values_match(self.zp, other.zp)
and self.axis == other.axis
and self.dtype == other.dtype
and self.qmin == other.qmin
and self.qmax == other.qmax
and self.is_dynamic == other.is_dynamic
and self.num_nonbatch_dims == other.num_nonbatch_dims
and self.group_size == other.group_size
)

def quantize_tensor(self, tensor: torch.Tensor) -> torch.Tensor:
# Do nothing if already quantized by the Quantizer
if tensor.dtype == self.dtype:
Expand Down
16 changes: 16 additions & 0 deletions backends/xnnpack/partition/config/generic_node_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import numpy as np
import torch
from executorch.backends.xnnpack.operators.quant_params import QuantParams
from executorch.backends.xnnpack.partition.config.xnnpack_config import (
ConfigPrecisionType,
XNNPartitionerConfig,
Expand Down Expand Up @@ -418,6 +419,21 @@ def check_constraints(self, node: torch.fx.Node, ep: ExportedProgram) -> bool:
if not self.check_common_constraints(node, ep):
return False

input_node = get_input_node(node, 0)
output_node = next(iter(node.users), None)
if is_dequant(input_node) and output_node is not None and is_quant(output_node):
input_quant_params = QuantParams.from_q_dq_node(input_node, ep)
output_quant_params = QuantParams.from_q_dq_node(output_node, ep)
if not input_quant_params.matches(output_quant_params):
why(
node,
reason=(
"XNNPACK static reshape requires matching input and "
"output quantization parameters."
),
)
return False

new_shape = node.args[1]

# Check for symbolic dims. They aren't lowerable to XNNPACK currently.
Expand Down
64 changes: 63 additions & 1 deletion backends/xnnpack/test/test_xnnpack_partitioner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@
import torch.nn.functional as F

from executorch.backends.xnnpack.partition.xnnpack_partitioner import XnnpackPartitioner
from executorch.exir import to_edge, to_edge_transform_and_lower
from executorch.backends.xnnpack.quantizer.xnnpack_quantizer import (
get_symmetric_quantization_config,
XNNPACKQuantizer,
)
from executorch.backends.xnnpack.utils.configs import get_transform_passes
from executorch.exir import EdgeCompileConfig, to_edge, to_edge_transform_and_lower
from executorch.extension.pybindings.portable_lib import (
_load_for_executorch_from_buffer,
)
from torch.export import export
from torchao.quantization.pt2e.quantize_pt2e import convert_pt2e, prepare_pt2e


class TestXnnpackPartitioner(unittest.TestCase):
Expand Down Expand Up @@ -88,6 +94,62 @@ def test_no_warning_for_to_edge_transform_and_lower_workflow(self):
log_contents = log_capture_string.getvalue()
self.assertNotIn("DEPRECATION WARNING", log_contents)

def test_quantized_view_with_mismatched_qparams_stays_portable(self):
class ConvPoolFlattenLinear(torch.nn.Module):
def __init__(self):
super().__init__()
self.conv = torch.nn.Conv2d(3, 4, 3)
self.linear = torch.nn.Linear(4, 2)

def forward(self, x):
x = self.conv(x)
x = F.adaptive_avg_pool2d(x, (1, 1))
return self.linear(torch.flatten(x, 1))

inputs = (torch.randn(1, 3, 8, 8),)
exported = export(ConvPoolFlattenLinear().eval(), inputs, strict=False)
quantizer = XNNPACKQuantizer().set_global(
get_symmetric_quantization_config(is_per_channel=False)
)
prepared = prepare_pt2e(exported.module(), quantizer)
prepared(*inputs)
converted = convert_pt2e(prepared)

output_quant = next(
node
for node in converted.graph.nodes
if "quantize_per_tensor" in str(node.target)
and any("flatten" in str(inp.target) for inp in node.all_input_nodes)
)
output_scale = output_quant.args[1] * 2
output_quant.args = (
output_quant.args[0],
output_scale,
*output_quant.args[2:],
)
output_dequant = next(iter(output_quant.users))
output_dequant.args = (
output_dequant.args[0],
output_scale,
*output_dequant.args[2:],
)
converted.recompile()

lowered = to_edge_transform_and_lower(
export(converted, inputs, strict=False),
partitioner=[XnnpackPartitioner()],
transform_passes=get_transform_passes(),
compile_config=EdgeCompileConfig(
_check_ir_validity=False, _skip_dim_order=True
),
)
self.assertTrue(
any(
"view_copy" in str(node.target)
for node in lowered.exported_program().graph.nodes
)
)

def test_multi_method_partitioning_with_shared_weights(self):
"""
Test that multi-method models with shared weights are correctly partitioned.
Expand Down
10 changes: 1 addition & 9 deletions runtime/core/portable_type/c10/c10/util/complex.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,11 @@ C10_HOST_DEVICE T abs(const c10::complex<T>& z) {
#endif
}

#if defined(USE_ROCM)
#define ROCm_Bug(x)
#else
#define ROCm_Bug(x) x
#endif

template <typename T>
C10_HOST_DEVICE T arg(const c10::complex<T>& z) {
return ROCm_Bug(std)::atan2(std::imag(z), std::real(z));
return std::atan2(std::imag(z), std::real(z));
}

#undef ROCm_Bug

template <typename T>
constexpr T norm(const c10::complex<T>& z) {
return z.real() * z.real() + z.imag() * z.imag();
Expand Down
1 change: 1 addition & 0 deletions runtime/core/portable_type/c10/c10/util/llvmMathExtras.h
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,7 @@ constexpr inline bool isShiftedUInt(uint64_t x) {
N + S <= 64, "isShiftedUInt<N, S> with N + S > 64 is too wide.");
// Per the two static_asserts above, S must be strictly less than 64. So
// 1 << S is not undefined behavior.
// NOLINTNEXTLINE(bugprone-chained-comparison)
return isUInt<N + S>(x) && (x % (UINT64_C(1) << S) == 0);
}

Expand Down
17 changes: 16 additions & 1 deletion runtime/core/portable_type/c10/c10/util/overflows.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,28 @@ template <typename To, typename From>
std::enable_if_t<std::is_floating_point_v<From>, bool> overflows(
From f,
bool strict_unsigned [[maybe_unused]] = false) {
using limit = std::numeric_limits<typename scalar_value_type<To>::type>;
using ToScalar = typename scalar_value_type<To>::type;
using limit = std::numeric_limits<ToScalar>;
if (limit::has_infinity && std::isinf(static_cast<double>(f))) {
return false;
}
if (!limit::has_quiet_NaN && (f != f)) {
return true;
}
if constexpr (std::is_integral_v<ToScalar>) {
// limit::max() for wide integer types is NOT exactly representable in
// floating point (e.g. int64 max = 2^63-1 rounds up to 2^63), so `f >
// limit::max()` lets a just-out-of-range value like 2^63 slip through and
// then become INT64_MIN via static_cast. Compare against the
// exactly-representable upper bound max()+1 == 2^digits instead. lowest()
// is 0 or a negated power of two, so it stays exact. (digits-1 keeps the
// shift < 64 for the uint64 case; the *2 recovers 2^digits without a 1<<64
// overflow.)
constexpr int digits = limit::digits;
constexpr From upper =
static_cast<From>(uint64_t{1} << (digits - 1)) * From{2};
return f < static_cast<From>(limit::lowest()) || f >= upper;
}
return f < limit::lowest() || f > limit::max();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@
#define C10_HAS_CPP_ATTRIBUTE(x) (0)
#endif

/// Bind a returned reference/pointer's lifetime to a parameter (or *this) so
/// Clang can warn when it would dangle. Expands to nothing on compilers that
/// lack the attribute (e.g. non-clang, older nvcc).
#if C10_HAS_CPP_ATTRIBUTE(clang::lifetimebound)
#define C10_LIFETIMEBOUND [[clang::lifetimebound]]
#else
#define C10_LIFETIMEBOUND
#endif

#ifndef FBCODE_CAFFE2
/// DEPRECATED: Warn if a type or return value is discarded.
#define C10_NODISCARD [[nodiscard]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ C10_HOST_DEVICE inline float fp16_ieee_to_fp32_value(uint16_t h) {
* Now, remember that denormalized half-precision numbers are represented as:
* FP16 = mantissa * 2**(-24).
* The trick is to construct a normalized single-precision number with the
* same mantissa and thehalf-precision input and with an exponent which would
* same mantissa and the half-precision input and with an exponent which would
* scale the corresponding mantissa bits to 2**(-24). A normalized
* single-precision floating-point number is represented as: FP32 = (1 +
* mantissa * 2**(-23)) * 2**(exponent - 127) Therefore, when the biased
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,19 @@ C10_CLANG_DIAGNOSTIC_IGNORE("-Wimplicit-int-float-conversion")

namespace c10 {

/// Returns false since we cannot have x < 0 if x is unsigned.
template <typename T>
inline constexpr bool is_negative(
const T& /*x*/,
std::true_type /*is_unsigned*/) {
return false;
}

/// Returns true if a signed variable x < 0
template <typename T>
inline constexpr bool is_negative(const T& x, std::false_type /*is_unsigned*/) {
return x < T(0);
}

/// Returns true if x < 0
/// NOTE: Will fail on an unsigned custom type
/// For the most part it's possible to fix this if
/// the custom type has a constexpr constructor.
/// However, notably, c10::Half does not :-(
template <typename T>
inline constexpr bool is_negative(const T& x) {
return is_negative(x, std::is_unsigned<T>());
}

/// Returns the sign of an unsigned variable x as 0, 1
template <typename T>
inline constexpr int signum(const T& x, std::true_type /*is_unsigned*/) {
return T(0) < x;
}

/// Returns the sign of a signed variable x as -1, 0, 1
template <typename T>
inline constexpr int signum(const T& x, std::false_type /*is_unsigned*/) {
return (T(0) < x) - (x < T(0));
if constexpr (std::is_unsigned_v<T>) {
// An unsigned value can never be less than zero.
return false;
} else {
return x < T(0);
}
}

/// Returns the sign of x as -1, 0, 1
Expand All @@ -57,7 +36,11 @@ inline constexpr int signum(const T& x, std::false_type /*is_unsigned*/) {
/// However, notably, c10::Half does not :-(
template <typename T>
inline constexpr int signum(const T& x) {
return signum(x, std::is_unsigned<T>());
if constexpr (std::is_unsigned_v<T>) {
return T(0) < x;
} else {
return (T(0) < x) - (x < T(0));
}
}

/// Returns true if a and b are not both negative
Expand Down Expand Up @@ -86,53 +69,22 @@ inline constexpr bool greater_than_max(const T& x) {
#pragma GCC diagnostic pop
#endif

/// Returns true if x < lowest(Limit). Standard comparison
template <typename Limit, typename T>
inline constexpr bool less_than_lowest(
const T& x,
std::false_type /*limit_is_unsigned*/,
std::false_type /*x_is_unsigned*/) {
return x < std::numeric_limits<Limit>::lowest();
}

/// Returns false since all the limit is signed and therefore includes
/// negative values but x cannot be negative because it is unsigned
template <typename Limit, typename T>
inline constexpr bool less_than_lowest(
const T& /*x*/,
std::false_type /*limit_is_unsigned*/,
std::true_type /*x_is_unsigned*/) {
return false;
}

/// Returns true if x < 0, where 0 is constructed from T.
/// Limit is not signed, so its lower value is zero
template <typename Limit, typename T>
inline constexpr bool less_than_lowest(
const T& x,
std::true_type /*limit_is_unsigned*/,
std::false_type /*x_is_unsigned*/) {
return x < T(0);
}

/// Returns false sign both types are unsigned
template <typename Limit, typename T>
inline constexpr bool less_than_lowest(
const T& /*x*/,
std::true_type /*limit_is_unsigned*/,
std::true_type /*x_is_unsigned*/) {
return false;
}

/// Returns true if x is less than the lowest value of type T
/// Returns true if x is less than the lowest value of type Limit
/// NOTE: Will fail on an unsigned custom type
/// For the most part it's possible to fix this if
/// the custom type has a constexpr constructor.
/// However, notably, c10::Half does not :
template <typename Limit, typename T>
inline constexpr bool less_than_lowest(const T& x) {
return less_than_lowest<Limit>(
x, std::is_unsigned<Limit>(), std::is_unsigned<T>());
if constexpr (std::is_unsigned_v<T>) {
// x is unsigned, so it can never be below the lowest value of any type.
return false;
} else if constexpr (std::is_unsigned_v<Limit>) {
// Limit is unsigned, so its lowest value is zero.
return x < T(0);
} else {
return x < std::numeric_limits<Limit>::lowest();
}
}

} // namespace c10
Expand Down
Loading
Loading