chore: import upstream snapshot with attribution
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:14:16 +08:00
commit 8a852e4b4e
36502 changed files with 9277225 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+861
View File
@@ -0,0 +1,861 @@
# XNNPACK backend for TensorFlow Lite
XNNPACK is a highly optimized library of neural network inference operators for
ARM, x86, and WebAssembly architectures in Android, iOS, Windows, Linux, macOS,
and Emscripten environments. This document describes how to use the XNNPACK
library as an inference engine for TensorFlow Lite.
## Using XNNPACK engine with TensorFlow Lite interpreter
XNNPACK integrates with TensorFlow Lite interpreter through the delegation
mechanism. TensorFlow Lite supports several methods to enable XNNPACK for
floating-point inference.
### Enable XNNPACK via Java API on Android (recommended on Android)
Pre-built
[nightly TensorFlow Lite binaries for Android](https://www.tensorflow.org/lite/guide/android#use_the_tensorflow_lite_aar_from_mavencentral)
include XNNPACK, albeit it is disabled by default. Use the `setUseXNNPACK`
method in `Interpreter.Options` class to enable it:
```java
Interpreter.Options interpreterOptions = new Interpreter.Options();
interpreterOptions.setUseXNNPACK(true);
Interpreter interpreter = new Interpreter(model, interpreterOptions);
```
### Enable XNNPACK via Swift/Objective-C API on iOS (recommended on iOS)
Pre-built
[nightly TensorFlow Lite CocoaPods](https://www.tensorflow.org/lite/guide/ios#specifying_versions)
include XNNPACK, but do not enable it by default. Swift developers can use
`InterpreterOptions` object to enable XNNPACK:
```swift
var options = InterpreterOptions()
options.isXNNPackEnabled = true
var interpreter = try Interpreter(modelPath: "model/path", options: options)
```
Objective-C developers can enable XNNPACK via a new property in the
`TFLInterpreterOptions` class:
```objc
TFLInterpreterOptions *options = [[TFLInterpreterOptions alloc] init];
options.useXNNPACK = YES;
NSError *error;
TFLInterpreter *interpreter =
[[TFLInterpreter alloc] initWithModelPath:@"model/path"
options:options
error:&error];
```
### Enable XNNPACK via Bazel build flags (recommended on desktop)
When building TensorFlow Lite with Bazel, add `--define
tflite_with_xnnpack=true`, and the TensorFlow Lite interpreter will use XNNPACK
engine by default.
The exact command depends on the target platform, e.g. for Android AAR you'd use
```
bazel build -c opt --fat_apk_cpu=x86,x86_64,arm64-v8a,armeabi-v7a \
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain \
--define android_dexmerger_tool=d8_dexmerger \
--define android_incremental_dexing_tool=d8_dexbuilder \
--define tflite_with_xnnpack=true \
//tensorflow/lite/java:tensorflow-lite
```
Note that in this case `Interpreter::SetNumThreads` invocation does not take
effect on number of threads used by XNNPACK engine. In order to specify number
of threads available for XNNPACK engine you should manually pass the value when
constructing the interpreter. The snippet below illustrates this assuming you
are using `InterpreterBuilder` to construct the interpreter:
```c++
// Load model
tflite::Model* model;
...
// Construct the interprepter
tflite::ops::builtin::BuiltinOpResolver resolver;
std::unique_ptr<tflite::Interpreter> interpreter;
TfLiteStatus res = tflite::InterpreterBuilder(model, resolver, num_threads);
```
**XNNPACK engine used by TensorFlow Lite interpreter uses a single thread for
inference by default.**
### Enable XNNPACK via additional dependency
Another way to enable XNNPACK is to build and link the
`//tensorflow/lite:tflite_with_xnnpack` target into your application alongside
the TensorFlow Lite framework.
This method works on platforms which support POSIX-style weak symbols (Android,
iOS, Linux, Mac, but **NOT** Windows).
### Enable XNNPACK via low-level delegate API (not recommended)
While it is possible to use low-level delegate API to enable XNNPACK, this
method is **NOT RECOMMENDED** unless you need to use TensorFlow Lite both with
and without XNNPACK (e.g. for benchmarking).
With low-level delegate API users create an XNNPACK delegate with the
`TfLiteXNNPackDelegateCreate` function, and then call
`Interpreter::ModifyGraphWithDelegate` to delegate supported parts of the model
to the XNNPACK delegate. The users must destroy the delegate with
`TfLiteXNNPackDelegateDelete` **after** releasing the TensorFlow Lite
interpreter. The snippet below illustrates the typical usage:
```c++
// Build the interpreter
std::unique_ptr<tflite::Interpreter> interpreter;
...
// IMPORTANT: initialize options with TfLiteXNNPackDelegateOptionsDefault() for
// API-compatibility with future extensions of the TfLiteXNNPackDelegateOptions
// structure.
TfLiteXNNPackDelegateOptions xnnpack_options =
TfLiteXNNPackDelegateOptionsDefault();
xnnpack_options.num_threads = num_threads;
TfLiteDelegate* xnnpack_delegate =
TfLiteXNNPackDelegateCreate(&xnnpack_options);
if (interpreter->ModifyGraphWithDelegate(xnnpack_delegate) != kTfLiteOk) {
// Report error and fall back to another delegate, or the default backend
}
// IMPORTANT: AllocateTensors can be called only AFTER ModifyGraphWithDelegate
...
// Run inference using XNNPACK
interpreter->Invoke()
...
// IMPORTANT: release the interpreter before destroying the delegate
interpreter.reset();
TfLiteXNNPackDelegateDelete(xnnpack_delegate);
```
### Using the XNNPACK Weights Cache
XNNPACK internally packs static weights for operations (like convolutions) in
order to make accessing weights more memory friendly. XNNPACK needs to allocate
memory internally to hold these packed weights. If you are starting multiple
TFLite interpreter instances based on the same model, there can be multiple
copies of the same packed weights in each instance which can cause high memory
usage.
The weights cache can be used to store these packed weights to a file to avoid
re-packing on every run and to share share packed weights between multiple
TFLite instances. Depending on your use case, **this can lead to significant
intialization speed-up and memory savings**.
The initialization speed-up happens because the packing operations are only done
once and read from the cache file for subsequent runs. We are skipping the most
expensive part of XNNPack's initialization.
The memory savings have multiple reasons, which don't all apply to all use
cases:
1. The original weights are never read when using the cache as packing doesn't
happen. This is because TFLite usually uses `mmap` to load the model files
and that only pulls data that you actually read into memory.
2. The weight cache provides buffer de-duplication: if multiple tensors share
the same weights, it only keeps one copy of the corresponding packed
weights. This is usually the case for LLM models and models that have
several signatures.
3. The weight cache can be shared between interpreter instances, further
de-duplicating packed data.
4. Thanks to using `mmap`, the file-backed cache can be shared between
processes, further de-duplicating packed data. *This is automatic, you don't
need to do anything.*
The weights cache is a contents-based cache. Every time XNNPACK has to pack
weights, it first tries to look up if the packed weights can be found in the
weights cache. If they can be found, we access the packed weights in the cache
for subsequent operations. Otherwise, the weights are packed and added to the
cache.
Warning: The weight cache cannot be shared between models or hardware
architectures, a different cache file must be used for each *(model,
architecture)* pair.
Warning: XNNPack does it's best to detect outdated cache files but cannot check
for model changes. Checking that the model has been updated and deleting old
cache files is left to the user.
#### Saving the Cache to Disk
Saving the cache to disk bring you the full list of advantages listed above.
```c++
std::unique_ptr<tflite::Interpreter> interpreter;
// Like using the low-level API above, initialize options, and pass this cache
// to an XNNPACK delegate via the options.
TfLiteXNNPackDelegateOptions xnnpack_options =
TfLiteXNNPackDelegateOptionsDefault();
xnnpack_options.weight_cache_file_path = "path/to/the/cache/file";
// Modify graph with delegate, as above...
TfLiteDelegate* delegate = TfLiteXNNPackDelegateCreate(&xnnpack_options);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) {
// Handle errors...
}
// You can now run the interpreter.
//
// Static weights will be packed and written into weights_cache the first time,
// directly read from disk the 2nd time.
```
#### Using the Cache In-Memory
If you cannot access a file system, the cache can also be used "in-memory"
instead of saving it to disk.
You will lose advantages 1 and 4 but can still profit from 2 and 3.
Note: Currently, this is only accessible on systems that have the `memfd_create`
system call.
```c++
std::unique_ptr<tflite::Interpreter> interpreter;
// Like using the low-level API above, initialize options, and pass this cache
// to an XNNPACK delegate via the options.
TfLiteXNNPackDelegateOptions xnnpack_options =
TfLiteXNNPackDelegateOptionsDefault();
xnnpack_options.weight_cache_file_path =
TfLiteXNNPackDelegateInMemoryFilePath();
// Modify graph with delegate, as above...
TfLiteDelegate* delegate = TfLiteXNNPackDelegateCreate(&xnnpack_options);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) {
// Handle errors...
}
// You can now run the interpreter.
//
// Static weights will be packed and written into weights_cache the first time,
// directly read from disk the 2nd time.
```
#### Sharing the Cache Between TFLite Interpreter Instances
This is independent of using file-backed or in-memory caching. To share a cache
between interpreters, you need to create the cache outside of the delegate and
pass it down to it.
```c++
std::unique_ptr<tflite::Interpreter> interpreter1;
std::unique_ptr<tflite::Interpreter> interpreter2;
// Create a weight cache. This should outlive the interpreter.
tflite::xnnpack::MMapWeightCacheProvider weight_cache;
// Like using the low-level API above, initialize options, and pass this cache
// to an XNNPACK delegate via the options.
TfLiteXNNPackDelegateOptions xnnpack_options =
TfLiteXNNPackDelegateOptionsDefault();
// When sharing an existing cache, the path will be used by the first
// interpreter that is run to load it or create it.
xnnpack_options.weight_cache_file_path = /* See previous examples. */;
// Share the cache.
xnnpack_options.weight_cache_provider = &weight_cache;
// Modify graph with delegate, as above...
TfLiteDelegate* delegate1 = TfLiteXNNPackDelegateCreate(&xnnpack_options);
if (interpreter1->ModifyGraphWithDelegate(delegate1) != kTfLiteOk) {
// Handle errors...
}
// Signal to the weight cache provider that there's no building to be done
// anymore. That way subsequent interpreter setups won't try to continue
// building the cache.
weight_cache.StopBuild();
// Modify graph with delegate, as above...
TfLiteDelegate* delegate2 = TfLiteXNNPackDelegateCreate(&xnnpack_options);
if (interpreter2->ModifyGraphWithDelegate(delegate2) != kTfLiteOk) {
// Handle errors...
}
// You can now run the interpreters.
//
// Static weights will be packed and written into weights_cache the first time,
// directly read from disk the 2nd time.
```
Warning: Sharing the cache is not thread safe for building. You should always do
one full run of one of the interpreters before starting threading. **Once the
building run is done**, call `weight_cache.StopBuild()` before using the weight
cache provider to build other delegate instances.
## Profiling
When TfLite profiling is enabled, XNNPACK will time each operator and report the
results to TfLite which will print them as part of the overall execution
profile.
## Limitations and supported operators
XNNPACK delegate is a work-in-progress, and currently supports a limited set of
operators. Unsupported operators will fall back to the default implementations,
so models using a combination of supported and unsupported operators can still
benefit from XNNPACK delegate.
### Floating-Point (IEEE FP32) Operators
Below is the list of currently supported floating-point operators:
#### `ABS`
* Inputs and outputs must be in 32-bit floating-point format.
#### `ADD`
* Inputs and outputs must be in 32-bit floating-point format.
* Only addition with two inputs is supported.
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `AVERAGE_POOL_2D`
* Inputs and outputs must be in 32-bit floating-point format.
* 1x1 pooling with non-unit stride is not supported.
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `CEIL`
* Inputs and outputs must be in 32-bit floating-point format.
#### `CONCATENATION`
* Inputs and outputs must be in 32-bit floating-point format.
* Only concatenation with two, three, or four inputs is supported.
#### `CONV_2D`
* Inputs and outputs must be in 32-bit floating-point format.
* Bias is mandatory.
* Both filter and bias must be static (use `kTfLiteMmapRo` allocation type).
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `DEPTH_TO_SPACE`
* Inputs and outputs must be in 32-bit floating-point format.
* Block size must be greater than 1.
#### `DEPTHWISE_CONV_2D`
* Inputs and outputs must be in 32-bit floating-point format.
* Bias is mandatory.
* Both filter and bias must be static (use `kTfLiteMmapRo` allocation type).
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `DIV`
* Inputs and outputs must be in 32-bit floating-point format.
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `ELU`
* Inputs and outputs must be in 32-bit floating-point format.
#### `FULLY_CONNECTED`
* Inputs and outputs must be in 32-bit floating-point format.
* Both filter and bias must be static (use `kTfLiteMmapRo` allocation type).
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `FLOOR`
* Inputs and outputs must be in 32-bit floating-point format.
#### `HARD_SWISH`
* Inputs and outputs must be in 32-bit floating-point format.
#### `LEAKY_RELU`
* Inputs and outputs must be in 32-bit floating-point format.
#### `LOGISTIC`
* Inputs and outputs must be in 32-bit floating-point format.
#### `MAX_POOL_2D`
* Inputs and outputs must be in 32-bit floating-point format.
* 1x1 pooling with non-unit stride is not supported.
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `MAXIMUM`
* Inputs and outputs must be in 32-bit floating-point format.
#### `MEAN`
* The first input and the output must be 4D tensors in 32-bit floating-point
format.
* The second input (the input with the axes specification) must be static (use
`kTfLiteMmapRo` allocation type).
* Only [1, 2], [2, 1], and [2] axes specification (i.e. reduction across
either both spatial dimensions or across the width dimension) is supported.
#### `MINIMUM`
* Inputs and outputs must be in 32-bit floating-point format.
#### `MUL`
* Inputs and outputs must be in 32-bit floating-point format.
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `NEG`
* Inputs and outputs must be in 32-bit floating-point format.
#### `PAD`
* The first input and the output must be in 32-bit floating-point format.
* The second input (the input with the padding specification) must be static
(use `kTfLiteMmapRo` allocation type).
* The numbers of padding elements must be non-negative.
#### `PRELU`
* Inputs and outputs must be in 32-bit floating-point format.
* Slope must be static (use `kTfLiteMmapRo` allocation type).
* Slope must be either a 1D tensor, or have all its non-channel dimensions
equal 1.
#### `RELU`
* Inputs and outputs must be in 32-bit floating-point format.
#### `RELU6`
* Inputs and outputs must be in 32-bit floating-point format.
#### `RELU_N1_TO_1`
* Inputs and outputs must be in 32-bit floating-point format.
#### `RESHAPE`
* The first input and the output must be in 32-bit floating-point format.
* The second input (the input with the new shape specification) must be either
static (use `kTfLiteMmapRo` allocation type), or absent (with the new shape
specified via `ReshapeOptions` table).
#### `RESIZE_BILINEAR`
* The first input and the output must be 4D tensors in 32-bit floating-point
format.
* The second input (the input with the new shape specification) must be static
(use `kTfLiteMmapRo` allocation type).
#### `ROUND`
* Inputs and outputs must be in 32-bit floating-point format.
#### `SLICE`
* The first input and the output must be in 32-bit floating-point format.
* The second and third inputs (the inputs with the slices' begin and size
specification) must be static (use `kTfLiteMmapRo` allocation type).
#### `SOFTMAX`
* Inputs and outputs must be in 32-bit floating-point format.
* Only `beta = 1.0` is supported.
#### `SPACE_TO_DEPTH`
* Inputs and outputs must be in 32-bit floating-point format.
* Block size must be greater than 1.
#### `SPLIT`
* Inputs and outputs must be in 32-bit floating-point format.
* Only split into two, three, or four outputs is supported.
#### `SQRT`
* Inputs and outputs must be in 32-bit floating-point format.
#### `SQUARE`
* Inputs and outputs must be in 32-bit floating-point format.
#### `SQUARED_DIFFERENCE`
* Inputs and outputs must be in 32-bit floating-point format.
#### `STRIDED_SLICE`
* The first input and the output must be in 32-bit floating-point format.
* The second, third, and fourth inputs (the inputs with the slices' begin,
end, and stride specification) must be static (use `kTfLiteMmapRo`
allocation type).
* The fourth input (strides) must be all ones.
* The ellipsis mask, new axis mask, and shrink axis mask must be 0.
#### `SUB`
* Inputs and outputs must be in 32-bit floating-point format.
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `TANH`
* Inputs and outputs must be in 32-bit floating-point format.
#### `TRANSPOSE`
* The first input and the output must be in 32-bit floating-point format.
* The second input (the input with the permutation specification) must be
static (use `kTfLiteMmapRo` allocation type).
#### `TRANSPOSE_CONV`
* Input, filter, bias (if present) and output tensors must be in 32-bit
floating-point format.
* Output size, filter and bias (if present) must be static (use
`kTfLiteMmapRo` allocation type).
### Floating-Point (IEEE FP16) Operators
XNNPACK supports half-precision (using IEEE FP16 format) inference for all
floating-point operators. XNNPACK automatically enables half-precision inference
when the following conditions are met:
* XNNPACK runs on hardware that natively supports computations in IEEE FP16
format. Currently, this hardware is limited to ARM & ARM64 devices with
ARMv8.2 FP16 arithmetics extension, and includes Android phones starting
with Pixel 3, Galaxy S9 (Snapdragon SoC), Galaxy S10 (Exynos SoC), iOS
devices with A11 or newer SoCs, all Apple Silicon Macs, and Windows ARM64
laptops based with Snapdragon 850 SoC or newer.
* The model's "reduced_precision_support" metadata indicates that the model is
compatible with FP16 inference. The metadata can be added during model
conversion using the `_experimental_supported_accumulation_type` attribute
of the
[tf.lite.TargetSpec](https://www.tensorflow.org/api_docs/python/tf/lite/TargetSpec)
object:
```python
converter.optimizations = [tf.lite.Optimize.DEFAULT]
...
converter.target_spec.supported_types = [tf.float16]
converter.target_spec._experimental_supported_accumulation_type = tf.dtypes.float16
```
When the above conditions are met, XNNPACK replace FP32 operators with their
FP16 equivalents, and insert additional operators to convert model inputs from
FP32 to FP16 and convert model outputs back from FP16 to FP32. If the above
conditions are not met, XNNPACK will perform model inference with FP32
calculations.
Additionally, XNNPACK delegate provides an option to force FP16 inference
regardless of model metadata. This option is intended for development workflows,
and in particular for testing end-to-end accuracy of model when FP16 inference
is used. Forcing FP16 inference has several effects:
* Besides ARM64 devices with ARMv8.2 FP16 arithmetics extension, forced FP16
inference is supported on x86/x86-64 devices with AVX2 extension in
emulation mode: all elementary floating-point operations are computed in
FP32, then converted to FP16 and back to FP32. Note that such simulation is
not bit-exact equivalent to native FP16 inference, but simulates the effects
of restricted mantissa precision and exponent range in the native FP16
arithmetics.
* On devices that support neither the native FP16 arithmetics (ARM64 devices
with ARMv8.2 FP16 arithmetics extension), nor emulation (x86/x86-64 devices
with AVX2 extension), inference will fail rather than fall back to FP32.
* If any floating-point operator offloaded to XNNPACK is not supported for
FP16 inference, inference will fail rather than fall back to FP32.
To force FP16 inference, either build the delegate with `--define
xnnpack_force_float_precision=fp16` option, or add
`TFLITE_XNNPACK_DELEGATE_FLAG_FORCE_FP16` flag to the
`TfLiteXNNPackDelegateOptions.flags` bitmask passed into the
`TfLiteXNNPackDelegateCreate` call:
```c
TfLiteXNNPackDelegateOptions xnnpack_options =
TfLiteXNNPackDelegateOptionsDefault();
...
xnnpack_options.flags |= TFLITE_XNNPACK_DELEGATE_FLAG_FORCE_FP16;
TfLiteDelegate* xnnpack_delegate =
TfLiteXNNPackDelegateCreate(&xnnpack_options);
```
XNNPACK has full feature parity between FP32 and FP16 operators: all operators
that are supported for FP32 inference are also supported for FP16 inference, and
vice versa. In particular, sparse inference operators are supported for FP16
inference on ARM processors.
### Quantized Operators
By default, quantized inference in XNNPACK delegate is disabled, and XNNPACK is
used only for floating-point models. Support for quantized inference in XNNPACK
must be enabled by adding extra Bazel flags when building TensorFlow Lite.
* `--define tflite_with_xnnpack_qs8=true` flag enables XNNPACK inference for
quantized operators using signed quantization schema. This schema is used by
models produced by
[Model Optimization Toolkit](https://www.tensorflow.org/model_optimization)
through either post-training integer quantization or quantization-aware
training. Post-training dynamic range quantization is not supported in
XNNPACK.
* `--define tflite_with_xnnpack_qu8=true` flag enables XNNPACK inference for
quantized operators using unsigned quantization schema, produced via the
legacy TensorFlow 1.X quantization tooling. This option is experimental and
may perform suboptimally on mobile processors with NEON DOT product
instructions.
Below is the list of currently supported quantized operators:
#### `ADD`
* Inputs and outputs must be in 8-bit quantized format.
* Only addition with two inputs is supported.
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `CONCATENATION`
* Inputs and outputs must be in 8-bit quantized format.
* Only concatenation with two, three, or four inputs is supported.
#### `CONV_2D`
* Inputs and outputs must be in 8-bit quantized format (bias must be in 32-bit
quantized format).
* Bias is mandatory.
* Both filter and bias must be static (use `kTfLiteMmapRo` allocation type),
and can use either per-tensor or per-channel quantization parameters.
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `DEPTH_TO_SPACE`
* Inputs and outputs must be in 8-bit quantized format.
* Block size must be greater than 1.
#### `DEPTHWISE_CONV_2D`
* Inputs and outputs must be in 8-bit quantized format (bias must be in 32-bit
quantized format).
* Bias is mandatory.
* Both filter and bias must be static (use `kTfLiteMmapRo` allocation type),
and can use either per-tensor or per-channel quantization parameters.
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `DEQUANTIZE`
* Input tensor must be in 8-bit quantized format without per-channel
quantization.
* Output tensor must be in 32-bit floating-point format.
#### `ELU`
* Inputs and outputs must be in 8-bit signed quantized format.
#### `FULLY_CONNECTED`
* Inputs and outputs must be in 8-bit quantized format (bias, if present, must
be in 32-bit quantized format).
* Both filter and bias must be static (use `kTfLiteMmapRo` allocation type).
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `LEAKY_RELU`
* Inputs and outputs must be in 8-bit quantized format.
* The ratio of input scale to output scale must be within [1/256, 128].
* The product of negative slope by the ratio of input scale to output scale
must be within either [-127.99609375, -1/256] range or [1/256, 128] range.
#### `LOGISTIC`
* Inputs and outputs must be in 8-bit quantized format.
#### `MAX_POOL_2D`
* Inputs and outputs must be in 8-bit quantized format.
* 1x1 pooling with non-unit stride is not supported.
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `MEAN`
* The first input and the output must be 4D tensors in 8-bit quantized format.
* The second input (the input with the axes specification) must be static (use
`kTfLiteMmapRo` allocation type).
* Only [1, 2], [2, 1], and [2] axes specification (i.e. reduction across
either both spatial dimensions or across the width dimension) is supported.
#### `MUL`
* Inputs and outputs must be in 8-bit quantized format.
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `PAD`
* The first input and the output must be in 8-bit quantized format.
* The second input (the input with the padding specification) must be static
(use `kTfLiteMmapRo` allocation type).
* The numbers of padding elements must be non-negative.
#### `QUANTIZE`
* Input tensor must be in 32-bit floating-point format or in 8-bit quantized
format.
* Output tensor must be in 8-bit quantized format without per-channel
quantization.
* If inputs are in 8-bit quantized format, they must have the same signedness
as the outputs, and the ratio of input scale to output scale must be in the
[2**-8, 2**7] range.
#### `RESHAPE`
* The first input and the output must be in 8-bit quantized format.
* The second input (the input with the new shape specification) must be either
static (use `kTfLiteMmapRo` allocation type), or absent (with the new shape
specified via `ReshapeOptions` table).
#### `RESIZE_BILINEAR`
* The first input and the output must be 4D tensors in 8-bit quantized format.
* The second input (the input with the new shape specification) must be static
(use `kTfLiteMmapRo` allocation type).
#### `SLICE`
* The first input and the output must be in 8-bit quantized format.
* The second and third inputs (the inputs with the slices' begin and size
specification) must be static (use `kTfLiteMmapRo` allocation type).
#### `SPACE_TO_DEPTH`
* Inputs and outputs must be in 8-bit quantized format.
* Block size must be greater than 1.
#### `SPLIT`
* Inputs and outputs must be in 8-bit quantized format.
* Only split into two, three, or four outputs is supported.
#### `SUB`
* Inputs and outputs must be in 8-bit quantized format.
* Fused `NONE`, `RELU`, `RELU_N1_TO_1`, and `RELU6` activations are supported,
but fused `TANH` and `SIGN_BIT` activations are not.
#### `TANH`
* Inputs and outputs must be in 8-bit quantized format.
#### `TRANSPOSE`
* The first input and the output must be in 8-bit quantized format.
* The second input (the input with the permutation specification) must be
static (use `kTfLiteMmapRo` allocation type).
#### `TRANSPOSE_CONV`
* Input, filter, and output tensors must be in 8-bit quantized format (bias,
if present, must be in 32-bit quantized format).
* Output size, filter and bias (if present) must be static (use
`kTfLiteMmapRo` allocation type).
### Sparse Inference
XNNPACK backend supports sparse inference for CNN models described in the
[Fast Sparse ConvNets](https://arxiv.org/abs/1911.09723) paper. Sparse inference
is restricted to subgraphs with the following floating-point operators:
* Sparse subgraph must store its weights in sparse representation (using
`DENSIFY` operators in the TensorFlow Lite schema).
* Sparse subgraph must start with a 3x3 stride-2 `CONV_2D` operator with
padding 1 on each side, no dilation, and 3 input channels.
* Sparse subgraph must end with either a `MEAN` operator with reduction across
spatial axes, or a `DEPTH_TO_SPACE` operator.
* Sparse subgraph may contain the following operators:
* `CONV_2D` with 1x1 kernel and no padding. At least 2/3rd of filter
weights in the 1x1 `CONV_2D` operators across the sparse subgraph must
be zeroes to enable sparse inference.
* `DEPTHWISE_CONV_2D` with 3x3 kernel, stride 1, no dilation, and padding
1 on each side.
* `DEPTHWISE_CONV_2D` with 3x3 kernel, stride 2, no dilation, and padding
1 on each side.
* `DEPTHWISE_CONV_2D` with 5x5 kernel, stride 1, no dilation, and padding
2 on each side.
* `DEPTHWISE_CONV_2D` with 5x5 kernel, stride 2, no dilation, and padding
2 on each side.
* `RESIZE_BILINEAR` operator with output dimensions greater than 1.
* `MEAN` operator with reduction across spatial axes.
* `ADD` and `MUL` operators where both inputs are 4D tensors. If one of
the inputs to `ADD` or `MUL` is a constant tensor, it must be
representable as either a scalar, or a 1D vector.
* Unary elementwise operators `ABS`, `CEIL`, `ELU`, `FLOOR`, `HARD_SWISH`,
`LEAKY_RELU`, `LOGISTIC`, `NEG`, `RELU`, `RELU6`, `RELU_N1_TO_1`,
`ROUND`, `SIGMOID`, and `SQUARE`.
Pre-trained
[Fast Sparse ConvNets models](https://github.com/google-research/google-research/tree/master/fastconvnets)
provide examples that satisfy these constraints.
### Transient Indirection Buffer
Some of XNNPACK operators, such as `CONV_2D`, use indirection buffers to supply
locations of input for the operators. Indirection buffers are created for each
operator instance, and are persistent by default. It causes XNNPACK to use
substantial amount of memory, especially when the input is in high resolution.
To reduce the memory footprint of indirection buffers, either build the delegate
with `--define tflite_with_xnnpack_transient_indirection_buffer=true` option, or
add `TFLITE_XNNPACK_DELEGATE_FLAG_TRANSIENT_INDIRECTION_BUFFER` flag to the
`TfLiteXNNPackDelegateOptions.flags` bitmask passed into the
`TfLiteXNNPackDelegateCreate` call:
```c
TfLiteXNNPackDelegateOptions xnnpack_options =
TfLiteXNNPackDelegateOptionsDefault();
...
xnnpack_options.flags |= TFLITE_XNNPACK_DELEGATE_FLAG_TRANSIENT_INDIRECTION_BUFFER;
TfLiteDelegate* xnnpack_delegate =
TfLiteXNNPackDelegateCreate(&xnnpack_options);
```
XNNPACK will now use the temporary memory in the workspace for indirection
buffers. However, instead of initializing the indirection buffers once during
the initialization of the operators, the indirection buffers will be initialized
during every inference run.
Below is the list of currently supported operators:
* `CONV_2D`
* `DEPTHWISE_CONV_2D`
* `RESIZE_BILINEAR`
@@ -0,0 +1,426 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/pool_2d_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
TEST(AveragePool2D, UnitPoolSamePadding) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(1)
.PoolingWidth(1)
.StrideHeight(1)
.StrideWidth(1)
.SamePadding()
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
TEST(AveragePool2D, UnitPoolValidPadding) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(1)
.PoolingWidth(1)
.StrideHeight(1)
.StrideWidth(1)
.ValidPadding()
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
TEST(AveragePool2D, EqualPoolAndStrideWithSamePadding) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
const int32_t pool_height = pool_rng();
const int32_t pool_width = pool_rng();
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_height)
.PoolingWidth(pool_width)
.StrideHeight(pool_height)
.StrideWidth(pool_width)
.SamePadding()
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
TEST(AveragePool2D, EqualPoolAndStrideWithValidPadding) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
const int32_t pool_height = pool_rng();
const int32_t pool_width = pool_rng();
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_height)
.PoolingWidth(pool_width)
.StrideHeight(pool_height)
.StrideWidth(pool_width)
.ValidPadding()
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
TEST(AveragePool2D, LargePoolSmallStrideWithSamePadding) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(4, 7), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SamePadding()
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
TEST(AveragePool2D, LargePoolSmallStrideWithValidPadding) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(4, 7), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ValidPadding()
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
TEST(AveragePool2D, GlobalPooling) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
const int32_t height = input_rng();
const int32_t width = input_rng();
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(height)
.InputWidth(width)
.Channels(channel_rng())
.PoolingHeight(height)
.PoolingWidth(width)
.ValidPadding()
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
TEST(AveragePool2D, ReluActivation) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluActivation()
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
TEST(AveragePool2D, Relu6Activation) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Relu6Activation()
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
TEST(AveragePool2D, ReluMinus1To1Activation) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluMinus1To1Activation()
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
TEST(AveragePool2D, DISABLED_TanhActivation) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.TanhActivation()
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
TEST(AveragePool2D, DISABLED_SignBitActivation) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SignBitActivation()
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
TEST(AveragePool2D, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Test(BuiltinOperator_AVERAGE_POOL_2D, xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,321 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/batch_matrix_multiply_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
class BatchMatrixMultiplyTest : public testing::Test {
public:
// std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
auto get_delegate(int num_threads = 1) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_ENABLE_LATEST_OPERATORS;
delegate_options.num_threads = num_threads;
return std::unique_ptr<TfLiteDelegate,
decltype(&TfLiteXNNPackDelegateDelete)>(
TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
}
int32_t shape_rng() {
return std::uniform_int_distribution<int32_t>(2, 5)(rng_);
}
int32_t channels_rng() {
return std::uniform_int_distribution<int32_t>(2, 9)(rng_);
}
private:
std::random_device random_device_;
std::mt19937 rng_ = std::mt19937(random_device_());
};
TEST_F(BatchMatrixMultiplyTest, 3D) {
const auto batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({batch, height, input_channels})
.InputBDims({batch, input_channels, output_channels})
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest, DynamicallyQuantizedPerChannelWeights2D) {
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({height, input_channels})
.InputBDims({input_channels, output_channels})
.InputBQuant(BatchMatrixMultiplyTester::kChannel)
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest,
DynamicallyQuantizedPerChannelWeights2DTransposeB) {
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({height, input_channels})
.InputBDims({output_channels, input_channels})
.InputBQuant(BatchMatrixMultiplyTester::kChannel)
.TransposeB(true)
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest, DynamicallyQuantizedPerTensorWeights3D) {
const auto batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({batch, height, input_channels})
.InputBDims({batch, input_channels, output_channels})
.InputBQuant(BatchMatrixMultiplyTester::kTensor)
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest,
DynamicallyQuantizedPerTensorWeights3DTransposeB) {
const auto batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({batch, height, input_channels})
.InputBDims({batch, output_channels, input_channels})
.InputBQuant(BatchMatrixMultiplyTester::kTensor)
.TransposeB(true)
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest, DynamicallyQuantizedPerChannelWeights3D) {
const auto batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({batch, height, input_channels})
.InputBDims({batch, input_channels, output_channels})
.InputBQuant(BatchMatrixMultiplyTester::kChannel)
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest,
DynamicallyQuantizedPerChannelWeights3DTransposeB) {
const auto batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({batch, height, input_channels})
.InputBDims({batch, output_channels, input_channels})
.InputBQuant(BatchMatrixMultiplyTester::kChannel)
.TransposeB(true)
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest, BroadcastOne3D) {
const auto batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({batch, height, input_channels})
.InputBDims({1, input_channels, output_channels})
.Test(xnnpack_delegate.get());
BatchMatrixMultiplyTester()
.InputADims({1, height, input_channels})
.InputBDims({batch, input_channels, output_channels})
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest, BroadcastImplicit3D) {
const auto batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({batch, height, input_channels})
.InputBDims({input_channels, output_channels})
.Test(xnnpack_delegate.get());
BatchMatrixMultiplyTester()
.InputADims({height, input_channels})
.InputBDims({batch, input_channels, output_channels})
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest, 4D) {
const auto outer_batch = shape_rng();
const auto inner_batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({outer_batch, inner_batch, height, input_channels})
.InputBDims({outer_batch, inner_batch, input_channels, output_channels})
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest, BroadcastOne4D) {
const auto outer_batch = shape_rng();
const auto inner_batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({1, inner_batch, height, input_channels})
.InputBDims({outer_batch, inner_batch, input_channels, output_channels})
.Test(xnnpack_delegate.get());
BatchMatrixMultiplyTester()
.InputADims({outer_batch, inner_batch, height, input_channels})
.InputBDims({1, inner_batch, input_channels, output_channels})
.Test(xnnpack_delegate.get());
BatchMatrixMultiplyTester()
.InputADims({outer_batch, 1, height, input_channels})
.InputBDims({outer_batch, inner_batch, input_channels, output_channels})
.Test(xnnpack_delegate.get());
BatchMatrixMultiplyTester()
.InputADims({outer_batch, inner_batch, height, input_channels})
.InputBDims({outer_batch, 1, input_channels, output_channels})
.Test(xnnpack_delegate.get());
BatchMatrixMultiplyTester()
.InputADims({1, 1, height, input_channels})
.InputBDims({outer_batch, inner_batch, input_channels, output_channels})
.Test(xnnpack_delegate.get());
BatchMatrixMultiplyTester()
.InputADims({outer_batch, inner_batch, height, input_channels})
.InputBDims({1, 1, input_channels, output_channels})
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest, BroadcastImplicit4D) {
const auto outer_batch = shape_rng();
const auto inner_batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({inner_batch, height, input_channels})
.InputBDims({outer_batch, inner_batch, input_channels, output_channels})
.Test(xnnpack_delegate.get());
BatchMatrixMultiplyTester()
.InputADims({outer_batch, inner_batch, height, input_channels})
.InputBDims({inner_batch, input_channels, output_channels})
.Test(xnnpack_delegate.get());
BatchMatrixMultiplyTester()
.InputADims({height, input_channels})
.InputBDims({outer_batch, inner_batch, input_channels, output_channels})
.Test(xnnpack_delegate.get());
BatchMatrixMultiplyTester()
.InputADims({outer_batch, inner_batch, height, input_channels})
.InputBDims({input_channels, output_channels})
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest, 4D_TransposeB) {
const auto outer_batch = shape_rng();
const auto inner_batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate();
BatchMatrixMultiplyTester()
.InputADims({outer_batch, inner_batch, height, input_channels})
.InputBDims({outer_batch, inner_batch, output_channels, input_channels})
.TransposeB(true)
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest, MultiThreading) {
const auto batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
auto xnnpack_delegate = get_delegate(/*num_threads=*/2);
BatchMatrixMultiplyTester()
.InputADims({batch, height, input_channels})
.InputBDims({batch, input_channels, output_channels})
.Test(xnnpack_delegate.get());
}
TEST_F(BatchMatrixMultiplyTest, WeightsCache) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
std::unique_ptr<TfLiteXNNPackDelegateWeightsCache,
decltype(&TfLiteXNNPackDelegateWeightsCacheDelete)>
weights_cache(TfLiteXNNPackDelegateWeightsCacheCreate(),
TfLiteXNNPackDelegateWeightsCacheDelete);
delegate_options.weights_cache = weights_cache.get();
delegate_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_ENABLE_LATEST_OPERATORS;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
const auto batch = shape_rng();
const auto height = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
BatchMatrixMultiplyTester()
.InputADims({batch, height, input_channels})
.InputBDims({batch, input_channels, output_channels})
.WeightsCache(weights_cache.get())
.Test(xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,265 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/batch_matrix_multiply_tester.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
std::vector<int32_t> BatchMatrixMultiplyTester::OutputShape() const {
std::vector<int32_t> output_shape = InputADims();
const size_t output_dimensions = output_shape.size();
output_shape[output_dimensions - 1] =
TransposeB() ? InputBDims()[InputBDims().size() - 2]
: InputBDims()[InputBDims().size() - 1];
return output_shape;
}
void BatchMatrixMultiplyTester::Test(TfLiteDelegate* delegate) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng_f32 =
std::bind(std::uniform_real_distribution<float>(), std::ref(rng));
std::vector<char> buffer = CreateTfLiteModel();
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 2);
ASSERT_EQ(default_interpreter->inputs().size(), 2);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
ASSERT_TRUE(delegate_interpreter->primary_subgraph().IsFullyDelegated());
if (weights_cache_ != nullptr) {
TfLiteXNNPackDelegateWeightsCacheFinalizeHard(weights_cache_);
}
float* default_input1_data =
default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input1_data, Input1Size(), std::ref(input_rng_f32));
float* delegate_input1_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input1_data, Input1Size(), delegate_input1_data);
if (InputBQuant() == kNone) {
float* default_input2_data =
default_interpreter->typed_input_tensor<float>(1);
std::generate_n(default_input2_data, Input2Size(), std::ref(input_rng_f32));
float* delegate_input2_data =
delegate_interpreter->typed_input_tensor<float>(1);
std::copy_n(default_input2_data, Input2Size(), delegate_input2_data);
}
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* delegate_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
// The error estimate used here assume that the inputs are all in the range
// `[-1, 1]`. When no quantization is applied, the error measure is the value
// $\gamma_k$ for the dot products used to compute the entries of the output
// matrix. For quantized inputs, the error bound is the maximum accumulated
// quantization error for said dot product.
const int32_t output_size = ComputeSize(OutputShape());
const int32_t k = InputADims().back();
float max_abs_error =
(InputBQuant() == kNone)
? k * std::numeric_limits<float>::epsilon() /
(1.0f - k * std::numeric_limits<float>::epsilon())
: k * 0.5f / 127;
for (size_t i = 0; i < output_size; i++) {
ASSERT_NEAR(default_output_data[i], delegate_output_data[i], max_abs_error);
}
}
std::vector<char> BatchMatrixMultiplyTester::CreateTfLiteModel() const {
/*************************** Define operator codes **************************/
flatbuffers::FlatBufferBuilder builder;
std::vector<flatbuffers::Offset<OperatorCode>> operator_codes{
{CreateOperatorCode(builder, BuiltinOperator_BATCH_MATMUL)}};
/****************************** Define buffers ******************************/
std::vector<flatbuffers::Offset<Buffer>> buffers{
{CreateBuffer(builder, builder.CreateVector({})),
CreateBuffer(builder, builder.CreateVector({}))}};
/****************************** Define tensors ******************************/
std::vector<flatbuffers::Offset<Tensor>> tensors;
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(InputADims().data(), InputADims().size()),
TensorType_FLOAT32, /*buffer=*/0));
if (InputBQuant() != kNone) {
std::vector<float> input2_data(Input2Size());
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng_f32 = [&]() {
return std::uniform_real_distribution<float>()(rng);
};
std::generate(input2_data.begin(), input2_data.end(), input_rng_f32);
std::vector<float> filter_scales;
std::vector<int64_t> filter_zero_points;
int32_t filter_quantized_dimension = 0;
std::vector<int8_t> quantized_input2_data(input2_data.size());
if (InputBQuant() == kChannel) {
const int32_t num_dims_b = InputBDims().size();
filter_quantized_dimension =
TransposeB() ? num_dims_b - 2 : num_dims_b - 1;
filter_scales = GetInt8QuantizationScalePerChannel(
input2_data.data(), filter_quantized_dimension, InputBDims());
filter_zero_points.resize(filter_scales.size(), 0);
QuantizeInt8PerChannel(filter_scales.data(), filter_zero_points.data(),
filter_quantized_dimension, input2_data.data(),
quantized_input2_data.data(), InputBDims());
} else {
filter_scales.resize(1, GetInt8QuantizationScale(input2_data));
filter_zero_points.resize(1, 0);
std::transform(
input2_data.begin(), input2_data.end(), quantized_input2_data.begin(),
std::bind(QuantizeInt8, std::placeholders::_1, 0, filter_scales[0]));
}
const int quantized_filter_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_input2_data.data()),
sizeof(int8_t) * quantized_input2_data.size())));
flatbuffers::Offset<tflite::QuantizationParameters>
filter_quantization_params = CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>(filter_scales),
builder.CreateVector<int64_t>(filter_zero_points),
/*details_type=*/QuantizationDetails_NONE,
/*details=*/0, filter_quantized_dimension);
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(InputBDims().data(), InputBDims().size()),
/*type=*/TensorType_INT8,
/*buffer=*/quantized_filter_buffer_id,
/*name=*/0, filter_quantization_params));
} else {
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(InputBDims().data(), InputBDims().size()),
TensorType_FLOAT32, /*buffer=*/0));
}
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(OutputShape().data(), OutputShape().size()),
TensorType_FLOAT32));
/***************************** Define operators *****************************/
std::vector<int32_t> op_inputs{{0, 1}};
const std::array<int32_t, 1> op_outputs{{2}};
const flatbuffers::Offset<BatchMatMulOptions> batch_matmul_options =
CreateBatchMatMulOptions(builder, false, TransposeB());
const flatbuffers::Offset<Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_BatchMatMulOptions, batch_matmul_options.Union());
/****************************** Define subgraph *****************************/
const std::array<int32_t, 2> subgraph_inputs{{0, 1}};
const std::array<int32_t, 1> subgraph_outputs{{2}};
const flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
const flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Batch Matrix Multiply model");
const flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t BatchMatrixMultiplyTester::ComputeSize(
const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,115 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_BATCH_MATRIX_MULTIPLY_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_BATCH_MATRIX_MULTIPLY_TESTER_H_
#include <cstdint>
#include <initializer_list>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
class BatchMatrixMultiplyTester {
public:
enum class WeightsType {
kFP32,
};
enum QuantizationType {
kNone,
kChannel,
kTensor,
};
BatchMatrixMultiplyTester() = default;
BatchMatrixMultiplyTester(const BatchMatrixMultiplyTester&) = delete;
BatchMatrixMultiplyTester& operator=(const BatchMatrixMultiplyTester&) =
delete;
BatchMatrixMultiplyTester& InputADims(std::initializer_list<int32_t> shape) {
EXPECT_THAT(shape, testing::Each(testing::Gt(0)));
input_a_dims_ = std::vector<int32_t>(shape.begin(), shape.end());
input1_size_ = ComputeSize(input_a_dims_);
return *this;
}
const std::vector<int32_t>& InputADims() const { return input_a_dims_; }
BatchMatrixMultiplyTester& InputBDims(std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
input_b_dims_ = std::vector<int32_t>(shape.begin(), shape.end());
input2_size_ = ComputeSize(input_b_dims_);
return *this;
}
const std::vector<int32_t>& InputBDims() const { return input_b_dims_; }
BatchMatrixMultiplyTester& InputBQuant(QuantizationType quantization) {
quant_b_ = quantization;
return *this;
}
QuantizationType InputBQuant() const { return quant_b_; }
int32_t Input1Size() const { return input1_size_; }
int32_t Input2Size() const { return input2_size_; }
std::vector<int32_t> OutputShape() const;
BatchMatrixMultiplyTester& TransposeB(bool adj_y) {
transpose_b_ = adj_y;
return *this;
}
bool TransposeB() const { return transpose_b_; }
BatchMatrixMultiplyTester& WeightsCache(
TfLiteXNNPackDelegateWeightsCache* weights_cache) {
weights_cache_ = weights_cache;
return *this;
}
void Test(TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel() const;
WeightsType WeightsType() const { return weights_type_; }
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> input_a_dims_;
std::vector<int32_t> input_b_dims_;
QuantizationType quant_b_ = kNone;
int32_t input1_size_ = 1;
int32_t input2_size_ = 1;
bool transpose_b_ = false;
enum WeightsType weights_type_ { WeightsType::kFP32 };
TfLiteXNNPackDelegateWeightsCache* weights_cache_ = nullptr;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_BATCH_MATRIX_MULTIPLY_TESTER_H_
@@ -0,0 +1,402 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <algorithm>
#include <cstdint>
#include <cstddef>
#include <functional>
#include <memory>
#include <random>
#include <string>
#include <tuple>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/binary_elementwise_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "absl/strings/str_cat.h"
namespace tflite {
namespace xnnpack {
BuiltinOperator all_binary_ops[] = {
BuiltinOperator_ADD,
BuiltinOperator_SUB,
BuiltinOperator_MUL,
BuiltinOperator_DIV,
BuiltinOperator_MAXIMUM,
BuiltinOperator_MINIMUM,
BuiltinOperator_SQUARED_DIFFERENCE,
};
ActivationFunctionType all_activations[] = {
ActivationFunctionType_NONE, ActivationFunctionType_RELU,
ActivationFunctionType_RELU_N1_TO_1, ActivationFunctionType_RELU6,
// ActivationFunctionType_SIGN_BIT,
// ActivationFunctionType_TANH,
};
struct StaticParams {
bool input1_static;
bool input2_static;
};
struct ShapeParams {
int input1_rank;
int input2_rank;
int input1_broadcast_mask;
int input2_broadcast_mask;
static std::string BroadcastMaskToString(int rank, int mask) {
std::string result;
for (int i = 0; i < rank; ++i) {
if ((mask >> i) & 1) {
result += "1";
} else {
result += "X";
}
}
return result;
}
std::string ToString(const StaticParams& static_params) const {
std::string input1_broadcast, input2_broadcast;
if (input1_broadcast_mask != 0) {
input1_broadcast =
"_" + BroadcastMaskToString(input1_rank, input1_broadcast_mask);
}
if (input2_broadcast_mask != 0) {
input2_broadcast =
"_" + BroadcastMaskToString(input2_rank, input2_broadcast_mask);
}
const char* input1_static = static_params.input1_static ? "_Static" : "";
const char* input2_static = static_params.input2_static ? "_Static" : "";
return absl::StrCat(input1_rank, "D", input1_static, input1_broadcast,
"_By_", input2_rank, "D", input2_static,
input2_broadcast);
}
static ShapeParams _4DBy4D() { return {4, 4, 0, 0}; }
static ShapeParams _4DBy4DBroadcastChannels() { return {4, 4, 0, 0xE}; }
static ShapeParams _4DBroadcastChannelsBy4D() { return {4, 4, 0xE, 0}; }
static ShapeParams _4DBy4DBroadcastWidth() { return {4, 4, 0, 0xD}; }
static ShapeParams _4DBroadcastWidthBy4D() { return {4, 4, 0xD, 0}; }
static ShapeParams _4DBy4DBroadcastHeight() { return {4, 4, 0, 0xB}; }
static ShapeParams _4DBroadcastHeightBy4D() { return {4, 4, 0xB, 0}; }
static ShapeParams _4DBy4DBroadcastBatch() { return {4, 4, 0, 0x7}; }
static ShapeParams _4DBroadcastBatchBy4D() { return {4, 4, 0x7, 0}; }
static ShapeParams _4DBy4DBroadcastHeightWidthChannels() {
return {4, 4, 0, 8};
}
static ShapeParams _4DBroadcastHeightWidthChannelsBy4D() {
return {4, 4, 8, 0};
}
static ShapeParams _4DBy3D() { return {4, 3, 0, 0}; }
static ShapeParams _4DBy2D() { return {4, 2, 0, 0}; }
static ShapeParams _4DBy1D() { return {4, 1, 0, 0}; }
static ShapeParams _4DBy0D() { return {4, 0, 0, 0}; }
static ShapeParams _2DBy2D() { return {2, 2, 0, 0}; }
static ShapeParams _2DBy1D() { return {2, 1, 0, 0}; }
static ShapeParams _2DBy0D() { return {2, 0, 0, 0}; }
};
class ShapeTest : public testing::TestWithParam<
std::tuple<BuiltinOperator, ShapeParams, StaticParams>> {
};
TEST_P(ShapeTest, Shape) {
BuiltinOperator op = std::get<0>(GetParam());
const ShapeParams& shape_params = std::get<1>(GetParam());
const StaticParams& static_params = std::get<2>(GetParam());
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
// Make a random result shape.
std::vector<int> shape;
for (int i = 0;
i < std::max(shape_params.input1_rank, shape_params.input2_rank); ++i) {
shape.push_back(shape_rng());
}
std::vector<int> input1_shape(shape);
std::vector<int> input2_shape(shape);
for (size_t i = 0; i < input1_shape.size(); ++i) {
if (((shape_params.input1_broadcast_mask >> i) & 1) != 0) {
input1_shape[i] = 1;
}
}
for (size_t i = 0; i < input2_shape.size(); ++i) {
if (((shape_params.input2_broadcast_mask >> i) & 1) != 0) {
input2_shape[i] = 1;
}
}
while (input1_shape.size() > shape_params.input1_rank) {
input1_shape.erase(input1_shape.begin());
}
while (input2_shape.size() > shape_params.input2_rank) {
input2_shape.erase(input2_shape.begin());
}
BinaryElementwiseTester()
.Input1Shape(input1_shape)
.Input2Shape(input2_shape)
.Input1Static(static_params.input1_static)
.Input2Static(static_params.input2_static)
.Test(op, xnnpack_delegate.get());
}
const ShapeParams all_shape_params[] = {
ShapeParams::_4DBy4D(),
ShapeParams::_4DBy4DBroadcastChannels(),
ShapeParams::_4DBroadcastChannelsBy4D(),
ShapeParams::_4DBy4DBroadcastWidth(),
ShapeParams::_4DBroadcastWidthBy4D(),
ShapeParams::_4DBy4DBroadcastHeight(),
ShapeParams::_4DBroadcastHeightBy4D(),
ShapeParams::_4DBy4DBroadcastBatch(),
ShapeParams::_4DBroadcastBatchBy4D(),
ShapeParams::_4DBy4DBroadcastHeightWidthChannels(),
ShapeParams::_4DBroadcastHeightWidthChannelsBy4D(),
ShapeParams::_4DBy3D(),
ShapeParams::_4DBy2D(),
ShapeParams::_4DBy1D(),
ShapeParams::_4DBy0D(),
ShapeParams::_2DBy2D(),
ShapeParams::_2DBy1D(),
ShapeParams::_2DBy0D(),
};
const StaticParams all_static_params[] = {
{true, false},
{false, true},
{false, false},
};
INSTANTIATE_TEST_SUITE_P(
BroadcastTest, ShapeTest,
testing::Combine(testing::ValuesIn(all_binary_ops),
testing::ValuesIn(all_shape_params),
testing::ValuesIn(all_static_params)),
[](const testing::TestParamInfo<ShapeTest::ParamType>& info) {
return EnumNameBuiltinOperator(std::get<0>(info.param)) +
std::string("_") +
std::get<1>(info.param).ToString(std::get<2>(info.param));
});
class BinaryTest : public testing::TestWithParam<BuiltinOperator> {};
TEST_P(BinaryTest, FP16Weights) {
BuiltinOperator op = GetParam();
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
BinaryElementwiseTester()
.Input1Shape({batch, height, width, channels})
.Input2Shape({batch, height, width, channels})
.Input1Static(true)
.FP16Weights()
.Test(op, xnnpack_delegate.get());
BinaryElementwiseTester()
.Input1Shape({batch, height, width, channels})
.Input2Shape({batch, height, width, channels})
.Input2Static(true)
.FP16Weights()
.Test(op, xnnpack_delegate.get());
}
TEST_P(BinaryTest, INT8Weights) {
BuiltinOperator op = GetParam();
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
BinaryElementwiseTester()
.Input1Shape({batch, height, width, channels})
.Input2Shape({batch, height, width, channels})
.Input1Static(true)
.INT8Weights()
.Test(op, xnnpack_delegate.get());
BinaryElementwiseTester()
.Input1Shape({batch, height, width, channels})
.Input2Shape({batch, height, width, channels})
.Input2Static(true)
.INT8Weights()
.Test(op, xnnpack_delegate.get());
}
TEST_P(BinaryTest, INT8ChannelWiseWeights) {
BuiltinOperator op = GetParam();
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
BinaryElementwiseTester()
.Input1Shape({batch, height, width, channels})
.Input2Shape({batch, height, width, channels})
.Input1Static(true)
.INT8ChannelWiseWeights()
.Test(op, xnnpack_delegate.get());
BinaryElementwiseTester()
.Input1Shape({batch, height, width, channels})
.Input2Shape({batch, height, width, channels})
.Input2Static(true)
.INT8ChannelWiseWeights()
.Test(op, xnnpack_delegate.get());
}
TEST_P(BinaryTest, SparseWeights) {
BuiltinOperator op = GetParam();
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
BinaryElementwiseTester()
.Input1Shape({batch, height, width, channels})
.Input2Shape({batch, height, width, channels})
.Input1Static(true)
.SparseWeights()
.Test(op, xnnpack_delegate.get());
BinaryElementwiseTester()
.Input1Shape({batch, height, width, channels})
.Input2Shape({batch, height, width, channels})
.Input2Static(true)
.SparseWeights()
.Test(op, xnnpack_delegate.get());
}
class ActivationTest
: public testing::TestWithParam<
std::tuple<BuiltinOperator, ActivationFunctionType>> {};
TEST_P(ActivationTest, Activation) {
BuiltinOperator op = std::get<0>(GetParam());
ActivationFunctionType activation = std::get<1>(GetParam());
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
BinaryElementwiseTester()
.Input1Shape({batch, height, width, channels})
.Input2Shape({batch, height, width, channels})
.Activation(activation)
.Test(op, xnnpack_delegate.get());
}
TEST_P(BinaryTest, MultiThreading) {
BuiltinOperator op = GetParam();
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
BinaryElementwiseTester()
.Input1Shape({batch, height, width, channels})
.Input2Shape({batch, height, width, channels})
.Test(op, xnnpack_delegate.get());
}
INSTANTIATE_TEST_SUITE_P(
BinaryTest, BinaryTest, testing::ValuesIn(all_binary_ops),
[](const testing::TestParamInfo<BinaryTest::ParamType>& info) {
return EnumNameBuiltinOperator(info.param);
});
INSTANTIATE_TEST_SUITE_P(
ActivationTest, ActivationTest,
testing::Combine(testing::Values(BuiltinOperator_ADD, BuiltinOperator_SUB,
BuiltinOperator_MUL, BuiltinOperator_DIV),
testing::ValuesIn(all_activations)),
[](const testing::TestParamInfo<ActivationTest::ParamType>& info) {
return EnumNameBuiltinOperator(std::get<0>(info.param)) +
std::string("_") +
EnumNameActivationFunctionType(std::get<1>(info.param));
});
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,520 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/binary_elementwise_tester.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "fp16.h" // from @FP16
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
std::vector<int32_t> BinaryElementwiseTester::OutputShape() const {
std::vector<int32_t> output_shape;
if (!input1_shape_.empty()) {
output_shape.insert(
output_shape.end(), input1_shape_.cbegin(),
input1_shape_.cbegin() +
std::max(input1_shape_.size(), input2_shape_.size()) -
input2_shape_.size());
}
if (!input2_shape_.empty()) {
output_shape.insert(
output_shape.end(), input2_shape_.cbegin(),
input2_shape_.cbegin() +
std::max(input2_shape_.size(), input1_shape_.size()) -
input1_shape_.size());
}
for (size_t i = std::min(input1_shape_.size(), input2_shape_.size()); i >= 1;
i--) {
output_shape.push_back(
std::max(*(input1_shape_.cend() - i), *(input2_shape_.cend() - i)));
}
return output_shape;
}
void BinaryElementwiseTester::Test(tflite::BuiltinOperator binary_op,
TfLiteDelegate* delegate) const {
if (Input1Static()) {
ASSERT_FALSE(Input2Static());
}
if (FP16Weights() || INT8Weights() || INT8ChannelWiseWeights()) {
ASSERT_TRUE(Input1Static() || Input2Static());
if (INT8ChannelWiseWeights() && Input1Static()) {
ASSERT_FALSE(Input1Shape().empty());
}
if (INT8ChannelWiseWeights() && Input2Static()) {
ASSERT_FALSE(Input2Shape().empty());
}
}
std::random_device random_device;
auto rng = std::mt19937(random_device());
std::uniform_real_distribution<float> input1_distribution(-25.0f, 25.0f);
std::uniform_real_distribution<float> input2_distribution(-25.0f, 25.0f);
switch (binary_op) {
case BuiltinOperator_DIV:
input1_distribution = std::uniform_real_distribution<float>(-5.0f, 5.0f);
input2_distribution = std::uniform_real_distribution<float>(0.1f, 1.0f);
break;
case BuiltinOperator_MUL:
case BuiltinOperator_PRELU:
input1_distribution = std::uniform_real_distribution<float>(-5.0f, 5.0f);
input2_distribution = std::uniform_real_distribution<float>(-5.0f, 5.0f);
break;
default:
break;
}
auto input1_rng = std::bind(input1_distribution, std::ref(rng));
auto input2_rng = std::bind(input2_distribution, std::ref(rng));
std::vector<char> buffer = CreateTfLiteModel(binary_op);
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
if (Input1Static() || Input2Static()) {
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
} else {
ASSERT_EQ(delegate_interpreter->inputs().size(), 2);
ASSERT_EQ(default_interpreter->inputs().size(), 2);
}
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (!Input1Static()) {
float* default_input1_data =
default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input1_data, ComputeSize(Input1Shape()),
std::ref(input1_rng));
float* xnnpack_input1_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input1_data, ComputeSize(Input1Shape()),
xnnpack_input1_data);
}
if (!Input2Static()) {
float* default_input2_data =
default_interpreter->typed_input_tensor<float>(Input1Static() ? 0 : 1);
std::generate_n(default_input2_data, ComputeSize(Input2Shape()),
std::ref(input2_rng));
float* xnnpack_input2_data =
delegate_interpreter->typed_input_tensor<float>(Input1Static() ? 0 : 1);
std::copy_n(default_input2_data, ComputeSize(Input2Shape()),
xnnpack_input2_data);
}
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* xnnpack_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
for (size_t i = 0; i < ComputeSize(OutputShape()); i++) {
ASSERT_NEAR(default_output_data[i], xnnpack_output_data[i],
std::numeric_limits<float>::epsilon() *
std::max(std::abs(default_output_data[i]) * 2.0f, 1.0f));
}
}
std::vector<char> BinaryElementwiseTester::CreateTfLiteModel(
tflite::BuiltinOperator binary_op) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
std::uniform_real_distribution<float> input1_distribution(-25.0f, 25.0f);
std::uniform_real_distribution<float> input2_distribution(-25.0f, 25.0f);
switch (binary_op) {
case BuiltinOperator_DIV:
input1_distribution = std::uniform_real_distribution<float>(-5.0f, 5.0f);
input2_distribution = std::uniform_real_distribution<float>(0.1f, 1.0f);
break;
case BuiltinOperator_MUL:
input1_distribution = std::uniform_real_distribution<float>(-5.0f, 5.0f);
input2_distribution = std::uniform_real_distribution<float>(-5.0f, 5.0f);
break;
default:
break;
}
auto input1_rng = std::bind(input1_distribution, std::ref(rng));
auto input2_rng = std::bind(input2_distribution, std::ref(rng));
flatbuffers::FlatBufferBuilder builder;
std::vector<flatbuffers::Offset<OperatorCode>> operator_codes{
{CreateOperatorCode(builder, binary_op)}};
if (FP16Weights() || INT8Weights() || INT8ChannelWiseWeights()) {
operator_codes.emplace_back(
CreateOperatorCode(builder, BuiltinOperator_DEQUANTIZE));
} else if (SparseWeights()) {
operator_codes.emplace_back(
CreateOperatorCode(builder, BuiltinOperator_DENSIFY));
}
std::vector<flatbuffers::Offset<Buffer>> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
}};
std::vector<float> input1_scales;
std::vector<int64_t> input1_zero_points;
int32_t input1_quantized_dimension = 0;
int32_t input1_buffer = 0;
if (Input1Static()) {
if (FP16Weights()) {
std::vector<uint16_t> input1_data(ComputeSize(Input1Shape()));
std::generate(input1_data.begin(), input1_data.end(),
std::bind(fp16_ieee_from_fp32_value, input1_rng));
buffers.push_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(input1_data.data()),
sizeof(uint16_t) * input1_data.size())));
} else {
std::vector<float> input1_data(ComputeSize(Input1Shape()));
std::generate(input1_data.begin(), input1_data.end(), input1_rng);
if (INT8Weights()) {
std::vector<int8_t> quantized_input1_data(input1_data.size());
input1_scales.resize(1, GetInt8QuantizationScale(input1_data));
input1_zero_points.resize(1, 0);
std::transform(input1_data.begin(), input1_data.end(),
quantized_input1_data.begin(),
std::bind(QuantizeInt8, std::placeholders::_1, 0,
input1_scales[0]));
buffers.push_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_input1_data.data()),
sizeof(int8_t) * quantized_input1_data.size())));
} else if (INT8ChannelWiseWeights()) {
std::vector<int8_t> quantized_input1_data(input1_data.size());
input1_quantized_dimension =
static_cast<int32_t>(Input1Shape().size()) - 1;
const int32_t num_scales = Input1Shape()[input1_quantized_dimension];
input1_scales = GetInt8QuantizationScalePerChannel(
input1_data.data(), input1_quantized_dimension, Input1Shape());
input1_zero_points.resize(num_scales, 0);
QuantizeInt8PerChannel(input1_scales.data(), input1_zero_points.data(),
input1_quantized_dimension, input1_data.data(),
quantized_input1_data.data(), Input1Shape());
buffers.push_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_input1_data.data()),
sizeof(int8_t) * quantized_input1_data.size())));
} else {
if (!SparseWeights()) {
input1_buffer = buffers.size();
}
buffers.push_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(input1_data.data()),
sizeof(float) * input1_data.size())));
}
}
}
std::vector<float> input2_scales;
std::vector<int64_t> input2_zero_points;
int32_t input2_quantized_dimension = 0;
int32_t input2_buffer = 0;
if (Input2Static()) {
if (FP16Weights()) {
std::vector<uint16_t> input2_data(ComputeSize(Input2Shape()));
std::generate(input2_data.begin(), input2_data.end(),
std::bind(fp16_ieee_from_fp32_value, input1_rng));
buffers.push_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(input2_data.data()),
sizeof(uint16_t) * input2_data.size())));
} else {
std::vector<float> input2_data(ComputeSize(Input2Shape()));
std::generate(input2_data.begin(), input2_data.end(), input2_rng);
if (INT8Weights()) {
std::vector<int8_t> quantized_input2_data(input2_data.size());
input2_scales.resize(1, GetInt8QuantizationScale(input2_data));
input2_zero_points.resize(1, 0);
std::transform(input2_data.begin(), input2_data.end(),
quantized_input2_data.begin(),
std::bind(QuantizeInt8, std::placeholders::_1, 0,
input2_scales[0]));
buffers.push_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_input2_data.data()),
sizeof(int8_t) * quantized_input2_data.size())));
} else if (INT8ChannelWiseWeights()) {
std::vector<int8_t> quantized_input2_data(input2_data.size());
input2_quantized_dimension =
static_cast<int32_t>(Input2Shape().size()) - 1;
const int32_t num_scales = Input1Shape()[input2_quantized_dimension];
input2_scales = GetInt8QuantizationScalePerChannel(
input2_data.data(), input2_quantized_dimension, Input2Shape());
input2_zero_points.resize(num_scales, 0);
QuantizeInt8PerChannel(input2_scales.data(), input2_zero_points.data(),
input2_quantized_dimension, input2_data.data(),
quantized_input2_data.data(), Input2Shape());
buffers.push_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_input2_data.data()),
sizeof(int8_t) * quantized_input2_data.size())));
} else {
if (!SparseWeights()) {
input2_buffer = buffers.size();
}
buffers.push_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(input2_data.data()),
sizeof(float) * input2_data.size())));
}
}
}
const std::vector<int32_t> output_shape = OutputShape();
std::vector<flatbuffers::Offset<Tensor>> tensors;
std::vector<flatbuffers::Offset<Operator>> operators;
if (FP16Weights() && Input1Static()) {
tensors.emplace_back(
CreateTensor(builder,
builder.CreateVector<int32_t>(Input1Shape().data(),
Input1Shape().size()),
TensorType_FLOAT16, 1));
} else if ((INT8Weights() || INT8ChannelWiseWeights()) && Input1Static()) {
tensors.emplace_back(
CreateTensor(builder,
builder.CreateVector<int32_t>(Input1Shape().data(),
Input1Shape().size()),
TensorType_INT8, 1, 0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>(input1_scales),
builder.CreateVector<int64_t>(input1_zero_points),
/*details_type=*/QuantizationDetails_NONE,
/*details=*/0, input1_quantized_dimension)));
} else if (SparseWeights() && Input1Static()) {
int dims_count = Input1Shape().size();
std::vector<flatbuffers::Offset<DimensionMetadata>> dim_metadata(
dims_count);
std::vector<int> traversal_order(dims_count);
for (int i = 0; i < dims_count; i++) {
traversal_order[i] = i;
dim_metadata[i] = CreateDimensionMetadata(builder, DimensionType_DENSE,
Input1Shape()[i]);
}
flatbuffers::Offset<SparsityParameters> sparsity_param =
CreateSparsityParameters(builder, builder.CreateVector(traversal_order),
0, builder.CreateVector(dim_metadata));
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(Input1Shape().data(),
Input1Shape().size()),
TensorType_FLOAT32, /*buffer=*/1, /*name=*/0, /*quantization=*/0,
/*is_variable=*/false, /*sparsity=*/sparsity_param));
}
if (FP16Weights() && Input2Static()) {
tensors.emplace_back(
CreateTensor(builder,
builder.CreateVector<int32_t>(Input2Shape().data(),
Input2Shape().size()),
TensorType_FLOAT16, 1));
} else if ((INT8Weights() || INT8ChannelWiseWeights()) && Input2Static()) {
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(Input2Shape().data(),
Input2Shape().size()),
TensorType_INT8, 1, 0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>(input2_scales),
builder.CreateVector<int64_t>(input2_zero_points),
QuantizationDetails_NONE, 0, input2_quantized_dimension)));
} else if (SparseWeights() && Input2Static()) {
int dims_count = Input2Shape().size();
std::vector<flatbuffers::Offset<DimensionMetadata>> dim_metadata(
dims_count);
std::vector<int> traversal_order(dims_count);
for (int i = 0; i < dims_count; i++) {
traversal_order[i] = i;
dim_metadata[i] = CreateDimensionMetadata(builder, DimensionType_DENSE,
Input2Shape()[i]);
}
flatbuffers::Offset<SparsityParameters> sparsity_param =
CreateSparsityParameters(builder, builder.CreateVector(traversal_order),
0, builder.CreateVector(dim_metadata));
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(Input2Shape().data(),
Input2Shape().size()),
TensorType_FLOAT32, /*buffer=*/1, /*name=*/0, /*quantization=*/0,
/*is_variable=*/false, /*sparsity=*/sparsity_param));
}
if (FP16Weights() || INT8Weights() || INT8ChannelWiseWeights()) {
const std::array<int32_t, 1> dequantize_inputs{{0}};
const std::array<int32_t, 1> dequantize_outputs{{Input1Static() ? 1 : 2}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/1,
builder.CreateVector<int32_t>(dequantize_inputs.data(),
dequantize_inputs.size()),
builder.CreateVector<int32_t>(dequantize_outputs.data(),
dequantize_outputs.size())));
} else if (SparseWeights()) {
const std::array<int32_t, 1> densify_inputs{{0}};
const std::array<int32_t, 1> densify_outputs{{Input1Static() ? 1 : 2}};
operators.emplace_back(
CreateOperator(builder, /*opcode_index=*/1,
builder.CreateVector<int32_t>(densify_inputs.data(),
densify_inputs.size()),
builder.CreateVector<int32_t>(densify_outputs.data(),
densify_outputs.size())));
}
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(Input1Shape().data(), Input1Shape().size()),
TensorType_FLOAT32, input1_buffer));
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(Input2Shape().data(), Input2Shape().size()),
TensorType_FLOAT32, input2_buffer));
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(output_shape.data(), output_shape.size()),
TensorType_FLOAT32));
tflite::BuiltinOptions builtin_options_type = tflite::BuiltinOptions_NONE;
flatbuffers::Offset<void> builtin_options = 0;
switch (binary_op) {
case BuiltinOperator_ADD:
builtin_options_type = BuiltinOptions_AddOptions;
builtin_options = CreateAddOptions(builder, Activation()).Union();
break;
case BuiltinOperator_DIV:
builtin_options_type = BuiltinOptions_DivOptions;
builtin_options = CreateDivOptions(builder, Activation()).Union();
break;
case BuiltinOperator_MUL:
builtin_options_type = BuiltinOptions_MulOptions;
builtin_options = CreateMulOptions(builder, Activation()).Union();
break;
case BuiltinOperator_SUB:
builtin_options_type = BuiltinOptions_SubOptions;
builtin_options = CreateSubOptions(builder, Activation()).Union();
break;
default:
EXPECT_EQ(Activation(), ActivationFunctionType_NONE);
}
const std::array<int32_t, 2> op_inputs{
{static_cast<int>(tensors.size()) - 3,
static_cast<int>(tensors.size()) - 2}};
const std::array<int32_t, 1> op_outputs{
{static_cast<int>(tensors.size()) - 1}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
builtin_options_type, builtin_options));
std::vector<int32_t> subgraph_inputs;
if (!Input1Static()) {
subgraph_inputs.push_back(tensors.size() - 3);
}
if (!Input2Static()) {
subgraph_inputs.push_back(tensors.size() - 2);
}
const std::array<int32_t, 1> subgraph_outputs{
{static_cast<int>(tensors.size()) - 1}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(operators.data(), operators.size()));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Binary operator model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t BinaryElementwiseTester::ComputeSize(
const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,138 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_BINARY_ELEMENTWISE_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_BINARY_ELEMENTWISE_TESTER_H_
#include <cstdint>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class BinaryElementwiseTester {
public:
BinaryElementwiseTester() = default;
BinaryElementwiseTester(const BinaryElementwiseTester&) = delete;
BinaryElementwiseTester& operator=(const BinaryElementwiseTester&) = delete;
inline BinaryElementwiseTester& Input1Shape(std::vector<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
input1_shape_ = std::move(shape);
return *this;
}
inline const std::vector<int32_t>& Input1Shape() const {
return input1_shape_;
}
inline BinaryElementwiseTester& Input2Shape(std::vector<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
input2_shape_ = std::move(shape);
return *this;
}
inline const std::vector<int32_t>& Input2Shape() const {
return input2_shape_;
}
std::vector<int32_t> OutputShape() const;
inline BinaryElementwiseTester& Input1Static(bool is_static) {
input1_static_ = is_static;
return *this;
}
inline bool Input1Static() const { return input1_static_; }
inline BinaryElementwiseTester& Input2Static(bool is_static) {
input2_static_ = is_static;
return *this;
}
inline bool Input2Static() const { return input2_static_; }
inline BinaryElementwiseTester& FP16Weights() {
fp16_weights_ = true;
return *this;
}
inline bool FP16Weights() const { return fp16_weights_; }
inline BinaryElementwiseTester& INT8Weights() {
int8_weights_ = true;
return *this;
}
inline bool INT8Weights() const { return int8_weights_; }
inline BinaryElementwiseTester& INT8ChannelWiseWeights() {
int8_channel_wise_weights_ = true;
return *this;
}
inline bool INT8ChannelWiseWeights() const {
return int8_channel_wise_weights_;
}
inline BinaryElementwiseTester& SparseWeights() {
sparse_weights_ = true;
return *this;
}
inline bool SparseWeights() const { return sparse_weights_; }
inline BinaryElementwiseTester& Activation(
ActivationFunctionType activation) {
activation_ = activation;
return *this;
}
void Test(tflite::BuiltinOperator binary_op, TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel(tflite::BuiltinOperator binary_op) const;
inline ::tflite::ActivationFunctionType Activation() const {
return activation_;
}
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> input1_shape_;
std::vector<int32_t> input2_shape_;
bool input1_static_ = false;
bool input2_static_ = false;
bool fp16_weights_ = false;
bool int8_weights_ = false;
bool int8_channel_wise_weights_ = false;
bool sparse_weights_ = false;
::tflite::ActivationFunctionType activation_ =
::tflite::ActivationFunctionType_NONE;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_BINARY_ELEMENTWISE_TESTER_H_
@@ -0,0 +1,677 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <algorithm>
#include <cstdint>
#include <functional>
#include <iterator>
#include <limits>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/fingerprint_test_helpers.h"
#include "tensorflow/lite/delegates/xnnpack/quantized_conv_2d_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
struct ChannelwiseQuantizedConv2D : DelegateTest {};
TEST_F(ChannelwiseQuantizedConv2D, 1x1) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(1)
.KernelWidth(1)
.ValidPadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, 3x3) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(3)
.KernelWidth(3)
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, 3x3Stride2) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(3)
.KernelWidth(3)
.StrideHeight(2)
.StrideWidth(2)
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, SmallKernelWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, SmallKernelWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.ValidPadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, StrideWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, StrideWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ValidPadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, DilationWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto dilation_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.DilationHeight(dilation_rng())
.DilationWidth(dilation_rng())
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, DilationWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto dilation_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.DilationHeight(dilation_rng())
.DilationWidth(dilation_rng())
.ValidPadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, ReluActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(0)
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluActivation()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, Relu6Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(0)
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Relu6Activation()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, ReluMinus1To1Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluMinus1To1Activation()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, WeightsCache) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
std::unique_ptr<TfLiteXNNPackDelegateWeightsCache,
decltype(&TfLiteXNNPackDelegateWeightsCacheDelete)>
weights_cache(TfLiteXNNPackDelegateWeightsCacheCreate(),
TfLiteXNNPackDelegateWeightsCacheDelete);
delegate_options.weights_cache = weights_cache.get();
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.WeightsCache(weights_cache.get())
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedConv2D, TransientIndirectionBuffer) {
TfLiteXNNPackDelegateOptions xnnpack_options =
TfLiteXNNPackDelegateOptionsDefault();
xnnpack_options.num_threads = 2;
xnnpack_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_TRANSIENT_INDIRECTION_BUFFER;
UseCustomDelegate(xnnpack_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
const int32_t num_output_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(num_output_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Test(xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,798 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <algorithm>
#include <cstdint>
#include <functional>
#include <iterator>
#include <limits>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/xnnpack/fingerprint_test_helpers.h"
#include "tensorflow/lite/delegates/xnnpack/quantized_depthwise_conv_2d_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
struct ChannelwiseQuantizedDepthwiseConv2D : DelegateTest {};
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, 1x1) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(1)
.KernelWidth(1)
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, 2x2) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(2)
.KernelWidth(2)
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, 3x3) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(3)
.KernelWidth(3)
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, 3x3Stride2) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(3)
.KernelWidth(3)
.StrideHeight(2)
.StrideWidth(2)
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, 5x5) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(3)
.KernelWidth(3)
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, 5x5Stride2) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(3)
.KernelWidth(3)
.StrideHeight(2)
.StrideWidth(2)
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, SmallKernelWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, SmallKernelWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.ValidPadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, StrideWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, StrideWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ValidPadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, DilationWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto dilation_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.DilationHeight(dilation_rng())
.DilationWidth(dilation_rng())
.SamePadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, DilationWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto dilation_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.DilationHeight(dilation_rng())
.DilationWidth(dilation_rng())
.ValidPadding()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, DepthMultiplier) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
auto multiplier_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 8), std::ref(rng));
const int32_t num_input_channels = channel_rng();
const int32_t depth_multiplier = multiplier_rng();
const int32_t num_output_channels = num_input_channels * depth_multiplier;
std::vector<float> filter_scales;
filter_scales.reserve(num_output_channels);
std::generate_n(std::back_inserter(filter_scales), num_output_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_input_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.DepthMultiplier(depth_multiplier)
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, ReluActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(0)
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluActivation()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, Relu6Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(0)
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Relu6Activation()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, ReluMinus1To1Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluMinus1To1Activation()
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, WeightsCache) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
std::unique_ptr<TfLiteXNNPackDelegateWeightsCache,
decltype(&TfLiteXNNPackDelegateWeightsCacheDelete)>
weights_cache(TfLiteXNNPackDelegateWeightsCacheCreate(),
TfLiteXNNPackDelegateWeightsCacheDelete);
delegate_options.weights_cache = weights_cache.get();
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.WeightsCache(weights_cache.get())
.Test(xnnpack_delegate.get());
}
TEST_F(ChannelwiseQuantizedDepthwiseConv2D, TransientIndirectionBuffer) {
TfLiteXNNPackDelegateOptions xnnpack_options =
TfLiteXNNPackDelegateOptionsDefault();
xnnpack_options.num_threads = 2;
xnnpack_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_TRANSIENT_INDIRECTION_BUFFER;
UseCustomDelegate(xnnpack_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto scale_rng = std::bind(
std::uniform_real_distribution<float>(0.25f, 1.25f), std::ref(rng));
auto zero_point_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
const int32_t num_channels = channel_rng();
std::vector<float> filter_scales;
filter_scales.reserve(num_channels);
std::generate_n(std::back_inserter(filter_scales), num_channels,
std::ref(scale_rng));
QuantizedDepthwiseConv2DTester()
.InputZeroPoint(zero_point_rng())
.OutputZeroPoint(zero_point_rng())
.KernelScales(filter_scales)
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(num_channels)
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Test(xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,436 @@
/* Copyright 2022 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/concatenation_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
TEST(Concatenation, 1D_2_inputs) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
const std::vector<int32_t> shape1({shape_rng()});
const std::vector<int32_t> shape2({shape_rng()});
for (int i = -1; i < 1; i++) {
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 2D_2_inputs) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
for (int i = -2; i < 2; i++) {
// All dimensions must be the same, except for axis.
const std::vector<int32_t> shape1({shape_rng(), shape_rng()});
auto shape2 = SameShapeDifferentAxis(shape1, i, shape_rng());
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 3D_2_inputs) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
for (int i = -3; i < 3; i++) {
// All dimensions must be the same, except for axis.
const std::vector<int32_t> shape1({shape_rng(), shape_rng(), shape_rng()});
auto shape2 = SameShapeDifferentAxis(shape1, i, shape_rng());
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 4D_2_inputs) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
for (int i = -4; i < 4; i++) {
// All dimensions must be the same, except for axis.
const std::vector<int32_t> shape1(
{shape_rng(), shape_rng(), shape_rng(), shape_rng()});
auto shape2 = SameShapeDifferentAxis(shape1, i, shape_rng());
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 1D_of_3) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
const std::vector<int32_t> shape1({shape_rng()});
const std::vector<int32_t> shape2({shape_rng()});
const std::vector<int32_t> shape3({shape_rng()});
for (int i = -1; i < 1; i++) {
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2, shape3})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 2D_of_3) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
for (int i = -2; i < 2; i++) {
// All dimensions must be the same, except for axis.
const std::vector<int32_t> shape1({shape_rng(), shape_rng()});
auto shape2 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape3 = SameShapeDifferentAxis(shape1, i, shape_rng());
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2, shape3})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 3D_of_3) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
for (int i = -3; i < 3; i++) {
// All dimensions must be the same, except for axis.
const std::vector<int32_t> shape1({shape_rng(), shape_rng(), shape_rng()});
auto shape2 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape3 = SameShapeDifferentAxis(shape1, i, shape_rng());
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2, shape3})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 4D_of_3) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
for (int i = -4; i < 4; i++) {
// All dimensions must be the same, except for axis.
const std::vector<int32_t> shape1(
{shape_rng(), shape_rng(), shape_rng(), shape_rng()});
auto shape2 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape3 = SameShapeDifferentAxis(shape1, i, shape_rng());
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2, shape3})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 1D_of_4) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
const std::vector<int32_t> shape1({shape_rng()});
const std::vector<int32_t> shape2({shape_rng()});
const std::vector<int32_t> shape3({shape_rng()});
const std::vector<int32_t> shape4({shape_rng()});
for (int i = -1; i < 1; i++) {
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2, shape3, shape4})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 2D_of_4) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
for (int i = -2; i < 2; i++) {
// All dimensions must be the same, except for axis.
const std::vector<int32_t> shape1({shape_rng(), shape_rng()});
auto shape2 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape3 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape4 = SameShapeDifferentAxis(shape1, i, shape_rng());
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2, shape3, shape4})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 3D_of_4) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
for (int i = -3; i < 3; i++) {
// All dimensions must be the same, except for axis.
const std::vector<int32_t> shape1({shape_rng(), shape_rng(), shape_rng()});
auto shape2 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape3 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape4 = SameShapeDifferentAxis(shape1, i, shape_rng());
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2, shape3, shape4})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 4D_of_4) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
for (int i = -4; i < 4; i++) {
// All dimensions must be the same, except for axis.
const std::vector<int32_t> shape1(
{shape_rng(), shape_rng(), shape_rng(), shape_rng()});
auto shape2 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape3 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape4 = SameShapeDifferentAxis(shape1, i, shape_rng());
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2, shape3, shape4})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 1D_of_5) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
const std::vector<int32_t> shape1({shape_rng()});
const std::vector<int32_t> shape2({shape_rng()});
const std::vector<int32_t> shape3({shape_rng()});
const std::vector<int32_t> shape4({shape_rng()});
const std::vector<int32_t> shape5({shape_rng()});
for (int i = -1; i < 1; i++) {
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2, shape3, shape4, shape5})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 2D_of_5) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
for (int i = -2; i < 2; i++) {
// All dimensions must be the same, except for axis.
const std::vector<int32_t> shape1({shape_rng(), shape_rng()});
auto shape2 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape3 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape4 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape5 = SameShapeDifferentAxis(shape1, i, shape_rng());
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2, shape3, shape4, shape5})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 3D_of_5) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
for (int i = -3; i < 3; i++) {
// All dimensions must be the same, except for axis.
const std::vector<int32_t> shape1({shape_rng(), shape_rng(), shape_rng()});
auto shape2 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape3 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape4 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape5 = SameShapeDifferentAxis(shape1, i, shape_rng());
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2, shape3, shape4, shape5})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
TEST(Concatenation, 4D_of_5) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 10), std::ref(rng));
for (int i = -4; i < 4; i++) {
// All dimensions must be the same, except for axis.
const std::vector<int32_t> shape1(
{shape_rng(), shape_rng(), shape_rng(), shape_rng()});
auto shape2 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape3 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape4 = SameShapeDifferentAxis(shape1, i, shape_rng());
auto shape5 = SameShapeDifferentAxis(shape1, i, shape_rng());
// clang-format off
ConcatenationTester()
.InputShapes({shape1, shape2, shape3, shape4, shape5})
.Axis(i)
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
// clang-format on
}
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,240 @@
/* Copyright 2022 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/concatenation_tester.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
std::vector<int32_t> SameShapeDifferentAxis(std::vector<int32_t> shape,
int axis, int32_t size) {
std::vector<int32_t> new_shape{shape};
new_shape[axis < 0 ? axis + shape.size() : axis] = size;
return new_shape;
}
template <class T>
void ConcatenationTester::Test(Interpreter *delegate_interpreter,
Interpreter *default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
std::uniform_int_distribution<int32_t> input_distribution(
std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
auto input_rng = std::bind(input_distribution, std::ref(rng));
for (size_t i = 0; i < NumInputs(); i++) {
T *default_input_data = default_interpreter->typed_input_tensor<T>(i);
std::generate_n(default_input_data, ComputeSize(InputShape(i)),
std::ref(input_rng));
T *xnnpack_input_data = delegate_interpreter->typed_input_tensor<T>(i);
std::copy_n(default_input_data, ComputeSize(InputShape(i)),
xnnpack_input_data);
}
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
T *default_output_data = default_interpreter->typed_output_tensor<T>(0);
T *xnnpack_output_data = delegate_interpreter->typed_output_tensor<T>(0);
for (size_t i = 0; i < ComputeSize(OutputShape()); i++) {
ASSERT_EQ(static_cast<int32_t>(default_output_data[i]),
static_cast<int32_t>(xnnpack_output_data[i]));
}
}
template <>
void ConcatenationTester::Test<float>(Interpreter *delegate_interpreter,
Interpreter *default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
std::uniform_real_distribution<float> input_distribution(-25.0f, 25.0f);
auto input_rng = std::bind(input_distribution, std::ref(rng));
for (size_t i = 0; i < NumInputs(); i++) {
float *default_input_data =
default_interpreter->typed_input_tensor<float>(i);
std::generate_n(default_input_data, ComputeSize(InputShape(i)),
std::ref(input_rng));
float *xnnpack_input_data =
delegate_interpreter->typed_input_tensor<float>(i);
std::copy_n(default_input_data, ComputeSize(InputShape(i)),
xnnpack_input_data);
}
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float *default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float *xnnpack_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
for (size_t i = 0; i < ComputeSize(OutputShape()); i++) {
ASSERT_EQ(default_output_data[i], xnnpack_output_data[i]);
}
}
void ConcatenationTester::Test(TensorType tensor_type,
TfLiteDelegate *delegate) const {
std::vector<char> buffer = CreateTfLiteModel(tensor_type);
const Model *model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), NumInputs());
ASSERT_EQ(default_interpreter->inputs().size(), NumInputs());
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
switch (tensor_type) {
case TensorType_FLOAT32:
Test<float>(delegate_interpreter.get(), default_interpreter.get());
break;
case TensorType_INT8:
Test<int8_t>(delegate_interpreter.get(), default_interpreter.get());
break;
case TensorType_UINT8:
Test<uint8_t>(delegate_interpreter.get(), default_interpreter.get());
break;
default:
GTEST_FAIL();
}
}
std::vector<char> ConcatenationTester::CreateTfLiteModel(
TensorType tensor_type) const {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<OperatorCode> operator_code =
CreateOperatorCode(builder, BuiltinOperator_CONCATENATION, 0);
std::vector<flatbuffers::Offset<Buffer>> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
}};
std::vector<flatbuffers::Offset<Tensor>> tensors;
tensors.reserve(NumInputs());
for (size_t i = 0; i < NumInputs(); i++) {
tensors.push_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(InputShape(i).data(),
InputShape(i).size()),
tensor_type,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({input_scales_[i]}),
builder.CreateVector<int64_t>({input_zero_points_[i]}))));
}
tensors.push_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(OutputShape().data(), OutputShape().size()),
tensor_type,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({output_scale_}),
builder.CreateVector<int64_t>({output_zero_point_}))));
std::vector<int32_t> op_inputs;
op_inputs.reserve(NumInputs());
for (size_t i = 0; i < NumInputs(); i++) {
op_inputs.push_back(static_cast<int32_t>(i));
}
const std::array<int32_t, 1> op_outputs{static_cast<int32_t>(NumInputs())};
BuiltinOptions builtin_options_type = tflite::BuiltinOptions_NONE;
flatbuffers::Offset<void> builtin_options = 0;
builtin_options_type = tflite::BuiltinOptions_ConcatenationOptions;
builtin_options = CreateConcatenationOptions(builder, Axis()).Union();
const flatbuffers::Offset<Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
builtin_options_type, builtin_options);
const std::vector<int32_t> subgraph_inputs = op_inputs;
const std::array<int32_t, 1> subgraph_outputs = op_outputs;
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
const flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1),
builder.CreateString("Concatenation model"),
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t ConcatenationTester::ComputeSize(const std::vector<int32_t> &shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,133 @@
/* Copyright 2022 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_CONCATENATION_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_CONCATENATION_TESTER_H_
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
// Creates a new shape with the same dimensions as `shape`, except for the axis
// dimension, which will have the value `size`.
std::vector<int32_t> SameShapeDifferentAxis(std::vector<int32_t> shape,
int axis, int32_t size);
class ConcatenationTester {
public:
ConcatenationTester() = default;
ConcatenationTester(const ConcatenationTester&) = delete;
ConcatenationTester& operator=(const ConcatenationTester&) = delete;
inline ConcatenationTester& Axis(int axis) {
axis_ = axis;
return *this;
}
inline const int Axis() const { return axis_; }
inline ConcatenationTester& InputShapes(
const std::initializer_list<std::vector<int32_t>> shapes) {
for (auto shape : shapes) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
}
input_shapes_ = shapes;
input_scales_.resize(shapes.size(), 1);
output_scale_ = 1.f;
input_zero_points_.resize(shapes.size(), 0);
output_zero_point_ = 0;
return *this;
}
inline std::vector<int32_t> InputShape(size_t i) const {
return input_shapes_[i];
}
inline ConcatenationTester& InputScales(const std::vector<float> scales) {
input_scales_ = scales;
return *this;
}
inline float InputScale(size_t i) const { return input_scales_[i]; }
inline ConcatenationTester& InputZeroPoint(
const std::vector<int32_t> zero_points) {
input_zero_points_ = zero_points;
return *this;
}
inline float InputZeroPoint(size_t i) const { return input_zero_points_[i]; }
inline ConcatenationTester& OutputScale(float scale) {
output_scale_ = scale;
return *this;
}
inline float OutputScale() const { return output_scale_; }
inline ConcatenationTester& OutputZeroPoint(int32_t zero_point) {
output_zero_point_ = zero_point;
return *this;
}
inline int32_t OutputZeroPoint() const { return output_scale_; }
inline size_t NumInputs() const { return input_shapes_.size(); }
std::vector<int32_t> OutputShape() const {
std::vector<int32_t> output_shape = InputShape(0);
int concat_axis = Axis() < 0 ? Axis() + output_shape.size() : Axis();
size_t axis_dim_size = 0;
for (size_t i = 0; i < NumInputs(); i++) {
axis_dim_size += InputShape(i)[concat_axis];
}
output_shape[concat_axis] = axis_dim_size;
return output_shape;
}
template <typename T>
void Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(TensorType tensor_type, TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel(TensorType tensor_type) const;
static int32_t ComputeSize(const std::vector<int32_t>& shape);
int axis_;
std::vector<int32_t> output_shape_;
std::vector<std::vector<int32_t>> input_shapes_;
std::vector<float> input_scales_;
float output_scale_;
std::vector<int32_t> input_zero_points_;
int32_t output_zero_point_;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_CONCATENATION_TESTER_H_
@@ -0,0 +1,803 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/xnnpack/conv_2d_tester.h"
#include "tensorflow/lite/delegates/xnnpack/fingerprint_test_helpers.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
struct Conv2D : DelegateTest {};
TEST_F(Conv2D, 1x1) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(1)
.KernelWidth(1)
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, 3x3) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(3)
.KernelWidth(3)
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, 3x3Stride2) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(3)
.KernelWidth(3)
.StrideHeight(2)
.StrideWidth(2)
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, Grouped) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_per_group_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
auto groups_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 8), std::ref(rng));
auto groups = groups_rng();
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(groups * channel_per_group_rng())
.OutputChannels(groups * channel_per_group_rng())
.Groups(groups)
.KernelHeight(3)
.KernelWidth(3)
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, SmallKernelWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, SmallKernelWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, StrideWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, StrideWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, DilationWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto dilation_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.DilationHeight(dilation_rng())
.DilationWidth(dilation_rng())
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, DilationWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto dilation_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.DilationHeight(dilation_rng())
.DilationWidth(dilation_rng())
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, FP16Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.FP16Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, TensorWiseQuantizedInt8Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.TensorWiseQuantizedInt8Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, ChannelWiseQuantizedInt8Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ChannelWiseQuantizedInt8Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, SparseWeights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SparseWeights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, SparseFP16Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SparseWeights()
.FP16Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, SparseTensorWiseQuantizedInt8Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SparseWeights()
.TensorWiseQuantizedInt8Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, SparseChannelWiseQuantizedInt8Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SparseWeights()
.ChannelWiseQuantizedInt8Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, ReluActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluActivation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, Relu6Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Relu6Activation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, ReluMinus1To1Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluMinus1To1Activation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, DISABLED_TanhActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.TanhActivation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, DISABLED_SignBitActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SignBitActivation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, WeightsCache) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
std::unique_ptr<TfLiteXNNPackDelegateWeightsCache,
decltype(&TfLiteXNNPackDelegateWeightsCacheDelete)>
weights_cache(TfLiteXNNPackDelegateWeightsCacheCreate(),
TfLiteXNNPackDelegateWeightsCacheDelete);
delegate_options.weights_cache = weights_cache.get();
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.WeightsCache(weights_cache.get())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(Conv2D, TransientIndirectionBuffer) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
delegate_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_TRANSIENT_INDIRECTION_BUFFER;
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
Conv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,444 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/conv_2d_tester.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "fp16.h" // from @FP16
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
void Conv2DTester::Test(TfLiteDelegate* delegate) {
const Model* model = GetModel();
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (weights_cache_ != nullptr) {
TfLiteXNNPackDelegateWeightsCacheFinalizeSoft(weights_cache_);
}
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_real_distribution<float>(), std::ref(rng));
float* default_input_data = default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
input_rng);
float* delegate_input_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* delegate_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
for (int32_t i = 0; i < BatchSize(); i++) {
for (int32_t y = 0; y < OutputHeight(); y++) {
for (int32_t x = 0; x < OutputWidth(); x++) {
for (int32_t c = 0; c < OutputChannels(); c++) {
const int32_t index = ((i * OutputHeight() + y) * OutputWidth() + x) *
OutputChannels() +
c;
ASSERT_NEAR(default_output_data[index], delegate_output_data[index],
std::abs(default_output_data[index]) * 3.0e-6f)
<< "batch " << i << " / " << BatchSize() << ", y position " << y
<< " / " << OutputHeight() << ", x position " << x << " / "
<< OutputWidth() << ", channel " << c << " / "
<< OutputChannels();
}
}
}
}
}
std::vector<char> Conv2DTester::CreateTfLiteModel() const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto range_rng = std::bind(
std::uniform_real_distribution<float>(-25.0f, 25.0f), std::ref(rng));
/*************************** Define operator codes **************************/
flatbuffers::FlatBufferBuilder builder;
std::vector<flatbuffers::Offset<OperatorCode>> operator_codes{
{CreateOperatorCode(builder, BuiltinOperator_CONV_2D)}};
int dequantize_operator_code = -1;
switch (WeightsType()) {
case WeightsType::kFP32:
break;
case WeightsType::kFP16:
case WeightsType::kTensorWiseQuantizedInt8:
case WeightsType::kChannelWiseQuantizedInt8:
dequantize_operator_code = operator_codes.size();
operator_codes.emplace_back(
CreateOperatorCode(builder, BuiltinOperator_DEQUANTIZE));
break;
}
int densify_operator_code = -1;
if (SparseWeights()) {
densify_operator_code = operator_codes.size();
operator_codes.emplace_back(
CreateOperatorCode(builder, BuiltinOperator_DENSIFY));
}
/*********************** Generate filter and bias data **********************/
std::vector<float> filter_data(OutputChannels() * KernelHeight() *
KernelWidth() * KernelInputChannels());
std::vector<float> bias_data(OutputChannels());
for (int32_t oc = 0; oc < OutputChannels(); oc++) {
// Use the same range of all-positive or all-negative values to generate
// all weights within the same output channel, but different ranges for
// different output channels. This ensures that no catastrophic
// cancellation occur, but test covers both positive and negative
// inputs.
const float range = range_rng();
const auto value_dist = std::uniform_real_distribution<float>(
std::min(range, 0.0f), std::max(range, 0.0f));
auto value_rng = std::bind(value_dist, std::ref(rng));
bias_data[oc] = value_rng();
for (int32_t ic = 0; ic < KernelInputChannels(); ic++) {
for (int32_t y = 0; y < KernelHeight(); y++) {
for (int32_t x = 0; x < KernelWidth(); x++) {
const int32_t index =
((oc * KernelHeight() + y) * KernelWidth() + x) *
KernelInputChannels() +
ic;
filter_data[index] = value_rng();
}
}
}
}
/************************ Define sparsity parameters ************************/
flatbuffers::Offset<SparsityParameters> filter_sparsity_params = 0;
const std::vector<int32_t> filter_shape = {
OutputChannels(), KernelHeight(), KernelWidth(), KernelInputChannels()};
if (SparseWeights()) {
// Sparse tensor in TFLite can be in different formats. Here we choose the
// simplest configuration that
// 1. all dimensions are dense,
// 2. in-order traversal, and
// 3. no block configuration.
const int dims_count = filter_shape.size();
std::vector<flatbuffers::Offset<DimensionMetadata>> dim_metadata(
dims_count);
std::vector<int> traversal_order(dims_count);
for (int i = 0; i < dims_count; i++) {
traversal_order[i] = i;
dim_metadata[i] = CreateDimensionMetadata(builder, DimensionType_DENSE,
filter_shape[i]);
}
filter_sparsity_params =
CreateSparsityParameters(builder, builder.CreateVector(traversal_order),
0, builder.CreateVector(dim_metadata));
}
/****************************** Define buffers ******************************/
std::vector<flatbuffers::Offset<tflite::Buffer>> buffers{
{CreateBuffer(builder, builder.CreateVector({}))}};
tflite::TensorType quantized_filter_type = TensorType_FLOAT32;
flatbuffers::Offset<tflite::QuantizationParameters>
filter_quantization_params = 0;
int filter_buffer_id = 0, quantized_filter_buffer_id = 0;
switch (WeightsType()) {
case WeightsType::kFP32:
filter_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(filter_data.data()),
sizeof(float) * filter_data.size())));
break;
case WeightsType::kFP16: {
std::vector<uint16_t> quantized_filter_data(filter_data.size());
std::transform(filter_data.begin(), filter_data.end(),
quantized_filter_data.begin(), fp16_ieee_from_fp32_value);
quantized_filter_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_filter_data.data()),
sizeof(uint16_t) * quantized_filter_data.size())));
quantized_filter_type = TensorType_FLOAT16;
break;
}
case WeightsType::kTensorWiseQuantizedInt8:
case WeightsType::kChannelWiseQuantizedInt8: {
std::vector<float> filter_scales;
std::vector<int64_t> filter_zero_points;
int32_t filter_quantized_dimension = 0;
std::vector<int8_t> quantized_filter_data(filter_data.size());
if (WeightsType() == WeightsType::kChannelWiseQuantizedInt8) {
filter_quantized_dimension =
static_cast<int32_t>(filter_shape.size()) - 1;
const int32_t num_scales = filter_shape[filter_quantized_dimension];
filter_scales = GetInt8QuantizationScalePerChannel(
filter_data.data(), filter_quantized_dimension, filter_shape);
filter_zero_points.resize(num_scales, 0);
QuantizeInt8PerChannel(filter_scales.data(), filter_zero_points.data(),
filter_quantized_dimension, filter_data.data(),
quantized_filter_data.data(), filter_shape);
} else {
filter_scales.resize(1, GetInt8QuantizationScale(filter_data));
filter_zero_points.resize(1, 0);
std::transform(filter_data.begin(), filter_data.end(),
quantized_filter_data.begin(),
std::bind(QuantizeInt8, std::placeholders::_1, 0,
filter_scales[0]));
}
quantized_filter_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_filter_data.data()),
sizeof(int8_t) * quantized_filter_data.size())));
quantized_filter_type = TensorType_INT8;
filter_quantization_params = CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>(filter_scales),
builder.CreateVector<int64_t>(filter_zero_points),
/*details_type=*/QuantizationDetails_NONE,
/*details=*/0, filter_quantized_dimension);
break;
}
}
tflite::TensorType quantized_bias_type = TensorType_FLOAT32;
int bias_buffer_id = 0, quantized_bias_buffer_id = 0;
switch (BiasType()) {
case BiasType::kFP32:
bias_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(bias_data.data()),
sizeof(float) * bias_data.size())));
break;
case BiasType::kFP16: {
std::vector<uint16_t> quantized_bias_data(bias_data.size());
std::transform(bias_data.begin(), bias_data.end(),
quantized_bias_data.begin(), fp16_ieee_from_fp32_value);
quantized_bias_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_bias_data.data()),
sizeof(uint16_t) * quantized_bias_data.size())));
quantized_bias_type = TensorType_FLOAT16;
break;
}
}
/****************************** Define tensors ******************************/
std::vector<flatbuffers::Offset<tflite::Tensor>> tensors;
int sparse_filter_tensor_id = -1;
if (SparseWeights()) {
sparse_filter_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(filter_shape.data(), filter_shape.size()),
/*type=*/quantized_filter_type,
/*buffer=*/std::max(filter_buffer_id, quantized_filter_buffer_id),
/*name=*/0, filter_quantization_params,
/*is_variable=*/false, filter_sparsity_params));
}
int quantized_filter_tensor_id = -1;
if (quantized_filter_type != TensorType_FLOAT32) {
quantized_filter_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(filter_shape.data(), filter_shape.size()),
/*type=*/quantized_filter_type,
/*buffer=*/SparseWeights() ? 0 : quantized_filter_buffer_id,
/*name=*/0, filter_quantization_params));
}
int quantized_bias_tensor_id = -1;
const std::vector<int32_t> bias_shape = {OutputChannels()};
if (quantized_bias_type != TensorType_FLOAT32) {
quantized_bias_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(bias_shape.data(), bias_shape.size()),
quantized_bias_type, /*buffer=*/quantized_bias_buffer_id));
}
const int input_tensor_id = tensors.size();
const std::array<int32_t, 4> input_shape{
{BatchSize(), InputHeight(), InputWidth(), InputChannels()}};
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(input_shape.data(), input_shape.size()),
TensorType_FLOAT32));
const int filter_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(filter_shape.data(), filter_shape.size()),
TensorType_FLOAT32,
/*buffer=*/SparseWeights() ? 0 : filter_buffer_id));
const int bias_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(bias_shape.data(), bias_shape.size()),
TensorType_FLOAT32, bias_buffer_id));
const int output_tensor_id = tensors.size();
const std::array<int32_t, 4> output_shape{
{BatchSize(), OutputHeight(), OutputWidth(), OutputChannels()}};
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(output_shape.data(), output_shape.size()),
TensorType_FLOAT32));
/***************************** Define operators *****************************/
std::vector<flatbuffers::Offset<tflite::Operator>> operators;
if (SparseWeights()) {
const std::array<int32_t, 1> densify_filter_inputs{
{sparse_filter_tensor_id}};
const std::array<int32_t, 1> densify_filter_outputs{
{quantized_filter_tensor_id >= 0 ? quantized_filter_tensor_id
: filter_tensor_id}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/densify_operator_code,
builder.CreateVector<int32_t>(densify_filter_inputs.data(),
densify_filter_inputs.size()),
builder.CreateVector<int32_t>(densify_filter_outputs.data(),
densify_filter_outputs.size())));
}
if (quantized_filter_tensor_id >= 0) {
const std::array<int32_t, 1> dequantize_filter_inputs{
{quantized_filter_tensor_id}};
const std::array<int32_t, 1> dequantize_filter_outputs{{filter_tensor_id}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/dequantize_operator_code,
builder.CreateVector<int32_t>(dequantize_filter_inputs.data(),
dequantize_filter_inputs.size()),
builder.CreateVector<int32_t>(dequantize_filter_outputs.data(),
dequantize_filter_outputs.size())));
}
if (quantized_bias_tensor_id >= 0) {
const std::array<int32_t, 1> dequantize_bias_inputs{
{quantized_bias_tensor_id}};
const std::array<int32_t, 1> dequantize_bias_outputs{{bias_tensor_id}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/dequantize_operator_code,
builder.CreateVector<int32_t>(dequantize_bias_inputs.data(),
dequantize_bias_inputs.size()),
builder.CreateVector<int32_t>(dequantize_bias_outputs.data(),
dequantize_bias_outputs.size())));
}
const std::array<int32_t, 3> op_inputs{
{input_tensor_id, filter_tensor_id, bias_tensor_id}};
const std::array<int32_t, 1> op_outputs{{output_tensor_id}};
const flatbuffers::Offset<Conv2DOptions> conv2d_options =
CreateConv2DOptions(builder, Padding(), StrideWidth(), StrideHeight(),
Activation(), DilationWidth(), DilationHeight());
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_Conv2DOptions, conv2d_options.Union()));
/****************************** Define subgraph *****************************/
const std::array<int32_t, 1> subgraph_inputs{{input_tensor_id}};
const std::array<int32_t, 1> subgraph_outputs{{output_tensor_id}};
const flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(operators.data(), operators.size()));
const flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Conv2D model");
const flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,284 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_CONV_2D_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_CONV_2D_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class Conv2DTester : public ModelCache<Conv2DTester> {
public:
enum class WeightsType {
kFP32,
kFP16,
kTensorWiseQuantizedInt8,
kChannelWiseQuantizedInt8,
};
enum class BiasType {
kFP32,
kFP16,
};
Conv2DTester() = default;
Conv2DTester(const Conv2DTester&) = delete;
Conv2DTester& operator=(const Conv2DTester&) = delete;
inline Conv2DTester& BatchSize(int32_t batch_size) {
EXPECT_GT(batch_size, 0);
batch_size_ = batch_size;
return *this;
}
inline int32_t BatchSize() const { return batch_size_; }
inline Conv2DTester& InputChannels(int32_t input_channels) {
EXPECT_GT(input_channels, 0);
input_channels_ = input_channels;
return *this;
}
inline int32_t InputChannels() const { return input_channels_; }
inline Conv2DTester& OutputChannels(int32_t output_channels) {
EXPECT_GT(output_channels, 0);
output_channels_ = output_channels;
return *this;
}
inline int32_t OutputChannels() const { return output_channels_; }
inline Conv2DTester& Groups(int32_t groups) {
EXPECT_EQ(InputChannels() % groups, 0);
EXPECT_EQ(OutputChannels() % groups, 0);
groups_ = groups;
return *this;
}
inline int32_t Groups() const { return groups_; }
inline int32_t KernelInputChannels() const {
return input_channels_ / groups_;
}
inline Conv2DTester& InputHeight(int32_t input_height) {
EXPECT_GT(input_height, 0);
input_height_ = input_height;
return *this;
}
inline int32_t InputHeight() const { return input_height_; }
inline Conv2DTester& InputWidth(int32_t input_width) {
EXPECT_GT(input_width, 0);
input_width_ = input_width;
return *this;
}
inline int32_t InputWidth() const { return input_width_; }
inline int32_t OutputWidth() const {
if (Padding() == ::tflite::Padding_SAME) {
EXPECT_GE(InputWidth(), 1);
return (InputWidth() - 1) / StrideWidth() + 1;
} else {
EXPECT_GE(InputWidth(), DilatedKernelWidth());
return 1 + (InputWidth() - DilatedKernelWidth()) / StrideWidth();
}
}
inline int32_t OutputHeight() const {
if (Padding() == ::tflite::Padding_SAME) {
EXPECT_GE(InputHeight(), 1);
return (InputHeight() - 1) / StrideHeight() + 1;
} else {
EXPECT_GE(InputHeight(), DilatedKernelHeight());
return 1 + (InputHeight() - DilatedKernelHeight()) / StrideHeight();
}
}
inline Conv2DTester& KernelHeight(int32_t kernel_height) {
EXPECT_GT(kernel_height, 0);
kernel_height_ = kernel_height;
return *this;
}
inline int32_t KernelHeight() const { return kernel_height_; }
inline Conv2DTester& KernelWidth(int32_t kernel_width) {
EXPECT_GT(kernel_width, 0);
kernel_width_ = kernel_width;
return *this;
}
inline int32_t KernelWidth() const { return kernel_width_; }
inline Conv2DTester& StrideHeight(int32_t stride_height) {
EXPECT_GT(stride_height, 0);
stride_height_ = stride_height;
return *this;
}
inline int32_t StrideHeight() const { return stride_height_; }
inline Conv2DTester& StrideWidth(int32_t stride_width) {
EXPECT_GT(stride_width, 0);
stride_width_ = stride_width;
return *this;
}
inline int32_t StrideWidth() const { return stride_width_; }
inline Conv2DTester& DilationHeight(int32_t dilation_height) {
EXPECT_GT(dilation_height, 0);
dilation_height_ = dilation_height;
return *this;
}
inline int32_t DilationHeight() const { return dilation_height_; }
inline Conv2DTester& DilationWidth(int32_t dilation_width) {
EXPECT_GT(dilation_width, 0);
dilation_width_ = dilation_width;
return *this;
}
inline int32_t DilationWidth() const { return dilation_width_; }
inline int32_t DilatedKernelHeight() const {
return (KernelHeight() - 1) * DilationHeight() + 1;
}
inline int32_t DilatedKernelWidth() const {
return (KernelWidth() - 1) * DilationWidth() + 1;
}
inline Conv2DTester& FP16Weights() {
weights_type_ = WeightsType::kFP16;
bias_type_ = BiasType::kFP16;
return *this;
}
inline Conv2DTester& TensorWiseQuantizedInt8Weights() {
weights_type_ = WeightsType::kTensorWiseQuantizedInt8;
// Bias is stored in FP32 even when filter is quantized to INT8
bias_type_ = BiasType::kFP32;
return *this;
}
inline Conv2DTester& ChannelWiseQuantizedInt8Weights() {
weights_type_ = WeightsType::kChannelWiseQuantizedInt8;
// Bias is stored in FP32 even when filter is quantized to INT8
bias_type_ = BiasType::kFP32;
return *this;
}
inline Conv2DTester& SparseWeights() {
sparse_weights_ = true;
return *this;
}
inline bool SparseWeights() const { return sparse_weights_; }
inline Conv2DTester& SamePadding() {
padding_ = ::tflite::Padding_SAME;
return *this;
}
inline Conv2DTester& ValidPadding() {
padding_ = ::tflite::Padding_VALID;
return *this;
}
inline Conv2DTester& ReluActivation() {
activation_ = ::tflite::ActivationFunctionType_RELU;
return *this;
}
inline Conv2DTester& Relu6Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU6;
return *this;
}
inline Conv2DTester& ReluMinus1To1Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU_N1_TO_1;
return *this;
}
inline Conv2DTester& TanhActivation() {
activation_ = ::tflite::ActivationFunctionType_TANH;
return *this;
}
inline Conv2DTester& SignBitActivation() {
activation_ = ::tflite::ActivationFunctionType_SIGN_BIT;
return *this;
}
inline Conv2DTester& WeightsCache(
TfLiteXNNPackDelegateWeightsCache* weights_cache) {
weights_cache_ = weights_cache;
return *this;
}
void Test(TfLiteDelegate* delegate);
std::vector<char> CreateTfLiteModel() const override;
private:
inline WeightsType WeightsType() const { return weights_type_; }
inline BiasType BiasType() const { return bias_type_; }
inline ::tflite::Padding Padding() const { return padding_; }
inline ::tflite::ActivationFunctionType Activation() const {
return activation_;
}
int32_t batch_size_ = 1;
int32_t input_channels_ = 1;
int32_t output_channels_ = 1;
int32_t groups_ = 1;
int32_t input_height_ = 1;
int32_t input_width_ = 1;
int32_t kernel_height_ = 1;
int32_t kernel_width_ = 1;
int32_t stride_height_ = 1;
int32_t stride_width_ = 1;
int32_t dilation_height_ = 1;
int32_t dilation_width_ = 1;
enum WeightsType weights_type_ { WeightsType::kFP32 };
enum BiasType bias_type_ { BiasType::kFP32 };
bool sparse_weights_ = false;
::tflite::Padding padding_ = ::tflite::Padding_VALID;
::tflite::ActivationFunctionType activation_ =
::tflite::ActivationFunctionType_NONE;
TfLiteXNNPackDelegateWeightsCache* weights_cache_ = nullptr;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_CONV_2D_TESTER_H_
@@ -0,0 +1,64 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <memory>
#include <gtest/gtest.h>
#include "pthreadpool.h" // from @pthreadpool
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
TEST(Delegate, CreateWithoutParams) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
}
TEST(Delegate, CreateWithDefaultParams) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
}
TEST(Delegate, CreateWithNumThreadsParam) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
}
TEST(Delegate, GetThreadPool) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
pthreadpool_t threadpool = static_cast<pthreadpool_t>(
TfLiteXNNPackDelegateGetThreadPool(xnnpack_delegate.get()));
ASSERT_TRUE(threadpool);
ASSERT_EQ(2, pthreadpool_get_threads_count(threadpool));
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,145 @@
/* Copyright 2025 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <memory>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/model_building.h"
#include "tensorflow/lite/delegates/xnnpack/weight_cache.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
namespace tflite {
namespace {
class XnnpackDelegateWeightCacheTest : public testing::Test {
public:
TfLiteXNNPackDelegateOptions GetXNNPackOptions() {
TfLiteXNNPackDelegateOptions xnnpack_options =
TfLiteXNNPackDelegateOptionsDefault();
xnnpack_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_ENABLE_SUBGRAPH_RESHAPING;
xnnpack_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_ENABLE_LATEST_OPERATORS;
return xnnpack_options;
}
model_builder::ModelBuilder BuildFullyConnectedGraph() {
model_builder::ModelBuilder builder;
model_builder::Quantization weights_quantization =
model_builder::AffineQuantization{/*zero_points=*/{0},
/*scales=*/{1.45},
/*axis=*/1};
auto weights = NewConstantBuffer(builder);
Assign<kTfLiteInt8>(
weights, {3, 4},
std::vector<int>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
std::move(weights_quantization));
auto graph = NewGraph(builder);
auto inputs = NewInput(graph, kTfLiteFloat32);
SetShape(inputs, {2, 4});
auto out = FullyConnected(inputs, weights);
model_builder::MarkOutputs({out});
return builder;
}
};
TEST_F(XnnpackDelegateWeightCacheTest,
ReuseImplicitlyLoadedCacheAcrossDelegates) {
TfLiteXNNPackDelegateOptions xnnpack_options = GetXNNPackOptions();
xnnpack::MMapWeightCacheProvider shared_weight_cache_provider;
xnnpack_options.weight_cache_provider = &shared_weight_cache_provider;
xnnpack_options.weight_cache_file_path =
TfLiteXNNPackDelegateInMemoryFilePath();
for (int i = 0; i < 2; ++i) {
model_builder::ModelBuilder builder = BuildFullyConnectedGraph();
tflite::Interpreter interpreter;
builder.Build(interpreter);
std::unique_ptr<TfLiteDelegate, void (*)(TfLiteDelegate*)> xnnpack_delegate(
TfLiteXNNPackDelegateCreate(&xnnpack_options),
TfLiteXNNPackDelegateDelete);
interpreter.ModifyGraphWithDelegate(std::move(xnnpack_delegate));
EXPECT_TRUE(shared_weight_cache_provider.IsActive());
interpreter.AllocateTensors();
TfLiteTensor* input = interpreter.input_tensor(0);
for (int i = 0; i < 8; ++i) {
input->data.f[i] = i;
}
ASSERT_EQ(interpreter.Invoke(), kTfLiteOk);
}
}
TEST_F(XnnpackDelegateWeightCacheTest,
ReuseExplicitlyLoadedCacheAcrossDelegates) {
TfLiteXNNPackDelegateOptions xnnpack_options = GetXNNPackOptions();
xnnpack::MMapWeightCacheProvider shared_weight_cache_provider;
xnnpack_options.weight_cache_provider = &shared_weight_cache_provider;
ASSERT_TRUE(shared_weight_cache_provider.LoadOrStartBuild(
TfLiteXNNPackDelegateInMemoryFilePath()));
for (int i = 0; i < 2; ++i) {
model_builder::ModelBuilder builder = BuildFullyConnectedGraph();
tflite::Interpreter interpreter;
builder.Build(interpreter);
std::unique_ptr<TfLiteDelegate, void (*)(TfLiteDelegate*)> xnnpack_delegate(
TfLiteXNNPackDelegateCreate(&xnnpack_options),
TfLiteXNNPackDelegateDelete);
interpreter.ModifyGraphWithDelegate(std::move(xnnpack_delegate));
interpreter.AllocateTensors();
TfLiteTensor* input = interpreter.input_tensor(0);
for (int i = 0; i < 8; ++i) {
input->data.f[i] = i;
}
ASSERT_EQ(interpreter.Invoke(), kTfLiteOk);
}
}
TEST_F(XnnpackDelegateWeightCacheTest,
ImplicitlyLoadedCacheFailsIfNoPathOrFileDescriptorIsProvided) {
TfLiteXNNPackDelegateOptions xnnpack_options = GetXNNPackOptions();
xnnpack::MMapWeightCacheProvider shared_weight_cache_provider;
xnnpack_options.weight_cache_provider = &shared_weight_cache_provider;
model_builder::ModelBuilder builder = BuildFullyConnectedGraph();
tflite::Interpreter interpreter;
builder.Build(interpreter);
std::unique_ptr<TfLiteDelegate, void (*)(TfLiteDelegate*)> xnnpack_delegate(
TfLiteXNNPackDelegateCreate(&xnnpack_options),
TfLiteXNNPackDelegateDelete);
EXPECT_EQ(interpreter.ModifyGraphWithDelegate(std::move(xnnpack_delegate)),
kTfLiteOk);
EXPECT_FALSE(shared_weight_cache_provider.IsActive());
}
} // namespace
} // namespace tflite
@@ -0,0 +1,157 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/depth_to_space_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
TEST(DepthToSpace, SinglePixel) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto block_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DepthToSpaceTester()
.BatchSize(batch_rng())
.InputHeight(1)
.InputWidth(1)
.OutputChannels(channel_rng())
.BlockSize(block_rng())
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
}
TEST(DepthToSpace, SingleRow) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto width_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto block_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DepthToSpaceTester()
.BatchSize(batch_rng())
.InputHeight(1)
.InputWidth(width_rng())
.OutputChannels(channel_rng())
.BlockSize(block_rng())
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
}
TEST(DepthToSpace, SingleColumn) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto height_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto block_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DepthToSpaceTester()
.BatchSize(batch_rng())
.InputHeight(height_rng())
.InputWidth(1)
.OutputChannels(channel_rng())
.BlockSize(block_rng())
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
}
TEST(DepthToSpace, FullImage) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto size_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto block_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DepthToSpaceTester()
.BatchSize(batch_rng())
.InputHeight(size_rng())
.InputWidth(size_rng())
.OutputChannels(channel_rng())
.BlockSize(block_rng())
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
}
TEST(DepthToSpace, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto size_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto block_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DepthToSpaceTester()
.BatchSize(batch_rng())
.InputHeight(size_rng())
.InputWidth(size_rng())
.OutputChannels(channel_rng())
.BlockSize(block_rng())
.Test(TensorType_FLOAT32, xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,250 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/depth_to_space_tester.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
template <class T>
void DepthToSpaceTester::Test(TensorType tensor_type,
Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
std::uniform_int_distribution<int32_t> input_distribution(
std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
auto input_rng = std::bind(input_distribution, std::ref(rng));
T* default_input_data = default_interpreter->typed_input_tensor<T>(0);
std::generate_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
std::ref(input_rng));
T* delegate_input_data = delegate_interpreter->typed_input_tensor<T>(0);
std::copy_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
T* default_output_data = default_interpreter->typed_output_tensor<T>(0);
T* delegate_output_data = delegate_interpreter->typed_output_tensor<T>(0);
for (int32_t i = 0; i < BatchSize(); i++) {
for (int32_t y = 0; y < OutputHeight(); y++) {
for (int32_t x = 0; x < OutputWidth(); x++) {
for (int32_t c = 0; c < OutputChannels(); c++) {
const int32_t index = ((i * OutputHeight() + y) * OutputWidth() + x) *
OutputChannels() +
c;
ASSERT_EQ(static_cast<int32_t>(default_output_data[index]),
static_cast<int32_t>(delegate_output_data[index]))
<< "batch " << i << " / " << BatchSize() << ", y position " << y
<< " / " << OutputHeight() << ", x position " << x << " / "
<< OutputWidth() << ", channel " << c << " / "
<< OutputChannels();
}
}
}
}
}
template <>
void DepthToSpaceTester::Test<float>(TensorType tensor_type,
Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_real_distribution<float>(), std::ref(rng));
float* default_input_data = default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
std::ref(input_rng));
float* delegate_input_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* delegate_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
for (int32_t i = 0; i < BatchSize(); i++) {
for (int32_t y = 0; y < OutputHeight(); y++) {
for (int32_t x = 0; x < OutputWidth(); x++) {
for (int32_t c = 0; c < OutputChannels(); c++) {
const int32_t index = ((i * OutputHeight() + y) * OutputWidth() + x) *
OutputChannels() +
c;
ASSERT_EQ(default_output_data[index], delegate_output_data[index])
<< "batch " << i << " / " << BatchSize() << ", y position " << y
<< " / " << OutputHeight() << ", x position " << x << " / "
<< OutputWidth() << ", channel " << c << " / "
<< OutputChannels();
}
}
}
}
}
void DepthToSpaceTester::Test(TensorType tensor_type,
TfLiteDelegate* delegate) const {
const std::vector<char> buffer = CreateTfLiteModel(tensor_type);
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
switch (tensor_type) {
case TensorType_FLOAT32:
Test<float>(TensorType_FLOAT32, delegate_interpreter.get(),
default_interpreter.get());
break;
case TensorType_INT8:
Test<int8_t>(TensorType_INT8, delegate_interpreter.get(),
default_interpreter.get());
break;
case TensorType_UINT8:
Test<uint8_t>(TensorType_UINT8, delegate_interpreter.get(),
default_interpreter.get());
break;
default:
GTEST_FAIL();
}
}
std::vector<char> DepthToSpaceTester::CreateTfLiteModel(
TensorType tensor_type) const {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<OperatorCode> operator_code =
CreateOperatorCode(builder, BuiltinOperator_DEPTH_TO_SPACE, 0);
const std::array<flatbuffers::Offset<Buffer>, 1> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
}};
const std::array<int32_t, 4> input_shape{
{BatchSize(), InputHeight(), InputWidth(), InputChannels()}};
const std::array<int32_t, 4> output_shape{
{BatchSize(), OutputHeight(), OutputWidth(), OutputChannels()}};
const std::array<flatbuffers::Offset<Tensor>, 2> tensors{
{CreateTensor(builder,
builder.CreateVector<int32_t>(input_shape.data(),
input_shape.size()),
tensor_type,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({/*scale=*/1.0f}),
builder.CreateVector<int64_t>({/*zero_point=*/0}))),
CreateTensor(builder,
builder.CreateVector<int32_t>(output_shape.data(),
output_shape.size()),
tensor_type,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({/*scale=*/1.0f}),
builder.CreateVector<int64_t>({/*zero_point=*/0})))}};
const std::array<int32_t, 1> op_inputs{{0}};
const std::array<int32_t, 1> op_outputs{{1}};
const flatbuffers::Offset<Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
tflite::BuiltinOptions_DepthToSpaceOptions,
CreateDepthToSpaceOptions(builder, BlockSize()).Union());
const std::array<int32_t, 1> subgraph_inputs{{op_inputs.front()}};
const std::array<int32_t, 1> subgraph_outputs{{op_outputs.front()}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
const flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1),
builder.CreateString("Depth-To-Space model"),
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,103 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_DEPTH_TO_SPACE_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_DEPTH_TO_SPACE_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class DepthToSpaceTester {
public:
DepthToSpaceTester() = default;
DepthToSpaceTester(const DepthToSpaceTester&) = delete;
DepthToSpaceTester& operator=(const DepthToSpaceTester&) = delete;
inline DepthToSpaceTester& BatchSize(int32_t batch_size) {
EXPECT_GT(batch_size, 0);
batch_size_ = batch_size;
return *this;
}
inline int32_t BatchSize() const { return batch_size_; }
inline int32_t InputChannels() const {
return OutputChannels() * BlockSize() * BlockSize();
}
inline DepthToSpaceTester& OutputChannels(int32_t output_channels) {
EXPECT_GT(output_channels, 0);
output_channels_ = output_channels;
return *this;
}
inline int32_t OutputChannels() const { return output_channels_; }
inline DepthToSpaceTester& InputHeight(int32_t input_height) {
EXPECT_GT(input_height, 0);
input_height_ = input_height;
return *this;
}
inline int32_t InputHeight() const { return input_height_; }
inline DepthToSpaceTester& InputWidth(int32_t input_width) {
EXPECT_GT(input_width, 0);
input_width_ = input_width;
return *this;
}
inline int32_t InputWidth() const { return input_width_; }
inline int32_t OutputWidth() const { return InputWidth() * BlockSize(); }
inline int32_t OutputHeight() const { return InputHeight() * BlockSize(); }
inline DepthToSpaceTester& BlockSize(int32_t block_size) {
EXPECT_GT(block_size, 1);
block_size_ = block_size;
return *this;
}
inline int32_t BlockSize() const { return block_size_; }
template <class T>
void Test(TensorType tensor_type, Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(TensorType tensor_type, TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel(TensorType tensor_type) const;
int32_t batch_size_ = 1;
int32_t input_height_ = 1;
int32_t input_width_ = 1;
int32_t output_channels_ = 1;
int32_t block_size_ = 2;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_DEPTH_TO_SPACE_TESTER_H_
@@ -0,0 +1,834 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/xnnpack/depthwise_conv_2d_tester.h"
#include "tensorflow/lite/delegates/xnnpack/fingerprint_test_helpers.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
struct DepthwiseConv2D : DelegateTest {};
TEST_F(DepthwiseConv2D, 1x1) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(1)
.KernelWidth(1)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, 2x2) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(2)
.KernelWidth(2)
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, 3x3) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(3)
.KernelWidth(3)
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, 3x3Stride2) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(3)
.KernelWidth(3)
.StrideHeight(2)
.StrideWidth(2)
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, 5x5) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(5)
.KernelWidth(5)
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, 5x5Stride2) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(5)
.KernelWidth(5)
.StrideHeight(2)
.StrideWidth(2)
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, SmallKernelWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, SmallKernelWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, StrideWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, StrideWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, DilationWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto dilation_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.DilationHeight(dilation_rng())
.DilationWidth(dilation_rng())
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, DilationWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto dilation_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.DilationHeight(dilation_rng())
.DilationWidth(dilation_rng())
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, DepthMultiplier) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
auto multiplier_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 8), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.DepthMultiplier(multiplier_rng())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, FP16Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.FP16Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, TensorWiseQuantizedInt8Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.TensorWiseQuantizedInt8Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, ChannelWiseQuantizedInt8Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ChannelWiseQuantizedInt8Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, SparseWeights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SparseWeights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, SparseFP16Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SparseWeights()
.FP16Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, SparseTensorWiseQuantizedInt8Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SparseWeights()
.TensorWiseQuantizedInt8Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, SparseChannelWiseQuantizedInt8Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SparseWeights()
.ChannelWiseQuantizedInt8Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, ReluActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluActivation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, Relu6Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Relu6Activation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, ReluMinus1To1Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluMinus1To1Activation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, DISABLED_TanhActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.TanhActivation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, DISABLED_SignBitActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SignBitActivation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, WeightsCache) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
std::unique_ptr<TfLiteXNNPackDelegateWeightsCache,
decltype(&TfLiteXNNPackDelegateWeightsCacheDelete)>
weights_cache(TfLiteXNNPackDelegateWeightsCacheCreate(),
TfLiteXNNPackDelegateWeightsCacheDelete);
delegate_options.weights_cache = weights_cache.get();
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.WeightsCache(weights_cache.get())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DepthwiseConv2D, TransientIndirectionBuffer) {
TfLiteXNNPackDelegateOptions xnnpack_options =
TfLiteXNNPackDelegateOptionsDefault();
xnnpack_options.num_threads = 2;
xnnpack_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_TRANSIENT_INDIRECTION_BUFFER;
UseCustomDelegate(xnnpack_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 32), std::ref(rng));
DepthwiseConv2DTester tester;
tester.InputHeight(input_rng())
.InputWidth(input_rng())
.InputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,442 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/depthwise_conv_2d_tester.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "fp16.h" // from @FP16
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
void DepthwiseConv2DTester::Test(TfLiteDelegate* delegate) {
const Model* model = GetModel();
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (weights_cache_ != nullptr) {
TfLiteXNNPackDelegateWeightsCacheFinalizeSoft(weights_cache_);
}
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_real_distribution<float>(), std::ref(rng));
float* default_input_data = default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
input_rng);
float* delegate_input_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* delegate_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
for (int32_t i = 0; i < BatchSize(); i++) {
for (int32_t y = 0; y < OutputHeight(); y++) {
for (int32_t x = 0; x < OutputWidth(); x++) {
for (int32_t c = 0; c < OutputChannels(); c++) {
const int32_t index = ((i * OutputHeight() + y) * OutputWidth() + x) *
OutputChannels() +
c;
ASSERT_NEAR(default_output_data[index], delegate_output_data[index],
std::abs(default_output_data[index]) * 3.0e-6f)
<< "batch " << i << " / " << BatchSize() << ", y position " << y
<< " / " << OutputHeight() << ", x position " << x << " / "
<< OutputWidth() << ", channel " << c << " / "
<< OutputChannels();
}
}
}
}
}
std::vector<char> DepthwiseConv2DTester::CreateTfLiteModel() const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto range_rng = std::bind(
std::uniform_real_distribution<float>(-25.0f, 25.0f), std::ref(rng));
/*************************** Define operator codes **************************/
flatbuffers::FlatBufferBuilder builder;
std::vector<flatbuffers::Offset<OperatorCode>> operator_codes{
{CreateOperatorCode(builder, BuiltinOperator_DEPTHWISE_CONV_2D)}};
int dequantize_operator_code = -1;
switch (WeightsType()) {
case WeightsType::kFP32:
break;
case WeightsType::kFP16:
case WeightsType::kTensorWiseQuantizedInt8:
case WeightsType::kChannelWiseQuantizedInt8:
dequantize_operator_code = operator_codes.size();
operator_codes.emplace_back(
CreateOperatorCode(builder, BuiltinOperator_DEQUANTIZE));
break;
}
int densify_operator_code = -1;
if (SparseWeights()) {
densify_operator_code = operator_codes.size();
operator_codes.emplace_back(
CreateOperatorCode(builder, BuiltinOperator_DENSIFY));
}
/*********************** Generate filter and bias data **********************/
std::vector<float> filter_data(KernelHeight() * KernelWidth() *
OutputChannels());
std::vector<float> bias_data(OutputChannels());
for (int32_t ic = 0; ic < InputChannels(); ic++) {
// Use the same range of all-positive or all-negative values to generate
// all pixels within the same batch index & channel, but different ranges
// for different channels or batches. This ensures that no catastrophic
// cancellation occur, but test covers both positive and negative inputs.
const float range = range_rng();
const auto value_dist = std::uniform_real_distribution<float>(
std::min(range, 0.0f), std::max(range, 0.0f));
auto value_rng = std::bind(value_dist, std::ref(rng));
for (int32_t m = 0; m < DepthMultiplier(); m++) {
const int32_t oc = ic * DepthMultiplier() + m;
bias_data[oc] = value_rng();
for (int32_t y = 0; y < KernelHeight(); y++) {
for (int32_t x = 0; x < KernelWidth(); x++) {
const int32_t index = (y * KernelWidth() + x) * OutputChannels() + oc;
filter_data[index] = value_rng();
}
}
}
}
/************************ Define sparsity parameters ************************/
flatbuffers::Offset<SparsityParameters> filter_sparsity_params = 0;
const std::vector<int32_t> filter_shape = {1, KernelHeight(), KernelWidth(),
OutputChannels()};
if (SparseWeights()) {
// Sparse tensor in TFLite can be in different formats. Here we choose the
// simplest configuration that
// 1. all dimensions are dense,
// 2. in-order traversal, and
// 3. no block configuration.
const int dims_count = filter_shape.size();
std::vector<flatbuffers::Offset<DimensionMetadata>> dim_metadata(
dims_count);
std::vector<int> traversal_order(dims_count);
for (int i = 0; i < dims_count; i++) {
traversal_order[i] = i;
dim_metadata[i] = CreateDimensionMetadata(builder, DimensionType_DENSE,
filter_shape[i]);
}
filter_sparsity_params =
CreateSparsityParameters(builder, builder.CreateVector(traversal_order),
0, builder.CreateVector(dim_metadata));
}
/****************************** Define buffers ******************************/
std::vector<flatbuffers::Offset<tflite::Buffer>> buffers{
{CreateBuffer(builder, builder.CreateVector({}))}};
tflite::TensorType quantized_filter_type = TensorType_FLOAT32;
flatbuffers::Offset<tflite::QuantizationParameters>
filter_quantization_params = 0;
int filter_buffer_id = 0, quantized_filter_buffer_id = 0;
switch (WeightsType()) {
case WeightsType::kFP32:
filter_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(filter_data.data()),
sizeof(float) * filter_data.size())));
break;
case WeightsType::kFP16: {
std::vector<uint16_t> quantized_filter_data(filter_data.size());
std::transform(filter_data.begin(), filter_data.end(),
quantized_filter_data.begin(), fp16_ieee_from_fp32_value);
quantized_filter_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_filter_data.data()),
sizeof(uint16_t) * quantized_filter_data.size())));
quantized_filter_type = TensorType_FLOAT16;
break;
}
case WeightsType::kTensorWiseQuantizedInt8:
case WeightsType::kChannelWiseQuantizedInt8: {
std::vector<float> filter_scales;
std::vector<int64_t> filter_zero_points;
int32_t filter_quantized_dimension = 0;
std::vector<int8_t> quantized_filter_data(filter_data.size());
if (WeightsType() == WeightsType::kChannelWiseQuantizedInt8) {
filter_quantized_dimension =
static_cast<int32_t>(filter_shape.size()) - 1;
const int32_t num_scales = filter_shape[filter_quantized_dimension];
filter_scales = GetInt8QuantizationScalePerChannel(
filter_data.data(), filter_quantized_dimension, filter_shape);
filter_zero_points.resize(num_scales, 0);
QuantizeInt8PerChannel(filter_scales.data(), filter_zero_points.data(),
filter_quantized_dimension, filter_data.data(),
quantized_filter_data.data(), filter_shape);
} else {
filter_scales.resize(1, GetInt8QuantizationScale(filter_data));
filter_zero_points.resize(1, 0);
std::transform(filter_data.begin(), filter_data.end(),
quantized_filter_data.begin(),
std::bind(QuantizeInt8, std::placeholders::_1, 0,
filter_scales[0]));
}
quantized_filter_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_filter_data.data()),
sizeof(int8_t) * quantized_filter_data.size())));
quantized_filter_type = TensorType_INT8;
filter_quantization_params = CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>(filter_scales),
builder.CreateVector<int64_t>(filter_zero_points),
/*details_type=*/QuantizationDetails_NONE,
/*details=*/0, filter_quantized_dimension);
break;
}
}
tflite::TensorType quantized_bias_type = TensorType_FLOAT32;
int bias_buffer_id = 0, quantized_bias_buffer_id = 0;
switch (BiasType()) {
case BiasType::kFP32:
bias_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(bias_data.data()),
sizeof(float) * bias_data.size())));
break;
case BiasType::kFP16: {
std::vector<uint16_t> quantized_bias_data(bias_data.size());
std::transform(bias_data.begin(), bias_data.end(),
quantized_bias_data.begin(), fp16_ieee_from_fp32_value);
quantized_bias_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_bias_data.data()),
sizeof(uint16_t) * quantized_bias_data.size())));
quantized_bias_type = TensorType_FLOAT16;
break;
}
}
/****************************** Define tensors ******************************/
std::vector<flatbuffers::Offset<tflite::Tensor>> tensors;
int sparse_filter_tensor_id = -1;
if (SparseWeights()) {
sparse_filter_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(filter_shape.data(), filter_shape.size()),
/*type=*/quantized_filter_type,
/*buffer=*/std::max(filter_buffer_id, quantized_filter_buffer_id),
/*name=*/0, filter_quantization_params,
/*is_variable=*/false, filter_sparsity_params));
}
int quantized_filter_tensor_id = -1;
if (quantized_filter_type != TensorType_FLOAT32) {
quantized_filter_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(filter_shape.data(), filter_shape.size()),
/*type=*/quantized_filter_type,
/*buffer=*/SparseWeights() ? 0 : quantized_filter_buffer_id,
/*name=*/0, filter_quantization_params));
}
int quantized_bias_tensor_id = -1;
const std::vector<int32_t> bias_shape = {OutputChannels()};
if (quantized_bias_type != TensorType_FLOAT32) {
quantized_bias_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(bias_shape.data(), bias_shape.size()),
quantized_bias_type, /*buffer=*/quantized_bias_buffer_id));
}
const int input_tensor_id = tensors.size();
const std::array<int32_t, 4> input_shape{
{BatchSize(), InputHeight(), InputWidth(), InputChannels()}};
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(input_shape.data(), input_shape.size()),
TensorType_FLOAT32));
const int filter_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(filter_shape.data(), filter_shape.size()),
TensorType_FLOAT32,
/*buffer=*/SparseWeights() ? 0 : filter_buffer_id));
const int bias_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(bias_shape.data(), bias_shape.size()),
TensorType_FLOAT32, bias_buffer_id));
const int output_tensor_id = tensors.size();
const std::array<int32_t, 4> output_shape{
{BatchSize(), OutputHeight(), OutputWidth(), OutputChannels()}};
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(output_shape.data(), output_shape.size()),
TensorType_FLOAT32));
/***************************** Define operators *****************************/
std::vector<flatbuffers::Offset<tflite::Operator>> operators;
if (SparseWeights()) {
const std::array<int32_t, 1> densify_filter_inputs{
{sparse_filter_tensor_id}};
const std::array<int32_t, 1> densify_filter_outputs{
{quantized_filter_tensor_id >= 0 ? quantized_filter_tensor_id
: filter_tensor_id}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/densify_operator_code,
builder.CreateVector<int32_t>(densify_filter_inputs.data(),
densify_filter_inputs.size()),
builder.CreateVector<int32_t>(densify_filter_outputs.data(),
densify_filter_outputs.size())));
}
if (quantized_filter_tensor_id >= 0) {
const std::array<int32_t, 1> dequantize_filter_inputs{
{quantized_filter_tensor_id}};
const std::array<int32_t, 1> dequantize_filter_outputs{{filter_tensor_id}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/dequantize_operator_code,
builder.CreateVector<int32_t>(dequantize_filter_inputs.data(),
dequantize_filter_inputs.size()),
builder.CreateVector<int32_t>(dequantize_filter_outputs.data(),
dequantize_filter_outputs.size())));
}
if (quantized_bias_tensor_id >= 0) {
const std::array<int32_t, 1> dequantize_bias_inputs{
{quantized_bias_tensor_id}};
const std::array<int32_t, 1> dequantize_bias_outputs{{bias_tensor_id}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/dequantize_operator_code,
builder.CreateVector<int32_t>(dequantize_bias_inputs.data(),
dequantize_bias_inputs.size()),
builder.CreateVector<int32_t>(dequantize_bias_outputs.data(),
dequantize_bias_outputs.size())));
}
const std::array<int32_t, 3> op_inputs{
{input_tensor_id, filter_tensor_id, bias_tensor_id}};
const std::array<int32_t, 1> op_outputs{{output_tensor_id}};
const flatbuffers::Offset<DepthwiseConv2DOptions> depthwise_conv2d_options =
CreateDepthwiseConv2DOptions(
builder, Padding(), StrideWidth(), StrideHeight(), DepthMultiplier(),
Activation(), DilationWidth(), DilationHeight());
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_DepthwiseConv2DOptions, depthwise_conv2d_options.Union()));
/****************************** Define subgraph *****************************/
const std::array<int32_t, 1> subgraph_inputs{{input_tensor_id}};
const std::array<int32_t, 1> subgraph_outputs{{output_tensor_id}};
const flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(operators.data(), operators.size()));
const flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("DepthwiseConv2D model");
const flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,274 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_DEPTHWISE_CONV_2D_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_DEPTHWISE_CONV_2D_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class DepthwiseConv2DTester : public ModelCache<DepthwiseConv2DTester> {
public:
enum class WeightsType {
kFP32,
kFP16,
kTensorWiseQuantizedInt8,
kChannelWiseQuantizedInt8,
};
enum class BiasType {
kFP32,
kFP16,
};
DepthwiseConv2DTester() = default;
DepthwiseConv2DTester(const DepthwiseConv2DTester&) = delete;
DepthwiseConv2DTester& operator=(const DepthwiseConv2DTester&) = delete;
inline DepthwiseConv2DTester& BatchSize(int32_t batch_size) {
EXPECT_GT(batch_size, 0);
batch_size_ = batch_size;
return *this;
}
inline int32_t BatchSize() const { return batch_size_; }
inline DepthwiseConv2DTester& InputChannels(int32_t input_channels) {
EXPECT_GT(input_channels, 0);
input_channels_ = input_channels;
return *this;
}
inline int32_t InputChannels() const { return input_channels_; }
inline DepthwiseConv2DTester& DepthMultiplier(int32_t depth_multiplier) {
EXPECT_GT(depth_multiplier, 0);
depth_multiplier_ = depth_multiplier;
return *this;
}
inline int32_t DepthMultiplier() const { return depth_multiplier_; }
inline int32_t OutputChannels() const {
return DepthMultiplier() * InputChannels();
}
inline DepthwiseConv2DTester& InputHeight(int32_t input_height) {
EXPECT_GT(input_height, 0);
input_height_ = input_height;
return *this;
}
inline int32_t InputHeight() const { return input_height_; }
inline DepthwiseConv2DTester& InputWidth(int32_t input_width) {
EXPECT_GT(input_width, 0);
input_width_ = input_width;
return *this;
}
inline int32_t InputWidth() const { return input_width_; }
inline int32_t OutputWidth() const {
if (Padding() == ::tflite::Padding_SAME) {
EXPECT_GE(InputWidth(), 1);
return (InputWidth() - 1) / StrideWidth() + 1;
} else {
EXPECT_GE(InputWidth(), DilatedKernelWidth());
return 1 + (InputWidth() - DilatedKernelWidth()) / StrideWidth();
}
}
inline int32_t OutputHeight() const {
if (Padding() == ::tflite::Padding_SAME) {
EXPECT_GE(InputHeight(), 1);
return (InputHeight() - 1) / StrideHeight() + 1;
} else {
EXPECT_GE(InputHeight(), DilatedKernelHeight());
return 1 + (InputHeight() - DilatedKernelHeight()) / StrideHeight();
}
}
inline DepthwiseConv2DTester& KernelHeight(int32_t kernel_height) {
EXPECT_GT(kernel_height, 0);
kernel_height_ = kernel_height;
return *this;
}
inline int32_t KernelHeight() const { return kernel_height_; }
inline DepthwiseConv2DTester& KernelWidth(int32_t kernel_width) {
EXPECT_GT(kernel_width, 0);
kernel_width_ = kernel_width;
return *this;
}
inline int32_t KernelWidth() const { return kernel_width_; }
inline DepthwiseConv2DTester& StrideHeight(int32_t stride_height) {
EXPECT_GT(stride_height, 0);
stride_height_ = stride_height;
return *this;
}
inline int32_t StrideHeight() const { return stride_height_; }
inline DepthwiseConv2DTester& StrideWidth(int32_t stride_width) {
EXPECT_GT(stride_width, 0);
stride_width_ = stride_width;
return *this;
}
inline int32_t StrideWidth() const { return stride_width_; }
inline DepthwiseConv2DTester& DilationHeight(int32_t dilation_height) {
EXPECT_GT(dilation_height, 0);
dilation_height_ = dilation_height;
return *this;
}
inline int32_t DilationHeight() const { return dilation_height_; }
inline DepthwiseConv2DTester& DilationWidth(int32_t dilation_width) {
EXPECT_GT(dilation_width, 0);
dilation_width_ = dilation_width;
return *this;
}
inline int32_t DilationWidth() const { return dilation_width_; }
inline int32_t DilatedKernelHeight() const {
return (KernelHeight() - 1) * DilationHeight() + 1;
}
inline int32_t DilatedKernelWidth() const {
return (KernelWidth() - 1) * DilationWidth() + 1;
}
inline DepthwiseConv2DTester& FP16Weights() {
weights_type_ = WeightsType::kFP16;
bias_type_ = BiasType::kFP16;
return *this;
}
inline DepthwiseConv2DTester& TensorWiseQuantizedInt8Weights() {
weights_type_ = WeightsType::kTensorWiseQuantizedInt8;
// Bias is stored in FP32 even when filter is quantized to INT8
bias_type_ = BiasType::kFP32;
return *this;
}
inline DepthwiseConv2DTester& ChannelWiseQuantizedInt8Weights() {
weights_type_ = WeightsType::kChannelWiseQuantizedInt8;
// Bias is stored in FP32 even when filter is quantized to INT8
bias_type_ = BiasType::kFP32;
return *this;
}
inline DepthwiseConv2DTester& SparseWeights() {
sparse_weights_ = true;
return *this;
}
inline bool SparseWeights() const { return sparse_weights_; }
inline DepthwiseConv2DTester& SamePadding() {
padding_ = ::tflite::Padding_SAME;
return *this;
}
inline DepthwiseConv2DTester& ValidPadding() {
padding_ = ::tflite::Padding_VALID;
return *this;
}
inline DepthwiseConv2DTester& ReluActivation() {
activation_ = ::tflite::ActivationFunctionType_RELU;
return *this;
}
inline DepthwiseConv2DTester& Relu6Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU6;
return *this;
}
inline DepthwiseConv2DTester& ReluMinus1To1Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU_N1_TO_1;
return *this;
}
inline DepthwiseConv2DTester& TanhActivation() {
activation_ = ::tflite::ActivationFunctionType_TANH;
return *this;
}
inline DepthwiseConv2DTester& SignBitActivation() {
activation_ = ::tflite::ActivationFunctionType_SIGN_BIT;
return *this;
}
inline DepthwiseConv2DTester& WeightsCache(
TfLiteXNNPackDelegateWeightsCache* weights_cache) {
weights_cache_ = weights_cache;
return *this;
}
void Test(TfLiteDelegate* delegate);
private:
std::vector<char> CreateTfLiteModel() const override;
inline WeightsType WeightsType() const { return weights_type_; }
inline BiasType BiasType() const { return bias_type_; }
inline ::tflite::Padding Padding() const { return padding_; }
inline ::tflite::ActivationFunctionType Activation() const {
return activation_;
}
int32_t batch_size_ = 1;
int32_t input_channels_ = 1;
int32_t depth_multiplier_ = 1;
int32_t input_height_ = 1;
int32_t input_width_ = 1;
int32_t kernel_height_ = 1;
int32_t kernel_width_ = 1;
int32_t stride_height_ = 1;
int32_t stride_width_ = 1;
int32_t dilation_height_ = 1;
int32_t dilation_width_ = 1;
enum WeightsType weights_type_ { WeightsType::kFP32 };
enum BiasType bias_type_ { BiasType::kFP32 };
bool sparse_weights_ = false;
::tflite::Padding padding_ = ::tflite::Padding_VALID;
::tflite::ActivationFunctionType activation_ =
::tflite::ActivationFunctionType_NONE;
TfLiteXNNPackDelegateWeightsCache* weights_cache_ = nullptr;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_DEPTHWISE_CONV_2D_TESTER_H_
@@ -0,0 +1,174 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/dequantize_tester.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
template <class T>
void DequantizeTester::Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
std::uniform_int_distribution<int32_t> input_distribution(
std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
auto input_rng = std::bind(input_distribution, std::ref(rng));
T* default_input_data = default_interpreter->typed_input_tensor<T>(0);
std::generate_n(default_input_data, ComputeSize(Shape()),
std::ref(input_rng));
T* delegate_input_data = delegate_interpreter->typed_input_tensor<T>(0);
std::copy_n(default_input_data, ComputeSize(Shape()), delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* delegate_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
for (size_t i = 0; i < ComputeSize(Shape()); i++) {
ASSERT_EQ(default_output_data[i], delegate_output_data[i])
<< " at index " << i << " / " << ComputeSize(Shape());
}
}
void DequantizeTester::Test(TfLiteDelegate* delegate) const {
std::vector<char> buffer = CreateTfLiteModel();
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (Unsigned()) {
Test<uint8_t>(delegate_interpreter.get(), default_interpreter.get());
} else {
Test<int8_t>(delegate_interpreter.get(), default_interpreter.get());
}
}
std::vector<char> DequantizeTester::CreateTfLiteModel() const {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<OperatorCode> operator_code =
CreateOperatorCode(builder, BuiltinOperator_DEQUANTIZE);
const std::array<flatbuffers::Offset<Buffer>, 1> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
}};
const std::array<flatbuffers::Offset<Tensor>, 2> tensors{{
CreateTensor(
builder,
builder.CreateVector<int32_t>(Shape().data(), Shape().size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({InputScale()}),
builder.CreateVector<int64_t>({InputZeroPoint()}))),
CreateTensor(
builder,
builder.CreateVector<int32_t>(Shape().data(), Shape().size()),
TensorType_FLOAT32),
}};
const std::array<int32_t, 1> op_inputs{{0}};
const std::array<int32_t, 1> op_outputs{{1}};
flatbuffers::Offset<Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()));
const std::array<int32_t, 1> subgraph_inputs{{0}};
const std::array<int32_t, 1> subgraph_outputs{{1}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Dequantize operator model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t DequantizeTester::ComputeSize(const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,92 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_DEQUANTIZE_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_DEQUANTIZE_TESTER_H_
#include <cstdint>
#include <initializer_list>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class DequantizeTester {
public:
DequantizeTester() = default;
DequantizeTester(const DequantizeTester&) = delete;
DequantizeTester& operator=(const DequantizeTester&) = delete;
inline DequantizeTester& Shape(std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
shape_ = std::vector<int32_t>(shape.begin(), shape.end());
size_ = DequantizeTester::ComputeSize(shape_);
return *this;
}
const std::vector<int32_t>& Shape() const { return shape_; }
int32_t Size() const { return size_; }
inline DequantizeTester& InputZeroPoint(int32_t input_zero_point) {
input_zero_point_ = input_zero_point;
return *this;
}
inline int32_t InputZeroPoint() const { return input_zero_point_; }
inline DequantizeTester& InputScale(float input_scale) {
input_scale_ = input_scale;
return *this;
}
inline float InputScale() const { return input_scale_; }
inline DequantizeTester& Unsigned(bool is_unsigned) {
unsigned_ = is_unsigned;
return *this;
}
inline bool Unsigned() const { return unsigned_; }
template <class T>
void Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel() const;
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> shape_;
int32_t size_;
int32_t input_zero_point_ = 0;
float input_scale_ = 1.0f;
bool unsigned_ = false;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_DEQUANTIZE_TESTER_H_
@@ -0,0 +1,623 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/xnnpack/dynamically_quantized_conv_2d_tester.h"
#include "tensorflow/lite/delegates/xnnpack/fingerprint_test_helpers.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
struct DynamicallyQuantizedConv2D : DelegateTest {};
TEST_F(DynamicallyQuantizedConv2D, 3x3) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(3)
.KernelWidth(3)
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, 3x3Stride2) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(3)
.KernelWidth(3)
.StrideHeight(2)
.StrideWidth(2)
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, Grouped) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_per_group_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
auto groups_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 8), std::ref(rng));
auto groups = groups_rng();
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(groups * channel_per_group_rng())
.OutputChannels(groups * channel_per_group_rng())
.Groups(groups)
.KernelHeight(3)
.KernelWidth(3)
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, SmallKernelWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, SmallKernelWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, StrideWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, StrideWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, DilationWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto dilation_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.DilationHeight(dilation_rng())
.DilationWidth(dilation_rng())
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, DilationWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto dilation_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.DilationHeight(dilation_rng())
.DilationWidth(dilation_rng())
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, TensorWiseQuantizedInt8Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, ChannelWiseQuantizedInt8Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, ReluActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluActivation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, Relu6Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Relu6Activation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, ReluMinus1To1Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluMinus1To1Activation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, TanhActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.TanhActivation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, SignBitActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SignBitActivation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
delegate_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_ENABLE_LATEST_OPERATORS;
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, WeightsCache) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
std::unique_ptr<TfLiteXNNPackDelegateWeightsCache,
decltype(&TfLiteXNNPackDelegateWeightsCacheDelete)>
weights_cache(TfLiteXNNPackDelegateWeightsCacheCreate(),
TfLiteXNNPackDelegateWeightsCacheDelete);
delegate_options.weights_cache = weights_cache.get();
delegate_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_ENABLE_LATEST_OPERATORS;
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 15), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.WeightsCache(weights_cache.get())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedConv2D, TransientIndirectionBuffer) {
TfLiteXNNPackDelegateOptions xnnpack_options =
TfLiteXNNPackDelegateOptionsDefault();
xnnpack_options.num_threads = 2;
xnnpack_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_TRANSIENT_INDIRECTION_BUFFER;
xnnpack_options.flags |= TFLITE_XNNPACK_DELEGATE_FLAG_ENABLE_LATEST_OPERATORS;
UseCustomDelegate(xnnpack_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 16), std::ref(rng));
DynamicallyQuantizedConv2DTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,246 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/dynamically_quantized_conv_2d_tester.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
void DynamicallyQuantizedConv2DTester::Test(TfLiteDelegate* delegate) {
const Model* model = GetModel();
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (weights_cache_ != nullptr) {
TfLiteXNNPackDelegateWeightsCacheFinalizeSoft(weights_cache_);
}
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_real_distribution<float>(-10, 10), std::ref(rng));
float* default_input_data = default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
input_rng);
float* delegate_input_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* delegate_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
const int num_output_values =
BatchSize() * OutputHeight() * OutputWidth() * OutputChannels();
int different_output_values = 0;
// TFLite rounds to nearest with ties to Away. XNNPACK rounds to nearest with
// ties to even. IEEE 754 states: "Round to nearest, ties to even" is the
// default for binary floating point and the recommended default for decimal.
// For this reason, many output values may differ slightly.
for (int32_t i = 0; i < BatchSize(); i++) {
for (int32_t y = 0; y < OutputHeight(); y++) {
for (int32_t x = 0; x < OutputWidth(); x++) {
for (int32_t c = 0; c < OutputChannels(); c++) {
const int32_t index = ((i * OutputHeight() + y) * OutputWidth() + x) *
OutputChannels() +
c;
if (std::abs(default_output_data[index] -
delegate_output_data[index]) >
0.005 * std::abs(default_output_data[index])) {
++different_output_values;
}
}
}
}
}
if (different_output_values > 0.05 * num_output_values) {
GTEST_FAIL() << (float)different_output_values / num_output_values * 100.f
<< "% of output values differ";
}
}
std::vector<char> DynamicallyQuantizedConv2DTester::CreateTfLiteModel() const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto filter_rng = std::bind(std::uniform_int_distribution<int32_t>(
-std::numeric_limits<int8_t>::max(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto bias_rng =
std::bind(std::uniform_real_distribution<float>(-10, 10), std::ref(rng));
auto kernel_scale_rng =
std::bind(std::uniform_real_distribution<float>(0.1, 3), std::ref(rng));
flatbuffers::FlatBufferBuilder builder;
const std::array<flatbuffers::Offset<OperatorCode>, 1> operator_codes{
{CreateOperatorCode(builder, BuiltinOperator_CONV_2D)}};
std::vector<flatbuffers::Offset<Operator>> operators;
std::vector<int8_t> filter_data(OutputChannels() * KernelHeight() *
KernelWidth() * KernelInputChannels());
std::generate(filter_data.begin(), filter_data.end(), std::ref(filter_rng));
std::vector<float> bias_data(OutputChannels());
std::generate(bias_data.begin(), bias_data.end(), std::ref(bias_rng));
std::vector<float> kernel_scale(OutputChannels());
std::generate(kernel_scale.begin(), kernel_scale.end(),
std::ref(kernel_scale_rng));
const std::array<flatbuffers::Offset<Buffer>, 3> buffersq{{
CreateBuffer(builder, builder.CreateVector({})),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(filter_data.data()),
sizeof(int8_t) * filter_data.size())),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(bias_data.data()),
sizeof(float) * bias_data.size())),
}};
const std::array<int32_t, 4> filter_shape{
{OutputChannels(), KernelHeight(), KernelWidth(), KernelInputChannels()}};
const std::array<int32_t, 1> bias_shape{{OutputChannels()}};
const std::array<int32_t, 4> input_shape{
{BatchSize(), InputHeight(), InputWidth(), InputChannels()}};
const std::array<int32_t, 4> output_shape{
{BatchSize(), OutputHeight(), OutputWidth(), OutputChannels()}};
std::vector<flatbuffers::Offset<Tensor>> tensors;
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(input_shape.data(), input_shape.size()),
TensorType_FLOAT32, /*buffer=*/0));
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(filter_shape.data(), filter_shape.size()),
TensorType_INT8, /*buffer=*/1, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
// builder.CreateVector<float>({1}),
// builder.CreateVector<int64_t>({0}))));
builder.CreateVector<float>(kernel_scale),
builder.CreateVector<int64_t>(
std::vector<int64_t>(OutputChannels(), 0)))));
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(bias_shape.data(), bias_shape.size()),
TensorType_FLOAT32, /*buffer=*/2));
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(output_shape.data(), output_shape.size()),
TensorType_FLOAT32));
const flatbuffers::Offset<Conv2DOptions> conv2d_options =
CreateConv2DOptions(builder, Padding(), StrideWidth(), StrideHeight(),
Activation(), DilationWidth(), DilationHeight());
std::vector<int32_t> op_inputs{{static_cast<int32_t>(tensors.size()) - 3,
static_cast<int32_t>(tensors.size()) - 2}};
op_inputs.insert(op_inputs.begin(), static_cast<int32_t>(tensors.size()) - 4);
const std::array<int32_t, 1> op_outputs{
{static_cast<int32_t>(tensors.size()) - 1}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_Conv2DOptions, conv2d_options.Union()));
const std::array<int32_t, 1> subgraph_inputs{
{static_cast<int>(tensors.size()) - 3 - static_cast<int>(1)}};
const std::array<int32_t, 1> subgraph_outputs{
{static_cast<int>(tensors.size()) - 1}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(operators.data(), operators.size()));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Dynamically Quantized Conv2D model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffersq.data(), buffersq.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,247 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_DYNAMICALLY_QUANTIZED_CONV_2D_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_DYNAMICALLY_QUANTIZED_CONV_2D_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class DynamicallyQuantizedConv2DTester
: public ModelCache<DynamicallyQuantizedConv2DTester> {
public:
DynamicallyQuantizedConv2DTester() = default;
DynamicallyQuantizedConv2DTester(const DynamicallyQuantizedConv2DTester&) =
delete;
DynamicallyQuantizedConv2DTester& operator=(
const DynamicallyQuantizedConv2DTester&) = delete;
inline DynamicallyQuantizedConv2DTester& BatchSize(int32_t batch_size) {
EXPECT_GT(batch_size, 0);
batch_size_ = batch_size;
return *this;
}
inline int32_t BatchSize() const { return batch_size_; }
inline DynamicallyQuantizedConv2DTester& InputChannels(
int32_t input_channels) {
EXPECT_GT(input_channels, 0);
input_channels_ = input_channels;
return *this;
}
inline int32_t InputChannels() const { return input_channels_; }
inline DynamicallyQuantizedConv2DTester& OutputChannels(
int32_t output_channels) {
EXPECT_GT(output_channels, 0);
output_channels_ = output_channels;
return *this;
}
inline int32_t OutputChannels() const { return output_channels_; }
inline DynamicallyQuantizedConv2DTester& Groups(int32_t groups) {
EXPECT_EQ(InputChannels() % groups, 0);
EXPECT_EQ(OutputChannels() % groups, 0);
groups_ = groups;
return *this;
}
inline int32_t Groups() const { return groups_; }
inline int32_t KernelInputChannels() const {
return input_channels_ / groups_;
}
inline DynamicallyQuantizedConv2DTester& OutputHeight(int32_t output_height) {
EXPECT_GT(output_height, 0);
output_height_ = output_height;
return *this;
}
inline int32_t OutputHeight() const { return output_height_; }
inline DynamicallyQuantizedConv2DTester& OutputWidth(int32_t output_width) {
EXPECT_GT(output_width, 0);
output_width_ = output_width;
return *this;
}
inline int32_t OutputWidth() const { return output_width_; }
inline int32_t InputWidth() const {
EXPECT_GE(OutputWidth(), 1);
int32_t input_width = (OutputWidth() - 1) * StrideWidth() + 1;
if (Padding() != ::tflite::Padding_SAME) {
input_width += DilatedKernelWidth() - 1;
}
EXPECT_GE(input_width, 1);
return input_width;
}
inline int32_t InputHeight() const {
EXPECT_GE(OutputHeight(), 1);
int32_t input_height = (OutputHeight() - 1) * StrideHeight() + 1;
if (Padding() != ::tflite::Padding_SAME) {
input_height += DilatedKernelHeight() - 1;
}
EXPECT_GE(input_height, 1);
return input_height;
}
inline DynamicallyQuantizedConv2DTester& KernelHeight(int32_t kernel_height) {
EXPECT_GT(kernel_height, 0);
kernel_height_ = kernel_height;
return *this;
}
inline int32_t KernelHeight() const { return kernel_height_; }
inline DynamicallyQuantizedConv2DTester& KernelWidth(int32_t kernel_width) {
EXPECT_GT(kernel_width, 0);
kernel_width_ = kernel_width;
return *this;
}
inline int32_t KernelWidth() const { return kernel_width_; }
inline DynamicallyQuantizedConv2DTester& StrideHeight(int32_t stride_height) {
EXPECT_GT(stride_height, 0);
stride_height_ = stride_height;
return *this;
}
inline int32_t StrideHeight() const { return stride_height_; }
inline DynamicallyQuantizedConv2DTester& StrideWidth(int32_t stride_width) {
EXPECT_GT(stride_width, 0);
stride_width_ = stride_width;
return *this;
}
inline int32_t StrideWidth() const { return stride_width_; }
inline DynamicallyQuantizedConv2DTester& DilationHeight(
int32_t dilation_height) {
EXPECT_GT(dilation_height, 0);
dilation_height_ = dilation_height;
return *this;
}
inline int32_t DilationHeight() const { return dilation_height_; }
inline DynamicallyQuantizedConv2DTester& DilationWidth(
int32_t dilation_width) {
EXPECT_GT(dilation_width, 0);
dilation_width_ = dilation_width;
return *this;
}
inline int32_t DilationWidth() const { return dilation_width_; }
inline int32_t DilatedKernelHeight() const {
return (KernelHeight() - 1) * DilationHeight() + 1;
}
inline int32_t DilatedKernelWidth() const {
return (KernelWidth() - 1) * DilationWidth() + 1;
}
inline DynamicallyQuantizedConv2DTester& SamePadding() {
padding_ = ::tflite::Padding_SAME;
return *this;
}
inline DynamicallyQuantizedConv2DTester& ValidPadding() {
padding_ = ::tflite::Padding_VALID;
return *this;
}
inline DynamicallyQuantizedConv2DTester& ReluActivation() {
activation_ = ::tflite::ActivationFunctionType_RELU;
return *this;
}
inline DynamicallyQuantizedConv2DTester& Relu6Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU6;
return *this;
}
inline DynamicallyQuantizedConv2DTester& ReluMinus1To1Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU_N1_TO_1;
return *this;
}
inline DynamicallyQuantizedConv2DTester& TanhActivation() {
activation_ = ::tflite::ActivationFunctionType_TANH;
return *this;
}
inline DynamicallyQuantizedConv2DTester& SignBitActivation() {
activation_ = ::tflite::ActivationFunctionType_SIGN_BIT;
return *this;
}
inline DynamicallyQuantizedConv2DTester& WeightsCache(
TfLiteXNNPackDelegateWeightsCache* weights_cache) {
weights_cache_ = weights_cache;
return *this;
}
void Test(TfLiteDelegate* delegate);
std::vector<char> CreateTfLiteModel() const override;
private:
inline ::tflite::Padding Padding() const { return padding_; }
inline ::tflite::ActivationFunctionType Activation() const {
return activation_;
}
int32_t batch_size_ = 1;
int32_t input_channels_ = 1;
int32_t output_channels_ = 1;
int32_t groups_ = 1;
int32_t output_height_ = 1;
int32_t output_width_ = 1;
int32_t kernel_height_ = 1;
int32_t kernel_width_ = 1;
int32_t stride_height_ = 1;
int32_t stride_width_ = 1;
int32_t dilation_height_ = 1;
int32_t dilation_width_ = 1;
::tflite::Padding padding_ = ::tflite::Padding_VALID;
::tflite::ActivationFunctionType activation_ =
::tflite::ActivationFunctionType_NONE;
TfLiteXNNPackDelegateWeightsCache* weights_cache_ = nullptr;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_DYNAMICALLY_QUANTIZED_CONV_2D_TESTER_H_
@@ -0,0 +1,420 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cassert>
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <string>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/xnnpack/dynamically_quantized_fully_connected_tester.h"
#include "tensorflow/lite/delegates/xnnpack/fingerprint_test_helpers.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
// Dummy class to use with parameterized test.
class DynamicallyQuantizedFullyConnectedTest
: public testing::WithParamInterface<WeightsType>,
public DelegateTest {};
int GenInputChannels(const std::function<int()>& rng,
WeightsType weights_type) {
switch (weights_type) {
case WeightsType::kChannelWiseQuantizedInt8:
case WeightsType::kTensorWiseQuantizedInt8:
return rng();
case WeightsType::kChannelWiseQuantizedInt4:
// Int4 quantized kernels only support even number of channels.
return (rng() / 2) * 2;
}
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, 1D) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.KeepDims(true)
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, 2D) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, 2DKeepDims) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.KeepDims(true)
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, 3D) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, width, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, 3DReshape) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, width, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.WeightsType(GetParam())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, 3DKeepDims) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, width, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.KeepDims(true)
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, 4D) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, height, width, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, 4DKeepDims) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, height, width, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.KeepDims(true)
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, NoBias) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.NoBias()
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, ReluActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.ReluActivation()
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, Relu6Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.Relu6Activation()
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, ReluMinus1To1Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.ReluMinus1To1Activation()
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_P(DynamicallyQuantizedFullyConnectedTest, WeightsCache) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_ENABLE_LATEST_OPERATORS;
std::unique_ptr<TfLiteXNNPackDelegateWeightsCache,
decltype(&TfLiteXNNPackDelegateWeightsCacheDelete)>
weights_cache(TfLiteXNNPackDelegateWeightsCacheCreate(),
TfLiteXNNPackDelegateWeightsCacheDelete);
delegate_options.weights_cache = weights_cache.get();
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
WeightsType weights_type = GetParam();
const auto input_channels = GenInputChannels(channels_rng, weights_type);
const auto output_channels = channels_rng();
DynamicallyQuantizedFullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.WeightsCache(weights_cache.get())
.WeightsType(weights_type)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
// Returns a human readable string representation of the test parameter.
std::string TestParamToString(testing::TestParamInfo<WeightsType> param) {
switch (param.param) {
case WeightsType::kChannelWiseQuantizedInt4:
return "ChannelWiseQuantizedInt4";
case WeightsType::kChannelWiseQuantizedInt8:
return "ChannelWiseQuantizedInt8";
case WeightsType::kTensorWiseQuantizedInt8:
return "TensorWiseQuantizedInt8";
default:
assert(false);
return "???";
}
}
INSTANTIATE_TEST_SUITE_P(
DynamicallyQuantizedFullyConnectedTest,
DynamicallyQuantizedFullyConnectedTest,
testing::Values(WeightsType::kTensorWiseQuantizedInt8,
WeightsType::kChannelWiseQuantizedInt4,
WeightsType::kChannelWiseQuantizedInt8),
TestParamToString);
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,294 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/dynamically_quantized_fully_connected_tester.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
std::vector<int32_t> DynamicallyQuantizedFullyConnectedTester::OutputShape()
const {
EXPECT_NE(input_shape_.size(), 0);
if (KeepDims()) {
std::vector<int32_t> output_shape(input_shape_.cbegin(),
input_shape_.cend() - 1);
output_shape.push_back(OutputChannels());
return output_shape;
} else {
EXPECT_EQ(InputSize() % InputChannels(), 0);
return std::vector<int32_t>(
{InputSize() / InputChannels(), OutputChannels()});
}
}
void DynamicallyQuantizedFullyConnectedTester::Test(
Interpreter* delegate_interpreter, Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_real_distribution<float>(-10, 10), std::ref(rng));
float* default_input_data = default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input_data, InputSize(), std::ref(input_rng));
float* delegate_input_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input_data, InputSize(), delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* delegate_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
const int num_output_values = ComputeSize(OutputShape());
int different_output_values = 0;
// TFLite rounds to nearest with ties to Away. XNNPACK rounds to nearest with
// ties to even. IEEE 754 states: "Round to nearest, ties to even" is the
// default for binary floating point and the recommended default for decimal.
// For this reason, many output values may differ slightly.
for (size_t i = 0; i < num_output_values; i++) {
if (std::abs(default_output_data[i] - delegate_output_data[i]) >
0.005 * std::abs(default_output_data[i])) {
++different_output_values;
}
}
if (different_output_values > 0.05 * num_output_values) {
GTEST_FAIL() << (float)different_output_values / num_output_values * 100.f
<< "% of output values differ";
}
}
void DynamicallyQuantizedFullyConnectedTester::Test(TfLiteDelegate* delegate) {
const Model* model = GetModel();
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (weights_cache_ != nullptr) {
TfLiteXNNPackDelegateWeightsCacheFinalizeSoft(weights_cache_);
}
Test(delegate_interpreter.get(), default_interpreter.get());
}
std::vector<char> DynamicallyQuantizedFullyConnectedTester::CreateTfLiteModel()
const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto filter_rng = std::bind(std::uniform_int_distribution<int32_t>(
-std::numeric_limits<int8_t>::max(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto bias_rng =
std::bind(std::uniform_real_distribution<float>(-10, 10), std::ref(rng));
flatbuffers::FlatBufferBuilder builder;
/*************************** Define operator codes **************************/
const std::array<flatbuffers::Offset<OperatorCode>, 1> operator_codes{
{CreateOperatorCode(builder, BuiltinOperator_FULLY_CONNECTED)}};
std::vector<flatbuffers::Offset<Operator>> operators;
/*********************** Generate filter and bias data **********************/
int filter_size_bytes = -1;
switch (WeightsType()) {
case WeightsType::kChannelWiseQuantizedInt4: {
filter_size_bytes = (InputChannels() * OutputChannels() + 1) / 2;
break;
}
case WeightsType::kChannelWiseQuantizedInt8:
case WeightsType::kTensorWiseQuantizedInt8: {
filter_size_bytes = InputChannels() * OutputChannels();
break;
}
}
std::vector<int8_t> filter_data(filter_size_bytes);
std::generate(filter_data.begin(), filter_data.end(), std::ref(filter_rng));
std::vector<float> bias_data(OutputChannels());
std::generate(bias_data.begin(), bias_data.end(), std::ref(bias_rng));
/****************************** Define buffers ******************************/
const std::array<flatbuffers::Offset<Buffer>, 3> buffersq{{
CreateBuffer(builder, builder.CreateVector({})),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(filter_data.data()),
sizeof(int8_t) * filter_data.size())),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(bias_data.data()),
sizeof(float) * bias_data.size())),
}};
/****************************** Define tensors ******************************/
const std::array<int32_t, 2> filter_shape{
{OutputChannels(), InputChannels()}};
const std::array<int32_t, 1> bias_shape{{OutputChannels()}};
const std::vector<int32_t> output_shape = OutputShape();
std::vector<flatbuffers::Offset<Tensor>> tensors;
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(InputShape().data(), InputShape().size()),
TensorType_FLOAT32, /*buffer=*/0));
tflite::TensorType filter_tensor_type;
std::vector<float> filter_scale;
std::vector<int64_t> filter_zero_point;
switch (WeightsType()) {
case WeightsType::kChannelWiseQuantizedInt4:
filter_tensor_type = tflite::TensorType_INT4;
filter_scale.assign(OutputChannels(), FilterScale());
filter_zero_point.assign(OutputChannels(), 0);
break;
case WeightsType::kChannelWiseQuantizedInt8:
filter_tensor_type = tflite::TensorType_INT8;
filter_scale.assign(OutputChannels(), FilterScale());
filter_zero_point.assign(OutputChannels(), 0);
break;
case WeightsType::kTensorWiseQuantizedInt8: {
filter_tensor_type = tflite::TensorType_INT8;
filter_scale = {FilterScale()};
filter_zero_point = {0};
break;
}
}
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(filter_shape.data(), filter_shape.size()),
filter_tensor_type, /*buffer=*/1, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>(filter_scale),
builder.CreateVector<int64_t>(filter_zero_point))));
if (HasBias()) {
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(bias_shape.data(), bias_shape.size()),
TensorType_FLOAT32, /*buffer=*/2));
}
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(output_shape.data(), output_shape.size()),
TensorType_FLOAT32));
/***************************** Define operators *****************************/
flatbuffers::Offset<FullyConnectedOptions> fully_connected_options =
CreateFullyConnectedOptions(
builder, Activation(), FullyConnectedOptionsWeightsFormat_DEFAULT,
KeepDims(), /*asymmetric_quantize_inputs=*/true);
std::vector<int32_t> op_inputs{{static_cast<int32_t>(tensors.size()) - 3,
static_cast<int32_t>(tensors.size()) - 2}};
if (HasBias()) {
op_inputs.insert(op_inputs.begin(),
static_cast<int32_t>(tensors.size()) - 4);
}
const std::array<int32_t, 1> op_outputs{
{static_cast<int32_t>(tensors.size()) - 1}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_FullyConnectedOptions, fully_connected_options.Union()));
/****************************** Define subgraph *****************************/
const std::array<int32_t, 1> subgraph_inputs{
{static_cast<int>(tensors.size()) - 3 - static_cast<int>(HasBias())}};
const std::array<int32_t, 1> subgraph_outputs{
{static_cast<int>(tensors.size()) - 1}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(operators.data(), operators.size()));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Fully Connected model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffersq.data(), buffersq.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t DynamicallyQuantizedFullyConnectedTester::ComputeSize(
const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,178 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_DYNAMICALLY_QUANTIZED_FULLY_CONNECTED_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_DYNAMICALLY_QUANTIZED_FULLY_CONNECTED_TESTER_H_
#include <cstdint>
#include <initializer_list>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
enum class WeightsType {
kChannelWiseQuantizedInt4,
kChannelWiseQuantizedInt8,
kTensorWiseQuantizedInt8,
};
class DynamicallyQuantizedFullyConnectedTester
: public ModelCache<DynamicallyQuantizedFullyConnectedTester> {
public:
DynamicallyQuantizedFullyConnectedTester() = default;
DynamicallyQuantizedFullyConnectedTester(
const DynamicallyQuantizedFullyConnectedTester&) = delete;
DynamicallyQuantizedFullyConnectedTester& operator=(
const DynamicallyQuantizedFullyConnectedTester&) = delete;
inline DynamicallyQuantizedFullyConnectedTester& InputShape(
std::initializer_list<int32_t> shape) {
EXPECT_THAT(shape, testing::Each(testing::Gt(0)));
input_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
input_size_ = ComputeSize(input_shape_);
return *this;
}
inline const std::vector<int32_t>& InputShape() const { return input_shape_; }
inline int32_t InputSize() const { return input_size_; }
inline DynamicallyQuantizedFullyConnectedTester& InputChannels(
int32_t input_channels) {
EXPECT_GT(input_channels, 0);
input_channels_ = input_channels;
return *this;
}
inline int32_t InputChannels() const { return input_channels_; }
inline DynamicallyQuantizedFullyConnectedTester& OutputChannels(
int32_t output_channels) {
EXPECT_GT(output_channels, 0);
output_channels_ = output_channels;
return *this;
}
inline int32_t OutputChannels() const { return output_channels_; }
std::vector<int32_t> OutputShape() const;
inline DynamicallyQuantizedFullyConnectedTester& WeightsType(
WeightsType weights_type) {
weights_type_ = weights_type;
return *this;
}
inline DynamicallyQuantizedFullyConnectedTester& FilterZeroPoint(
int32_t filter_zero_point) {
filter_zero_point_ = filter_zero_point;
return *this;
}
inline int32_t FilterZeroPoint() const { return filter_zero_point_; }
inline DynamicallyQuantizedFullyConnectedTester& FilterScale(
float filter_scale) {
filter_scale_ = filter_scale;
return *this;
}
inline float FilterScale() const { return filter_scale_; }
inline DynamicallyQuantizedFullyConnectedTester& KeepDims(bool keep_dims) {
keep_dims_ = keep_dims;
return *this;
}
inline bool KeepDims() const { return keep_dims_; }
inline DynamicallyQuantizedFullyConnectedTester& NoBias() {
has_bias_ = false;
return *this;
}
inline DynamicallyQuantizedFullyConnectedTester& WithBias() {
has_bias_ = true;
return *this;
}
inline DynamicallyQuantizedFullyConnectedTester& ReluActivation() {
activation_ = ::tflite::ActivationFunctionType_RELU;
return *this;
}
inline DynamicallyQuantizedFullyConnectedTester& Relu6Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU6;
return *this;
}
inline DynamicallyQuantizedFullyConnectedTester& ReluMinus1To1Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU_N1_TO_1;
return *this;
}
inline DynamicallyQuantizedFullyConnectedTester& WeightsCache(
TfLiteXNNPackDelegateWeightsCache* weights_cache) {
weights_cache_ = weights_cache;
return *this;
}
void Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(TfLiteDelegate* delegate);
private:
std::vector<char> CreateTfLiteModel() const override;
inline bool HasBias() const { return has_bias_; }
inline ::tflite::ActivationFunctionType Activation() const {
return activation_;
}
inline enum WeightsType WeightsType() const { return weights_type_; }
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> input_shape_;
int32_t input_size_ = 1;
int32_t input_channels_ = 1;
int32_t output_channels_ = 1;
enum WeightsType weights_type_ = WeightsType::kTensorWiseQuantizedInt8;
int32_t filter_zero_point_ = 0;
float filter_scale_ = 0.75f;
bool keep_dims_ = false;
bool has_bias_ = true;
::tflite::ActivationFunctionType activation_ =
::tflite::ActivationFunctionType_NONE;
TfLiteXNNPackDelegateWeightsCache* weights_cache_ = nullptr;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_DYNAMICALLY_QUANTIZED_FULLY_CONNECTED_TESTER_H_
@@ -0,0 +1,321 @@
/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/xnnpack/dynamically_quantized_transpose_conv_tester.h"
#include "tensorflow/lite/delegates/xnnpack/fingerprint_test_helpers.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
struct DynamicallyQuantizedTransposeConvTest : DelegateTest {};
TEST_F(DynamicallyQuantizedTransposeConvTest, 2x2Stride2) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
DynamicallyQuantizedTransposeConvTester tester;
tester.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(2)
.KernelWidth(2)
.StrideHeight(2)
.StrideWidth(2)
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedTransposeConvTest, 3x3Stride2) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
DynamicallyQuantizedTransposeConvTester tester;
tester.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(3)
.KernelWidth(3)
.StrideHeight(2)
.StrideWidth(2)
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedTransposeConvTest, 4x4Stride2) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
DynamicallyQuantizedTransposeConvTester tester;
tester.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(4)
.KernelWidth(4)
.StrideHeight(2)
.StrideWidth(2)
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedTransposeConvTest, 4x4Stride4) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
DynamicallyQuantizedTransposeConvTester tester;
tester.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(4)
.KernelWidth(4)
.StrideHeight(4)
.StrideWidth(4)
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedTransposeConvTest, SmallKernelWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
DynamicallyQuantizedTransposeConvTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedTransposeConvTest, SmallKernelWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
DynamicallyQuantizedTransposeConvTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedTransposeConvTest, StrideWithSamePadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
DynamicallyQuantizedTransposeConvTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedTransposeConvTest, StrideWithValidPadding) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
DynamicallyQuantizedTransposeConvTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ValidPadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedTransposeConvTest, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
DynamicallyQuantizedTransposeConvTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SamePadding()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(DynamicallyQuantizedTransposeConvTest, WeightsCache) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
std::unique_ptr<TfLiteXNNPackDelegateWeightsCache,
decltype(&TfLiteXNNPackDelegateWeightsCacheDelete)>
weights_cache(TfLiteXNNPackDelegateWeightsCacheCreate(),
TfLiteXNNPackDelegateWeightsCacheDelete);
delegate_options.weights_cache = weights_cache.get();
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto output_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto kernel_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
DynamicallyQuantizedTransposeConvTester tester;
tester.BatchSize(batch_rng())
.OutputHeight(output_rng())
.OutputWidth(output_rng())
.InputChannels(channel_rng())
.OutputChannels(channel_rng())
.KernelHeight(kernel_rng())
.KernelWidth(kernel_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SamePadding()
.WeightsCache(weights_cache.get())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,295 @@
/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/dynamically_quantized_transpose_conv_tester.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
void DynamicallyQuantizedTransposeConvTester::Test(TfLiteDelegate* delegate) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto f32rng = std::bind(std::uniform_real_distribution<float>(-10, 10), rng);
const Model* model = GetModel();
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (weights_cache_ != nullptr) {
TfLiteXNNPackDelegateWeightsCacheFinalizeSoft(weights_cache_);
}
const int input_data_size =
BatchSize() * InputHeight() * InputWidth() * InputChannels();
float* default_input_data = default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input_data, input_data_size, std::ref(f32rng));
float* xnnpack_input_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input_data, input_data_size, xnnpack_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data = default_interpreter->typed_tensor<float>(
default_interpreter->outputs()[0]);
float* xnnpack_output_data = delegate_interpreter->typed_tensor<float>(
delegate_interpreter->outputs()[0]);
const int num_output_values =
BatchSize() * OutputHeight() * OutputWidth() * OutputChannels();
int different_output_values = 0;
for (size_t i = 0; i < num_output_values; i++) {
if (std::abs(default_output_data[i] - xnnpack_output_data[i]) >
0.005 * std::abs(default_output_data[i])) {
++different_output_values;
}
}
if (different_output_values > 0.05 * num_output_values) {
GTEST_FAIL() << (float)different_output_values / num_output_values * 100.f
<< "% of output values differ";
}
}
std::vector<int8_t>
DynamicallyQuantizedTransposeConvTester::GenerateKernelData() const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto range_rng = std::bind(std::uniform_int_distribution<int32_t>(
std::numeric_limits<int8_t>::min(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
std::vector<int8_t> filter_data(OutputChannels() * KernelHeight() *
KernelWidth() * InputChannels());
for (int32_t oc = 0; oc < OutputChannels(); oc++) {
// Use the same range of all-positive or all-negative values to generate
// all weights within the same output channel, but different ranges for
// different output channels. This ensures that no catastrophic
// cancellation occur, but test covers both positive and negative
// inputs.
const int32_t range = range_rng();
const auto value_dist = std::uniform_int_distribution<int32_t>(
std::min(range, 0), std::max(range, 0));
auto value_rng = std::bind(value_dist, std::ref(rng));
for (int32_t ic = 0; ic < InputChannels(); ic++) {
for (int32_t y = 0; y < KernelHeight(); y++) {
for (int32_t x = 0; x < KernelWidth(); x++) {
const int32_t index =
((oc * KernelHeight() + y) * KernelWidth() + x) *
InputChannels() +
ic;
filter_data[index] = value_rng();
}
}
}
}
return filter_data;
}
std::vector<float> DynamicallyQuantizedTransposeConvTester::GenerateBiasData()
const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto bias_rng =
std::bind(std::uniform_real_distribution<float>(-10, 10), std::ref(rng));
std::vector<float> bias_data(OutputChannels());
std::generate(bias_data.begin(), bias_data.end(), std::ref(bias_rng));
return bias_data;
}
std::vector<float>
DynamicallyQuantizedTransposeConvTester::GenerateKernelScaleData() const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto kernel_scale_rng =
std::bind(std::uniform_real_distribution<float>(0.1, 3), std::ref(rng));
std::vector<float> kernel_scale(OutputChannels());
std::generate(kernel_scale.begin(), kernel_scale.end(),
std::ref(kernel_scale_rng));
return kernel_scale;
}
std::vector<char> DynamicallyQuantizedTransposeConvTester::CreateTfLiteModel()
const {
const std::vector<int8_t> filter_data = GenerateKernelData();
const std::vector<float> bias_data = GenerateBiasData();
const std::vector<float> kernel_scale = GenerateKernelScaleData();
/*************************** Define operator codes **************************/
flatbuffers::FlatBufferBuilder builder;
std::vector<flatbuffers::Offset<OperatorCode>> operator_codes{
{CreateOperatorCode(builder, BuiltinOperator_TRANSPOSE_CONV)}};
/****************************** Define buffers ******************************/
std::vector<flatbuffers::Offset<tflite::Buffer>> buffers{
{CreateBuffer(builder, builder.CreateVector({}))}};
const int filter_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(reinterpret_cast<const uint8_t*>(filter_data.data()),
sizeof(int8_t) * filter_data.size())));
const int bias_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(reinterpret_cast<const uint8_t*>(bias_data.data()),
sizeof(float) * bias_data.size())));
const std::array<int32_t, 4> output_shape{
{BatchSize(), OutputHeight(), OutputWidth(), OutputChannels()}};
const int output_shape_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(output_shape.data()),
sizeof(int32_t) * output_shape.size())));
/****************************** Define tensors ******************************/
const std::vector<int32_t> filter_shape = {OutputChannels(), KernelHeight(),
KernelWidth(), InputChannels()};
const std::vector<int32_t> bias_shape = {OutputChannels()};
std::vector<flatbuffers::Offset<tflite::Tensor>> tensors;
const int input_tensor_id = tensors.size();
const std::array<int32_t, 4> input_shape{
{BatchSize(), InputHeight(), InputWidth(), InputChannels()}};
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(input_shape.data(), input_shape.size()),
TensorType_FLOAT32));
const int filter_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(filter_shape.data(), filter_shape.size()),
TensorType_INT8,
/*buffer=*/filter_buffer_id, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>(kernel_scale),
builder.CreateVector<int64_t>(
std::vector<int64_t>(OutputChannels(), 0)))));
const int bias_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(bias_shape.data(), bias_shape.size()),
TensorType_FLOAT32, bias_buffer_id));
const int output_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(output_shape.data(), output_shape.size()),
TensorType_FLOAT32));
const int output_shape_tensor_id = tensors.size();
const std::array<int32_t, 1> output_shape_shape{{4}};
tensors.emplace_back(
CreateTensor(builder,
builder.CreateVector<int32_t>(output_shape_shape.data(),
output_shape_shape.size()),
TensorType_INT32, output_shape_buffer_id));
/***************************** Define operators *****************************/
std::vector<flatbuffers::Offset<tflite::Operator>> operators;
std::vector<int32_t> op_inputs{{output_shape_tensor_id, filter_tensor_id,
input_tensor_id, bias_tensor_id}};
const std::array<int32_t, 1> op_outputs{{output_tensor_id}};
const flatbuffers::Offset<TransposeConvOptions> transpose_conv_options =
CreateTransposeConvOptions(builder, Padding(), StrideWidth(),
StrideHeight());
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_TransposeConvOptions, transpose_conv_options.Union()));
/****************************** Define subgraph *****************************/
const std::array<int32_t, 1> subgraph_inputs{{input_tensor_id}};
const std::array<int32_t, 1> subgraph_outputs{{output_tensor_id}};
const flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(operators.data(), operators.size()));
const flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Dynamically Quantized Transpose Conv2D model");
const flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,210 @@
/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_DYNAMICALLY_QUANTIZED_TRANSPOSE_CONV_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_DYNAMICALLY_QUANTIZED_TRANSPOSE_CONV_TESTER_H_
#include <cassert>
#include <cstdint>
#include <functional>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class DynamicallyQuantizedTransposeConvTester
: public ModelCache<DynamicallyQuantizedTransposeConvTester> {
public:
DynamicallyQuantizedTransposeConvTester() = default;
DynamicallyQuantizedTransposeConvTester(
const DynamicallyQuantizedTransposeConvTester&) = delete;
DynamicallyQuantizedTransposeConvTester& operator=(
const DynamicallyQuantizedTransposeConvTester&) = delete;
inline DynamicallyQuantizedTransposeConvTester& BatchSize(
int32_t batch_size) {
EXPECT_GT(batch_size, 0);
batch_size_ = batch_size;
return *this;
}
inline int32_t BatchSize() const { return batch_size_; }
inline DynamicallyQuantizedTransposeConvTester& InputChannels(
int32_t input_channels) {
EXPECT_GT(input_channels, 0);
input_channels_ = input_channels;
return *this;
}
inline int32_t InputChannels() const { return input_channels_; }
inline DynamicallyQuantizedTransposeConvTester& OutputChannels(
int32_t output_channels) {
EXPECT_GT(output_channels, 0);
output_channels_ = output_channels;
return *this;
}
inline int32_t OutputChannels() const { return output_channels_; }
inline DynamicallyQuantizedTransposeConvTester& OutputHeight(
int32_t output_height) {
EXPECT_GT(output_height, 0);
output_height_ = output_height;
return *this;
}
inline int32_t OutputHeight() const { return output_height_; }
inline DynamicallyQuantizedTransposeConvTester& OutputWidth(
int32_t output_width) {
EXPECT_GT(output_width, 0);
output_width_ = output_width;
return *this;
}
inline int32_t OutputWidth() const { return output_width_; }
inline DynamicallyQuantizedTransposeConvTester& KernelHeight(
int32_t kernel_height) {
EXPECT_GT(kernel_height, 0);
kernel_height_ = kernel_height;
return *this;
}
inline int32_t KernelHeight() const { return kernel_height_; }
inline DynamicallyQuantizedTransposeConvTester& KernelWidth(
int32_t kernel_width) {
EXPECT_GT(kernel_width, 0);
kernel_width_ = kernel_width;
return *this;
}
inline int32_t KernelWidth() const { return kernel_width_; }
inline DynamicallyQuantizedTransposeConvTester& StrideHeight(
int32_t stride_height) {
EXPECT_GT(stride_height, 0);
stride_height_ = stride_height;
return *this;
}
inline int32_t StrideHeight() const { return stride_height_; }
inline DynamicallyQuantizedTransposeConvTester& StrideWidth(
int32_t stride_width) {
EXPECT_GT(stride_width, 0);
stride_width_ = stride_width;
return *this;
}
inline int32_t StrideWidth() const { return stride_width_; }
inline DynamicallyQuantizedTransposeConvTester& DilationHeight(
int32_t dilation_height) {
EXPECT_GT(dilation_height, 0);
dilation_height_ = dilation_height;
return *this;
}
inline int32_t DilationHeight() const { return dilation_height_; }
inline DynamicallyQuantizedTransposeConvTester& DilationWidth(
int32_t dilation_width) {
EXPECT_GT(dilation_width, 0);
dilation_width_ = dilation_width;
return *this;
}
inline int32_t DilationWidth() const { return dilation_width_; }
inline DynamicallyQuantizedTransposeConvTester& SamePadding() {
padding_ = ::tflite::Padding_SAME;
return *this;
}
inline DynamicallyQuantizedTransposeConvTester& ValidPadding() {
padding_ = ::tflite::Padding_VALID;
return *this;
}
inline ::tflite::Padding Padding() const { return padding_; }
inline int32_t InputWidth() const {
return ComputeInputSize(OutputWidth(), KernelWidth(), StrideWidth());
}
inline int32_t InputHeight() const {
return ComputeInputSize(OutputHeight(), KernelHeight(), StrideHeight());
}
inline DynamicallyQuantizedTransposeConvTester& WeightsCache(
TfLiteXNNPackDelegateWeightsCache* weights_cache) {
weights_cache_ = weights_cache;
return *this;
}
void Test(TfLiteDelegate* delegate);
private:
int32_t ComputeInputSize(int32_t output_size, int32_t kernel_size,
int32_t stride) const {
// Roughly follows TFLite's `ComputeOutSize`.
switch (padding_) {
case ::tflite::Padding_VALID:
return (output_size + stride - kernel_size) / stride;
break;
case ::tflite::Padding_SAME:
return (output_size + stride - 1) / stride;
break;
default:
assert(false);
}
}
private:
std::vector<int8_t> GenerateKernelData() const;
std::vector<float> GenerateBiasData() const;
std::vector<float> GenerateKernelScaleData() const;
std::vector<char> CreateTfLiteModel() const override;
int32_t batch_size_ = 1;
int32_t input_channels_ = 1;
int32_t output_channels_ = 1;
int32_t output_height_ = 1;
int32_t output_width_ = 1;
int32_t kernel_height_ = 1;
int32_t kernel_width_ = 1;
int32_t stride_height_ = 1;
int32_t stride_width_ = 1;
int32_t dilation_height_ = 1;
int32_t dilation_width_ = 1;
::tflite::Padding padding_ = ::tflite::Padding_VALID;
TfLiteXNNPackDelegateWeightsCache* weights_cache_ = nullptr;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_DYNAMICALLY_QUANTIZED_TRANSPOSE_CONV_TESTER_H_
@@ -0,0 +1,184 @@
/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/file_util.h"
#include <fcntl.h>
#if defined(_WIN32)
#include <io.h>
#define F_OK 0
#define ftruncate _chsize_s
#define XNN_LSEEK _lseeki64
#else
#include <unistd.h>
#define XNN_LSEEK lseek
#endif // defined(_WIN32)
// We currently use the memfd_create system call to create in-memory files which
// is only supported on Linux and Android.
#if defined(__linux__) || defined(__ANDROID__)
#ifndef TFLITE_XNNPACK_IN_MEMORY_FILE_ENABLED
// Some systems have syscall.h but don't define the SYS_memfd_create macro. We
// detect those by actually doing the include and checking for its definition.
#include <sys/syscall.h>
#ifdef SYS_memfd_create
#define TFLITE_XNNPACK_IN_MEMORY_FILE_ENABLED 1
#endif // SYS_memfd_create
#endif // TFLITE_XNNPACK_IN_MEMORY_FILE_ENABLED
#endif // defined(__linux__) || defined(__ANDROID__)
#include <sys/stat.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include "tensorflow/lite/delegates/xnnpack/macros.h"
#if !TFLITE_XNNPACK_IN_MEMORY_FILE_ENABLED
#include "tensorflow/lite/logger.h"
#include "tensorflow/lite/minimal_logging.h"
#endif
namespace tflite {
namespace xnnpack {
FileDescriptor FileDescriptor::Duplicate(int fd) {
return FileDescriptor(dup(fd));
}
FileDescriptor FileDescriptor::Duplicate() const {
if (!IsValid()) {
return FileDescriptor(-1);
}
return FileDescriptor::Duplicate(fd_);
}
void FileDescriptor::Reset(int new_fd) {
if (fd_ == new_fd) {
return;
}
if (IsValid()) {
close(fd_);
}
fd_ = new_fd;
}
FileDescriptor::Offset FileDescriptorView::GetPos() const {
return XNN_LSEEK(fd_, 0, SEEK_CUR);
}
FileDescriptor::Offset FileDescriptorView::SetPos(
FileDescriptor::Offset position) const {
return XNN_LSEEK(fd_, position, SEEK_SET);
}
FileDescriptor::Offset FileDescriptorView::SetPosFromEnd(
FileDescriptor::Offset offset) const {
return XNN_LSEEK(fd_, offset, SEEK_END);
}
FileDescriptor::Offset FileDescriptorView::MovePos(
FileDescriptor::Offset offset) const {
return XNN_LSEEK(fd_, offset, SEEK_CUR);
}
FileDescriptor FileDescriptor::Open(const char* path, int flags, mode_t mode) {
if (!path) {
return {};
}
#if defined(_WIN32)
if (!(flags & O_TEXT)) {
flags |= O_BINARY;
}
#endif
return FileDescriptor(open(path, flags, mode));
}
void FileDescriptor::Close() { Reset(-1); }
bool FileDescriptorView::Read(void* dst, size_t count) const {
char* dst_it = reinterpret_cast<char*>(dst);
while (count > 0) {
const auto bytes = read(fd_, dst_it, count);
if (bytes == -1 /* error */ || bytes == 0 /* EOF */) {
return false;
}
count -= bytes;
dst_it += bytes;
}
return true;
}
bool FileDescriptorView::Write(const void* src, size_t count) const {
const char* src_it = reinterpret_cast<const char*>(src);
while (count > 0) {
const auto bytes = write(fd_, src_it, count);
if (bytes == -1) {
return false;
}
count -= bytes;
src_it += bytes;
}
return true;
}
bool FileDescriptorView::Truncate(size_t size) const {
return ftruncate(fd_, size) == 0;
}
bool InMemoryFileDescriptorAvailable() {
#if TFLITE_XNNPACK_IN_MEMORY_FILE_ENABLED
// Test if the syscall memfd_create is available.
const int test_fd = syscall(SYS_memfd_create, "test fd", 0);
if (test_fd != -1) {
close(test_fd);
return true;
}
#endif
return false;
}
FileDescriptor CreateInMemoryFileDescriptor(const char* path) {
#ifdef TFLITE_XNNPACK_IN_MEMORY_FILE_ENABLED
return FileDescriptor(syscall(
SYS_memfd_create, path ? path : "XNNPack in-memory weight cache", 0));
#else
TFLITE_LOG_PROD(tflite::TFLITE_LOG_ERROR,
"XNNPack weight cache: in-memory cache is not enabled for "
"this build.");
return FileDescriptor(-1);
#endif
}
bool IsFileEmpty(const char* path, const FileDescriptor& fd) {
#if defined(_WIN32)
struct _stat64 file_stats{};
const int res = fd.IsValid() ? _fstat64(fd.Value(), &file_stats)
: _stat64(path, &file_stats);
#else
struct stat file_stats{};
const int res =
fd.IsValid() ? fstat(fd.Value(), &file_stats) : stat(path, &file_stats);
#endif
XNNPACK_RETURN_CHECK(
res == 0 || errno == ENOENT,
"could not access file descriptor %d stats to get size ('%s'): %s.",
fd.Value(), path, strerror(errno));
return file_stats.st_size == 0;
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,191 @@
/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_FILE_UTIL_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_FILE_UTIL_H_
#if !defined(_WIN32)
#include <sys/types.h>
#endif
#include <cstddef>
#include <utility>
namespace tflite {
namespace xnnpack {
#if defined(_WIN32)
using mode_t = int;
#endif
class FileDescriptorView {
public:
#if defined(_WIN32)
using Offset = __int64;
#else
using Offset = off_t;
#endif
explicit FileDescriptorView(int fd) : fd_(fd) {}
FileDescriptorView() = default;
// Checks that the file descriptor has a valid value.
//
// WARNING: this does not check that the descriptor points to an open file.
bool IsValid() const { return fd_ >= 0; }
// Returns the file descriptor value.
int Value() const { return fd_; }
// Returns the cursor position in the current file.
//
// Equivalent to MovePos(0).
//
// WARNING: the file descriptor must be valid and the file must be opened.
Offset GetPos() const;
// Sets the absolute cursor position in the current file.
//
// Returns the cursor position in the file or -1 on error.
//
// WARNING: the file descriptor must be valid and the file must be opened.
Offset SetPos(Offset position) const;
// Sets the cursor position relative to the file end.
//
// Returns the cursor position in the file or -1 on error.
//
// WARNING: the file descriptor must be valid and the file must be opened.
Offset SetPosFromEnd(Offset offset) const;
// Moves the cursor position by the given offset in the current file.
//
// Returns the cursor position in the file or -1 on error.
//
// WARNING: the file descriptor must be valid and the file must be opened.
Offset MovePos(Offset offset) const;
// Returns the size of the file.
Offset Size() const {
Offset pos = GetPos();
Offset size = SetPosFromEnd(0);
SetPos(pos);
return size;
}
// Reads `count` bytes from the file at the current position to `dst`.
//
// Returns true if all the data available in the file was read to the buffer
// (i.e. `count` bytes were read or EOF was reached).
//
// This is a convenience function wrapping the standard `read` function. If
// you need finer grain control use that directly.
[[nodiscard /*Reading from a file may fail.*/]]
bool Read(void* dst, size_t count) const;
// Writes `count` bytes to the file at the current position from `src`.
//
// This is a convenience function wrapping the standard `write` function. If
// you need finer grain control use that directly.
[[nodiscard /*Reading from a file may fail.*/]]
bool Write(const void* src, size_t count) const;
// Truncates the file to the given size.
//
// Returns true if the file was truncated successfully.
[[nodiscard /*Reading from a file may fail.*/]]
bool Truncate(size_t size) const;
protected:
int fd_ = -1;
};
// Wraps a C file descriptor and closes it when destroyed.
//
// Note that constness of the wrapped does NOT propagate to the file operations.
class FileDescriptor : public FileDescriptorView {
public:
explicit FileDescriptor(int fd) : FileDescriptorView(fd) {}
FileDescriptor() = default;
FileDescriptor(const FileDescriptor&) = delete;
FileDescriptor& operator=(const FileDescriptor&) = delete;
FileDescriptor(FileDescriptor&& other) : FileDescriptorView{other.fd_} {
other.fd_ = -1;
}
FileDescriptor& operator=(FileDescriptor&& other) {
if (other.fd_ != fd_) {
Close();
fd_ = other.fd_;
other.fd_ = -1;
}
return *this;
}
~FileDescriptor() { Close(); }
// Duplicates an existing raw file descriptor.
static FileDescriptor Duplicate(int fd);
// Closes the current file descriptor if needed and assigns the given value.
void Reset(int new_fd);
// Duplicates the current file descriptor and returns the new file descriptor.
//
// If the file descriptor is invalid, returns a new invalid FileDescriptor
// object.
FileDescriptor Duplicate() const;
// Opens a file.
//
// Directly maps to the standard C function `open`.
static FileDescriptor Open(const char* path, int flags, mode_t mode = 0);
// Closes the current file descriptor and sets it to -1.
void Close();
// Returns the current file descriptor value and stops managing it.
int Release() {
const int fd = fd_;
fd_ = -1;
return fd;
}
friend void swap(FileDescriptor& f1, FileDescriptor& f2) {
using std::swap;
swap(f1.fd_, f2.fd_);
}
};
// Checks if the current build and system support creating an in-memory file
// descriptor.
bool InMemoryFileDescriptorAvailable();
// Returns true if the file is empty (the file may exist)
//
// Note: if `fd` is valid, then `path` is ignored.
bool IsFileEmpty(const char* path, const FileDescriptor& fd);
// Creates a new file descriptor that isn't backed by a file system. The file
// will be automatically cleaned up when the last file descriptor pointing to it
// is closed.
FileDescriptor CreateInMemoryFileDescriptor(const char* path);
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_FILE_UTIL_H_
@@ -0,0 +1,142 @@
/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/file_util.h"
#include <fcntl.h>
#include <atomic>
#include <string>
#include <type_traits>
#include <utility>
#include <gtest/gtest.h>
namespace tflite::xnnpack {
namespace {
// Returns a path for a temporary file.
//
// Each call will return a new path.
std::string NewTempFilePath() {
static std::atomic<int> i = 0;
return testing::TempDir() + "test_file_" + std::to_string(i++);
}
TEST(FileDescriptorTest, DefaultConstructedIsInvalid) {
FileDescriptor fd;
EXPECT_FALSE(fd.IsValid());
}
TEST(FileDescriptorTest, ConstructAndRelease) {
const int kFd = 53;
// Construct from int.
FileDescriptor fd(kFd);
EXPECT_TRUE(fd.IsValid());
EXPECT_EQ(fd.Value(), kFd);
// Move construction
FileDescriptor fd2(std::move(fd));
EXPECT_FALSE(fd.IsValid());
EXPECT_TRUE(fd2.IsValid());
EXPECT_EQ(fd2.Value(), kFd);
// We release because we don't own kFd.
EXPECT_EQ(fd2.Release(), kFd);
EXPECT_FALSE(fd2.IsValid());
EXPECT_FALSE(std::is_copy_constructible_v<FileDescriptor>);
}
TEST(FileDescriptorTest, OpenNullFileFails) {
FileDescriptor fd =
FileDescriptor::Open(nullptr, O_CREAT | O_TRUNC | O_RDWR, 0644);
EXPECT_FALSE(fd.IsValid());
}
TEST(FileDescriptorTest, OpenWriteRewindAndReadWorks) {
const std::string tmp_file = NewTempFilePath();
FileDescriptor fd =
FileDescriptor::Open(tmp_file.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0644);
ASSERT_TRUE(fd.IsValid());
const std::string src_data = "The quick brown fox jumps over the lazy dog.";
EXPECT_TRUE(fd.Write(src_data.data(), src_data.size()));
EXPECT_EQ(fd.SetPos(0), 0);
std::string dst_data(src_data.size(), ' ');
EXPECT_TRUE(fd.Read(dst_data.data(), src_data.size()));
EXPECT_EQ(dst_data, src_data);
}
TEST(FileDescriptorTest, WriteFailureReturnsFalse) {
const std::string tmp_file = NewTempFilePath();
FileDescriptor fd = FileDescriptor::Open(tmp_file.c_str(),
O_CREAT | O_TRUNC | O_RDONLY, 0644);
ASSERT_TRUE(fd.IsValid());
const std::string src_data = "The quick brown fox jumps over the lazy dog.";
EXPECT_FALSE(fd.Write(src_data.data(), src_data.size()));
}
TEST(FileDescriptorTest, ReadFailureReturnsFalse) {
const std::string tmp_file = NewTempFilePath();
FileDescriptor fd = FileDescriptor::Open(tmp_file.c_str(),
O_CREAT | O_TRUNC | O_WRONLY, 0644);
ASSERT_TRUE(fd.IsValid());
std::string dst_data(5, ' ');
EXPECT_FALSE(fd.Read(dst_data.data(), dst_data.size()));
}
TEST(FileDescriptorTest, IsFileEmptyReturnTrueForAnEmptyFileThatExists) {
const std::string tmp_file = NewTempFilePath();
FileDescriptor fd = FileDescriptor::Open(tmp_file.c_str(),
O_CREAT | O_TRUNC | O_WRONLY, 0644);
fd.Close();
EXPECT_TRUE(IsFileEmpty(tmp_file.c_str(), FileDescriptor()));
}
TEST(FileDescriptorTest, IsFileEmptyReturnTrueForAnNonExistingFile) {
const std::string tmp_file = NewTempFilePath();
EXPECT_TRUE(IsFileEmpty(tmp_file.c_str(), FileDescriptor()));
}
TEST(FileDescriptorTest,
IsFileEmptyReturnTrueForAnNonExistingFileWithFileDescriptor) {
const std::string tmp_file = NewTempFilePath();
FileDescriptor fd = FileDescriptor::Open(tmp_file.c_str(),
O_CREAT | O_TRUNC | O_WRONLY, 0644);
EXPECT_TRUE(IsFileEmpty("asdfasdf", FileDescriptor()));
}
TEST(FileDescriptorTest, IsFileEmptyReturnFalseForAFileThatHasContents) {
const std::string tmp_file = NewTempFilePath();
FileDescriptor fd = FileDescriptor::Open(tmp_file.c_str(),
O_CREAT | O_TRUNC | O_WRONLY, 0644);
const std::string src_data = "The quick brown fox jumps over the lazy dog.";
EXPECT_TRUE(fd.Write(src_data.data(), src_data.size()));
EXPECT_FALSE(IsFileEmpty(tmp_file.c_str(), fd));
}
TEST(FileDescriptorTest, IsFileEmptyPrioritizesTheFileDescriptor) {
// We open 2 files, put some data only in one and then pass the file name of
// the one that has data and the file descriptor of the empty one.
const std::string tmp_file = NewTempFilePath();
const std::string tmp_file2 = NewTempFilePath();
FileDescriptor fd = FileDescriptor::Open(tmp_file.c_str(),
O_CREAT | O_TRUNC | O_WRONLY, 0644);
FileDescriptor fd2 = FileDescriptor::Open(tmp_file2.c_str(),
O_CREAT | O_TRUNC | O_WRONLY, 0644);
const std::string src_data = "The quick brown fox jumps over the lazy dog.";
EXPECT_TRUE(fd.Write(src_data.data(), src_data.size()));
fd.Close();
EXPECT_TRUE(IsFileEmpty(tmp_file.c_str(), fd2));
}
} // namespace
} // namespace tflite::xnnpack
@@ -0,0 +1,115 @@
/* Copyright 2025 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_FINGERPRINT_TEST_HELPERS_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_FINGERPRINT_TEST_HELPERS_H_
#include <iostream>
#include <memory>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "experimental.h" // from @XNNPACK
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/delegates/xnnpack/weight_cache.h"
#include "tensorflow/lite/delegates/xnnpack/weight_cache_test_helpers.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite::xnnpack {
struct TfLiteDelegateDeleter {
void operator()(TfLiteDelegate* delegate) {
TfLiteXNNPackDelegateDelete(delegate);
}
};
using TfLiteDelegatePtr =
std::unique_ptr<TfLiteDelegate, TfLiteDelegateDeleter>;
struct DelegateTest : public virtual testing::Test {
void SetUp() override {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
// By default, we try to setup a file weight cache to also check fingerprint
// generation. If the test system doesn't support a file system, then the
// cache file will be invalid.
if (cache_file.IsValid()) {
xnn_clear_fingerprints();
delegate_options.weight_cache_file_path = cache_file.GetCPath();
delegate_options.weight_cache_file_descriptor =
cache_file.Duplicate().Release();
delegate_options.flags |=
TFLITE_XNNPACK_DELEGATE_FLAG_ENABLE_LATEST_OPERATORS;
check_for_cache_fingerprints = true;
}
xnnpack_delegate =
TfLiteDelegatePtr(TfLiteXNNPackDelegateCreate(&delegate_options));
ASSERT_THAT(xnnpack_delegate, testing::NotNull());
}
void TearDown() override {
if (check_for_cache_fingerprints) {
ASSERT_TRUE(cache_file.IsValid());
EXPECT_TRUE(IsCompatibleCacheFile(cache_file));
if (AlterXNNPackFingerprints()) {
EXPECT_FALSE(IsCompatibleCacheFile(cache_file));
}
}
}
// Artificially change fingerprint values.
//
// This allows us to check that changing a fingerprint value will make the
// cache file incompatible.
//
// Returns the current number of fingerprints.
int AlterXNNPackFingerprints() {
int i = 0;
int modified = 0;
for (const xnn_fingerprint* fingerprint = xnn_get_fingerprint_by_idx(i);
fingerprint != nullptr;
fingerprint = xnn_get_fingerprint_by_idx(++i)) {
xnn_fingerprint new_fingerprint = *fingerprint;
++new_fingerprint.value;
xnn_set_fingerprint(new_fingerprint);
++modified;
}
std::cerr << "Fingerprint modified. The next call to IsCompatibleCacheFile "
"should fail.\n";
return modified;
}
// Replaces the xnnpack delegate with a custom one.
void UseCustomDelegate(const TfLiteXNNPackDelegateOptions& delegate_options) {
check_for_cache_fingerprints = false;
xnnpack_delegate =
TfLiteDelegatePtr(TfLiteXNNPackDelegateCreate(&delegate_options));
ASSERT_THAT(xnnpack_delegate, testing::NotNull());
}
// Replaces the xnnpack delegate with one that sets up a file backed weight
// cache.
void UseDelegateWithFileWeightCache() {}
// The default delegate is created in a generic way.
TfLiteDelegatePtr xnnpack_delegate;
tflite::xnnpack::TempFileDesc cache_file;
bool check_for_cache_fingerprints = false;
};
} // namespace tflite::xnnpack
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_FINGERPRINT_TEST_HELPERS_H_
@@ -0,0 +1,59 @@
/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_FLEXBUFFERS_UTIL_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_FLEXBUFFERS_UTIL_H_
#include "flatbuffers/base.h" // from @flatbuffers
#include "flatbuffers/flexbuffers.h" // from @flatbuffers
namespace tflite::xnnpack {
// We use this class defined with internal linkage as a key to prevent the
// following workaround to leak into other translation units.
struct FloatPointer {
const float* ptr = nullptr;
};
} // namespace tflite::xnnpack
namespace flexbuffers {
// TODO(b/359351192): switch to xnnpack builtin. This is a workaround until we
// are able to use just the value.
//
// We go around the access policy of the `Reference` class by specializing a
// template function that was not specialized for our use case.
//
// This is weakly tolerant to an update to the `Reference` class because:
// - THIS IS MEANT TO BE TEMPORARY until we actually use the XNNPack
// implementation of SDPA (and dependent on not needing data ptr).
// - The flexbuffer spec is public and set, so the layout should not evolve
// much.
//
// The alternative was to copy/paste the code to get to the map data and grab
// the pointer which basically means rewriting flexbuffer.h.
template <>
tflite::xnnpack::FloatPointer inline flexbuffers::Reference::As<
tflite::xnnpack::FloatPointer>() const {
#if !FLATBUFFERS_LITTLEENDIAN
// Flexbuffers are always stored in little endian order. Returning a pointer
// to the float data on a big endian architecture is meaningless.
return {nullptr};
#else
return {IsFloat() ? reinterpret_cast<const float*>(data_) : nullptr};
#endif
}
} // namespace flexbuffers
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_FLEXBUFFERS_UTIL_H_
@@ -0,0 +1,53 @@
/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/flexbuffers_util.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "flatbuffers/flexbuffers.h" // from @flatbuffers
namespace tflite::xnnpack {
namespace {
using ::testing::Pointee;
TEST(FlexbuffersUtilTest, FloatPointer) {
constexpr float kAValue = 3.14;
constexpr float kBValue = 56;
flexbuffers::Builder fbb;
fbb.Map([&] {
fbb.Float("a", kAValue);
fbb.Float("b", kBValue);
});
fbb.Finish();
const flexbuffers::Map map = flexbuffers::GetRoot(fbb.GetBuffer()).AsMap();
const flexbuffers::Reference a = map["a"];
EXPECT_TRUE(a.IsFloat());
EXPECT_THAT(a.As<FloatPointer>().ptr, Pointee(kAValue));
const flexbuffers::Reference b = map["b"];
EXPECT_TRUE(b.IsFloat());
EXPECT_THAT(b.As<FloatPointer>().ptr, Pointee(kBValue));
const flexbuffers::Reference c = map["c"];
ASSERT_TRUE(c.IsNull());
EXPECT_EQ(c.As<FloatPointer>().ptr, nullptr);
}
} // namespace
} // namespace tflite::xnnpack
@@ -0,0 +1,596 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/xnnpack/fingerprint_test_helpers.h"
#include "tensorflow/lite/delegates/xnnpack/fully_connected_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
struct FullyConnectedTest : public DelegateTest {};
TEST_F(FullyConnectedTest, 1D) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, 1DKeepDims) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.KeepDims(true)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, 2D) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, 2DKeepDims) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.KeepDims(true)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, 3D) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, width, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, 3DReshape) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, width, input_channels})
.InputChannels(width * input_channels)
.OutputChannels(output_channels)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, 3DKeepDims) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, width, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.KeepDims(true)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, 4D) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, height, width, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, 4DKeepDims) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, height, width, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.KeepDims(true)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, NoBias) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.NoBias()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, FP16Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.FP16Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, FP16WeightsNoBias) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.FP16Weights()
.NoBias()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, DynamicWeights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.DynamicWeights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, DynamicWeightsNoBias) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.DynamicWeights()
.NoBias()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, DynamicBias) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.DynamicBias()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, DynamicWeightsAndBias) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.DynamicWeights()
.DynamicBias()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, TensorWiseQuantizedInt8Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.TensorWiseQuantizedInt8Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, TensorWiseQuantizedInt8WeightsNoBias) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.TensorWiseQuantizedInt8Weights()
.NoBias()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, ChannelWiseQuantizedInt8Weights) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.ChannelWiseQuantizedInt8Weights()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, ChannelWiseQuantizedInt8WeightsNoBias) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.ChannelWiseQuantizedInt8Weights()
.NoBias()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, ReluActivation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.ReluActivation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, Relu6Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.Relu6Activation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, ReluMinus1To1Activation) {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.ReluMinus1To1Activation()
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
TEST_F(FullyConnectedTest, WeightsCache) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
std::unique_ptr<TfLiteXNNPackDelegateWeightsCache,
decltype(&TfLiteXNNPackDelegateWeightsCacheDelete)>
weights_cache(TfLiteXNNPackDelegateWeightsCacheCreate(),
TfLiteXNNPackDelegateWeightsCacheDelete);
delegate_options.weights_cache = weights_cache.get();
UseCustomDelegate(delegate_options);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
auto channels_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 9), std::ref(rng));
const auto batch = batch_rng();
const auto input_channels = channels_rng();
const auto output_channels = channels_rng();
FullyConnectedTester tester;
tester.InputShape({batch, input_channels})
.InputChannels(input_channels)
.OutputChannels(output_channels)
.WeightsCache(weights_cache.get())
.ReuseGeneratedModel(true);
tester.Test(xnnpack_delegate.get());
// Second run to test cache lookup runs.
tester.Test(xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,428 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/fully_connected_tester.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "fp16.h" // from @FP16
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
std::vector<int32_t> FullyConnectedTester::OutputShape() const {
EXPECT_NE(input_shape_.size(), 0);
if (KeepDims()) {
std::vector<int32_t> output_shape(input_shape_.cbegin(),
input_shape_.cend() - 1);
output_shape.push_back(OutputChannels());
return output_shape;
} else {
EXPECT_EQ(InputSize() % InputChannels(), 0);
return std::vector<int32_t>(
{InputSize() / InputChannels(), OutputChannels()});
}
}
void FullyConnectedTester::Test(TfLiteDelegate* delegate) {
const Model* model = GetModel();
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_real_distribution<float>(), std::ref(rng));
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(),
1 + static_cast<int>(WeightsType() == WeightsType::kDynamic) +
static_cast<int>(BiasType() == BiasType::kDynamic));
ASSERT_EQ(default_interpreter->inputs().size(),
1 + static_cast<int>(WeightsType() == WeightsType::kDynamic) +
static_cast<int>(BiasType() == BiasType::kDynamic));
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (weights_cache_ != nullptr) {
TfLiteXNNPackDelegateWeightsCacheFinalizeSoft(weights_cache_);
}
float* default_input_data = default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input_data, InputSize(), std::ref(input_rng));
float* delegate_input_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input_data, InputSize(), delegate_input_data);
if (WeightsType() == WeightsType::kDynamic) {
float* default_kernel_data =
default_interpreter->typed_input_tensor<float>(1);
std::generate_n(default_kernel_data, InputChannels() * OutputChannels(),
std::ref(input_rng));
float* delegate_kernel_data =
delegate_interpreter->typed_input_tensor<float>(1);
std::copy_n(default_kernel_data, InputChannels() * OutputChannels(),
delegate_kernel_data);
}
if (BiasType() == BiasType::kDynamic) {
float* default_bias_data = default_interpreter->typed_tensor<float>(
default_interpreter->inputs().back());
std::generate_n(default_bias_data, OutputChannels(), std::ref(input_rng));
float* delegate_bias_data = delegate_interpreter->typed_tensor<float>(
delegate_interpreter->inputs().back());
std::copy_n(default_bias_data, OutputChannels(), delegate_bias_data);
}
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* delegate_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
for (size_t i = 0; i < ComputeSize(OutputShape()); i++) {
ASSERT_NEAR(default_output_data[i], delegate_output_data[i],
std::numeric_limits<float>::epsilon() *
std::max(std::abs(default_output_data[i]) * 20.0f, 1.0f));
}
}
std::vector<char> FullyConnectedTester::CreateTfLiteModel() const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto range_rng = std::bind(
std::uniform_real_distribution<float>(-25.0f, 25.0f), std::ref(rng));
/*************************** Define operator codes **************************/
flatbuffers::FlatBufferBuilder builder;
std::vector<flatbuffers::Offset<OperatorCode>> operator_codes{
{CreateOperatorCode(builder, BuiltinOperator_FULLY_CONNECTED)}};
int dequantize_operator_code = -1;
switch (WeightsType()) {
case WeightsType::kFP32:
case WeightsType::kDynamic:
break;
case WeightsType::kFP16:
case WeightsType::kTensorWiseQuantizedInt8:
case WeightsType::kChannelWiseQuantizedInt8:
dequantize_operator_code = operator_codes.size();
operator_codes.emplace_back(
CreateOperatorCode(builder, BuiltinOperator_DEQUANTIZE));
break;
}
/*********************** Generate filter and bias data **********************/
std::vector<float> filter_data(InputChannels() * OutputChannels());
std::vector<float> bias_data(OutputChannels());
for (int32_t oc = 0; oc < OutputChannels(); oc++) {
// Use the same range of all-positive or all-negative values to generate
// all filter & bias weights within the same channel, but different ranges
// for different output channels. This ensures that no catastrophic
// cancellation occur, but test covers both positive and negative inputs.
const float range = range_rng();
const auto value_dist = std::uniform_real_distribution<float>(
std::min(range, 0.0f), std::max(range, 0.0f));
auto value_rng = std::bind(value_dist, std::ref(rng));
bias_data[oc] = value_rng();
for (int32_t ic = 0; ic < InputChannels(); ic++) {
filter_data[oc * InputChannels() + ic] = value_rng();
}
}
/****************************** Define buffers ******************************/
std::vector<flatbuffers::Offset<tflite::Buffer>> buffers{
{CreateBuffer(builder, builder.CreateVector({}))}};
tflite::TensorType quantized_filter_type = TensorType_FLOAT32;
flatbuffers::Offset<tflite::QuantizationParameters>
filter_quantization_params = 0;
int filter_buffer_id = 0, quantized_filter_buffer_id = 0;
const std::vector<int32_t> filter_shape = {OutputChannels(), InputChannels()};
switch (WeightsType()) {
case WeightsType::kFP32:
filter_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(filter_data.data()),
sizeof(float) * filter_data.size())));
break;
case WeightsType::kFP16: {
std::vector<uint16_t> quantized_filter_data(filter_data.size());
std::transform(filter_data.begin(), filter_data.end(),
quantized_filter_data.begin(), fp16_ieee_from_fp32_value);
quantized_filter_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_filter_data.data()),
sizeof(uint16_t) * quantized_filter_data.size())));
quantized_filter_type = TensorType_FLOAT16;
break;
}
case WeightsType::kTensorWiseQuantizedInt8:
case WeightsType::kChannelWiseQuantizedInt8: {
std::vector<float> filter_scales;
std::vector<int64_t> filter_zero_points;
int32_t filter_quantized_dimension = 0;
std::vector<int8_t> quantized_filter_data(filter_data.size());
if (WeightsType() == WeightsType::kChannelWiseQuantizedInt8) {
filter_quantized_dimension =
static_cast<int32_t>(filter_shape.size()) - 1;
const int32_t num_scales = filter_shape[filter_quantized_dimension];
filter_scales = GetInt8QuantizationScalePerChannel(
filter_data.data(), filter_quantized_dimension, filter_shape);
filter_zero_points.resize(num_scales, 0);
QuantizeInt8PerChannel(filter_scales.data(), filter_zero_points.data(),
filter_quantized_dimension, filter_data.data(),
quantized_filter_data.data(), filter_shape);
} else {
filter_scales.resize(1, GetInt8QuantizationScale(filter_data));
filter_zero_points.resize(1, 0);
std::transform(filter_data.begin(), filter_data.end(),
quantized_filter_data.begin(),
std::bind(QuantizeInt8, std::placeholders::_1, 0,
filter_scales[0]));
}
quantized_filter_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_filter_data.data()),
sizeof(int8_t) * quantized_filter_data.size())));
quantized_filter_type = TensorType_INT8;
filter_quantization_params = CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>(filter_scales),
builder.CreateVector<int64_t>(filter_zero_points),
/*details_type=*/QuantizationDetails_NONE,
/*details=*/0, filter_quantized_dimension);
break;
}
case WeightsType::kDynamic:
break;
}
tflite::TensorType quantized_bias_type = TensorType_FLOAT32;
int bias_buffer_id = 0, quantized_bias_buffer_id = 0;
switch (BiasType()) {
case BiasType::kNone:
case BiasType::kDynamic:
break;
case BiasType::kFP32:
bias_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(bias_data.data()),
sizeof(float) * bias_data.size())));
break;
case BiasType::kFP16: {
std::vector<uint16_t> quantized_bias_data(bias_data.size());
std::transform(bias_data.begin(), bias_data.end(),
quantized_bias_data.begin(), fp16_ieee_from_fp32_value);
quantized_bias_buffer_id = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_bias_data.data()),
sizeof(uint16_t) * quantized_bias_data.size())));
quantized_bias_type = TensorType_FLOAT16;
break;
}
}
/****************************** Define tensors ******************************/
std::vector<flatbuffers::Offset<tflite::Tensor>> tensors;
int quantized_filter_tensor_id = -1;
if (quantized_filter_type != TensorType_FLOAT32) {
quantized_filter_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(filter_shape.data(), filter_shape.size()),
/*type=*/quantized_filter_type,
/*buffer=*/quantized_filter_buffer_id,
/*name=*/0, filter_quantization_params));
}
int quantized_bias_tensor_id = -1;
const std::vector<int32_t> bias_shape = {OutputChannels()};
if (HasBias() && quantized_bias_type != TensorType_FLOAT32) {
quantized_bias_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(bias_shape.data(), bias_shape.size()),
quantized_bias_type, /*buffer=*/quantized_bias_buffer_id));
}
const int input_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(InputShape().data(), InputShape().size()),
TensorType_FLOAT32));
const int filter_tensor_id = tensors.size();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(filter_shape.data(), filter_shape.size()),
TensorType_FLOAT32,
/*buffer=*/filter_buffer_id));
const int bias_tensor_id = HasBias() ? tensors.size() : -1;
if (HasBias()) {
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(bias_shape.data(), bias_shape.size()),
TensorType_FLOAT32, bias_buffer_id));
}
const int output_tensor_id = tensors.size();
const std::vector<int32_t> output_shape = OutputShape();
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(output_shape.data(), output_shape.size()),
TensorType_FLOAT32));
/***************************** Define operators *****************************/
std::vector<flatbuffers::Offset<tflite::Operator>> operators;
if (quantized_filter_tensor_id >= 0) {
const std::array<int32_t, 1> dequantize_filter_inputs{
{quantized_filter_tensor_id}};
const std::array<int32_t, 1> dequantize_filter_outputs{{filter_tensor_id}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/dequantize_operator_code,
builder.CreateVector<int32_t>(dequantize_filter_inputs.data(),
dequantize_filter_inputs.size()),
builder.CreateVector<int32_t>(dequantize_filter_outputs.data(),
dequantize_filter_outputs.size())));
}
if (quantized_bias_tensor_id >= 0) {
const std::array<int32_t, 1> dequantize_bias_inputs{
{quantized_bias_tensor_id}};
const std::array<int32_t, 1> dequantize_bias_outputs{{bias_tensor_id}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/dequantize_operator_code,
builder.CreateVector<int32_t>(dequantize_bias_inputs.data(),
dequantize_bias_inputs.size()),
builder.CreateVector<int32_t>(dequantize_bias_outputs.data(),
dequantize_bias_outputs.size())));
}
std::vector<int32_t> op_inputs{{input_tensor_id, filter_tensor_id}};
if (HasBias()) {
op_inputs.push_back(bias_tensor_id);
}
const std::array<int32_t, 1> op_outputs{{output_tensor_id}};
const flatbuffers::Offset<FullyConnectedOptions> fully_connected_options =
CreateFullyConnectedOptions(builder, Activation(),
FullyConnectedOptionsWeightsFormat_DEFAULT,
KeepDims());
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_FullyConnectedOptions, fully_connected_options.Union()));
/****************************** Define subgraph *****************************/
std::vector<int32_t> subgraph_inputs{input_tensor_id};
if (WeightsType() == WeightsType::kDynamic) {
subgraph_inputs.push_back(filter_tensor_id);
}
if (BiasType() == BiasType::kDynamic) {
subgraph_inputs.push_back(bias_tensor_id);
}
const std::array<int32_t, 1> subgraph_outputs{{output_tensor_id}};
const flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(operators.data(), operators.size()));
const flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Fully Connected model");
const flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t FullyConnectedTester::ComputeSize(const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,180 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_FULLY_CONNECTED_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_FULLY_CONNECTED_TESTER_H_
#include <cstdint>
#include <initializer_list>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class FullyConnectedTester : public ModelCache<FullyConnectedTester> {
public:
enum class WeightsType {
kFP32,
kFP16,
kTensorWiseQuantizedInt8,
kChannelWiseQuantizedInt8,
kDynamic,
};
enum class BiasType {
kNone,
kFP32,
kFP16,
kDynamic,
};
FullyConnectedTester() = default;
FullyConnectedTester(const FullyConnectedTester&) = delete;
FullyConnectedTester& operator=(const FullyConnectedTester&) = delete;
inline FullyConnectedTester& InputShape(
std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
input_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
input_size_ = ComputeSize(input_shape_);
return *this;
}
inline const std::vector<int32_t>& InputShape() const { return input_shape_; }
inline int32_t InputSize() const { return input_size_; }
inline FullyConnectedTester& InputChannels(int32_t input_channels) {
EXPECT_GT(input_channels, 0);
input_channels_ = input_channels;
return *this;
}
inline int32_t InputChannels() const { return input_channels_; }
inline FullyConnectedTester& OutputChannels(int32_t output_channels) {
EXPECT_GT(output_channels, 0);
output_channels_ = output_channels;
return *this;
}
inline int32_t OutputChannels() const { return output_channels_; }
std::vector<int32_t> OutputShape() const;
inline FullyConnectedTester& KeepDims(bool keep_dims) {
keep_dims_ = keep_dims;
return *this;
}
inline bool KeepDims() const { return keep_dims_; }
inline FullyConnectedTester& FP16Weights() {
weights_type_ = WeightsType::kFP16;
bias_type_ = BiasType::kFP16;
return *this;
}
inline FullyConnectedTester& TensorWiseQuantizedInt8Weights() {
weights_type_ = WeightsType::kTensorWiseQuantizedInt8;
// Bias is stored in FP32 even when filter is quantized to INT8
bias_type_ = BiasType::kFP32;
return *this;
}
inline FullyConnectedTester& ChannelWiseQuantizedInt8Weights() {
weights_type_ = WeightsType::kChannelWiseQuantizedInt8;
// Bias is stored in FP32 even when filter is quantized to INT8
bias_type_ = BiasType::kFP32;
return *this;
}
inline FullyConnectedTester& DynamicWeights() {
weights_type_ = WeightsType::kDynamic;
bias_type_ = BiasType::kFP32;
return *this;
}
inline FullyConnectedTester& NoBias() {
bias_type_ = BiasType::kNone;
return *this;
}
inline FullyConnectedTester& DynamicBias() {
bias_type_ = BiasType::kDynamic;
return *this;
}
inline FullyConnectedTester& ReluActivation() {
activation_ = ::tflite::ActivationFunctionType_RELU;
return *this;
}
inline FullyConnectedTester& Relu6Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU6;
return *this;
}
inline FullyConnectedTester& ReluMinus1To1Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU_N1_TO_1;
return *this;
}
inline FullyConnectedTester& WeightsCache(
TfLiteXNNPackDelegateWeightsCache* weights_cache) {
weights_cache_ = weights_cache;
return *this;
}
void Test(TfLiteDelegate* delegate);
private:
std::vector<char> CreateTfLiteModel() const override;
inline bool HasBias() const { return bias_type_ != BiasType::kNone; }
inline WeightsType WeightsType() const { return weights_type_; }
inline BiasType BiasType() const { return bias_type_; }
inline ::tflite::ActivationFunctionType Activation() const {
return activation_;
}
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> input_shape_;
int32_t input_size_ = 1;
int32_t input_channels_ = 1;
int32_t output_channels_ = 1;
bool keep_dims_ = false;
enum WeightsType weights_type_ { WeightsType::kFP32 };
enum BiasType bias_type_ { BiasType::kFP32 };
::tflite::ActivationFunctionType activation_ =
::tflite::ActivationFunctionType_NONE;
TfLiteXNNPackDelegateWeightsCache* weights_cache_ = nullptr;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_FULLY_CONNECTED_TESTER_H_
@@ -0,0 +1,138 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/leaky_relu_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
TEST(LeakyRelu, 4D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
LeakyReluTester()
.Shape({batch, height, width, channels})
.Test(xnnpack_delegate.get());
}
TEST(LeakyRelu, 3D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
LeakyReluTester()
.Shape({batch, width, channels})
.Test(xnnpack_delegate.get());
}
TEST(LeakyRelu, 2D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto channels = shape_rng();
LeakyReluTester().Shape({batch, channels}).Test(xnnpack_delegate.get());
}
TEST(LeakyRelu, 1D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
LeakyReluTester().Shape({batch}).Test(xnnpack_delegate.get());
}
TEST(LeakyRelu, NegativeSlope) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
LeakyReluTester()
.Shape({batch, height, width, channels})
.NegativeSlope(-0.75f)
.Test(xnnpack_delegate.get());
}
TEST(LeakyRelu, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
LeakyReluTester()
.Shape({batch, height, width, channels})
.Test(xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,161 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/leaky_relu_tester.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
void LeakyReluTester::Test(TfLiteDelegate* delegate) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng = std::bind(
std::uniform_real_distribution<float>(-15.0f, 15.0f), std::ref(rng));
std::vector<char> buffer = CreateTfLiteModel();
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
float* default_input_data = default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input_data, Size(), std::ref(input_rng));
float* delegate_input_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input_data, Size(), delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* delegate_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
for (size_t i = 0; i < Size(); i++) {
ASSERT_EQ(default_output_data[i], delegate_output_data[i]);
}
}
std::vector<char> LeakyReluTester::CreateTfLiteModel() const {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<OperatorCode> operator_code =
CreateOperatorCode(builder, BuiltinOperator_LEAKY_RELU);
const std::array<flatbuffers::Offset<Buffer>, 1> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
}};
const std::array<flatbuffers::Offset<Tensor>, 2> tensors{{
CreateTensor(
builder,
builder.CreateVector<int32_t>(Shape().data(), Shape().size()),
TensorType_FLOAT32),
CreateTensor(
builder,
builder.CreateVector<int32_t>(Shape().data(), Shape().size()),
TensorType_FLOAT32),
}};
const flatbuffers::Offset<LeakyReluOptions> leaky_relu_options =
CreateLeakyReluOptions(builder, NegativeSlope());
const std::array<int32_t, 1> op_inputs{{0}};
const std::array<int32_t, 1> op_outputs{{1}};
flatbuffers::Offset<Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_LeakyReluOptions, leaky_relu_options.Union());
const std::array<int32_t, 1> subgraph_inputs{{0}};
const std::array<int32_t, 1> subgraph_outputs{{1}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Leaky ReLU model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t LeakyReluTester::ComputeSize(const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,69 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_LEAKY_RELU_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_LEAKY_RELU_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
namespace tflite {
namespace xnnpack {
class LeakyReluTester {
public:
LeakyReluTester() = default;
LeakyReluTester(const LeakyReluTester&) = delete;
LeakyReluTester& operator=(const LeakyReluTester&) = delete;
inline LeakyReluTester& Shape(std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
shape_ = std::vector<int32_t>(shape.begin(), shape.end());
size_ = LeakyReluTester::ComputeSize(shape_);
return *this;
}
inline const std::vector<int32_t>& Shape() const { return shape_; }
inline int32_t Size() const { return size_; }
inline LeakyReluTester& NegativeSlope(float negative_slope) {
negative_slope_ = negative_slope;
return *this;
}
inline float NegativeSlope() const { return negative_slope_; }
void Test(TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel() const;
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> shape_;
int32_t size_;
float negative_slope_ = 0.5f;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_LEAKY_RELU_TESTER_H_
@@ -0,0 +1,52 @@
/* Copyright 2025 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_MACROS_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_MACROS_H_
#include <cstdio>
#include "tensorflow/lite/minimal_logging.h"
#define XNNPACK_LOG_LIMIT 4048
#define XNNPACK_ABORT_CHECK(TEST, ...) \
do { \
if (!(TEST)) { \
char msg[XNNPACK_LOG_LIMIT] = {0}; \
int bytes = \
snprintf(msg, XNNPACK_LOG_LIMIT, "%s:%d: ", __FILE__, __LINE__); \
snprintf(msg + bytes, XNNPACK_LOG_LIMIT - bytes, "" __VA_ARGS__); \
TFLITE_LOG_PROD(tflite::TFLITE_LOG_ERROR, msg); \
std::abort(); \
} \
} while (0)
#define XNNPACK_VAR_ARG_HEAD(FIRST, ...) FIRST
#define XNNPACK_RETURN_CHECK(TEST, ...) \
do { \
if (!(TEST)) { \
if (sizeof(XNNPACK_VAR_ARG_HEAD("" __VA_ARGS__)) > sizeof("")) { \
char msg[XNNPACK_LOG_LIMIT] = {0}; \
int bytes = \
snprintf(msg, XNNPACK_LOG_LIMIT, "%s:%d: ", __FILE__, __LINE__); \
snprintf(msg + bytes, XNNPACK_LOG_LIMIT - bytes, "" __VA_ARGS__); \
TFLITE_LOG_PROD(tflite::TFLITE_LOG_ERROR, msg); \
} \
return false; \
} \
} while (0)
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_MACROS_H_
@@ -0,0 +1,426 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/pool_2d_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
TEST(MaxPool2D, UnitPoolSamePadding) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(1)
.PoolingWidth(1)
.StrideHeight(1)
.StrideWidth(1)
.SamePadding()
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
TEST(MaxPool2D, UnitPoolValidPadding) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(1)
.PoolingWidth(1)
.StrideHeight(1)
.StrideWidth(1)
.ValidPadding()
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
TEST(MaxPool2D, EqualPoolAndStrideWithSamePadding) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
const int32_t pool_height = pool_rng();
const int32_t pool_width = pool_rng();
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_height)
.PoolingWidth(pool_width)
.StrideHeight(pool_height)
.StrideWidth(pool_width)
.SamePadding()
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
TEST(MaxPool2D, EqualPoolAndStrideWithValidPadding) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 7), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
const int32_t pool_height = pool_rng();
const int32_t pool_width = pool_rng();
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_height)
.PoolingWidth(pool_width)
.StrideHeight(pool_height)
.StrideWidth(pool_width)
.ValidPadding()
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
TEST(MaxPool2D, LargePoolSmallStrideWithSamePadding) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(4, 7), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SamePadding()
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
TEST(MaxPool2D, LargePoolSmallStrideWithValidPadding) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(4, 7), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ValidPadding()
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
TEST(MaxPool2D, GlobalPooling) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
const int32_t height = input_rng();
const int32_t width = input_rng();
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(height)
.InputWidth(width)
.Channels(channel_rng())
.PoolingHeight(height)
.PoolingWidth(width)
.ValidPadding()
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
TEST(MaxPool2D, ReluActivation) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluActivation()
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
TEST(MaxPool2D, Relu6Activation) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Relu6Activation()
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
TEST(MaxPool2D, ReluMinus1To1Activation) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.ReluMinus1To1Activation()
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
TEST(MaxPool2D, DISABLED_TanhActivation) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.TanhActivation()
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
TEST(MaxPool2D, DISABLED_SignBitActivation) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.SignBitActivation()
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
TEST(MaxPool2D, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto batch_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 4), std::ref(rng));
auto input_rng =
std::bind(std::uniform_int_distribution<int32_t>(10, 25), std::ref(rng));
auto pool_rng =
std::bind(std::uniform_int_distribution<int32_t>(3, 5), std::ref(rng));
auto stride_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 3), std::ref(rng));
auto channel_rng =
std::bind(std::uniform_int_distribution<int32_t>(5, 16), std::ref(rng));
Pool2DTester()
.BatchSize(batch_rng())
.InputHeight(input_rng())
.InputWidth(input_rng())
.Channels(channel_rng())
.PoolingHeight(pool_rng())
.PoolingWidth(pool_rng())
.StrideHeight(stride_rng())
.StrideWidth(stride_rng())
.Test(BuiltinOperator_MAX_POOL_2D, xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,256 @@
/* Copyright 2025 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/mmap_handle.h"
#if defined(_WIN32)
#include <io.h>
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#endif
#if defined(__linux__) && !defined(MADV_PAGEOUT)
#define MADV_PAGEOUT 21
#endif
#include <fcntl.h>
#include <sys/stat.h>
#include <algorithm>
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include "tensorflow/lite/delegates/xnnpack/file_util.h"
#include "tensorflow/lite/delegates/xnnpack/macros.h"
#include "tensorflow/lite/delegates/xnnpack/windows_util.h"
namespace tflite::xnnpack {
// NOLINTBEGIN(whitespace/line_length)
#ifdef XNNPACK_CACHE_NO_MMAP_FOR_TEST
#pragma message( \
"XNNPACK_CACHE_NO_MMAP_FOR_TEST has been deprecated and doesn't do anything anymore. If you were using it, consider whether filing a bug to get your usecase fixed wouldn't be a better solution. If you really need that, the new define is XNNPACK_CACHE_NO_FILE_MAPPING_FOR_DEBUG.")
#endif
#ifdef XNNPACK_CACHE_NO_FILE_MAPPING_FOR_DEBUG
#pragma message( \
"XNNPACK_CACHE_NO_FILE_MAPPING_FOR_DEBUG is defined. This can lead to highly degraded performance.")
#endif
// NOLINTEND(whitespace/line_length)
#ifdef _WIN32
// Helper to split a value in high/low parts to pass to Windows APIs.
struct HighLow {
DWORD high;
DWORD low;
static HighLow From(uint64_t val) {
static_assert(sizeof(val) <= 2 * sizeof(DWORD),
"Value type doesn't fit in two DWORDs.");
return {static_cast<DWORD>(val >> CHAR_BIT * sizeof(DWORD)),
static_cast<DWORD>(val)};
}
};
#endif
void swap(MMapHandle& a, MMapHandle& b) {
using std::swap;
swap(a.size_, b.size_);
swap(a.offset_, b.offset_);
swap(a.offset_page_adjustment_, b.offset_page_adjustment_);
swap(a.data_, b.data_);
}
MMapHandle::~MMapHandle() { UnMap(); }
MMapHandle::MMapHandle(MMapHandle&& other) { swap(*this, other); }
MMapHandle& MMapHandle::operator=(MMapHandle&& other) {
swap(*this, other);
return *this;
}
bool MMapHandle::Map(const char* path, const size_t offset) {
return this->Map(FileDescriptor::Open(path, O_RDONLY), offset, path);
}
bool MMapHandle::Map(const FileDescriptorView& fd, const size_t offset,
const char* const path) {
this->UnMap();
const char* const safe_path = path != nullptr ? path : "[unspecified]";
XNNPACK_RETURN_CHECK(fd.IsValid(),
"cannot mmap invalid file descriptor %d ('%s').",
fd.Value(), path);
#if defined(_WIN32)
struct _stat64 file_stats;
XNNPACK_RETURN_CHECK(_fstat64(fd.Value(), &file_stats) == 0,
"could not access file stats to get size ('%s'): %s.",
safe_path, strerror(errno));
#else
struct stat file_stats;
XNNPACK_RETURN_CHECK(
fstat(fd.Value(), &file_stats) == 0,
"could not access file descriptor %d stats to get size ('%s'): %s.",
fd.Value(), safe_path, strerror(errno));
#endif
// This will reset data_ and size_ on return until it is deactivated.
ScopeGuard unmap_on_error([this] { UnMap(); });
size_ = file_stats.st_size - offset;
offset_ = offset;
#if defined(XNNPACK_CACHE_NO_FILE_MAPPING_FOR_DEBUG)
// This allocation is freed in UnMap and in the destructor.
data_ = new uint8_t[size_];
fd.SetPos(offset);
XNNPACK_RETURN_CHECK(fd.Read(data_, size_), "could not read file ('%s'): %s.",
safe_path, strerror(errno));
#elif defined(_WIN32)
HANDLE osf_handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd.Value()));
XNNPACK_RETURN_CHECK(osf_handle != INVALID_HANDLE_VALUE,
"could not convert file descriptor to file handle: %s.",
strerror(errno));
file_mapping_ =
CreateFileMappingA(osf_handle, /*lpFileMappingAttributes=*/nullptr,
/*flProtect=*/PAGE_READONLY, /*dwMaximumSizeHigh=*/0,
/*dwMaximumSizeLow=*/0, /*lpName=*/nullptr);
XNNPACK_RETURN_CHECK(file_mapping_ != NULL,
"could not create a file mapping: %s",
GetLastErrorString().c_str());
SYSTEM_INFO sys_info;
GetSystemInfo(&sys_info);
offset_page_adjustment_ = offset_ % sys_info.dwAllocationGranularity;
const size_t adjusted_offset = offset_ - offset_page_adjustment_;
const size_t adjusted_size = size_ + offset_page_adjustment_;
HighLow file_offset = HighLow::From(adjusted_offset);
data_ = static_cast<uint8_t*>(MapViewOfFile(
file_mapping_, FILE_MAP_READ, file_offset.high, file_offset.low,
/*dwNumberOfBytesToMap=*/adjusted_size));
XNNPACK_RETURN_CHECK(data_ != nullptr, "could not map file (%s): %s",
safe_path, GetLastErrorString().c_str());
#else
offset_page_adjustment_ = offset_ % getpagesize();
data_ = static_cast<uint8_t*>(
mmap(/*addr=*/nullptr, size_ + offset_page_adjustment_, PROT_READ,
MAP_SHARED, fd.Value(), offset_ - offset_page_adjustment_));
XNNPACK_RETURN_CHECK(data_ != MAP_FAILED,
"could not mmap file descriptor %d (%s): %s.",
fd.Value(), safe_path, strerror(errno));
#endif
unmap_on_error.Deactivate();
return true;
}
bool MMapHandle::Resize(size_t new_size) {
#if (defined(__linux__) || defined(__ANDROID__)) && \
!defined(XNNPACK_CACHE_NO_FILE_MAPPING_FOR_DEBUG)
void* const remapped_data =
mremap(data_, size_ + offset_page_adjustment_,
new_size + offset_page_adjustment_, /*flags=*/0);
if (remapped_data == MAP_FAILED) {
XNNPACK_RETURN_CHECK(errno == ENOMEM, "remap failed: %s", strerror(errno));
return false;
}
size_ = new_size;
return true;
#else
// The current implementation uses new/delete which doesn't provide a way to
// modify an allocation size. Changing to malloc/realloc/free doesn't ensure
// that a memory allocation will not be moved when reallocating
return false;
#endif
}
void MMapHandle::UnMap() {
if (data_) {
#if defined(XNNPACK_CACHE_NO_FILE_MAPPING_FOR_DEBUG)
delete[] data_;
#elif defined(_WIN32)
UnmapViewOfFile(data_);
CloseHandle(file_mapping_);
#else
munmap(data_, size_ + offset_page_adjustment_);
#endif
}
data_ = nullptr;
offset_ = 0;
offset_page_adjustment_ = 0;
size_ = 0;
}
bool MMapHandle::LockMemory() {
#if defined(XNNPACK_CACHE_NO_FILE_MAPPING_FOR_DEBUG)
return true;
#elif defined(_WIN32)
return VirtualLock(data_, size_) != 0;
#else
return mlock(data_, size_ + offset_page_adjustment_) == 0;
#endif
}
bool MMapHandle::UnlockMemory() {
#if defined(XNNPACK_CACHE_NO_FILE_MAPPING_FOR_DEBUG)
return true;
#elif defined(_WIN32)
return VirtualUnlock(data_, size_) != 0;
#else
return munlock(data_, size_ + offset_page_adjustment_) == 0;
#endif
}
bool MarkMemoryNotNeeded(void* data, size_t size) {
#if defined(_WIN32)
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
DWORD page_size = sysInfo.dwPageSize;
// We don't mark memory chunks that are too small.
// TODO: b/510899567 - This threshold should be re-evaluated on Windows.
constexpr const size_t size_threshold = 1024 * 1024;
if (size <= size_threshold) {
return true;
}
#else
size_t page_size = getpagesize();
#endif
// We align the data buffer to the next page boundary and the size to be a
// multiple of the page size.
//
// - Windows will unlock all pages that contain at least a byte of the given
// range, which we want to avoid.
// - Linux requires the address to be on a page boundary.
void* aligned_data = std::align(page_size, page_size, data, size);
size -= size % page_size;
if (aligned_data) {
#if defined(_WIN32)
return VirtualUnlock(aligned_data, size);
#elif defined(__ANDROID__) || defined(__linux__)
return madvise(aligned_data, size, MADV_PAGEOUT) == 0;
#endif
}
return true;
}
} // namespace tflite::xnnpack
@@ -0,0 +1,169 @@
/* Copyright 2025 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_MMAP_HANDLE_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_MMAP_HANDLE_H_
#if defined(_WIN32)
#include <windows.h>
#endif
#include <cstddef>
#include <cstdint>
#include <utility>
#include "tensorflow/lite/delegates/xnnpack/file_util.h"
namespace tflite::xnnpack {
// Calls the provided callback at then end of the scope this was created into.
template <class F>
class ScopeGuard {
public:
explicit ScopeGuard(F&& callback) : callback_(std::forward<F>(callback)) {}
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
ScopeGuard(ScopeGuard&& other)
: active_(other.active_), callback_(std::move(other.callback_)) {
other.Deactivate();
}
ScopeGuard& operator=(ScopeGuard&& other) {
if (this != &other) {
active_ = std::move(other.active_);
callback_ = std::move(other.callback_);
other.Deactivate();
}
}
~ScopeGuard() {
if (active_) {
callback_();
}
}
void Deactivate() { active_ = false; }
private:
F callback_;
bool active_ = true;
};
template <class F>
ScopeGuard(F&&) -> ScopeGuard<F>;
// Handles MMap allocations lifetime.
//
// When mapped, provides a view over the allocation for convenience.
//
// WARNING: the interface in this file is still under experimentation and WILL
// CHANGE. Do not rely on it.
class MMapHandle {
public:
using value_type = uint8_t;
MMapHandle() = default;
~MMapHandle();
MMapHandle(const MMapHandle&) = delete;
MMapHandle& operator=(const MMapHandle&) = delete;
MMapHandle(MMapHandle&&);
MMapHandle& operator=(MMapHandle&&);
// Maps the file at the given path.
[[nodiscard /*Mapping a file can fail.*/]]
bool Map(const char* path, size_t offset = 0);
// Maps the fd associated to the file descriptor.
//
// The debug_path is printed along the error messages.
[[nodiscard /*Mapping a file can fail.*/]]
bool Map(const FileDescriptorView& fd, size_t offset = 0,
const char* debug_path = nullptr);
// Tries to resize the current mapping.
//
// Only succeeds if the mapping could be resized without being moved.
//
// WARNING: expects `IsMapped()` to be true.
[[nodiscard /*Resizing a file can fail.*/]]
bool Resize(size_t new_size);
// Unmaps an existing mapping.
void UnMap();
// Returns true if a mapping exists.
bool IsMapped() const { return data_ != nullptr; }
// Tries to lock the mapping in memory.
//
// Only applicable when the OS supports memory locking.
//
// WARNING: expects `IsMapped()` to be true.
[[nodiscard /*Locking a file can fail.*/]]
bool LockMemory();
// Tries to unlock the mapping in memory.
//
// Only applicable when the OS supports memory locking.
//
// WARNING: expects `IsMapped()` to be true.
bool UnlockMemory();
// Returns the mapping buffer.
uint8_t* data() { return data_ + offset_page_adjustment_; }
// Returns the mapping buffer.
const uint8_t* data() const { return data_ + offset_page_adjustment_; }
// Returns the mapping size in bytes.
size_t size() const { return size_; }
size_t offset() const { return offset_; }
uint8_t* begin() { return data(); }
const uint8_t* begin() const { return data(); }
uint8_t* end() { return data() + size(); }
const uint8_t* end() const { return data() + size(); }
friend void swap(MMapHandle& a, MMapHandle& b);
private:
size_t size_ = 0;
size_t offset_ = 0;
size_t offset_page_adjustment_ = 0;
uint8_t* data_ = nullptr;
#if defined(_WIN32)
HANDLE file_mapping_ = 0;
#endif
};
// Marks a region of memory as not needed.
//
// That memory can be reclaimed by the system. Note that the given region will
// be shrunk to the memory pages that fully hold a subset of it.
//
// ```
// <-- page -->
// | |
// [***xxxxxxxxxxxxxxx*****] <--- buffer
// ^^^^ ^^^^^
// ignored ignored
// ```
bool MarkMemoryNotNeeded(void* data, size_t size);
} // namespace tflite::xnnpack
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_MMAP_HANDLE_H_
@@ -0,0 +1,272 @@
/* Copyright 2025 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/mmap_handle.h"
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <string>
#include <utility>
#if defined(_MSC_VER)
#include <io.h>
#define ftruncate64 _chsize_s
#else
#include <unistd.h>
#endif
#if defined(__APPLE__)
#define ftruncate64 ftruncate
#endif
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/delegates/xnnpack/weight_cache_test_helpers.h"
namespace tflite::xnnpack {
namespace {
using testing::ElementsAreArray;
using testing::Ge;
using testing::Gt;
TEST(MMapHandleTest, DefaultConstructs) {
MMapHandle handle;
EXPECT_FALSE(handle.IsMapped());
EXPECT_EQ(handle.data(), nullptr);
EXPECT_EQ(handle.size(), 0);
}
TEST(MMapHandleTest, MapNonExistingFileFails) {
// This path is unlikely to exist.
const char* file_path = "sdbgfd";
MMapHandle handle;
EXPECT_FALSE(handle.Map(file_path));
}
TEST(MMapHandleTest, MapExistingFileWorks) {
using std::size;
const std::string payload = "This is some data in the file.";
TempFileDesc tmp_file;
ASSERT_TRUE(tmp_file.IsValid());
ASSERT_TRUE(tmp_file.Write(payload.c_str(), size(payload)));
tmp_file.Close();
MMapHandle handle;
ASSERT_TRUE(handle.Map(tmp_file.GetCPath()));
EXPECT_TRUE(handle.IsMapped());
EXPECT_NE(handle.data(), nullptr);
EXPECT_THAT(handle.size(), Ge(size(payload)));
EXPECT_THAT(handle, ElementsAreArray(payload));
handle.UnMap();
EXPECT_FALSE(handle.IsMapped());
EXPECT_EQ(handle.data(), nullptr);
EXPECT_EQ(handle.size(), 0);
}
TEST(MMapHandleTest, MoveConstructs) {
const std::string payload = "This is some data in the file.";
TempFileDesc tmp_file;
ASSERT_TRUE(tmp_file.IsValid());
ASSERT_TRUE(tmp_file.Write(payload.c_str(), size(payload)));
tmp_file.Close();
MMapHandle handle;
ASSERT_TRUE(handle.Map(tmp_file.GetCPath()));
MMapHandle handle2(std::move(handle));
// We are checking that the moved from handle has lost control over the data.
// NOLINTBEGIN(bugprone-use-after-move)
EXPECT_FALSE(handle.IsMapped());
EXPECT_EQ(handle.data(), nullptr);
EXPECT_EQ(handle.size(), 0);
// NOLINTEND(bugprone-use-after-move)
EXPECT_TRUE(handle2.IsMapped());
EXPECT_NE(handle2.data(), nullptr);
EXPECT_THAT(handle2.size(), Ge(size(payload)));
EXPECT_THAT(handle2, ElementsAreArray(payload));
}
TEST(MMapHandleTest, Resize) {
const std::string payload = "This is some data in the file.";
TempFileDesc tmp_file;
ASSERT_TRUE(tmp_file.IsValid());
ASSERT_TRUE(tmp_file.Write(payload.c_str(), size(payload)));
tmp_file.Close();
MMapHandle handle;
ASSERT_TRUE(handle.Map(tmp_file.GetCPath()));
#if defined(__linux__) || defined(__ANDROID__)
const size_t kMaxResizeTestCount = 20;
bool was_resized = true;
for (size_t i = 0; i < kMaxResizeTestCount && was_resized; ++i) {
was_resized = handle.Resize(payload.size() * 2);
EXPECT_TRUE(was_resized || errno == ENOMEM);
}
#else
EXPECT_FALSE(handle.Resize(payload.size()));
#endif
}
TEST(MMapHandleTest, MapWithOffset) {
const std::string payload = "This is some data in the file.";
const std::string payload2 = "Some other data appended to the the offset.";
TempFileDesc tmp_file;
// We want to create a file that is bigger than the mapping granularity to
// test how alignment behaves.
const int64_t min_mapping_granularity = [] {
#ifdef _MSC_VER
SYSTEM_INFO sys_info;
GetSystemInfo(&sys_info);
return sys_info.dwAllocationGranularity;
#else
return getpagesize();
#endif
}();
ASSERT_TRUE(tmp_file.IsValid());
ASSERT_EQ(ftruncate64(tmp_file.Value(), min_mapping_granularity + 1), 0);
tmp_file.SetPos(0);
ASSERT_TRUE(tmp_file.Write(payload.c_str(), size(payload)));
tmp_file.SetPosFromEnd(0);
ASSERT_THAT(tmp_file.GetPos(), Gt(min_mapping_granularity));
ASSERT_TRUE(tmp_file.Write(payload2.c_str(), size(payload2)));
tmp_file.Close();
MMapHandle handle;
ASSERT_TRUE(
handle.Map(tmp_file.GetCPath(), /*offset=*/min_mapping_granularity + 1));
EXPECT_EQ(handle.size(), size(payload2));
EXPECT_THAT(std::string((const char*)handle.data(), handle.size()),
testing::StrEq(payload2));
}
// This test case is geared towards Windows that supports both forward slashes
// and backslashes as path separators.
TEST(MMapHandleTest, MapWorksWithBackslashInPath) {
const std::string payload = "This is some data in the file.";
TempFileDesc tmp_file;
ASSERT_TRUE(tmp_file.IsValid());
ASSERT_TRUE(tmp_file.Write(payload.c_str(), size(payload)));
MMapHandle handle;
ASSERT_TRUE(
handle.Map(tmp_file, /*offset=*/0, "C:\\\\A\\path\\with\\backslashes"));
}
TEST(MMapHandleTest, MapWorksWithUnspecifiedFilePath) {
const std::string payload = "This is some data in the file.";
TempFileDesc tmp_file;
ASSERT_TRUE(tmp_file.IsValid());
ASSERT_TRUE(tmp_file.Write(payload.c_str(), size(payload)));
MMapHandle handle;
ASSERT_TRUE(handle.Map(tmp_file, /*offset=*/0));
}
// This test case is geared towards Windows that supports both forward slashes
// and backslashes as path separators.
TEST(MMapHandleTest, MapWorksWithForwardSlashInPath) {
const std::string payload = "This is some data in the file.";
TempFileDesc tmp_file;
ASSERT_TRUE(tmp_file.IsValid());
ASSERT_TRUE(tmp_file.Write(payload.c_str(), size(payload)));
MMapHandle handle;
ASSERT_TRUE(
handle.Map(tmp_file, /*offset=*/0, "C:/A/path/with/forward/slashes"));
}
TEST(MMapHandleTest, ResizeMapWithOffset) {
const std::string payload = "This is some data in the file.";
const std::string payload2 = "Some other data appended to the the offset.";
const std::string payload3 =
"Yet some other data written after the initial mapping.";
TempFileDesc tmp_file;
ASSERT_TRUE(tmp_file.IsValid());
ASSERT_TRUE(tmp_file.Write(payload.c_str(), size(payload)));
ASSERT_TRUE(tmp_file.Write(payload2.c_str(), size(payload2)));
MMapHandle handle;
ASSERT_TRUE(handle.Map(tmp_file.GetCPath(), /*offset=*/size(payload)));
ASSERT_TRUE(tmp_file.Write(payload3.c_str(), size(payload3)));
tmp_file.Close();
#if defined(__linux__) || defined(__ANDROID__)
bool was_resized = handle.Resize(payload2.size() + payload3.size());
if (was_resized) {
EXPECT_THAT(std::string((const char*)handle.data(), handle.size()),
testing::StrEq(payload2 + payload3));
} else {
GTEST_SKIP()
<< "This run did not end up in a resize of the mmaped interval.";
}
#else
GTEST_SKIP() << "Resize is not supported for this build.";
#endif
}
TEST(MMapHandleTest, WorksWithHugeFiles) {
#if INTPTR_MAX == INT32_MAX
GTEST_SKIP()
<< "Files bigger than 2GiB cannot be mapped on 32 bit architectures.";
#endif
TempFileDesc tmp_file;
int64_t huge_file_size = 2254857830; // More than 2 GiB.
ASSERT_EQ(ftruncate64(tmp_file.Value(), huge_file_size), 0);
tmp_file.Close();
MMapHandle handle;
ASSERT_TRUE(handle.Map(tmp_file.GetCPath(), /*offset=*/0));
}
TEST(MMapHandleTest, MemoryLockAndUnlock) {
const std::string payload = "This is some data to be locked in memory.";
TempFileDesc tmp_file;
ASSERT_TRUE(tmp_file.IsValid());
ASSERT_TRUE(tmp_file.Write(payload.c_str(), std::size(payload)));
tmp_file.Close();
MMapHandle handle;
ASSERT_TRUE(handle.Map(tmp_file.GetCPath()));
ASSERT_TRUE(handle.IsMapped());
if (handle.LockMemory()) {
EXPECT_TRUE(handle.UnlockMemory());
} else {
// Locking might fail due to resource limits (RLIMIT_MEMLOCK).
// In that case, we log a warning but don't fail the test if it's a
// permission issue. However, for a small file, it should generally succeed
// in a test environment. Let's print the error for debugging purposes.
GTEST_SKIP() << "MemoryLock failed with errno: " << errno;
}
EXPECT_THAT(handle, ElementsAreArray(payload));
}
} // namespace
} // namespace tflite::xnnpack
@@ -0,0 +1,607 @@
/* Copyright 2026 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/moe_delegate_kernel.h"
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "xnnpack.h" // from @XNNPACK
#include "flatbuffers/flexbuffers.h" // from @flatbuffers
#include "pthreadpool.h" // from @pthreadpool
#include "tensorflow/lite/builtin_ops.h"
#include "tensorflow/lite/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/kernels/internal/tensor_ctypes.h"
#include "tensorflow/lite/kernels/kernel_util.h"
namespace tflite {
namespace xnnpack {
namespace {
constexpr char kMoeCustomOp[] = "moe";
constexpr uintptr_t kMoeXnnpackWorkspaceAlignment = 128;
struct MoeExpertsAttributes {
int num_experts = 0;
int num_active_experts = 0;
int model_dim = 0;
int hidden_dim = 0;
};
struct MoeExpertsAssignment {
int token = 0;
int route = 0;
};
} // namespace
class MoeExpertsDelegateKernel::Impl {
public:
using XnnOperatorPtr =
std::unique_ptr<xnn_operator, decltype(&xnn_delete_operator)>;
static bool IsMoeExpertsNode(const TfLiteRegistration* registration,
const TfLiteNode* node) {
(void)node;
return registration->builtin_code == kTfLiteBuiltinCustom &&
registration->custom_name != nullptr &&
std::strcmp(registration->custom_name, kMoeCustomOp) == 0;
}
static TfLiteStatus IsSupported(TfLiteContext* context,
const TfLiteNode* node,
const TfLiteRegistration* registration,
int node_index) {
if (!IsMoeExpertsNode(registration, node)) {
return kTfLiteError;
}
MoeExpertsAttributes attr;
if (!ReadAttributes(context, node, registration, node_index, &attr)) {
return kTfLiteError;
}
if (node->inputs == nullptr || node->outputs == nullptr) {
TF_LITE_KERNEL_LOG(context, "%s node #%d has null inputs or outputs",
kMoeCustomOp, node_index);
return kTfLiteError;
}
if (node->inputs->size != 7 || node->outputs->size != 1) {
TF_LITE_KERNEL_LOG(
context,
"%s node #%d expects 7 fp32 inputs and 1 output in the XNNPACK "
"prototype path",
kMoeCustomOp, node_index);
return kTfLiteError;
}
const TfLiteTensor* src = &context->tensors[node->inputs->data[0]];
const TfLiteTensor* top_weights = &context->tensors[node->inputs->data[1]];
const TfLiteTensor* top_indices = &context->tensors[node->inputs->data[2]];
const TfLiteTensor* gate_weight = &context->tensors[node->inputs->data[3]];
const TfLiteTensor* ff1_weight = &context->tensors[node->inputs->data[4]];
const TfLiteTensor* linear_weight =
&context->tensors[node->inputs->data[5]];
const TfLiteTensor* per_expert_scale =
&context->tensors[node->inputs->data[6]];
const TfLiteTensor* output = &context->tensors[node->outputs->data[0]];
if (src->type != kTfLiteFloat32 || top_weights->type != kTfLiteFloat32 ||
top_indices->type != kTfLiteInt32 ||
gate_weight->type != kTfLiteFloat32 ||
ff1_weight->type != kTfLiteFloat32 ||
linear_weight->type != kTfLiteFloat32 ||
per_expert_scale->type != kTfLiteFloat32 ||
output->type != kTfLiteFloat32) {
TF_LITE_KERNEL_LOG(context,
"%s node #%d currently supports fp32 weights, fp32 "
"activations, and int32 top_indices only",
kMoeCustomOp, node_index);
return kTfLiteError;
}
if (gate_weight->allocation_type != kTfLiteMmapRo ||
ff1_weight->allocation_type != kTfLiteMmapRo ||
linear_weight->allocation_type != kTfLiteMmapRo ||
per_expert_scale->allocation_type != kTfLiteMmapRo) {
TF_LITE_KERNEL_LOG(context,
"%s node #%d expects constant expert weights and "
"per_expert_scale",
kMoeCustomOp, node_index);
return kTfLiteError;
}
return kTfLiteOk;
}
static std::unique_ptr<Impl> Create(TfLiteContext* context,
const TfLiteDelegateParams* params,
pthreadpool_t threadpool) {
if (params->nodes_to_replace == nullptr ||
params->nodes_to_replace->size != 1) {
return nullptr;
}
const int node_index = params->nodes_to_replace->data[0];
TfLiteNode* node = nullptr;
TfLiteRegistration* registration = nullptr;
if (context->GetNodeAndRegistration(context, node_index, &node,
&registration) != kTfLiteOk) {
return nullptr;
}
if (node == nullptr ||
IsSupported(context, node, registration, node_index) != kTfLiteOk) {
return nullptr;
}
MoeExpertsAttributes attr;
if (!ReadAttributes(context, node, registration, node_index, &attr)) {
return nullptr;
}
XnnOperatorPtr gate_up_fc(nullptr, &xnn_delete_operator);
XnnOperatorPtr linear_fc(nullptr, &xnn_delete_operator);
if (!CreateDynamicFullyConnected(context, node_index, &gate_up_fc) ||
!CreateDynamicFullyConnected(context, node_index, &linear_fc)) {
return nullptr;
}
return std::unique_ptr<Impl>(new Impl(
attr, node->inputs->data[0], node->inputs->data[1],
node->inputs->data[2], node->inputs->data[3], node->inputs->data[4],
node->inputs->data[5], node->inputs->data[6], node->outputs->data[0],
std::move(gate_up_fc), std::move(linear_fc), threadpool));
}
TfLiteStatus Prepare(TfLiteContext* context) {
const TfLiteTensor& src = context->tensors[src_id_];
TfLiteTensor& output = context->tensors[output_id_];
if (src.dims != nullptr && output.dims != nullptr &&
!TfLiteIntArrayEqual(src.dims, output.dims)) {
TfLiteIntArray* new_shape = TfLiteIntArrayCopy(src.dims);
return context->ResizeTensor(context, &output, new_shape);
}
return kTfLiteOk;
}
TfLiteStatus Invoke(TfLiteContext* context) {
const TfLiteTensor& src_tensor = context->tensors[src_id_];
const TfLiteTensor& top_weights_tensor = context->tensors[top_weights_id_];
const TfLiteTensor& top_indices_tensor = context->tensors[top_indices_id_];
const TfLiteTensor& gate_weight_tensor = context->tensors[gate_weight_id_];
const TfLiteTensor& ff1_weight_tensor = context->tensors[ff1_weight_id_];
const TfLiteTensor& linear_weight_tensor =
context->tensors[linear_weight_id_];
const TfLiteTensor& per_expert_scale_tensor =
context->tensors[per_expert_scale_id_];
TfLiteTensor& output_tensor = context->tensors[output_id_];
const int src_elements = NumElements(&src_tensor);
if (src_elements % attr_.model_dim != 0) {
TF_LITE_KERNEL_LOG(context, "%s src element count is not divisible by %d",
kMoeCustomOp, attr_.model_dim);
return kTfLiteError;
}
const int tokens = src_elements / attr_.model_dim;
if (NumElements(&top_weights_tensor) != tokens * attr_.num_active_experts ||
NumElements(&top_indices_tensor) != tokens * attr_.num_active_experts ||
NumElements(&output_tensor) != tokens * attr_.model_dim) {
TF_LITE_KERNEL_LOG(context,
"%s runtime tensor sizes do not match parsed attrs",
kMoeCustomOp);
return kTfLiteError;
}
const float* src = GetTensorData<float>(&src_tensor);
const float* top_weights = GetTensorData<float>(&top_weights_tensor);
const int32_t* top_indices = GetTensorData<int32_t>(&top_indices_tensor);
const float* gate_weight = GetTensorData<float>(&gate_weight_tensor);
const float* ff1_weight = GetTensorData<float>(&ff1_weight_tensor);
const float* linear_weight = GetTensorData<float>(&linear_weight_tensor);
const float* per_expert_scale =
GetTensorData<float>(&per_expert_scale_tensor);
float* output = GetTensorData<float>(&output_tensor);
if (src == nullptr || top_weights == nullptr || top_indices == nullptr ||
gate_weight == nullptr || ff1_weight == nullptr ||
linear_weight == nullptr || per_expert_scale == nullptr ||
output == nullptr) {
TF_LITE_KERNEL_LOG(context, "%s received a null tensor data pointer",
kMoeCustomOp);
return kTfLiteError;
}
std::fill(output, output + tokens * attr_.model_dim, 0.0f);
const int dispatches = tokens * attr_.num_active_experts;
if (!BuildExpertAssignments(context, top_indices, tokens, dispatches)) {
return kTfLiteError;
}
for (int expert = 0; expert < attr_.num_experts; ++expert) {
const int begin = expert_offsets_[expert];
const int end = expert_offsets_[expert + 1];
const int routed_tokens = end - begin;
if (routed_tokens == 0) {
continue;
}
if (!RunExpert(context, expert, assignments_.data() + begin,
routed_tokens, src, top_weights, gate_weight, ff1_weight,
linear_weight, per_expert_scale, output)) {
return kTfLiteError;
}
}
return kTfLiteOk;
}
private:
Impl(MoeExpertsAttributes attr, int src_id, int top_weights_id,
int top_indices_id, int gate_weight_id, int ff1_weight_id,
int linear_weight_id, int per_expert_scale_id, int output_id,
XnnOperatorPtr gate_up_fc, XnnOperatorPtr linear_fc,
pthreadpool_t threadpool)
: attr_(attr),
src_id_(src_id),
top_weights_id_(top_weights_id),
top_indices_id_(top_indices_id),
gate_weight_id_(gate_weight_id),
ff1_weight_id_(ff1_weight_id),
linear_weight_id_(linear_weight_id),
per_expert_scale_id_(per_expert_scale_id),
output_id_(output_id),
gate_up_fc_(std::move(gate_up_fc)),
linear_fc_(std::move(linear_fc)),
threadpool_(threadpool) {}
static std::optional<flexbuffers::Map> ReadAttributeMap(
TfLiteContext* context, const TfLiteNode* node,
const TfLiteRegistration* registration, int node_index) {
if (registration->builtin_code == kTfLiteBuiltinCustom) {
if (node->custom_initial_data == nullptr ||
node->custom_initial_data_size == 0) {
TF_LITE_KERNEL_LOG(context, "%s node #%d is missing custom options",
kMoeCustomOp, node_index);
return std::nullopt;
}
return flexbuffers::GetRoot(
static_cast<const uint8_t*>(node->custom_initial_data),
node->custom_initial_data_size)
.AsMap();
}
return std::nullopt;
}
static bool ReadAttributes(TfLiteContext* context, const TfLiteNode* node,
const TfLiteRegistration* registration,
int node_index, MoeExpertsAttributes* attr) {
std::optional<flexbuffers::Map> map =
ReadAttributeMap(context, node, registration, node_index);
if (!map.has_value()) {
return false;
}
for (const char* key : {"num_experts", "num_active_experts", "model_dim",
"hidden_dim", "weight_type"}) {
if ((*map)[key].IsNull()) {
TF_LITE_KERNEL_LOG(context, "%s node #%d is missing attribute %s",
kMoeCustomOp, node_index, key);
return false;
}
}
const std::string weight_type = (*map)["weight_type"].AsString().str();
if (weight_type != "fp32") {
TF_LITE_KERNEL_LOG(context,
"%s node #%d has unsupported weight_type '%s' for the "
"XNNPACK prototype path",
kMoeCustomOp, node_index, weight_type.c_str());
return false;
}
if (!(*map)["activation"].IsNull() &&
(*map)["activation"].AsString().str() != "gelu") {
TF_LITE_KERNEL_LOG(context, "%s node #%d only supports activation='gelu'",
kMoeCustomOp, node_index);
return false;
}
attr->num_experts = (*map)["num_experts"].AsInt32();
attr->num_active_experts = (*map)["num_active_experts"].AsInt32();
attr->model_dim = (*map)["model_dim"].AsInt32();
attr->hidden_dim = (*map)["hidden_dim"].AsInt32();
if (attr->num_experts <= 0 || attr->num_active_experts <= 0 ||
attr->num_active_experts > attr->num_experts || attr->model_dim <= 0 ||
attr->hidden_dim <= 0) {
TF_LITE_KERNEL_LOG(context, "%s node #%d has invalid dimensions",
kMoeCustomOp, node_index);
return false;
}
return true;
}
static bool CreateDynamicFullyConnected(TfLiteContext* context,
int node_index, XnnOperatorPtr* op) {
xnn_operator_t raw_op = nullptr;
const xnn_status status = xnn_create_dynamic_fully_connected_nc_f32(
/*output_min=*/-std::numeric_limits<float>::infinity(),
/*output_max=*/+std::numeric_limits<float>::infinity(),
/*flags=*/0, &raw_op);
if (status != xnn_status_success) {
TF_LITE_KERNEL_LOG(context,
"failed to create XNNPACK dynamic FC for %s node #%d",
kMoeCustomOp, node_index);
return false;
}
*op = XnnOperatorPtr(raw_op, &xnn_delete_operator);
return true;
}
static float Gelu(float x) {
// TODO: lower this to xnn unary gelu once the expert body is expressed
// as a subgraph instead of host-stitched dynamic FC calls.
return 0.5f * x * std::erfc(x * -0.70710678118654752440f);
}
static void* AlignWorkspace(void* ptr) {
const uintptr_t address = reinterpret_cast<uintptr_t>(ptr);
const uintptr_t aligned = (address + kMoeXnnpackWorkspaceAlignment - 1) &
~(kMoeXnnpackWorkspaceAlignment - 1);
return reinterpret_cast<void*>(aligned);
}
template <typename T>
static void EnsureSize(std::vector<T>* buffer, size_t size) {
if (buffer->size() < size) {
buffer->resize(size);
}
}
bool BuildExpertAssignments(TfLiteContext* context,
const int32_t* top_indices, int tokens,
int dispatches) {
EnsureSize(&expert_counts_, attr_.num_experts);
std::fill(expert_counts_.begin(),
expert_counts_.begin() + attr_.num_experts, 0);
EnsureSize(&normalized_experts_, dispatches);
for (int token = 0; token < tokens; ++token) {
for (int route = 0; route < attr_.num_active_experts; ++route) {
const int dispatch = token * attr_.num_active_experts + route;
int expert = top_indices[dispatch];
if (expert < 0) {
expert += attr_.num_experts;
}
if (expert < 0 || expert >= attr_.num_experts) {
TF_LITE_KERNEL_LOG(context, "%s expert index %d is out of range",
kMoeCustomOp, expert);
return false;
}
normalized_experts_[dispatch] = expert;
++expert_counts_[expert];
}
}
EnsureSize(&expert_offsets_, attr_.num_experts + 1);
expert_offsets_[0] = 0;
for (int expert = 0; expert < attr_.num_experts; ++expert) {
expert_offsets_[expert + 1] =
expert_offsets_[expert] + expert_counts_[expert];
}
EnsureSize(&write_offsets_, attr_.num_experts);
std::copy_n(expert_offsets_.begin(), attr_.num_experts,
write_offsets_.begin());
EnsureSize(&assignments_, dispatches);
for (int token = 0; token < tokens; ++token) {
for (int route = 0; route < attr_.num_active_experts; ++route) {
const int dispatch = token * attr_.num_active_experts + route;
const int expert = normalized_experts_[dispatch];
assignments_[write_offsets_[expert]++] = {token, route};
}
}
return true;
}
static void CopyExpertWeightRows(const float* weight, int num_experts,
int expert, int output_channels,
int input_channels, float* dst) {
for (int out = 0; out < output_channels; ++out) {
const float* src = weight + (out * num_experts + expert) * input_channels;
std::memcpy(dst + out * input_channels, src,
input_channels * sizeof(float));
}
}
void CopyGateUpExpertWeight(const float* gate_weight, const float* ff1_weight,
int expert) {
const int rows = 2 * attr_.hidden_dim;
EnsureSize(&kernel_buffer_, rows * attr_.model_dim);
float* dst = kernel_buffer_.data();
// TODO: replace this host-side row gather when the delegate can either
// consume expert-major weights directly or lower this as a reusable gather.
CopyExpertWeightRows(gate_weight, attr_.num_experts, expert,
attr_.hidden_dim, attr_.model_dim, dst);
CopyExpertWeightRows(ff1_weight, attr_.num_experts, expert,
attr_.hidden_dim, attr_.model_dim,
dst + attr_.hidden_dim * attr_.model_dim);
}
void CopyExpertWeight(const float* weight, int expert, int output_channels,
int input_channels) {
EnsureSize(&kernel_buffer_, output_channels * input_channels);
// TODO: replace this host-side row gather when the xnn can either
// consume expert-major weights directly or lower this as a reusable gather.
CopyExpertWeightRows(weight, attr_.num_experts, expert, output_channels,
input_channels, kernel_buffer_.data());
}
bool RunDynamicFullyConnected(TfLiteContext* context, xnn_operator_t op,
int batch_size, int input_channels,
int output_channels, const float* input,
const float* kernel, float* output) {
size_t workspace_size = 0;
xnn_status status = xnn_reshape_dynamic_fully_connected_nc_f32(
op, batch_size, input_channels, output_channels, input_channels,
output_channels, &workspace_size, threadpool_);
if (status != xnn_status_success) {
TF_LITE_KERNEL_LOG(context, "%s failed to reshape dynamic FC",
kMoeCustomOp);
return false;
}
if (workspace_size == 0) {
TF_LITE_KERNEL_LOG(context, "%s dynamic FC returned empty workspace",
kMoeCustomOp);
return false;
}
EnsureSize(&workspace_, workspace_size + kMoeXnnpackWorkspaceAlignment - 1);
char* workspace = static_cast<char*>(AlignWorkspace(workspace_.data()));
status = xnn_setup_dynamic_fully_connected_nc_f32(
op, workspace, input, kernel, /*bias=*/nullptr, output);
if (status != xnn_status_success) {
TF_LITE_KERNEL_LOG(context, "%s failed to setup dynamic FC",
kMoeCustomOp);
return false;
}
status = xnn_run_operator(op, threadpool_);
if (status != xnn_status_success) {
TF_LITE_KERNEL_LOG(context, "%s failed to run dynamic FC", kMoeCustomOp);
return false;
}
return true;
}
bool RunExpert(TfLiteContext* context, int expert,
const MoeExpertsAssignment* expert_assignments,
int routed_tokens, const float* src, const float* top_weights,
const float* gate_weight, const float* ff1_weight,
const float* linear_weight, const float* per_expert_scale,
float* output) {
EnsureSize(&routed_src_, routed_tokens * attr_.model_dim);
EnsureSize(&gate_up_, routed_tokens * 2 * attr_.hidden_dim);
EnsureSize(&hidden_, routed_tokens * attr_.hidden_dim);
EnsureSize(&down_, routed_tokens * attr_.model_dim);
// TODO: lower this token dispatch as a gather-style delegate op. Keeping it
// here preserves correctness while XNNPACK lacks a ragged/grouped gather.
for (int i = 0; i < routed_tokens; ++i) {
const int token = expert_assignments[i].token;
const float* token_src = src + token * attr_.model_dim;
std::memcpy(routed_src_.data() + i * attr_.model_dim, token_src,
attr_.model_dim * sizeof(float));
}
CopyGateUpExpertWeight(gate_weight, ff1_weight, expert);
if (!RunDynamicFullyConnected(context, gate_up_fc_.get(), routed_tokens,
attr_.model_dim, 2 * attr_.hidden_dim,
routed_src_.data(), kernel_buffer_.data(),
gate_up_.data())) {
return false;
}
for (int token = 0; token < routed_tokens; ++token) {
const float* gate = gate_up_.data() + token * 2 * attr_.hidden_dim;
const float* ff1 = gate + attr_.hidden_dim;
float* hidden = hidden_.data() + token * attr_.hidden_dim;
for (int dim = 0; dim < attr_.hidden_dim; ++dim) {
hidden[dim] = Gelu(gate[dim]) * ff1[dim];
}
}
CopyExpertWeight(linear_weight, expert, attr_.model_dim, attr_.hidden_dim);
if (!RunDynamicFullyConnected(context, linear_fc_.get(), routed_tokens,
attr_.hidden_dim, attr_.model_dim,
hidden_.data(), kernel_buffer_.data(),
down_.data())) {
return false;
}
const float expert_scale = per_expert_scale[expert];
// TODO: lower this route-weighted scatter-add as a delegate op once the
// XNNPACK path has a reusable primitive for ragged MoE combine.
for (int i = 0; i < routed_tokens; ++i) {
const int token = expert_assignments[i].token;
const int route = expert_assignments[i].route;
const float route_scale =
expert_scale * top_weights[token * attr_.num_active_experts + route];
float* token_output = output + token * attr_.model_dim;
const float* token_down = down_.data() + i * attr_.model_dim;
for (int dim = 0; dim < attr_.model_dim; ++dim) {
token_output[dim] += token_down[dim] * route_scale;
}
}
return true;
}
MoeExpertsAttributes attr_;
int src_id_ = -1;
int top_weights_id_ = -1;
int top_indices_id_ = -1;
int gate_weight_id_ = -1;
int ff1_weight_id_ = -1;
int linear_weight_id_ = -1;
int per_expert_scale_id_ = -1;
int output_id_ = -1;
XnnOperatorPtr gate_up_fc_{nullptr, &xnn_delete_operator};
XnnOperatorPtr linear_fc_{nullptr, &xnn_delete_operator};
pthreadpool_t threadpool_ = nullptr;
std::vector<int> expert_counts_;
std::vector<int> expert_offsets_;
std::vector<int> write_offsets_;
std::vector<int> normalized_experts_;
std::vector<MoeExpertsAssignment> assignments_;
std::vector<float> routed_src_;
std::vector<float> gate_up_;
std::vector<float> hidden_;
std::vector<float> down_;
std::vector<float> kernel_buffer_;
std::vector<char> workspace_;
};
MoeExpertsDelegateKernel::MoeExpertsDelegateKernel(std::unique_ptr<Impl> impl)
: impl_(std::move(impl)) {}
MoeExpertsDelegateKernel::~MoeExpertsDelegateKernel() = default;
bool MoeExpertsDelegateKernel::IsMoeExpertsNode(
const TfLiteRegistration* registration, const TfLiteNode* node) {
return Impl::IsMoeExpertsNode(registration, node);
}
TfLiteStatus MoeExpertsDelegateKernel::IsSupported(
TfLiteContext* context, const TfLiteNode* node,
const TfLiteRegistration* registration, int node_index) {
return Impl::IsSupported(context, node, registration, node_index);
}
std::unique_ptr<MoeExpertsDelegateKernel> MoeExpertsDelegateKernel::Create(
TfLiteContext* context, const TfLiteDelegateParams* params,
pthreadpool_t threadpool) {
std::unique_ptr<Impl> impl = Impl::Create(context, params, threadpool);
if (impl == nullptr) {
return nullptr;
}
return std::unique_ptr<MoeExpertsDelegateKernel>(
new MoeExpertsDelegateKernel(std::move(impl)));
}
TfLiteStatus MoeExpertsDelegateKernel::Prepare(TfLiteContext* context) {
return impl_->Prepare(context);
}
TfLiteStatus MoeExpertsDelegateKernel::Invoke(TfLiteContext* context) {
return impl_->Invoke(context);
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,60 @@
/* Copyright 2026 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_MOE_DELEGATE_KERNEL_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_MOE_DELEGATE_KERNEL_H_
#include <memory>
#include "pthreadpool.h" // from @pthreadpool
#include "tensorflow/lite/core/c/common.h"
namespace tflite {
namespace xnnpack {
// Executor for the opt-in custom "moe" XNNPACK delegate path.
//
// This class handles only singleton MoE delegate partitions. Ordinary XNNPACK
// nodes are still lowered through the regular XNNPACK subgraph path.
class MoeExpertsDelegateKernel {
public:
~MoeExpertsDelegateKernel();
static bool IsMoeExpertsNode(const TfLiteRegistration* registration,
const TfLiteNode* node);
static TfLiteStatus IsSupported(TfLiteContext* context,
const TfLiteNode* node,
const TfLiteRegistration* registration,
int node_index);
static std::unique_ptr<MoeExpertsDelegateKernel> Create(
TfLiteContext* context, const TfLiteDelegateParams* params,
pthreadpool_t threadpool);
TfLiteStatus Prepare(TfLiteContext* context);
TfLiteStatus Invoke(TfLiteContext* context);
private:
class Impl;
explicit MoeExpertsDelegateKernel(std::unique_ptr<Impl> impl);
std::unique_ptr<Impl> impl_;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_MOE_DELEGATE_KERNEL_H_
@@ -0,0 +1,113 @@
/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <memory>
#include <ostream>
#include <string>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/odml_sdpa_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
struct SDPATestParams {
std::string model_name;
std::string custom_test_name;
int batch;
int input_seq_len;
int max_seq_len;
int q_heads;
int kv_heads;
int head_dim; // embedding_dim//q_heads
};
void PrintTo(const SDPATestParams& p, std::ostream* os) {
if (p.model_name != kOdmlSdpaCustom) {
*os << "{ TFLite file: " << p.model_name << ".tflite.bin }";
} else {
*os << "{ Custom test: " << p.custom_test_name << ", b:" << p.batch
<< ", isl:" << p.input_seq_len << ", msl:" << p.max_seq_len
<< ", q:" << p.q_heads << ", k:" << p.kv_heads << "h:" << p.head_dim
<< " }";
}
}
std::string TestName(const testing::TestParamInfo<SDPATestParams>& info) {
if (info.param.model_name != kOdmlSdpaCustom) {
return info.param.model_name;
}
return "CustomOp" + info.param.custom_test_name;
}
class SDPATest : public testing::TestWithParam<SDPATestParams> {};
TEST_P(SDPATest, CompareWithTFLiteReference) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
const SDPATestParams& p = GetParam();
ODMLSDPATester tester(p.model_name);
if (p.model_name == kOdmlSdpaCustom) {
tester
.QueryShape({p.batch, p.input_seq_len, p.q_heads, p.head_dim}) // q
.KeyShape({p.batch, p.max_seq_len, p.kv_heads, p.head_dim}) // k
.ValueShape({p.batch, p.max_seq_len, p.kv_heads, p.head_dim}) // v
.MaskShape({p.batch, 1, p.input_seq_len, p.max_seq_len}); // mask
}
tester.Test(xnnpack_delegate.get());
}
INSTANTIATE_TEST_SUITE_P(SDPA, SDPATest,
testing::Values(SDPATestParams{kOdmlSdpaCompositeMqa},
SDPATestParams{kOdmlSdpaCompositeMha},
SDPATestParams{kOdmlSdpaCompositeGqa},
SDPATestParams{
kOdmlSdpaCustom,
/*.custom_test_name=*/"MQA",
/*.batch=*/1,
/*.input_seq_len=*/1,
/*.max_seq_len=*/64,
/*.q_heads=*/32,
/*.kv_heads=*/1,
/*.head_dim=*/4,
},
SDPATestParams{
kOdmlSdpaCustom,
/*.custom_test_name=*/"MHA",
/*.batch=*/1,
/*.input_seq_len=*/1,
/*.max_seq_len=*/64,
/*.q_heads=*/32,
/*.kv_heads=*/32,
/*.head_dim=*/4,
},
SDPATestParams{
kOdmlSdpaCustom,
/*.custom_test_name=*/"GQA",
/*.batch=*/1,
/*.input_seq_len=*/1,
/*.max_seq_len=*/64,
/*.q_heads=*/32,
/*.kv_heads=*/4,
/*.head_dim=*/4,
}),
TestName);
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,211 @@
/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/odml_sdpa_tester.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <string>
#include <vector>
#include "flatbuffers/flexbuffers.h"
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "flatbuffers/util.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/compiler/mlir/lite/schema/schema_generated.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/experimental/genai/genai_ops.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/kernels/kernel_util.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
std::vector<int32_t> ODMLSDPATester::OutputShape() const {
std::vector<int32_t> output_shape = QueryShape();
return output_shape;
}
void ODMLSDPATester::Test(TfLiteDelegate* delegate) const {
// Test for SDPA XNNPACK delegate vs TfLite Reference SDPA op.
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_real_distribution<float>(), std::ref(rng));
std::vector<char> buffer = CreateTfLiteModel();
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
auto resolver =
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates();
resolver.AddCustom("odml.scaled_dot_product_attention",
tflite::ops::custom::Register_SDPA());
ASSERT_EQ(InterpreterBuilder(model, resolver)(&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(InterpreterBuilder(model, resolver)(&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 4);
ASSERT_EQ(default_interpreter->inputs().size(), 4);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
for (size_t i = 0; i < delegate_interpreter->inputs().size(); ++i) {
const TfLiteTensor* delegate_input_tensor = delegate_interpreter->tensor(i);
const size_t num_elts = NumElements(delegate_input_tensor);
float* const delegate_input_data =
delegate_interpreter->typed_input_tensor<float>(i);
float* const default_input_data =
default_interpreter->typed_input_tensor<float>(i);
std::generate_n(delegate_input_data, num_elts, std::ref(input_rng));
std::copy_n(delegate_input_data, num_elts, default_input_data);
}
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
float* delegate_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
const int32_t output_size =
NumElements(delegate_interpreter->output_tensor(0));
for (size_t i = 0; i < output_size; i++) {
ASSERT_NEAR(default_output_data[i], delegate_output_data[i],
std::numeric_limits<float>::epsilon() *
std::max(std::abs(default_output_data[i]) * 20.0f, 1.0f));
}
}
std::vector<char> ODMLSDPATester::CreateTfLiteModel() const {
if (!model_name_.empty() && model_name_ != kOdmlSdpaCustom) {
const char kTestModelFolder[] =
"tensorflow/lite/delegates/xnnpack/";
const std::string test_model =
kTestModelFolder + model_name_ + ".tflite.bin";
std::string model_data;
if (!flatbuffers::LoadFile(test_model.c_str(), /*binary=*/true,
&model_data)) {
ADD_FAILURE() << "file not loaded: " << test_model;
}
return std::vector<char>(model_data.begin(), model_data.end());
} else {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<OperatorCode> operator_code = CreateOperatorCode(
builder, BuiltinOperator_CUSTOM,
builder.CreateString("odml.scaled_dot_product_attention"));
const std::array<flatbuffers::Offset<Buffer>, 1> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
}};
const std::array<flatbuffers::Offset<Tensor>, 5> tensors{{
CreateTensor(builder,
builder.CreateVector<int32_t>(QueryShape().data(),
QueryShape().size()),
TensorType_FLOAT32),
CreateTensor(
builder,
builder.CreateVector<int32_t>(KeyShape().data(), KeyShape().size()),
TensorType_FLOAT32),
CreateTensor(builder,
builder.CreateVector<int32_t>(ValueShape().data(),
ValueShape().size()),
TensorType_FLOAT32),
CreateTensor(builder,
builder.CreateVector<int32_t>(MaskShape().data(),
MaskShape().size()),
TensorType_FLOAT32),
CreateTensor(builder,
builder.CreateVector<int32_t>(OutputShape().data(),
OutputShape().size()),
TensorType_FLOAT32),
}};
auto fbb = std::make_unique<flexbuffers::Builder>();
float scale = 1 / sqrt(QueryShape().data()[QueryShape().size() - 1]);
fbb->Map([&]() { fbb->Float("scale", scale); });
fbb->Finish();
const std::array<int32_t, 4> op_inputs{{0, 1, 2, 3}};
const std::array<int32_t, 1> op_outputs{{4}};
flatbuffers::Offset<Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
tflite::BuiltinOptions_NONE, 0,
builder.CreateVector<uint8_t>(
reinterpret_cast<const uint8_t*>(fbb->GetBuffer().data()),
fbb->GetSize()));
const std::array<int32_t, 4> subgraph_inputs{{0, 1, 2, 3}};
const std::array<int32_t, 1> subgraph_outputs{{4}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("ODML SDPA model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
}
int32_t ODMLSDPATester::ComputeSize(const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,119 @@
/* Copyright 2024 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_ODML_SDPA_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_ODML_SDPA_TESTER_H_
#include <cstdint>
#include <initializer_list>
#include <string>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/common.h"
namespace tflite {
namespace xnnpack {
constexpr const char kOdmlSdpaCompositeMqa[] = "odml_sdpa_composite_mqa";
constexpr const char kOdmlSdpaCompositeMha[] = "odml_sdpa_composite_mha";
constexpr const char kOdmlSdpaCompositeGqa[] = "odml_sdpa_composite_gqa";
constexpr const char kOdmlSdpaCustom[] = "odml_sdpa_custom";
class ODMLSDPATester {
public:
ODMLSDPATester() = default;
ODMLSDPATester(const ODMLSDPATester&) = delete;
ODMLSDPATester& operator=(const ODMLSDPATester&) = delete;
explicit ODMLSDPATester(const std::string& model_name)
: model_name_(model_name) {};
inline ODMLSDPATester& QueryShape(std::initializer_list<int32_t> shape) {
EXPECT_THAT(shape, testing::Each(testing::Gt(0)));
query_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
query_size_ = ComputeSize(query_shape_);
return *this;
}
inline const std::vector<int32_t>& QueryShape() const { return query_shape_; }
inline ODMLSDPATester& KeyShape(std::initializer_list<int32_t> shape) {
EXPECT_THAT(shape, testing::Each(testing::Gt(0)));
key_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
key_size_ = ComputeSize(key_shape_);
return *this;
}
inline const std::vector<int32_t>& KeyShape() const { return key_shape_; }
inline ODMLSDPATester& ValueShape(std::initializer_list<int32_t> shape) {
EXPECT_THAT(shape, testing::Each(testing::Gt(0)));
value_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
value_size_ = ComputeSize(value_shape_);
return *this;
}
inline const std::vector<int32_t>& ValueShape() const { return value_shape_; }
inline ODMLSDPATester& MaskShape(std::initializer_list<int32_t> shape) {
EXPECT_THAT(shape, testing::Each(testing::Gt(0)));
mask_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
mask_size_ = ComputeSize(mask_shape_);
return *this;
}
int32_t Batch() const { return query_shape_[0]; };
int32_t InputSeqLen() const { return query_shape_[1]; };
int32_t QHeads() const { return query_shape_[2]; };
int32_t HeadDim() const { return query_shape_[3]; };
int32_t MaxSeqLen() const { return key_shape_[1]; };
int32_t KVHeads() const { return key_shape_[2]; };
inline const std::vector<int32_t>& MaskShape() const { return mask_shape_; }
inline int32_t QuerySize() const { return query_size_; }
inline int32_t KeySize() const { return key_size_; }
inline int32_t ValueSize() const { return value_size_; }
inline int32_t MaskSize() const { return mask_size_; }
std::vector<int32_t> OutputShape() const;
static int32_t ComputeSize(const std::vector<int32_t>& shape);
void Test(TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel() const;
std::vector<int32_t> query_shape_;
std::vector<int32_t> key_shape_;
std::vector<int32_t> value_shape_;
std::vector<int32_t> mask_shape_;
int32_t query_size_ = 1;
int32_t key_size_ = 1;
int32_t value_size_ = 1;
int32_t mask_size_ = 1;
std::string model_name_;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_ODML_SDPA_TESTER_H_
@@ -0,0 +1,300 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/pad_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
TEST(Pad, Full4D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({pad_rng(), pad_rng(), pad_rng(), pad_rng()})
.InputPostPaddings({pad_rng(), pad_rng(), pad_rng(), pad_rng()})
.InputShape({shape_rng(), shape_rng(), shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, Batch4D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({pad_rng(), 0, 0, 0})
.InputPostPaddings({pad_rng(), 0, 0, 0})
.InputShape({shape_rng(), shape_rng(), shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, HeightAndWidth4D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({0, pad_rng(), pad_rng(), 0})
.InputPostPaddings({0, pad_rng(), pad_rng(), 0})
.InputShape({shape_rng(), shape_rng(), shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, Channels4D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({0, 0, 0, pad_rng()})
.InputPostPaddings({0, 0, 0, pad_rng()})
.InputShape({shape_rng(), shape_rng(), shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, Full3D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({pad_rng(), pad_rng(), pad_rng()})
.InputPostPaddings({pad_rng(), pad_rng(), pad_rng()})
.InputShape({shape_rng(), shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, Batch3D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({pad_rng(), 0, 0})
.InputPostPaddings({pad_rng(), 0, 0})
.InputShape({shape_rng(), shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, Width3D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({0, pad_rng(), 0})
.InputPostPaddings({0, pad_rng(), 0})
.InputShape({shape_rng(), shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, Channels3D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({0, 0, pad_rng()})
.InputPostPaddings({0, 0, pad_rng()})
.InputShape({shape_rng(), shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, Full2D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({pad_rng(), pad_rng()})
.InputPostPaddings({pad_rng(), pad_rng()})
.InputShape({shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, Batch2D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({pad_rng(), 0})
.InputPostPaddings({pad_rng(), 0})
.InputShape({shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, Channels2D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({0, pad_rng()})
.InputPostPaddings({0, pad_rng()})
.InputShape({shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, 1D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({pad_rng(), pad_rng()})
.InputPostPaddings({pad_rng(), pad_rng()})
.InputShape({shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({0, 0, 0, pad_rng()})
.InputPostPaddings({0, 0, 0, pad_rng()})
.InputShape({shape_rng(), shape_rng(), shape_rng(), shape_rng()})
.Test(xnnpack_delegate.get());
}
TEST(Pad, Full4D_FP16) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device; // NOLINT(runtime/random_device)
auto rng = std::mt19937(random_device()); // NOLINT(runtime/random_device)
auto pad_rng =
std::bind(std::uniform_int_distribution<int32_t>(1, 3), std::ref(rng));
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
PadTester()
.InputPrePaddings({pad_rng(), pad_rng(), pad_rng(), pad_rng()})
.InputPostPaddings({pad_rng(), pad_rng(), pad_rng(), pad_rng()})
.InputShape({shape_rng(), shape_rng(), shape_rng(), shape_rng()})
.FP16()
.Test(xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,223 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/pad_tester.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "fp16.h" // from @FP16
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
std::vector<int32_t> PadTester::OutputShape() const {
std::vector<int32_t> output_shape;
output_shape.reserve(InputShape().size());
for (size_t i = 0; i < InputShape().size(); i++) {
int32_t output_dim = InputShape()[i];
if (i < InputPrePaddings().size()) {
output_dim += InputPrePaddings()[i];
}
if (i < InputPostPaddings().size()) {
output_dim += InputPostPaddings()[i];
}
output_shape.push_back(output_dim);
}
return output_shape;
}
void PadTester::Test(TfLiteDelegate* delegate) const {
ASSERT_EQ(InputPrePaddings().size(), InputPostPaddings().size());
ASSERT_LE(InputPrePaddings().size(), InputShape().size());
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng =
std::bind(std::uniform_real_distribution<float>(), std::ref(rng));
std::vector<char> buffer = CreateTfLiteModel();
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (FP16()) {
TfLiteFloat16* default_input_data =
default_interpreter->typed_input_tensor<TfLiteFloat16>(0);
std::generate_n(
default_input_data, ComputeSize(InputShape()), [&]() -> TfLiteFloat16 {
return TfLiteFloat16{fp16_ieee_from_fp32_value(input_rng())};
});
TfLiteFloat16* delegate_input_data =
delegate_interpreter->typed_input_tensor<TfLiteFloat16>(0);
std::copy_n(default_input_data, ComputeSize(InputShape()),
delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
TfLiteFloat16* default_output_data =
default_interpreter->typed_output_tensor<TfLiteFloat16>(0);
TfLiteFloat16* delegate_output_data =
delegate_interpreter->typed_output_tensor<TfLiteFloat16>(0);
for (size_t i = 0; i < ComputeSize(OutputShape()); i++) {
ASSERT_EQ(default_output_data[i].data, delegate_output_data[i].data);
}
} else {
float* default_input_data =
default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input_data, ComputeSize(InputShape()),
std::ref(input_rng));
float* delegate_input_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input_data, ComputeSize(InputShape()),
delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* delegate_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
for (size_t i = 0; i < ComputeSize(OutputShape()); i++) {
ASSERT_EQ(default_output_data[i], delegate_output_data[i]);
}
}
}
std::vector<char> PadTester::CreateTfLiteModel() const {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<OperatorCode> operator_code =
CreateOperatorCode(builder, BuiltinOperator_PAD);
std::vector<int32_t> paddings(InputPrePaddings().size() +
InputPostPaddings().size());
for (size_t i = 0; i < InputPrePaddings().size(); i++) {
paddings[i * 2] = InputPrePaddings()[i];
paddings[i * 2 + 1] = InputPostPaddings()[i];
}
const std::array<flatbuffers::Offset<Buffer>, 2> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(paddings.data()),
sizeof(int32_t) * paddings.size())),
}};
const std::vector<int32_t> output_shape = OutputShape();
const std::array<int32_t, 2> paddings_shape{
{static_cast<int32_t>(InputPrePaddings().size()), 2}};
const std::array<flatbuffers::Offset<Tensor>, 3> tensors{{
CreateTensor(builder,
builder.CreateVector<int32_t>(InputShape().data(),
InputShape().size()),
FP16() ? TensorType_FLOAT16 : TensorType_FLOAT32),
CreateTensor(builder,
builder.CreateVector<int32_t>(paddings_shape.data(),
paddings_shape.size()),
TensorType_INT32, /*buffer=*/1),
CreateTensor(builder,
builder.CreateVector<int32_t>(output_shape.data(),
output_shape.size()),
FP16() ? TensorType_FLOAT16 : TensorType_FLOAT32),
}};
const std::array<int32_t, 2> op_inputs{{0, 1}};
const std::array<int32_t, 1> op_outputs{{2}};
flatbuffers::Offset<Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()));
const std::array<int32_t, 1> subgraph_inputs{{0}};
const std::array<int32_t, 1> subgraph_outputs{{2}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Pad model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t PadTester::ComputeSize(const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,95 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_PAD_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_PAD_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
namespace tflite {
namespace xnnpack {
class PadTester {
public:
PadTester() = default;
PadTester(const PadTester&) = delete;
PadTester& operator=(const PadTester&) = delete;
inline PadTester& InputShape(std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
input_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
return *this;
}
inline const std::vector<int32_t>& InputShape() const { return input_shape_; }
inline PadTester& InputPrePaddings(std::initializer_list<int32_t> paddings) {
for (auto it = paddings.begin(); it != paddings.end(); ++it) {
EXPECT_GE(*it, 0);
}
input_pre_paddings_ =
std::vector<int32_t>(paddings.begin(), paddings.end());
return *this;
}
inline const std::vector<int32_t> InputPrePaddings() const {
return input_pre_paddings_;
}
inline PadTester& InputPostPaddings(std::initializer_list<int32_t> paddings) {
for (auto it = paddings.begin(); it != paddings.end(); ++it) {
EXPECT_GE(*it, 0);
}
input_post_paddings_ =
std::vector<int32_t>(paddings.begin(), paddings.end());
return *this;
}
inline const std::vector<int32_t> InputPostPaddings() const {
return input_post_paddings_;
}
std::vector<int32_t> OutputShape() const;
void Test(TfLiteDelegate* delegate) const;
inline PadTester& FP16(bool fp16 = true) {
fp16_ = fp16;
return *this;
}
inline bool FP16() const { return fp16_; }
private:
std::vector<char> CreateTfLiteModel() const;
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> input_shape_;
std::vector<int32_t> input_pre_paddings_;
std::vector<int32_t> input_post_paddings_;
bool fp16_ = false;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_PAD_TESTER_H_
@@ -0,0 +1,205 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/pool_2d_tester.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
void Pool2DTester::Test(tflite::BuiltinOperator pool_op,
TfLiteDelegate* delegate) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto range_rng = std::bind(
std::uniform_real_distribution<float>(-25.0f, 25.0f), std::ref(rng));
std::vector<char> buffer = CreateTfLiteModel(pool_op);
const tflite::Model* model = tflite::GetModel(buffer.data());
std::unique_ptr<tflite::Interpreter> delegate_interpreter;
ASSERT_EQ(
tflite::InterpreterBuilder(
model,
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<tflite::Interpreter> default_interpreter;
ASSERT_EQ(
tflite::InterpreterBuilder(
model,
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
float* default_input_data = default_interpreter->typed_input_tensor<float>(0);
for (int32_t i = 0; i < BatchSize(); i++) {
for (int32_t c = 0; c < Channels(); c++) {
// Use the same range of all-positive or all-negative values to generate
// all pixels within the same batch index & channel, but different ranges
// for different channels or batches. This ensures that no catastrophic
// cancellation occur, but test covers both positive and negative inputs.
const float range = range_rng();
auto value_rng =
std::bind(std::uniform_real_distribution<float>(
std::min(range, 0.0f), std::max(range, 0.0f)),
std::ref(rng));
for (int32_t y = 0; y < InputHeight(); y++) {
for (int32_t x = 0; x < InputWidth(); x++) {
const int32_t index =
((i * InputHeight() + y) * InputWidth() + x) * Channels() + c;
default_input_data[index] = value_rng();
}
}
}
}
float* xnnpack_input_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy(default_input_data,
default_input_data +
BatchSize() * InputHeight() * InputWidth() * Channels(),
xnnpack_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* xnnpack_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
for (int32_t i = 0; i < BatchSize(); i++) {
for (int32_t y = 0; y < OutputHeight(); y++) {
for (int32_t x = 0; x < OutputWidth(); x++) {
for (int32_t c = 0; c < Channels(); c++) {
const int32_t index =
((i * OutputHeight() + y) * OutputWidth() + x) * Channels() + c;
if (pool_op == BuiltinOperator_MAX_POOL_2D) {
// MaxPooling results must be exact
ASSERT_EQ(default_output_data[index], xnnpack_output_data[index])
<< "batch " << i << " / " << BatchSize() << ", y position " << y
<< " / " << OutputHeight() << ", x position " << x << " / "
<< OutputWidth() << ", channel " << c << " / " << Channels();
} else {
ASSERT_NEAR(default_output_data[index], xnnpack_output_data[index],
std::abs(default_output_data[index]) * 3.0e-6f)
<< "batch " << i << " / " << BatchSize() << ", y position " << y
<< " / " << OutputHeight() << ", x position " << x << " / "
<< OutputWidth() << ", channel " << c << " / " << Channels();
}
}
}
}
}
}
std::vector<char> Pool2DTester::CreateTfLiteModel(
tflite::BuiltinOperator pool_op) const {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<tflite::OperatorCode> operator_code =
CreateOperatorCode(builder, pool_op, 0);
flatbuffers::Offset<tflite::Pool2DOptions> pool_2d_options =
CreatePool2DOptions(builder, Padding(), StrideWidth(), StrideHeight(),
PoolingWidth(), PoolingHeight(), Activation());
const flatbuffers::Offset<tflite::Buffer> null_buffer =
tflite::CreateBuffer(builder, builder.CreateVector({}));
const std::array<int32_t, 4> input_shape{
{BatchSize(), InputHeight(), InputWidth(), Channels()}};
const std::array<int32_t, 4> output_shape{
{BatchSize(), OutputHeight(), OutputWidth(), Channels()}};
const std::array<flatbuffers::Offset<tflite::Tensor>, 2> tensors{{
tflite::CreateTensor(
builder,
builder.CreateVector<int32_t>(input_shape.data(), input_shape.size()),
tflite::TensorType_FLOAT32),
tflite::CreateTensor(builder,
builder.CreateVector<int32_t>(output_shape.data(),
output_shape.size()),
tflite::TensorType_FLOAT32),
}};
const std::array<int32_t, 1> op_inputs{{0}};
const std::array<int32_t, 1> op_outputs{{1}};
flatbuffers::Offset<tflite::Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
tflite::BuiltinOptions_Pool2DOptions, pool_2d_options.Union());
const std::array<int32_t, 1> subgraph_inputs{{0}};
const std::array<int32_t, 1> subgraph_outputs{{1}};
flatbuffers::Offset<tflite::SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Pool2D model");
flatbuffers::Offset<tflite::Model> model_buffer = tflite::CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(&null_buffer, 1));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,177 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_POOL_2D_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_POOL_2D_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class Pool2DTester {
public:
Pool2DTester() = default;
Pool2DTester(const Pool2DTester&) = delete;
Pool2DTester& operator=(const Pool2DTester&) = delete;
inline Pool2DTester& BatchSize(int32_t batch_size) {
EXPECT_GT(batch_size, 0);
batch_size_ = batch_size;
return *this;
}
inline int32_t BatchSize() const { return batch_size_; }
inline Pool2DTester& Channels(int32_t channels) {
EXPECT_GT(channels, 0);
channels_ = channels;
return *this;
}
inline int32_t Channels() const { return channels_; }
inline Pool2DTester& InputHeight(int32_t input_height) {
EXPECT_GT(input_height, 0);
input_height_ = input_height;
return *this;
}
inline int32_t InputHeight() const { return input_height_; }
inline Pool2DTester& InputWidth(int32_t input_width) {
EXPECT_GT(input_width, 0);
input_width_ = input_width;
return *this;
}
inline int32_t InputWidth() const { return input_width_; }
inline int32_t OutputWidth() const {
if (Padding() == ::tflite::Padding_SAME) {
return (InputWidth() - 1) / StrideWidth() + 1;
} else {
return (InputWidth() - PoolingWidth()) / StrideWidth() + 1;
}
}
inline int32_t OutputHeight() const {
if (Padding() == ::tflite::Padding_SAME) {
return (InputHeight() - 1) / StrideHeight() + 1;
} else {
return (InputHeight() - PoolingHeight()) / StrideHeight() + 1;
}
}
inline Pool2DTester& PoolingHeight(int32_t pooling_height) {
EXPECT_GT(pooling_height, 0);
pooling_height_ = pooling_height;
return *this;
}
inline int32_t PoolingHeight() const { return pooling_height_; }
inline Pool2DTester& PoolingWidth(int32_t pooling_width) {
EXPECT_GT(pooling_width, 0);
pooling_width_ = pooling_width;
return *this;
}
inline int32_t PoolingWidth() const { return pooling_width_; }
inline Pool2DTester& StrideHeight(int32_t stride_height) {
EXPECT_GT(stride_height, 0);
stride_height_ = stride_height;
return *this;
}
inline int32_t StrideHeight() const { return stride_height_; }
inline Pool2DTester& StrideWidth(int32_t stride_width) {
EXPECT_GT(stride_width, 0);
stride_width_ = stride_width;
return *this;
}
inline int32_t StrideWidth() const { return stride_width_; }
inline Pool2DTester& SamePadding() {
padding_ = ::tflite::Padding_SAME;
return *this;
}
inline Pool2DTester& ValidPadding() {
padding_ = ::tflite::Padding_VALID;
return *this;
}
inline Pool2DTester& ReluActivation() {
activation_ = ::tflite::ActivationFunctionType_RELU;
return *this;
}
inline Pool2DTester& Relu6Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU6;
return *this;
}
inline Pool2DTester& ReluMinus1To1Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU_N1_TO_1;
return *this;
}
inline Pool2DTester& TanhActivation() {
activation_ = ::tflite::ActivationFunctionType_TANH;
return *this;
}
inline Pool2DTester& SignBitActivation() {
activation_ = ::tflite::ActivationFunctionType_SIGN_BIT;
return *this;
}
void Test(tflite::BuiltinOperator pool_op, TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel(tflite::BuiltinOperator pool_op) const;
inline ::tflite::Padding Padding() const { return padding_; }
inline ::tflite::ActivationFunctionType Activation() const {
return activation_;
}
int32_t batch_size_ = 1;
int32_t channels_ = 1;
int32_t input_height_ = 1;
int32_t input_width_ = 1;
int32_t pooling_height_ = 1;
int32_t pooling_width_ = 1;
int32_t stride_height_ = 1;
int32_t stride_width_ = 1;
::tflite::Padding padding_ = ::tflite::Padding_VALID;
::tflite::ActivationFunctionType activation_ =
::tflite::ActivationFunctionType_NONE;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_POOL_2D_TESTER_H_
@@ -0,0 +1,654 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/prelu_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
// TODO(b/159727692)
TEST(Prelu, DISABLED_4DBy4D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({batch, height, width, channels})
.Test(xnnpack_delegate.get());
}
TEST(Prelu, 4DBy4DBroadcastChannels) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({1, 1, 1, channels})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_4DBy4DBroadcastWidth) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({1, 1, width, 1})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_4DBy4DBroadcastHeight) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({1, height, 1, 1})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_4DBy4DBroadcastBatch) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({batch, 1, 1, 1})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_4DBy4DBroadcastHeightWidthChannels) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({1, height, width, channels})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_4DBy3D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({height, width, channels})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_4DBy2D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({width, channels})
.Test(xnnpack_delegate.get());
}
TEST(Prelu, 4DBy1D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({channels})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_4DBy0D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_3DBy3D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, width, channels})
.SlopeShape({batch, width, channels})
.Test(xnnpack_delegate.get());
}
TEST(Prelu, 3DBy3DBroadcastChannels) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, width, channels})
.SlopeShape({1, 1, channels})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_3DBy3DBroadcastWidth) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, width, channels})
.SlopeShape({1, width, 1})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_3DBy3DBroadcastBatch) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, width, channels})
.SlopeShape({batch, 1, 1})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_3DBy3DBroadcastWidthChannels) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, width, channels})
.SlopeShape({1, width, channels})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_3DBy2D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, width, channels})
.SlopeShape({width, channels})
.Test(xnnpack_delegate.get());
}
TEST(Prelu, 3DBy1D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, width, channels})
.SlopeShape({channels})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_3DBy0D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, width, channels})
.SlopeShape({})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_2DBy2D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, channels})
.SlopeShape({batch, channels})
.Test(xnnpack_delegate.get());
}
TEST(Prelu, 2DBy2DBroadcastChannels) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, channels})
.SlopeShape({1, channels})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_2DBy2DBroadcastBatch) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, channels})
.SlopeShape({batch, 1})
.Test(xnnpack_delegate.get());
}
TEST(Prelu, 2DBy1D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, channels})
.SlopeShape({channels})
.Test(xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_2DBy0D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, channels})
.SlopeShape({})
.Test(xnnpack_delegate.get());
}
TEST(Prelu, 1DBy1D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
PreluTester().InputShape({batch}).SlopeShape({batch}).Test(
xnnpack_delegate.get());
}
// TODO(b/159727692)
TEST(Prelu, DISABLED_1DBy0D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
PreluTester().InputShape({batch}).SlopeShape({}).Test(xnnpack_delegate.get());
}
TEST(Prelu, FP16Weights) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({channels})
.FP16Weights()
.Test(xnnpack_delegate.get());
}
TEST(Prelu, INT8Weights) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({channels})
.INT8Weights()
.Test(xnnpack_delegate.get());
}
TEST(Prelu, INT8ChannelWiseWeights) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({channels})
.INT8ChannelWiseWeights()
.Test(xnnpack_delegate.get());
}
TEST(Prelu, SparseWeights) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({channels})
.SparseWeights()
.Test(xnnpack_delegate.get());
}
TEST(Prelu, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({channels})
.Test(xnnpack_delegate.get());
}
TEST(Prelu, WeightsCache) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
std::unique_ptr<TfLiteXNNPackDelegateWeightsCache,
decltype(&TfLiteXNNPackDelegateWeightsCacheDelete)>
weights_cache(TfLiteXNNPackDelegateWeightsCacheCreate(),
TfLiteXNNPackDelegateWeightsCacheDelete);
delegate_options.weights_cache = weights_cache.get();
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
PreluTester()
.InputShape({batch, height, width, channels})
.SlopeShape({channels})
.WeightsCache(weights_cache.get())
.Test(xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,302 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/prelu_tester.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "fp16.h" // from @FP16
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
void PreluTester::Test(TfLiteDelegate* delegate) const {
if (INT8ChannelWiseWeights()) {
ASSERT_FALSE(SlopeShape().empty());
}
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng = std::bind(std::uniform_real_distribution<float>(-1.0f, 1.0f),
std::ref(rng));
std::vector<char> buffer = CreateTfLiteModel();
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (weights_cache_ != nullptr) {
TfLiteXNNPackDelegateWeightsCacheFinalizeHard(weights_cache_);
}
float* default_input_data = default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input_data, ComputeSize(InputShape()),
std::ref(input_rng));
float* xnnpack_input_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input_data, ComputeSize(InputShape()),
xnnpack_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
float* default_output_data =
default_interpreter->typed_output_tensor<float>(0);
float* xnnpack_output_data =
delegate_interpreter->typed_output_tensor<float>(0);
for (size_t i = 0; i < ComputeSize(OutputShape()); i++) {
ASSERT_EQ(default_output_data[i], xnnpack_output_data[i]);
}
}
std::vector<char> PreluTester::CreateTfLiteModel() const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto slope_rng = std::bind(std::uniform_real_distribution<float>(0.25f, 0.5f),
std::ref(rng));
flatbuffers::FlatBufferBuilder builder;
std::vector<flatbuffers::Offset<OperatorCode>> operator_codes{
{CreateOperatorCode(builder, BuiltinOperator_PRELU)}};
if (FP16Weights() || INT8Weights() || INT8ChannelWiseWeights()) {
operator_codes.emplace_back(
CreateOperatorCode(builder, BuiltinOperator_DEQUANTIZE));
} else if (SparseWeights()) {
operator_codes.emplace_back(
CreateOperatorCode(builder, BuiltinOperator_DENSIFY));
}
std::vector<flatbuffers::Offset<Buffer>> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
}};
std::vector<float> slope_scales;
std::vector<int64_t> slope_zero_points;
int32_t slope_quantized_dimension = 0;
if (FP16Weights()) {
std::vector<uint16_t> slope_data(ComputeSize(SlopeShape()));
std::generate(slope_data.begin(), slope_data.end(),
std::bind(fp16_ieee_from_fp32_value, slope_rng));
buffers.push_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(slope_data.data()),
sizeof(uint16_t) * slope_data.size())));
} else {
std::vector<float> slope_data(ComputeSize(SlopeShape()));
std::generate(slope_data.begin(), slope_data.end(), slope_rng);
if (INT8Weights()) {
std::vector<int8_t> quantized_slope_data(slope_data.size());
slope_scales.resize(1, GetInt8QuantizationScale(slope_data));
slope_zero_points.resize(1, 0);
std::transform(
slope_data.begin(), slope_data.end(), quantized_slope_data.begin(),
std::bind(QuantizeInt8, std::placeholders::_1, 0, slope_scales[0]));
buffers.push_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_slope_data.data()),
sizeof(int8_t) * quantized_slope_data.size())));
} else if (INT8ChannelWiseWeights()) {
std::vector<int8_t> quantized_slope_data(slope_data.size());
slope_quantized_dimension = static_cast<int32_t>(SlopeShape().size()) - 1;
const int32_t num_scales = SlopeShape()[slope_quantized_dimension];
slope_scales = GetInt8QuantizationScalePerChannel(
slope_data.data(), slope_quantized_dimension, SlopeShape());
slope_zero_points.resize(num_scales, 0);
QuantizeInt8PerChannel(slope_scales.data(), slope_zero_points.data(),
slope_quantized_dimension, slope_data.data(),
quantized_slope_data.data(), SlopeShape());
buffers.push_back(CreateBuffer(
builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(quantized_slope_data.data()),
sizeof(int8_t) * quantized_slope_data.size())));
} else {
buffers.push_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(slope_data.data()),
sizeof(float) * slope_data.size())));
}
}
std::vector<flatbuffers::Offset<Tensor>> tensors;
std::vector<flatbuffers::Offset<Operator>> operators;
if (FP16Weights()) {
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(SlopeShape().data(), SlopeShape().size()),
TensorType_FLOAT16, /*buffer=*/1));
} else if (INT8Weights() || INT8ChannelWiseWeights()) {
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(SlopeShape().data(), SlopeShape().size()),
TensorType_INT8, /*buffer=*/1, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>(slope_scales),
builder.CreateVector<int64_t>(slope_zero_points),
/*details_type=*/QuantizationDetails_NONE,
/*details=*/0, slope_quantized_dimension)));
} else if (SparseWeights()) {
const int dims_count = SlopeShape().size();
std::vector<flatbuffers::Offset<DimensionMetadata>> dim_metadata(
dims_count);
std::vector<int> traversal_order(dims_count);
for (int i = 0; i < dims_count; i++) {
traversal_order[i] = i;
dim_metadata[i] = CreateDimensionMetadata(builder, DimensionType_DENSE,
SlopeShape()[i]);
}
const flatbuffers::Offset<SparsityParameters> sparsity_param =
CreateSparsityParameters(builder, builder.CreateVector(traversal_order),
0, builder.CreateVector(dim_metadata));
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(SlopeShape().data(), SlopeShape().size()),
TensorType_FLOAT32, /*buffer=*/1, /*name=*/0, /*quantization=*/0,
/*is_variable=*/false, /*sparsity=*/sparsity_param));
}
if (FP16Weights() || INT8Weights() || INT8ChannelWiseWeights()) {
const std::array<int32_t, 1> dequantize_inputs{{0}};
const std::array<int32_t, 1> dequantize_outputs{{2}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/1,
builder.CreateVector<int32_t>(dequantize_inputs.data(),
dequantize_inputs.size()),
builder.CreateVector<int32_t>(dequantize_outputs.data(),
dequantize_outputs.size())));
} else if (SparseWeights()) {
const std::array<int32_t, 1> densify_inputs{{0}};
const std::array<int32_t, 1> densify_outputs{{2}};
operators.emplace_back(
CreateOperator(builder, /*opcode_index=*/1,
builder.CreateVector<int32_t>(densify_inputs.data(),
densify_inputs.size()),
builder.CreateVector<int32_t>(densify_outputs.data(),
densify_outputs.size())));
}
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(InputShape().data(), InputShape().size()),
TensorType_FLOAT32));
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(SlopeShape().data(), SlopeShape().size()),
TensorType_FLOAT32,
/*buffer=*/
(FP16Weights() || INT8Weights() || INT8ChannelWiseWeights() ||
SparseWeights())
? 0
: 1));
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(OutputShape().data(), OutputShape().size()),
TensorType_FLOAT32));
const std::array<int32_t, 2> op_inputs{
{static_cast<int>(tensors.size()) - 3,
static_cast<int>(tensors.size()) - 2}};
const std::array<int32_t, 1> op_outputs{
{static_cast<int>(tensors.size()) - 1}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size())));
const std::array<int32_t, 1> subgraph_inputs{
{static_cast<int32_t>(tensors.size() - 3)}};
const std::array<int32_t, 1> subgraph_outputs{
{static_cast<int32_t>(tensors.size()) - 1}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(operators.data(), operators.size()));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("PReLU model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t PreluTester::ComputeSize(const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,114 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_PRELU_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_PRELU_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
namespace tflite {
namespace xnnpack {
class PreluTester {
public:
PreluTester() = default;
PreluTester(const PreluTester&) = delete;
PreluTester& operator=(const PreluTester&) = delete;
inline PreluTester& InputShape(std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
input_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
return *this;
}
inline const std::vector<int32_t>& InputShape() const { return input_shape_; }
inline PreluTester& SlopeShape(std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
slope_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
return *this;
}
inline const std::vector<int32_t>& SlopeShape() const { return slope_shape_; }
inline const std::vector<int32_t>& OutputShape() const {
return InputShape();
}
inline PreluTester& FP16Weights() {
fp16_weights_ = true;
return *this;
}
inline bool FP16Weights() const { return fp16_weights_; }
inline PreluTester& INT8Weights() {
int8_weights_ = true;
return *this;
}
inline bool INT8Weights() const { return int8_weights_; }
inline PreluTester& INT8ChannelWiseWeights() {
int8_channel_wise_weights_ = true;
return *this;
}
inline bool INT8ChannelWiseWeights() const {
return int8_channel_wise_weights_;
}
inline PreluTester& SparseWeights() {
sparse_weights_ = true;
return *this;
}
inline bool SparseWeights() const { return sparse_weights_; }
inline PreluTester& WeightsCache(
TfLiteXNNPackDelegateWeightsCache* weights_cache) {
weights_cache_ = weights_cache;
return *this;
}
void Test(TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel() const;
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> input_shape_;
std::vector<int32_t> slope_shape_;
bool fp16_weights_ = false;
bool int8_weights_ = false;
bool int8_channel_wise_weights_ = false;
bool sparse_weights_ = false;
TfLiteXNNPackDelegateWeightsCache* weights_cache_ = nullptr;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_PRELU_TESTER_H_
@@ -0,0 +1,61 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantization_util.h"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include "fp16.h" // from @FP16
#include "tensorflow/lite/kernels/internal/optimized/optimized_ops.h"
#include "tensorflow/lite/kernels/internal/reference/dequantize.h"
#include "tensorflow/lite/kernels/internal/runtime_shape.h"
#include "tensorflow/lite/kernels/internal/types.h"
namespace tflite {
namespace xnnpack {
void DequantizeFloat16(const uint16_t *packed_fp16_data,
float *unpacked_fp32_data, size_t tensor_elements) {
std::transform(packed_fp16_data, packed_fp16_data + tensor_elements,
unpacked_fp32_data, fp16_ieee_to_fp32_value);
}
void DequantizeInt8(const int8_t *packed_s8_data, float *unpacked_fp32_data,
const RuntimeShape &tensor_shape, int32_t zero_point,
double scale) {
DequantizationParams op_params;
op_params.zero_point = zero_point;
op_params.scale = scale;
optimized_ops::Dequantize(op_params, tensor_shape, packed_s8_data,
tensor_shape, unpacked_fp32_data);
}
void PerChannelDequantizeInt8(const int8_t* packed_s8_data,
float* unpacked_fp32_data,
const RuntimeShape& tensor_shape,
const int32_t* zero_points, const float* scales,
int32_t quantized_dimension) {
PerChannelDequantizationParams op_params;
op_params.zero_point = zero_points;
op_params.scale = scales;
op_params.quantized_dimension = quantized_dimension;
reference_ops::PerChannelDequantize(op_params, tensor_shape, packed_s8_data,
tensor_shape, unpacked_fp32_data);
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,56 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZATION_UTIL_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZATION_UTIL_H_
#include <cstddef>
#include <cstdint>
#include "tensorflow/lite/kernels/internal/runtime_shape.h"
#include "tensorflow/lite/kernels/internal/types.h"
namespace tflite {
namespace xnnpack {
// Dequantizes INT8 value using given zero point and scale.
// packed_s8_data should contain raw tensor data corresponding to
// a given tensor_shape. unpacked_fp32_data should be preallocated
// to have the same size.
void DequantizeInt8(const int8_t* packed_s8_data, float* unpacked_fp32_data,
const RuntimeShape& tensor_shape, int32_t zero_point,
double scale);
// Per-channel dequantizes INT8 value using given zero points and
// scales. packed_s8_data should contain raw tensor data corresponding
// to a given tensor_shape. unpacked_fp32_data should be preallocated
// to have the same size.
void PerChannelDequantizeInt8(const int8_t* packed_s8_data,
float* unpacked_fp32_data,
const RuntimeShape& tensor_shape,
const int32_t* zero_points, const float* scales,
int32_t quantized_dimension);
// Dequantizes INT8 value using given zero point and scale.
// packed_fp16_data should have tensor_elements size and contain raw
// FP16 tensor data. unpacked_fp32_data should be preallocated to
// have the same size.
void DequantizeFloat16(const uint16_t* packed_fp16_data,
float* unpacked_fp32_data, size_t tensor_elements);
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZATION_UTIL_H_
@@ -0,0 +1,102 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantization_util.h"
#include <stdint.h>
#include <limits>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "tensorflow/lite/kernels/internal/runtime_shape.h"
using ::testing::FloatNear;
using ::testing::Pointwise;
namespace tflite {
namespace xnnpack {
namespace {
template <typename T>
inline double ScaleFromMinMax(const float min, const float max) {
return (max - min) / ((std::numeric_limits<T>::max() * 1.0) -
std::numeric_limits<T>::min());
}
template <typename T>
inline int32_t ZeroPointFromMinMax(const float min, const float max) {
return static_cast<int32_t>(std::numeric_limits<T>::min()) +
static_cast<int32_t>(-min / ScaleFromMinMax<T>(min, max) + 0.5f);
}
TEST(Dequantize, Int8) {
std::vector<int8_t> quantized_data = {-3, -2, -1, 1, 2, 3};
std::vector<float> dequantized_data(quantized_data.size());
RuntimeShape tensor_shape(1, quantized_data.size());
const float min = -12.8f;
const float max = 12.7f;
const double scale = ScaleFromMinMax<int8_t>(min, max);
const int32_t zero_point = ZeroPointFromMinMax<int8_t>(min, max);
DequantizeInt8(quantized_data.data(), dequantized_data.data(), tensor_shape,
zero_point, scale);
EXPECT_THAT(dequantized_data,
Pointwise(FloatNear(1e-6), {-0.3, -0.2, -0.1, 0.1, 0.2, 0.3}));
}
TEST(Dequantize, PerChannelInt8) {
const std::vector<float> scales = {0.5, 0.25};
const std::vector<int> zero_points = {-1, -1};
const int quantized_dimension = 0;
const RuntimeShape shape({2, 5});
const std::vector<int8_t> input = {-128, -127, -126, -125, -124,
123, 124, 125, 126, 127};
std::vector<float> output(10, -1);
PerChannelDequantizeInt8(input.data(), output.data(), shape,
zero_points.data(), scales.data(),
quantized_dimension);
EXPECT_THAT(output,
Pointwise(FloatNear(1e-6), {-63.5, -63., -62.5, -62., -61.5, 31.,
31.25, 31.5, 31.75, 32.}));
}
TEST(Dequantize, Float16) {
std::vector<uint16_t> quantized_data = {
UINT16_C(0x3000), // 0.125
UINT16_C(0x3400), // 0.25
UINT16_C(0x3800), // 0.5
UINT16_C(0x3C00), // 1
UINT16_C(0x4000), // 2
UINT16_C(0x4400) // 4
};
std::vector<float> dequantized_data(quantized_data.size());
DequantizeFloat16(quantized_data.data(), dequantized_data.data(),
quantized_data.size());
EXPECT_THAT(dequantized_data,
Pointwise(FloatNear(1e-6), {0.125, 0.25, 0.5, 1., 2., 4.}));
}
} // namespace
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,133 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/quantize_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
TEST(QuantizeFloat32ToInt8, 4D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Shape({batch, height, width, channels})
.OutputZeroPoint(-5)
.OutputScale(1.0f / 256.0f)
.Test(TensorType_FLOAT32, TensorType_INT8, xnnpack_delegate.get());
}
TEST(QuantizeFloat32ToInt8, 3D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Shape({batch, width, channels})
.OutputZeroPoint(-5)
.OutputScale(1.0f / 256.0f)
.Test(TensorType_FLOAT32, TensorType_INT8, xnnpack_delegate.get());
}
TEST(QuantizeFloat32ToInt8, 2D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Shape({batch, channels})
.OutputZeroPoint(-5)
.OutputScale(1.0f / 256.0f)
.Test(TensorType_FLOAT32, TensorType_INT8, xnnpack_delegate.get());
}
TEST(QuantizeFloat32ToInt8, 1D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
QuantizeTester()
.Shape({batch})
.OutputZeroPoint(-5)
.OutputScale(1.0f / 256.0f)
.Test(TensorType_FLOAT32, TensorType_INT8, xnnpack_delegate.get());
}
TEST(QuantizeFloat32ToInt8, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Shape({batch, height, width, channels})
.OutputZeroPoint(-5)
.OutputScale(1.0f / 256.0f)
.Test(TensorType_FLOAT32, TensorType_INT8, xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,138 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/quantize_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
TEST(QuantizeFloat32ToUInt8, 4D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Unsigned(true)
.Shape({batch, height, width, channels})
.OutputZeroPoint(50)
.OutputScale(1.0f / 256.0f)
.Test(TensorType_FLOAT32, TensorType_UINT8, xnnpack_delegate.get());
}
TEST(QuantizeFloat32ToUInt8, 3D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Unsigned(true)
.Shape({batch, width, channels})
.OutputZeroPoint(50)
.OutputScale(1.0f / 256.0f)
.Test(TensorType_FLOAT32, TensorType_UINT8, xnnpack_delegate.get());
}
TEST(QuantizeFloat32ToUInt8, 2D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Unsigned(true)
.Shape({batch, channels})
.OutputZeroPoint(50)
.OutputScale(1.0f / 256.0f)
.Test(TensorType_FLOAT32, TensorType_UINT8, xnnpack_delegate.get());
}
TEST(QuantizeFloat32ToUInt8, 1D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
QuantizeTester()
.Unsigned(true)
.Shape({batch})
.OutputZeroPoint(50)
.OutputScale(1.0f / 256.0f)
.Test(TensorType_FLOAT32, TensorType_UINT8, xnnpack_delegate.get());
}
TEST(QuantizeFloat32ToUInt8, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Unsigned(true)
.Shape({batch, height, width, channels})
.OutputZeroPoint(50)
.OutputScale(1.0f / 256.0f)
.Test(TensorType_FLOAT32, TensorType_UINT8, xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,143 @@
/* Copyright 2022 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/quantize_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
TEST(QuantizeInt8ToInt8, 4D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Shape({batch, height, width, channels})
.InputZeroPoint(7)
.InputScale(0.75f)
.OutputZeroPoint(-5)
.OutputScale(1.25f)
.Test(TensorType_INT8, TensorType_INT8, xnnpack_delegate.get());
}
TEST(QuantizeInt8ToInt8, 3D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Shape({batch, width, channels})
.InputZeroPoint(7)
.InputScale(0.75f)
.OutputZeroPoint(-5)
.OutputScale(1.25f)
.Test(TensorType_INT8, TensorType_INT8, xnnpack_delegate.get());
}
TEST(QuantizeInt8ToInt8, 2D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Shape({batch, channels})
.InputZeroPoint(7)
.InputScale(0.75f)
.OutputZeroPoint(-5)
.OutputScale(1.25f)
.Test(TensorType_INT8, TensorType_INT8, xnnpack_delegate.get());
}
TEST(QuantizeInt8ToInt8, 1D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
QuantizeTester()
.Shape({batch})
.InputZeroPoint(7)
.InputScale(0.75f)
.OutputZeroPoint(-5)
.OutputScale(1.25f)
.Test(TensorType_INT8, TensorType_INT8, xnnpack_delegate.get());
}
TEST(QuantizeInt8ToInt8, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Shape({batch, height, width, channels})
.InputZeroPoint(7)
.InputScale(0.75f)
.OutputZeroPoint(-5)
.OutputScale(1.25f)
.Test(TensorType_INT8, TensorType_INT8, xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,236 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantize_tester.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
template <class T>
void QuantizeTester::PopulateInput(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
std::uniform_int_distribution<int> input_distribution(
std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
auto input_rng = std::bind(input_distribution, std::ref(rng));
T* default_input_data = default_interpreter->typed_input_tensor<T>(0);
std::generate_n(default_input_data, ComputeSize(Shape()),
std::ref(input_rng));
T* xnnpack_input_data = delegate_interpreter->typed_input_tensor<T>(0);
std::copy_n(default_input_data, ComputeSize(Shape()), xnnpack_input_data);
}
template <>
void QuantizeTester::PopulateInput<float>(
Interpreter* delegate_interpreter, Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
std::uniform_real_distribution<float> input_distribution(-1.0f, 1.0f);
auto input_rng = std::bind(input_distribution, std::ref(rng));
float* default_input_data = default_interpreter->typed_input_tensor<float>(0);
std::generate_n(default_input_data, ComputeSize(Shape()),
std::ref(input_rng));
float* xnnpack_input_data =
delegate_interpreter->typed_input_tensor<float>(0);
std::copy_n(default_input_data, ComputeSize(Shape()), xnnpack_input_data);
}
template <class T>
void QuantizeTester::InvokeAndCheckOutput(
Interpreter* delegate_interpreter, Interpreter* default_interpreter) const {
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
T* default_output_data = default_interpreter->typed_output_tensor<T>(0);
T* delegate_output_data = delegate_interpreter->typed_output_tensor<T>(0);
for (size_t i = 0; i < ComputeSize(Shape()); i++) {
ASSERT_LE(std::abs(static_cast<int32_t>(default_output_data[i]) -
static_cast<int32_t>(delegate_output_data[i])),
1)
<< "default " << static_cast<int32_t>(default_output_data[i])
<< ", delegate " << static_cast<int32_t>(delegate_output_data[i])
<< " at index " << i << " / " << ComputeSize(Shape());
}
}
void QuantizeTester::Test(TensorType input_type, TensorType output_type,
TfLiteDelegate* delegate) const {
std::vector<char> buffer = CreateTfLiteModel(input_type, output_type);
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
switch (input_type) {
case TensorType_FLOAT32:
PopulateInput<float>(delegate_interpreter.get(),
default_interpreter.get());
break;
case TensorType_INT8:
PopulateInput<int8_t>(delegate_interpreter.get(),
default_interpreter.get());
break;
case TensorType_UINT8:
PopulateInput<uint8_t>(delegate_interpreter.get(),
default_interpreter.get());
break;
default:
GTEST_FAIL() << "unsupported input type "
<< EnumNameTensorType(input_type);
}
switch (output_type) {
case TensorType_INT8:
InvokeAndCheckOutput<int8_t>(delegate_interpreter.get(),
default_interpreter.get());
break;
case TensorType_UINT8:
InvokeAndCheckOutput<uint8_t>(delegate_interpreter.get(),
default_interpreter.get());
break;
default:
GTEST_FAIL() << "unsupported output type "
<< EnumNameTensorType(output_type);
}
}
std::vector<char> QuantizeTester::CreateTfLiteModel(
TensorType input_type, TensorType output_type) const {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<OperatorCode> operator_code =
CreateOperatorCode(builder, BuiltinOperator_QUANTIZE);
const std::array<flatbuffers::Offset<Buffer>, 1> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
}};
flatbuffers::Offset<QuantizationParameters> input_quantization = 0;
if (input_type != TensorType_FLOAT32) {
input_quantization = CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({InputScale()}),
builder.CreateVector<int64_t>({InputZeroPoint()}));
}
const std::array<flatbuffers::Offset<Tensor>, 2> tensors{{
CreateTensor(
builder,
builder.CreateVector<int32_t>(Shape().data(), Shape().size()),
input_type,
/*buffer=*/0, /*name=*/0, input_quantization),
CreateTensor(
builder,
builder.CreateVector<int32_t>(Shape().data(), Shape().size()),
output_type,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({OutputScale()}),
builder.CreateVector<int64_t>({OutputZeroPoint()}))),
}};
const std::array<int32_t, 1> op_inputs{{0}};
const std::array<int32_t, 1> op_outputs{{1}};
flatbuffers::Offset<Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()));
const std::array<int32_t, 1> subgraph_inputs{{0}};
const std::array<int32_t, 1> subgraph_outputs{{1}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Quantize operator model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t QuantizeTester::ComputeSize(const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,113 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZE_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZE_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class QuantizeTester {
public:
QuantizeTester() = default;
QuantizeTester(const QuantizeTester&) = delete;
QuantizeTester& operator=(const QuantizeTester&) = delete;
inline QuantizeTester& Shape(std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
shape_ = std::vector<int32_t>(shape.begin(), shape.end());
size_ = QuantizeTester::ComputeSize(shape_);
return *this;
}
const std::vector<int32_t>& Shape() const { return shape_; }
int32_t Size() const { return size_; }
inline QuantizeTester& InputZeroPoint(int32_t input_zero_point) {
input_zero_point_ = input_zero_point;
return *this;
}
inline int32_t InputZeroPoint() const { return input_zero_point_; }
inline QuantizeTester& InputScale(float input_scale) {
input_scale_ = input_scale;
return *this;
}
inline float InputScale() const { return input_scale_; }
inline QuantizeTester& OutputZeroPoint(int32_t output_zero_point) {
output_zero_point_ = output_zero_point;
return *this;
}
inline int32_t OutputZeroPoint() const { return output_zero_point_; }
inline QuantizeTester& OutputScale(float output_scale) {
output_scale_ = output_scale;
return *this;
}
inline float OutputScale() const { return output_scale_; }
inline QuantizeTester& Unsigned(bool is_unsigned) {
unsigned_ = is_unsigned;
return *this;
}
inline bool Unsigned() const { return unsigned_; }
template <class T>
void PopulateInput(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
template <class T>
void InvokeAndCheckOutput(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(TensorType input_type, TensorType output_type,
TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel(TensorType input_type,
TensorType output_type) const;
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> shape_;
int32_t size_;
int32_t input_zero_point_ = 0;
float input_scale_ = 1.0f;
int32_t output_zero_point_ = 0;
float output_scale_ = 1.0f;
bool unsigned_ = false;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZE_TESTER_H_
@@ -0,0 +1,143 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include <cstdint>
#include <functional>
#include <memory>
#include <random>
#include <gtest/gtest.h>
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/delegates/xnnpack/quantize_tester.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
TEST(QuantizeUInt8ToUInt8, 4D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Shape({batch, height, width, channels})
.InputZeroPoint(113)
.InputScale(0.75f)
.OutputZeroPoint(131)
.OutputScale(1.25f)
.Test(TensorType_UINT8, TensorType_UINT8, xnnpack_delegate.get());
}
TEST(QuantizeUInt8ToUInt8, 3D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Shape({batch, width, channels})
.InputZeroPoint(113)
.InputScale(0.75f)
.OutputZeroPoint(131)
.OutputScale(1.25f)
.Test(TensorType_UINT8, TensorType_UINT8, xnnpack_delegate.get());
}
TEST(QuantizeUInt8ToUInt8, 2D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Shape({batch, channels})
.InputZeroPoint(113)
.InputScale(0.75f)
.OutputZeroPoint(131)
.OutputScale(1.25f)
.Test(TensorType_UINT8, TensorType_UINT8, xnnpack_delegate.get());
}
TEST(QuantizeUInt8ToUInt8, 1D) {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(nullptr),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
QuantizeTester()
.Shape({batch})
.InputZeroPoint(113)
.InputScale(0.75f)
.OutputZeroPoint(131)
.OutputScale(1.25f)
.Test(TensorType_UINT8, TensorType_UINT8, xnnpack_delegate.get());
}
TEST(QuantizeUInt8ToUInt8, MultiThreading) {
TfLiteXNNPackDelegateOptions delegate_options =
TfLiteXNNPackDelegateOptionsDefault();
delegate_options.num_threads = 2;
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
xnnpack_delegate(TfLiteXNNPackDelegateCreate(&delegate_options),
TfLiteXNNPackDelegateDelete);
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto shape_rng =
std::bind(std::uniform_int_distribution<int32_t>(2, 5), std::ref(rng));
const auto batch = shape_rng();
const auto height = shape_rng();
const auto width = shape_rng();
const auto channels = shape_rng();
QuantizeTester()
.Shape({batch, height, width, channels})
.InputZeroPoint(113)
.InputScale(0.75f)
.OutputZeroPoint(131)
.OutputScale(1.25f)
.Test(TensorType_UINT8, TensorType_UINT8, xnnpack_delegate.get());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,289 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantized_binary_elementwise_tester.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
std::vector<int32_t> QuantizedBinaryElementwiseTester::OutputShape() const {
std::vector<int32_t> output_shape;
if (!input1_shape_.empty()) {
output_shape.insert(
output_shape.end(), input1_shape_.cbegin(),
input1_shape_.cbegin() +
std::max(input1_shape_.size(), input2_shape_.size()) -
input2_shape_.size());
}
if (!input2_shape_.empty()) {
output_shape.insert(
output_shape.end(), input2_shape_.cbegin(),
input2_shape_.cbegin() +
std::max(input2_shape_.size(), input1_shape_.size()) -
input1_shape_.size());
}
for (size_t i = std::min(input1_shape_.size(), input2_shape_.size()); i >= 1;
i--) {
output_shape.push_back(
std::max(*(input1_shape_.cend() - i), *(input2_shape_.cend() - i)));
}
return output_shape;
}
template <class T>
void QuantizedBinaryElementwiseTester::Test(
Interpreter* delegate_interpreter, Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
std::uniform_int_distribution<int32_t> input1_distribution(
std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
std::uniform_int_distribution<int32_t> input2_distribution(
std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
auto input1_rng = std::bind(input1_distribution, std::ref(rng));
auto input2_rng = std::bind(input2_distribution, std::ref(rng));
if (!Input1Static()) {
T* default_input1_data = default_interpreter->typed_input_tensor<T>(0);
std::generate_n(default_input1_data, ComputeSize(Input1Shape()),
std::ref(input1_rng));
T* xnnpack_input1_data = delegate_interpreter->typed_input_tensor<T>(0);
std::copy_n(default_input1_data, ComputeSize(Input1Shape()),
xnnpack_input1_data);
}
if (!Input2Static()) {
T* default_input2_data =
default_interpreter->typed_input_tensor<T>(Input1Static() ? 0 : 1);
std::generate_n(default_input2_data, ComputeSize(Input2Shape()),
std::ref(input2_rng));
T* xnnpack_input2_data =
delegate_interpreter->typed_input_tensor<T>(Input1Static() ? 0 : 1);
std::copy_n(default_input2_data, ComputeSize(Input2Shape()),
xnnpack_input2_data);
}
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
T* default_output_data = default_interpreter->typed_output_tensor<T>(0);
T* delegate_output_data = delegate_interpreter->typed_output_tensor<T>(0);
for (size_t i = 0; i < ComputeSize(OutputShape()); i++) {
ASSERT_LE(std::abs(static_cast<int32_t>(default_output_data[i]) -
static_cast<int32_t>(delegate_output_data[i])),
1)
<< "default " << static_cast<int32_t>(default_output_data[i])
<< ", delegate " << static_cast<int32_t>(delegate_output_data[i])
<< " at index " << i << " / " << ComputeSize(OutputShape());
}
}
void QuantizedBinaryElementwiseTester::Test(tflite::BuiltinOperator binary_op,
TfLiteDelegate* delegate) const {
if (Input1Static()) {
ASSERT_FALSE(Input2Static());
}
std::vector<char> buffer = CreateTfLiteModel(binary_op);
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
if (Input1Static() || Input2Static()) {
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
} else {
ASSERT_EQ(delegate_interpreter->inputs().size(), 2);
ASSERT_EQ(default_interpreter->inputs().size(), 2);
}
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (Unsigned()) {
Test<uint8_t>(delegate_interpreter.get(), default_interpreter.get());
} else {
Test<int8_t>(delegate_interpreter.get(), default_interpreter.get());
}
}
std::vector<char> QuantizedBinaryElementwiseTester::CreateTfLiteModel(
tflite::BuiltinOperator binary_op) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
std::uniform_int_distribution<int32_t> input1_distribution(
std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max());
std::uniform_int_distribution<int32_t> input2_distribution(
std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max());
auto input1_rng = std::bind(input1_distribution, std::ref(rng));
auto input2_rng = std::bind(input2_distribution, std::ref(rng));
flatbuffers::FlatBufferBuilder builder;
const std::array<flatbuffers::Offset<OperatorCode>, 1> operator_codes{
{CreateOperatorCode(builder, binary_op)}};
std::vector<flatbuffers::Offset<Buffer>> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
}};
int32_t input1_buffer = 0;
if (Input1Static()) {
std::vector<int8_t> input1_data(ComputeSize(Input1Shape()));
std::generate(input1_data.begin(), input1_data.end(), input1_rng);
input1_buffer = buffers.size();
buffers.push_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(input1_data.data()),
sizeof(int8_t) * input1_data.size())));
}
int32_t input2_buffer = 0;
if (Input2Static()) {
std::vector<int8_t> input2_data(ComputeSize(Input2Shape()));
std::generate(input2_data.begin(), input2_data.end(), input2_rng);
input2_buffer = buffers.size();
buffers.push_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(input2_data.data()),
sizeof(int8_t) * input2_data.size())));
}
const std::vector<int32_t> output_shape = OutputShape();
const std::array<flatbuffers::Offset<Tensor>, 3> tensors{{
CreateTensor(builder,
builder.CreateVector<int32_t>(Input1Shape().data(),
Input1Shape().size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
input1_buffer, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({Input1Scale()}),
builder.CreateVector<int64_t>({Input1ZeroPoint()}))),
CreateTensor(builder,
builder.CreateVector<int32_t>(Input2Shape().data(),
Input2Shape().size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
input2_buffer, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({Input2Scale()}),
builder.CreateVector<int64_t>({Input2ZeroPoint()}))),
CreateTensor(builder,
builder.CreateVector<int32_t>(OutputShape().data(),
OutputShape().size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({OutputScale()}),
builder.CreateVector<int64_t>({OutputZeroPoint()}))),
}};
const std::array<int32_t, 2> op_inputs{{0, 1}};
const std::array<int32_t, 1> op_outputs{{2}};
const std::array<flatbuffers::Offset<Operator>, 1> operators{{CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_AddOptions,
CreateAddOptions(builder, Activation()).Union())}};
std::vector<int32_t> subgraph_inputs;
if (!Input1Static()) {
subgraph_inputs.push_back(0);
}
if (!Input2Static()) {
subgraph_inputs.push_back(1);
}
const std::array<int32_t, 1> subgraph_outputs{{2}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(operators.data(), operators.size()));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Quantized binary operator model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t QuantizedBinaryElementwiseTester::ComputeSize(
const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,180 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_BINARY_ELEMENTWISE_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_BINARY_ELEMENTWISE_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class QuantizedBinaryElementwiseTester {
public:
QuantizedBinaryElementwiseTester() = default;
QuantizedBinaryElementwiseTester(const QuantizedBinaryElementwiseTester&) =
delete;
QuantizedBinaryElementwiseTester& operator=(
const QuantizedBinaryElementwiseTester&) = delete;
inline QuantizedBinaryElementwiseTester& Input1Shape(
std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
input1_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
return *this;
}
inline const std::vector<int32_t>& Input1Shape() const {
return input1_shape_;
}
inline QuantizedBinaryElementwiseTester& Input2Shape(
std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
input2_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
return *this;
}
inline const std::vector<int32_t>& Input2Shape() const {
return input2_shape_;
}
std::vector<int32_t> OutputShape() const;
inline QuantizedBinaryElementwiseTester& Input1Static(bool is_static) {
input1_static_ = is_static;
return *this;
}
inline bool Input1Static() const { return input1_static_; }
inline QuantizedBinaryElementwiseTester& Input2Static(bool is_static) {
input2_static_ = is_static;
return *this;
}
inline bool Input2Static() const { return input2_static_; }
inline QuantizedBinaryElementwiseTester& Input1ZeroPoint(
int32_t input1_zero_point) {
input1_zero_point_ = input1_zero_point;
return *this;
}
inline int32_t Input1ZeroPoint() const { return input1_zero_point_; }
inline QuantizedBinaryElementwiseTester& Input2ZeroPoint(
int32_t input2_zero_point) {
input2_zero_point_ = input2_zero_point;
return *this;
}
inline int32_t Input2ZeroPoint() const { return input2_zero_point_; }
inline QuantizedBinaryElementwiseTester& OutputZeroPoint(
int32_t output_zero_point) {
output_zero_point_ = output_zero_point;
return *this;
}
inline int32_t OutputZeroPoint() const { return output_zero_point_; }
inline QuantizedBinaryElementwiseTester& Input1Scale(float input1_scale) {
input1_scale_ = input1_scale;
return *this;
}
inline float Input1Scale() const { return input1_scale_; }
inline QuantizedBinaryElementwiseTester& Input2Scale(float input2_scale) {
input2_scale_ = input2_scale;
return *this;
}
inline float Input2Scale() const { return input2_scale_; }
inline QuantizedBinaryElementwiseTester& OutputScale(float output_scale) {
output_scale_ = output_scale;
return *this;
}
inline float OutputScale() const { return output_scale_; }
inline QuantizedBinaryElementwiseTester& Unsigned(bool is_unsigned) {
unsigned_ = is_unsigned;
return *this;
}
inline bool Unsigned() const { return unsigned_; }
inline QuantizedBinaryElementwiseTester& ReluActivation() {
activation_ = ::tflite::ActivationFunctionType_RELU;
return *this;
}
inline QuantizedBinaryElementwiseTester& Relu6Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU6;
return *this;
}
inline QuantizedBinaryElementwiseTester& ReluMinus1To1Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU_N1_TO_1;
return *this;
}
template <class T>
void Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(tflite::BuiltinOperator binary_op, TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel(tflite::BuiltinOperator binary_op) const;
inline ::tflite::ActivationFunctionType Activation() const {
return activation_;
}
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> input1_shape_;
std::vector<int32_t> input2_shape_;
bool input1_static_ = false;
bool input2_static_ = false;
int32_t input1_zero_point_ = 0;
int32_t input2_zero_point_ = 0;
int32_t output_zero_point_ = 0;
float input1_scale_ = 0.75f;
float input2_scale_ = 1.0f;
float output_scale_ = 1.75f;
bool unsigned_ = false;
::tflite::ActivationFunctionType activation_ =
::tflite::ActivationFunctionType_NONE;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_BINARY_ELEMENTWISE_TESTER_H_
@@ -0,0 +1,282 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantized_conv_2d_tester.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "flatbuffers/vector.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
template <class T>
void QuantizedConv2DTester::Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng = std::bind(
std::uniform_int_distribution<int32_t>(std::numeric_limits<T>::min(),
std::numeric_limits<T>::max()),
std::ref(rng));
T* default_input_data = default_interpreter->typed_input_tensor<T>(0);
std::generate_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
input_rng);
T* delegate_input_data = delegate_interpreter->typed_input_tensor<T>(0);
std::copy_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
T* default_output_data = default_interpreter->typed_output_tensor<T>(0);
T* delegate_output_data = delegate_interpreter->typed_output_tensor<T>(0);
for (int32_t i = 0; i < BatchSize(); i++) {
for (int32_t y = 0; y < OutputHeight(); y++) {
for (int32_t x = 0; x < OutputWidth(); x++) {
for (int32_t c = 0; c < OutputChannels(); c++) {
const int32_t index = ((i * OutputHeight() + y) * OutputWidth() + x) *
OutputChannels() +
c;
ASSERT_LE(std::abs(static_cast<int32_t>(default_output_data[index]) -
static_cast<int32_t>(delegate_output_data[index])),
1)
<< "default " << static_cast<int32_t>(default_output_data[index])
<< ", delegate "
<< static_cast<int32_t>(delegate_output_data[index]) << ", batch "
<< i << " / " << BatchSize() << ", y position " << y << " / "
<< OutputHeight() << ", x position " << x << " / "
<< OutputWidth() << ", channel " << c << " / "
<< OutputChannels();
}
}
}
}
}
void QuantizedConv2DTester::Test(TfLiteDelegate* delegate) {
const Model* model = GetModel();
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (weights_cache_ != nullptr) {
TfLiteXNNPackDelegateWeightsCacheFinalizeSoft(weights_cache_);
}
if (Unsigned()) {
Test<uint8_t>(delegate_interpreter.get(), default_interpreter.get());
} else {
Test<int8_t>(delegate_interpreter.get(), default_interpreter.get());
}
}
std::vector<char> QuantizedConv2DTester::CreateTfLiteModel() const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto filter_rng = std::bind(std::uniform_int_distribution<int32_t>(
-std::numeric_limits<int8_t>::max(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto bias_rng = std::bind(
std::uniform_int_distribution<int32_t>(-10000, 10000), std::ref(rng));
flatbuffers::FlatBufferBuilder builder;
const std::array<flatbuffers::Offset<OperatorCode>, 1> operator_codes{
{CreateOperatorCode(builder, BuiltinOperator_CONV_2D)}};
std::vector<int8_t> filter_data(OutputChannels() * KernelHeight() *
KernelWidth() * KernelInputChannels());
std::generate(filter_data.begin(), filter_data.end(), std::ref(filter_rng));
std::vector<int32_t> bias_data(OutputChannels());
std::generate(bias_data.begin(), bias_data.end(), std::ref(bias_rng));
const std::array<flatbuffers::Offset<tflite::Buffer>, 3> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(filter_data.data()),
sizeof(int8_t) * filter_data.size())),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(bias_data.data()),
sizeof(int32_t) * bias_data.size())),
}};
const std::array<int32_t, 4> input_shape{
{BatchSize(), InputHeight(), InputWidth(), InputChannels()}};
const std::array<int32_t, 4> output_shape{
{BatchSize(), OutputHeight(), OutputWidth(), OutputChannels()}};
const std::array<int32_t, 4> filter_shape{
{OutputChannels(), KernelHeight(), KernelWidth(), KernelInputChannels()}};
const std::array<int32_t, 1> bias_shape{{OutputChannels()}};
flatbuffers::Offset<flatbuffers::Vector<float>> filter_scale_offset = 0;
flatbuffers::Offset<flatbuffers::Vector<float>> bias_scale_offset = 0;
flatbuffers::Offset<flatbuffers::Vector<int64_t>> filter_zero_point_offset =
0;
flatbuffers::Offset<flatbuffers::Vector<int64_t>> bias_zero_point_offset = 0;
if (ChannelWise()) {
filter_scale_offset = builder.CreateVector<float>(KernelScales());
std::vector<float> bias_scales = std::vector<float>(KernelScales());
for (float& bias_scale : bias_scales) {
bias_scale *= InputScale();
}
bias_scale_offset = builder.CreateVector<float>(bias_scales);
const auto zero_points = std::vector<int64_t>(OutputChannels());
filter_zero_point_offset = builder.CreateVector<int64_t>(zero_points);
bias_zero_point_offset = filter_zero_point_offset;
} else {
filter_scale_offset = builder.CreateVector<float>({KernelScale()});
bias_scale_offset =
builder.CreateVector<float>({InputScale() * KernelScale()});
bias_zero_point_offset = builder.CreateVector<int64_t>({0});
if (Unsigned()) {
filter_zero_point_offset =
builder.CreateVector<int64_t>({KernelZeroPoint()});
} else {
filter_zero_point_offset = bias_zero_point_offset;
}
}
const std::array<flatbuffers::Offset<tflite::Tensor>, 4> tensors{{
CreateTensor(
builder,
builder.CreateVector<int32_t>(input_shape.data(), input_shape.size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8, /*buffer=*/0,
/*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({InputScale()}),
builder.CreateVector<int64_t>({InputZeroPoint()}))),
CreateTensor(builder,
builder.CreateVector<int32_t>(filter_shape.data(),
filter_shape.size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/1,
/*name=*/0,
CreateQuantizationParameters(builder, /*min=*/0, /*max=*/0,
filter_scale_offset,
filter_zero_point_offset)),
CreateTensor(
builder,
builder.CreateVector<int32_t>(bias_shape.data(), bias_shape.size()),
TensorType_INT32, /*buffer=*/2, /*name=*/0,
CreateQuantizationParameters(builder, /*min=*/0, /*max=*/0,
bias_scale_offset,
bias_zero_point_offset)),
CreateTensor(builder,
builder.CreateVector<int32_t>(output_shape.data(),
output_shape.size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({OutputScale()}),
builder.CreateVector<int64_t>({OutputZeroPoint()}))),
}};
const std::array<int32_t, 3> op_inputs{{0, 1, 2}};
const std::array<int32_t, 1> op_outputs{{3}};
const flatbuffers::Offset<Conv2DOptions> conv2d_options =
CreateConv2DOptions(builder, Padding(), StrideWidth(), StrideHeight(),
Activation(), DilationWidth(), DilationHeight());
const std::array<flatbuffers::Offset<tflite::Operator>, 1> operators{
{CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_Conv2DOptions, conv2d_options.Union())}};
const std::array<int32_t, 1> subgraph_inputs{{0}};
const std::array<int32_t, 1> subgraph_outputs{{3}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(operators.data(), operators.size()));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Quantized Conv2D model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,305 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_CONV_2D_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_CONV_2D_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
// Creates a model with a single CONV_2D operator with quantized input, output,
// and weights, runs this model in two TensorFlow Lite interpreters, one with
// the delegate applied, and the other without, and compares the results.
class QuantizedConv2DTester : public ModelCache<QuantizedConv2DTester> {
public:
QuantizedConv2DTester() = default;
QuantizedConv2DTester(const QuantizedConv2DTester&) = delete;
QuantizedConv2DTester& operator=(const QuantizedConv2DTester&) = delete;
inline QuantizedConv2DTester& BatchSize(int32_t batch_size) {
EXPECT_GT(batch_size, 0);
batch_size_ = batch_size;
return *this;
}
inline int32_t BatchSize() const { return batch_size_; }
inline QuantizedConv2DTester& InputChannels(int32_t input_channels) {
EXPECT_GT(input_channels, 0);
input_channels_ = input_channels;
return *this;
}
inline int32_t InputChannels() const { return input_channels_; }
inline QuantizedConv2DTester& OutputChannels(int32_t output_channels) {
EXPECT_GT(output_channels, 0);
output_channels_ = output_channels;
return *this;
}
inline int32_t OutputChannels() const { return output_channels_; }
inline QuantizedConv2DTester& InputHeight(int32_t input_height) {
EXPECT_GT(input_height, 0);
input_height_ = input_height;
return *this;
}
inline int32_t InputHeight() const { return input_height_; }
inline QuantizedConv2DTester& InputWidth(int32_t input_width) {
EXPECT_GT(input_width, 0);
input_width_ = input_width;
return *this;
}
inline int32_t InputWidth() const { return input_width_; }
inline int32_t OutputWidth() const {
if (Padding() == ::tflite::Padding_SAME) {
EXPECT_GE(InputWidth(), 1);
return (InputWidth() - 1) / StrideWidth() + 1;
} else {
EXPECT_GE(InputWidth(), DilatedKernelWidth());
return 1 + (InputWidth() - DilatedKernelWidth()) / StrideWidth();
}
}
inline int32_t OutputHeight() const {
if (Padding() == ::tflite::Padding_SAME) {
EXPECT_GE(InputHeight(), 1);
return (InputHeight() - 1) / StrideHeight() + 1;
} else {
EXPECT_GE(InputHeight(), DilatedKernelHeight());
return 1 + (InputHeight() - DilatedKernelHeight()) / StrideHeight();
}
}
inline QuantizedConv2DTester& KernelHeight(int32_t kernel_height) {
EXPECT_GT(kernel_height, 0);
kernel_height_ = kernel_height;
return *this;
}
inline int32_t KernelHeight() const { return kernel_height_; }
inline QuantizedConv2DTester& KernelWidth(int32_t kernel_width) {
EXPECT_GT(kernel_width, 0);
kernel_width_ = kernel_width;
return *this;
}
inline int32_t KernelWidth() const { return kernel_width_; }
inline QuantizedConv2DTester& StrideHeight(int32_t stride_height) {
EXPECT_GT(stride_height, 0);
stride_height_ = stride_height;
return *this;
}
inline int32_t StrideHeight() const { return stride_height_; }
inline QuantizedConv2DTester& StrideWidth(int32_t stride_width) {
EXPECT_GT(stride_width, 0);
stride_width_ = stride_width;
return *this;
}
inline int32_t StrideWidth() const { return stride_width_; }
inline QuantizedConv2DTester& DilationHeight(int32_t dilation_height) {
EXPECT_GT(dilation_height, 0);
dilation_height_ = dilation_height;
return *this;
}
inline int32_t DilationHeight() const { return dilation_height_; }
inline QuantizedConv2DTester& DilationWidth(int32_t dilation_width) {
EXPECT_GT(dilation_width, 0);
dilation_width_ = dilation_width;
return *this;
}
inline int32_t DilationWidth() const { return dilation_width_; }
inline int32_t DilatedKernelHeight() const {
return (KernelHeight() - 1) * DilationHeight() + 1;
}
inline int32_t DilatedKernelWidth() const {
return (KernelWidth() - 1) * DilationWidth() + 1;
}
inline QuantizedConv2DTester& Groups(int32_t groups) {
EXPECT_EQ(InputChannels() % groups, 0);
EXPECT_EQ(OutputChannels() % groups, 0);
groups_ = groups;
return *this;
}
inline int32_t Groups() const { return groups_; }
inline int32_t KernelInputChannels() const {
return input_channels_ / groups_;
}
inline QuantizedConv2DTester& InputZeroPoint(int32_t input_zero_point) {
input_zero_point_ = input_zero_point;
return *this;
}
inline int32_t InputZeroPoint() const { return input_zero_point_; }
inline QuantizedConv2DTester& OutputZeroPoint(int32_t output_zero_point) {
output_zero_point_ = output_zero_point;
return *this;
}
inline int32_t OutputZeroPoint() const { return output_zero_point_; }
inline QuantizedConv2DTester& KernelZeroPoint(int32_t kernel_zero_point) {
kernel_zero_point_ = kernel_zero_point;
return *this;
}
inline int32_t KernelZeroPoint() const { return kernel_zero_point_; }
inline QuantizedConv2DTester& InputScale(float input_scale) {
input_scale_ = input_scale;
return *this;
}
inline float InputScale() const { return input_scale_; }
inline QuantizedConv2DTester& KernelScale(float kernel_scale) {
kernel_scale_ = kernel_scale;
return *this;
}
inline float KernelScale() const {
EXPECT_FALSE(ChannelWise());
return kernel_scale_;
}
inline QuantizedConv2DTester& KernelScales(
const std::vector<float>& kernel_scales) {
EXPECT_GT(kernel_scales.size(), 0);
kernel_scales_ = kernel_scales;
return *this;
}
inline const std::vector<float>& KernelScales() const {
EXPECT_TRUE(ChannelWise());
return kernel_scales_;
}
inline bool Unsigned() const { return kernel_zero_point_ != 0; }
inline bool ChannelWise() const { return !kernel_scales_.empty(); }
inline QuantizedConv2DTester& OutputScale(float output_scale) {
output_scale_ = output_scale;
return *this;
}
inline float OutputScale() const { return output_scale_; }
inline QuantizedConv2DTester& SamePadding() {
padding_ = ::tflite::Padding_SAME;
return *this;
}
inline QuantizedConv2DTester& ValidPadding() {
padding_ = ::tflite::Padding_VALID;
return *this;
}
inline QuantizedConv2DTester& ReluActivation() {
activation_ = ::tflite::ActivationFunctionType_RELU;
return *this;
}
inline QuantizedConv2DTester& Relu6Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU6;
return *this;
}
inline QuantizedConv2DTester& ReluMinus1To1Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU_N1_TO_1;
return *this;
}
inline QuantizedConv2DTester& WeightsCache(
TfLiteXNNPackDelegateWeightsCache* weights_cache) {
weights_cache_ = weights_cache;
return *this;
}
template <class T>
void Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(TfLiteDelegate* delegate);
private:
std::vector<char> CreateTfLiteModel() const override;
inline ::tflite::Padding Padding() const { return padding_; }
inline ::tflite::ActivationFunctionType Activation() const {
return activation_;
}
int32_t batch_size_ = 1;
int32_t input_channels_ = 1;
int32_t output_channels_ = 1;
int32_t groups_ = 1;
int32_t input_height_ = 1;
int32_t input_width_ = 1;
int32_t kernel_height_ = 1;
int32_t kernel_width_ = 1;
int32_t stride_height_ = 1;
int32_t stride_width_ = 1;
int32_t dilation_height_ = 1;
int32_t dilation_width_ = 1;
int32_t input_zero_point_ = 0;
int32_t output_zero_point_ = 0;
int32_t kernel_zero_point_ = 0;
float input_scale_ = 0.125f;
float kernel_scale_ = 0.25f;
std::vector<float> kernel_scales_;
float output_scale_ = 1.5f;
::tflite::Padding padding_ = ::tflite::Padding_VALID;
::tflite::ActivationFunctionType activation_ =
::tflite::ActivationFunctionType_NONE;
TfLiteXNNPackDelegateWeightsCache* weights_cache_ = nullptr;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_CONV_2D_TESTER_H_
@@ -0,0 +1,287 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantized_depthwise_conv_2d_tester.h"
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "flatbuffers/vector.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
template <class T>
void QuantizedDepthwiseConv2DTester::Test(
Interpreter* delegate_interpreter, Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng = std::bind(
std::uniform_int_distribution<int32_t>(std::numeric_limits<T>::min(),
std::numeric_limits<T>::max()),
std::ref(rng));
T* default_input_data = default_interpreter->typed_input_tensor<T>(0);
std::generate_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
input_rng);
T* delegate_input_data = delegate_interpreter->typed_input_tensor<T>(0);
std::copy_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * InputChannels(),
delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
T* default_output_data = default_interpreter->typed_output_tensor<T>(0);
T* delegate_output_data = delegate_interpreter->typed_output_tensor<T>(0);
for (int32_t i = 0; i < BatchSize(); i++) {
for (int32_t y = 0; y < OutputHeight(); y++) {
for (int32_t x = 0; x < OutputWidth(); x++) {
for (int32_t c = 0; c < OutputChannels(); c++) {
const int32_t index = ((i * OutputHeight() + y) * OutputWidth() + x) *
OutputChannels() +
c;
ASSERT_LE(std::abs(static_cast<int32_t>(default_output_data[index]) -
static_cast<int32_t>(delegate_output_data[index])),
1)
<< "default " << static_cast<int32_t>(default_output_data[index])
<< ", delegate "
<< static_cast<int32_t>(delegate_output_data[index]) << ", batch "
<< i << " / " << BatchSize() << ", y position " << y << " / "
<< OutputHeight() << ", x position " << x << " / "
<< OutputWidth() << ", channel " << c << " / "
<< OutputChannels();
}
}
}
}
}
void QuantizedDepthwiseConv2DTester::Test(TfLiteDelegate* delegate) {
const Model* model = GetModel();
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (weights_cache_ != nullptr) {
TfLiteXNNPackDelegateWeightsCacheFinalizeSoft(weights_cache_);
}
if (Unsigned()) {
Test<uint8_t>(delegate_interpreter.get(), default_interpreter.get());
} else {
Test<int8_t>(delegate_interpreter.get(), default_interpreter.get());
}
}
std::vector<char> QuantizedDepthwiseConv2DTester::CreateTfLiteModel() const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto filter_rng = std::bind(std::uniform_int_distribution<int32_t>(
-std::numeric_limits<int8_t>::max(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto bias_rng = std::bind(
std::uniform_int_distribution<int32_t>(-10000, 10000), std::ref(rng));
flatbuffers::FlatBufferBuilder builder;
const std::array<flatbuffers::Offset<OperatorCode>, 1> operator_codes{
{CreateOperatorCode(builder, BuiltinOperator_DEPTHWISE_CONV_2D)}};
std::vector<int8_t> filter_data(KernelHeight() * KernelWidth() *
OutputChannels());
std::generate(filter_data.begin(), filter_data.end(), std::ref(filter_rng));
std::vector<int32_t> bias_data(OutputChannels());
std::generate(bias_data.begin(), bias_data.end(), std::ref(bias_rng));
const std::array<flatbuffers::Offset<tflite::Buffer>, 3> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(filter_data.data()),
sizeof(int8_t) * filter_data.size())),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(bias_data.data()),
sizeof(int32_t) * bias_data.size())),
}};
const std::array<int32_t, 4> input_shape{
{BatchSize(), InputHeight(), InputWidth(), InputChannels()}};
const std::array<int32_t, 4> output_shape{
{BatchSize(), OutputHeight(), OutputWidth(), OutputChannels()}};
const std::array<int32_t, 4> filter_shape{
{1, KernelHeight(), KernelWidth(), OutputChannels()}};
const std::array<int32_t, 1> bias_shape{{OutputChannels()}};
flatbuffers::Offset<flatbuffers::Vector<float>> filter_scale_offset = 0;
flatbuffers::Offset<flatbuffers::Vector<float>> bias_scale_offset = 0;
flatbuffers::Offset<flatbuffers::Vector<int64_t>> filter_zero_point_offset =
0;
flatbuffers::Offset<flatbuffers::Vector<int64_t>> bias_zero_point_offset = 0;
if (ChannelWise()) {
filter_scale_offset = builder.CreateVector<float>(KernelScales());
std::vector<float> bias_scales = std::vector<float>(KernelScales());
for (float& bias_scale : bias_scales) {
bias_scale *= InputScale();
}
bias_scale_offset = builder.CreateVector<float>(bias_scales);
const auto zero_points = std::vector<int64_t>(OutputChannels());
filter_zero_point_offset = builder.CreateVector<int64_t>(zero_points);
bias_zero_point_offset = filter_zero_point_offset;
} else {
filter_scale_offset = builder.CreateVector<float>({KernelScale()});
bias_scale_offset =
builder.CreateVector<float>({InputScale() * KernelScale()});
bias_zero_point_offset = builder.CreateVector<int64_t>({0});
if (Unsigned()) {
filter_zero_point_offset =
builder.CreateVector<int64_t>({KernelZeroPoint()});
} else {
filter_zero_point_offset = bias_zero_point_offset;
}
}
const std::array<flatbuffers::Offset<tflite::Tensor>, 4> tensors{{
CreateTensor(
builder,
builder.CreateVector<int32_t>(input_shape.data(), input_shape.size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8, /*buffer=*/0,
/*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({InputScale()}),
builder.CreateVector<int64_t>({InputZeroPoint()}))),
CreateTensor(builder,
builder.CreateVector<int32_t>(filter_shape.data(),
filter_shape.size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/1, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0, filter_scale_offset,
filter_zero_point_offset,
/*details_type=*/tflite::QuantizationDetails_NONE,
/*details=*/0,
/*quantized_dimension=*/ChannelWise() ? 3 : 0)),
CreateTensor(
builder,
builder.CreateVector<int32_t>(bias_shape.data(), bias_shape.size()),
TensorType_INT32, /*buffer=*/2, /*name=*/0,
CreateQuantizationParameters(builder, /*min=*/0, /*max=*/0,
bias_scale_offset,
bias_zero_point_offset)),
CreateTensor(builder,
builder.CreateVector<int32_t>(output_shape.data(),
output_shape.size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({OutputScale()}),
builder.CreateVector<int64_t>({OutputZeroPoint()}))),
}};
const std::array<int32_t, 3> op_inputs{{0, 1, 2}};
const std::array<int32_t, 1> op_outputs{{3}};
const flatbuffers::Offset<DepthwiseConv2DOptions> depthwise_conv2d_options =
CreateDepthwiseConv2DOptions(
builder, Padding(), StrideWidth(), StrideHeight(), DepthMultiplier(),
Activation(), DilationWidth(), DilationHeight());
const std::array<flatbuffers::Offset<tflite::Operator>, 1> operators{{
CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_DepthwiseConv2DOptions,
depthwise_conv2d_options.Union()),
}};
const std::array<int32_t, 1> subgraph_inputs{{0}};
const std::array<int32_t, 1> subgraph_outputs{{3}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(operators.data(), operators.size()));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Quantized DepthwiseConv2D model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,304 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_DEPTHWISE_CONV_2D_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_DEPTHWISE_CONV_2D_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
// Creates a model with a single DEPTHWISE_CONV_2D operator with quantized
// input, output, and weights, runs this model in two TensorFlow Lite
// interpreters, one with the delegate applied, and the other without, and
// compares the results.
class QuantizedDepthwiseConv2DTester
: public ModelCache<QuantizedDepthwiseConv2DTester> {
public:
QuantizedDepthwiseConv2DTester() = default;
QuantizedDepthwiseConv2DTester(const QuantizedDepthwiseConv2DTester&) =
delete;
QuantizedDepthwiseConv2DTester& operator=(
const QuantizedDepthwiseConv2DTester&) = delete;
inline QuantizedDepthwiseConv2DTester& BatchSize(int32_t batch_size) {
EXPECT_GT(batch_size, 0);
batch_size_ = batch_size;
return *this;
}
inline int32_t BatchSize() const { return batch_size_; }
inline QuantizedDepthwiseConv2DTester& InputChannels(int32_t input_channels) {
EXPECT_GT(input_channels, 0);
input_channels_ = input_channels;
return *this;
}
inline int32_t InputChannels() const { return input_channels_; }
inline QuantizedDepthwiseConv2DTester& DepthMultiplier(
int32_t depth_multiplier) {
EXPECT_GT(depth_multiplier, 0);
depth_multiplier_ = depth_multiplier;
return *this;
}
inline int32_t DepthMultiplier() const { return depth_multiplier_; }
inline int32_t OutputChannels() const {
return DepthMultiplier() * InputChannels();
}
inline QuantizedDepthwiseConv2DTester& InputHeight(int32_t input_height) {
EXPECT_GT(input_height, 0);
input_height_ = input_height;
return *this;
}
inline int32_t InputHeight() const { return input_height_; }
inline QuantizedDepthwiseConv2DTester& InputWidth(int32_t input_width) {
EXPECT_GT(input_width, 0);
input_width_ = input_width;
return *this;
}
inline int32_t InputWidth() const { return input_width_; }
inline int32_t OutputWidth() const {
if (Padding() == ::tflite::Padding_SAME) {
EXPECT_GE(InputWidth(), 1);
return (InputWidth() - 1) / StrideWidth() + 1;
} else {
EXPECT_GE(InputWidth(), DilatedKernelWidth());
return 1 + (InputWidth() - DilatedKernelWidth()) / StrideWidth();
}
}
inline int32_t OutputHeight() const {
if (Padding() == ::tflite::Padding_SAME) {
EXPECT_GE(InputHeight(), 1);
return (InputHeight() - 1) / StrideHeight() + 1;
} else {
EXPECT_GE(InputHeight(), DilatedKernelHeight());
return 1 + (InputHeight() - DilatedKernelHeight()) / StrideHeight();
}
}
inline QuantizedDepthwiseConv2DTester& KernelHeight(int32_t kernel_height) {
EXPECT_GT(kernel_height, 0);
kernel_height_ = kernel_height;
return *this;
}
inline int32_t KernelHeight() const { return kernel_height_; }
inline QuantizedDepthwiseConv2DTester& KernelWidth(int32_t kernel_width) {
EXPECT_GT(kernel_width, 0);
kernel_width_ = kernel_width;
return *this;
}
inline int32_t KernelWidth() const { return kernel_width_; }
inline QuantizedDepthwiseConv2DTester& StrideHeight(int32_t stride_height) {
EXPECT_GT(stride_height, 0);
stride_height_ = stride_height;
return *this;
}
inline int32_t StrideHeight() const { return stride_height_; }
inline QuantizedDepthwiseConv2DTester& StrideWidth(int32_t stride_width) {
EXPECT_GT(stride_width, 0);
stride_width_ = stride_width;
return *this;
}
inline int32_t StrideWidth() const { return stride_width_; }
inline QuantizedDepthwiseConv2DTester& DilationHeight(
int32_t dilation_height) {
EXPECT_GT(dilation_height, 0);
dilation_height_ = dilation_height;
return *this;
}
inline int32_t DilationHeight() const { return dilation_height_; }
inline QuantizedDepthwiseConv2DTester& DilationWidth(int32_t dilation_width) {
EXPECT_GT(dilation_width, 0);
dilation_width_ = dilation_width;
return *this;
}
inline int32_t DilationWidth() const { return dilation_width_; }
inline int32_t DilatedKernelHeight() const {
return (KernelHeight() - 1) * DilationHeight() + 1;
}
inline int32_t DilatedKernelWidth() const {
return (KernelWidth() - 1) * DilationWidth() + 1;
}
inline QuantizedDepthwiseConv2DTester& InputZeroPoint(
int32_t input_zero_point) {
input_zero_point_ = input_zero_point;
return *this;
}
inline int32_t InputZeroPoint() const { return input_zero_point_; }
inline QuantizedDepthwiseConv2DTester& OutputZeroPoint(
int32_t output_zero_point) {
output_zero_point_ = output_zero_point;
return *this;
}
inline int32_t OutputZeroPoint() const { return output_zero_point_; }
inline QuantizedDepthwiseConv2DTester& KernelZeroPoint(
int32_t kernel_zero_point) {
kernel_zero_point_ = kernel_zero_point;
return *this;
}
inline int32_t KernelZeroPoint() const { return kernel_zero_point_; }
inline QuantizedDepthwiseConv2DTester& InputScale(float input_scale) {
input_scale_ = input_scale;
return *this;
}
inline float InputScale() const { return input_scale_; }
inline QuantizedDepthwiseConv2DTester& KernelScale(float kernel_scale) {
kernel_scale_ = kernel_scale;
return *this;
}
inline float KernelScale() const {
EXPECT_FALSE(ChannelWise());
return kernel_scale_;
}
inline QuantizedDepthwiseConv2DTester& KernelScales(
const std::vector<float>& kernel_scales) {
EXPECT_GT(kernel_scales.size(), 0);
kernel_scales_ = kernel_scales;
return *this;
}
inline const std::vector<float>& KernelScales() const {
EXPECT_TRUE(ChannelWise());
return kernel_scales_;
}
inline bool Unsigned() const { return kernel_zero_point_ != 0; }
inline bool ChannelWise() const { return !kernel_scales_.empty(); }
inline QuantizedDepthwiseConv2DTester& OutputScale(float output_scale) {
output_scale_ = output_scale;
return *this;
}
inline float OutputScale() const { return output_scale_; }
inline QuantizedDepthwiseConv2DTester& SamePadding() {
padding_ = ::tflite::Padding_SAME;
return *this;
}
inline QuantizedDepthwiseConv2DTester& ValidPadding() {
padding_ = ::tflite::Padding_VALID;
return *this;
}
inline QuantizedDepthwiseConv2DTester& ReluActivation() {
activation_ = ::tflite::ActivationFunctionType_RELU;
return *this;
}
inline QuantizedDepthwiseConv2DTester& Relu6Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU6;
return *this;
}
inline QuantizedDepthwiseConv2DTester& ReluMinus1To1Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU_N1_TO_1;
return *this;
}
inline QuantizedDepthwiseConv2DTester& WeightsCache(
TfLiteXNNPackDelegateWeightsCache* weights_cache) {
weights_cache_ = weights_cache;
return *this;
}
template <class T>
void Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(TfLiteDelegate* delegate);
private:
std::vector<char> CreateTfLiteModel() const override;
inline ::tflite::Padding Padding() const { return padding_; }
inline ::tflite::ActivationFunctionType Activation() const {
return activation_;
}
int32_t batch_size_ = 1;
int32_t input_channels_ = 1;
int32_t depth_multiplier_ = 1;
int32_t input_height_ = 1;
int32_t input_width_ = 1;
int32_t kernel_height_ = 1;
int32_t kernel_width_ = 1;
int32_t stride_height_ = 1;
int32_t stride_width_ = 1;
int32_t dilation_height_ = 1;
int32_t dilation_width_ = 1;
int32_t input_zero_point_ = 0;
int32_t output_zero_point_ = 0;
int32_t kernel_zero_point_ = 0;
float input_scale_ = 0.8f;
float kernel_scale_ = 0.75f;
std::vector<float> kernel_scales_;
float output_scale_ = 1.5f;
::tflite::Padding padding_ = ::tflite::Padding_VALID;
::tflite::ActivationFunctionType activation_ =
::tflite::ActivationFunctionType_NONE;
TfLiteXNNPackDelegateWeightsCache* weights_cache_ = nullptr;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_DEPTHWISE_CONV_2D_TESTER_H_
@@ -0,0 +1,257 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantized_fully_connected_tester.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
std::vector<int32_t> QuantizedFullyConnectedTester::OutputShape() const {
EXPECT_NE(input_shape_.size(), 0);
if (KeepDims()) {
std::vector<int32_t> output_shape(input_shape_.cbegin(),
input_shape_.cend() - 1);
output_shape.push_back(OutputChannels());
return output_shape;
} else {
EXPECT_EQ(InputSize() % InputChannels(), 0);
return std::vector<int32_t>(
{InputSize() / InputChannels(), OutputChannels()});
}
}
template <class T>
void QuantizedFullyConnectedTester::Test(
Interpreter* delegate_interpreter, Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng = std::bind(
std::uniform_int_distribution<int32_t>(std::numeric_limits<T>::min(),
std::numeric_limits<T>::max()),
std::ref(rng));
T* default_input_data = default_interpreter->typed_input_tensor<T>(0);
std::generate_n(default_input_data, InputSize(), std::ref(input_rng));
T* delegate_input_data = delegate_interpreter->typed_input_tensor<T>(0);
std::copy_n(default_input_data, InputSize(), delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
T* default_output_data = default_interpreter->typed_output_tensor<T>(0);
T* delegate_output_data = delegate_interpreter->typed_output_tensor<T>(0);
for (size_t i = 0; i < ComputeSize(OutputShape()); i++) {
ASSERT_LE(std::abs(static_cast<int32_t>(default_output_data[i]) -
static_cast<int32_t>(delegate_output_data[i])),
1);
}
}
void QuantizedFullyConnectedTester::Test(TfLiteDelegate* delegate) {
const Model* model = GetModel();
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (weights_cache_ != nullptr) {
TfLiteXNNPackDelegateWeightsCacheFinalizeSoft(weights_cache_);
}
if (Unsigned()) {
Test<uint8_t>(delegate_interpreter.get(), default_interpreter.get());
} else {
Test<int8_t>(delegate_interpreter.get(), default_interpreter.get());
}
}
std::vector<char> QuantizedFullyConnectedTester::CreateTfLiteModel() const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto filter_rng = std::bind(std::uniform_int_distribution<int32_t>(
-std::numeric_limits<int8_t>::max(),
std::numeric_limits<int8_t>::max()),
std::ref(rng));
auto bias_rng = std::bind(
std::uniform_int_distribution<int32_t>(-10000, 10000), std::ref(rng));
flatbuffers::FlatBufferBuilder builder;
const std::array<flatbuffers::Offset<OperatorCode>, 1> operator_codes{
{CreateOperatorCode(builder, BuiltinOperator_FULLY_CONNECTED)}};
std::vector<flatbuffers::Offset<Operator>> operators;
std::vector<float> filter_data(InputChannels() * OutputChannels());
std::generate(filter_data.begin(), filter_data.end(), std::ref(filter_rng));
std::vector<float> bias_data(OutputChannels());
std::generate(bias_data.begin(), bias_data.end(), std::ref(bias_rng));
const std::array<flatbuffers::Offset<Buffer>, 3> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(filter_data.data()),
sizeof(int8_t) * filter_data.size())),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(bias_data.data()),
sizeof(int32_t) * bias_data.size())),
}};
const std::array<int32_t, 2> filter_shape{
{OutputChannels(), InputChannels()}};
const std::array<int32_t, 1> bias_shape{{OutputChannels()}};
const std::vector<int32_t> output_shape = OutputShape();
std::vector<flatbuffers::Offset<Tensor>> tensors;
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(InputShape().data(), InputShape().size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8, /*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({InputScale()}),
builder.CreateVector<int64_t>({InputZeroPoint()}))));
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(filter_shape.data(), filter_shape.size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8, /*buffer=*/1, /*name=*/0,
CreateQuantizationParameters(builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({FilterScale()}),
builder.CreateVector<int64_t>({0}))));
if (HasBias()) {
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(bias_shape.data(), bias_shape.size()),
TensorType_INT32, /*buffer=*/2, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({InputScale() * FilterScale()}),
builder.CreateVector<int64_t>({0}))));
}
tensors.emplace_back(CreateTensor(
builder,
builder.CreateVector<int32_t>(output_shape.data(), output_shape.size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8, /*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({OutputScale()}),
builder.CreateVector<int64_t>({OutputZeroPoint()}))));
flatbuffers::Offset<FullyConnectedOptions> fully_connected_options =
CreateFullyConnectedOptions(builder, Activation(),
FullyConnectedOptionsWeightsFormat_DEFAULT,
KeepDims());
std::vector<int32_t> op_inputs{{static_cast<int32_t>(tensors.size()) - 3,
static_cast<int32_t>(tensors.size()) - 2}};
if (HasBias()) {
op_inputs.insert(op_inputs.begin(),
static_cast<int32_t>(tensors.size()) - 4);
}
const std::array<int32_t, 1> op_outputs{
{static_cast<int32_t>(tensors.size()) - 1}};
operators.emplace_back(CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_FullyConnectedOptions, fully_connected_options.Union()));
const std::array<int32_t, 1> subgraph_inputs{
{static_cast<int>(tensors.size()) - 3 - static_cast<int>(HasBias())}};
const std::array<int32_t, 1> subgraph_outputs{
{static_cast<int>(tensors.size()) - 1}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(operators.data(), operators.size()));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Fully Connected model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t QuantizedFullyConnectedTester::ComputeSize(
const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,195 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_FULLY_CONNECTED_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_FULLY_CONNECTED_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class QuantizedFullyConnectedTester
: public ModelCache<QuantizedFullyConnectedTester> {
public:
QuantizedFullyConnectedTester() = default;
QuantizedFullyConnectedTester(const QuantizedFullyConnectedTester&) = delete;
QuantizedFullyConnectedTester& operator=(
const QuantizedFullyConnectedTester&) = delete;
inline QuantizedFullyConnectedTester& InputShape(
std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
input_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
input_size_ = ComputeSize(input_shape_);
return *this;
}
inline const std::vector<int32_t>& InputShape() const { return input_shape_; }
inline int32_t InputSize() const { return input_size_; }
inline QuantizedFullyConnectedTester& InputChannels(int32_t input_channels) {
EXPECT_GT(input_channels, 0);
input_channels_ = input_channels;
return *this;
}
inline int32_t InputChannels() const { return input_channels_; }
inline QuantizedFullyConnectedTester& OutputChannels(
int32_t output_channels) {
EXPECT_GT(output_channels, 0);
output_channels_ = output_channels;
return *this;
}
inline int32_t OutputChannels() const { return output_channels_; }
std::vector<int32_t> OutputShape() const;
inline QuantizedFullyConnectedTester& InputZeroPoint(
int32_t input_zero_point) {
input_zero_point_ = input_zero_point;
return *this;
}
inline int32_t InputZeroPoint() const { return input_zero_point_; }
inline QuantizedFullyConnectedTester& FilterZeroPoint(
int32_t filter_zero_point) {
filter_zero_point_ = filter_zero_point;
return *this;
}
inline int32_t FilterZeroPoint() const { return filter_zero_point_; }
inline QuantizedFullyConnectedTester& OutputZeroPoint(
int32_t output_zero_point) {
output_zero_point_ = output_zero_point;
return *this;
}
inline int32_t OutputZeroPoint() const { return output_zero_point_; }
inline QuantizedFullyConnectedTester& InputScale(float input_scale) {
input_scale_ = input_scale;
return *this;
}
inline float InputScale() const { return input_scale_; }
inline QuantizedFullyConnectedTester& FilterScale(float filter_scale) {
filter_scale_ = filter_scale;
return *this;
}
inline float FilterScale() const { return filter_scale_; }
inline QuantizedFullyConnectedTester& OutputScale(float output_scale) {
output_scale_ = output_scale;
return *this;
}
inline float OutputScale() const { return output_scale_; }
inline QuantizedFullyConnectedTester& KeepDims(bool keep_dims) {
keep_dims_ = keep_dims;
return *this;
}
inline bool KeepDims() const { return keep_dims_; }
inline bool Unsigned() const { return filter_zero_point_ != 0; }
inline QuantizedFullyConnectedTester& NoBias() {
has_bias_ = false;
return *this;
}
inline QuantizedFullyConnectedTester& WithBias() {
has_bias_ = true;
return *this;
}
inline QuantizedFullyConnectedTester& ReluActivation() {
activation_ = ::tflite::ActivationFunctionType_RELU;
return *this;
}
inline QuantizedFullyConnectedTester& Relu6Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU6;
return *this;
}
inline QuantizedFullyConnectedTester& ReluMinus1To1Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU_N1_TO_1;
return *this;
}
inline QuantizedFullyConnectedTester& WeightsCache(
TfLiteXNNPackDelegateWeightsCache* weights_cache) {
weights_cache_ = weights_cache;
return *this;
}
template <class T>
void Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(TfLiteDelegate* delegate);
private:
std::vector<char> CreateTfLiteModel() const override;
inline bool HasBias() const { return has_bias_; }
inline ::tflite::ActivationFunctionType Activation() const {
return activation_;
}
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> input_shape_;
int32_t input_size_ = 1;
int32_t input_channels_ = 1;
int32_t output_channels_ = 1;
int32_t input_zero_point_ = 0;
int32_t filter_zero_point_ = 0;
int32_t output_zero_point_ = 0;
float input_scale_ = 0.8f;
float filter_scale_ = 0.75f;
float output_scale_ = 1.5f;
bool keep_dims_ = false;
bool has_bias_ = true;
::tflite::ActivationFunctionType activation_ =
::tflite::ActivationFunctionType_NONE;
TfLiteXNNPackDelegateWeightsCache* weights_cache_ = nullptr;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_FULLY_CONNECTED_TESTER_H_
@@ -0,0 +1,187 @@
/* Copyright 2022 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantized_leaky_relu_tester.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
template <class T>
void QuantizedLeakyReluTester::Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
std::uniform_int_distribution<int32_t> input_distribution(
std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
auto input_rng = std::bind(input_distribution, std::ref(rng));
T* default_input_data = default_interpreter->typed_input_tensor<T>(0);
std::generate_n(default_input_data, ComputeSize(Shape()),
std::ref(input_rng));
T* xnnpack_input_data = delegate_interpreter->typed_input_tensor<T>(0);
std::copy_n(default_input_data, ComputeSize(Shape()), xnnpack_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
T* default_output_data = default_interpreter->typed_output_tensor<T>(0);
T* delegate_output_data = delegate_interpreter->typed_output_tensor<T>(0);
for (size_t i = 0; i < ComputeSize(Shape()); i++) {
ASSERT_LE(std::abs(static_cast<int32_t>(default_output_data[i]) -
static_cast<int32_t>(delegate_output_data[i])),
1)
<< "default " << static_cast<int32_t>(default_output_data[i])
<< ", delegate " << static_cast<int32_t>(delegate_output_data[i])
<< " at index " << i << " / " << ComputeSize(Shape());
}
}
void QuantizedLeakyReluTester::Test(TfLiteDelegate* delegate) const {
std::vector<char> buffer = CreateTfLiteModel();
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (Unsigned()) {
Test<uint8_t>(delegate_interpreter.get(), default_interpreter.get());
} else {
Test<int8_t>(delegate_interpreter.get(), default_interpreter.get());
}
}
std::vector<char> QuantizedLeakyReluTester::CreateTfLiteModel() const {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<OperatorCode> operator_code =
CreateOperatorCode(builder, BuiltinOperator_LEAKY_RELU);
const std::array<flatbuffers::Offset<Buffer>, 1> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
}};
const std::array<flatbuffers::Offset<Tensor>, 2> tensors{{
CreateTensor(
builder,
builder.CreateVector<int32_t>(Shape().data(), Shape().size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({InputScale()}),
builder.CreateVector<int64_t>({InputZeroPoint()}))),
CreateTensor(
builder,
builder.CreateVector<int32_t>(Shape().data(), Shape().size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({OutputScale()}),
builder.CreateVector<int64_t>({OutputZeroPoint()}))),
}};
const flatbuffers::Offset<LeakyReluOptions> leaky_relu_options =
CreateLeakyReluOptions(builder, NegativeSlope());
const std::array<int32_t, 1> op_inputs{{0}};
const std::array<int32_t, 1> op_outputs{{1}};
flatbuffers::Offset<Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_LeakyReluOptions, leaky_relu_options.Union());
const std::array<int32_t, 1> subgraph_inputs{{0}};
const std::array<int32_t, 1> subgraph_outputs{{1}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Quantized unary operator model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t QuantizedLeakyReluTester::ComputeSize(
const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,115 @@
/* Copyright 2022 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_LEAKY_RELU_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_LEAKY_RELU_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class QuantizedLeakyReluTester {
public:
QuantizedLeakyReluTester() = default;
QuantizedLeakyReluTester(const QuantizedLeakyReluTester&) = delete;
QuantizedLeakyReluTester& operator=(const QuantizedLeakyReluTester&) = delete;
inline QuantizedLeakyReluTester& Shape(std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
shape_ = std::vector<int32_t>(shape.begin(), shape.end());
size_ = QuantizedLeakyReluTester::ComputeSize(shape_);
return *this;
}
const std::vector<int32_t>& Shape() const { return shape_; }
int32_t Size() const { return size_; }
inline QuantizedLeakyReluTester& InputZeroPoint(int32_t input_zero_point) {
input_zero_point_ = input_zero_point;
return *this;
}
inline int32_t InputZeroPoint() const { return input_zero_point_; }
inline QuantizedLeakyReluTester& OutputZeroPoint(int32_t output_zero_point) {
output_zero_point_ = output_zero_point;
return *this;
}
inline int32_t OutputZeroPoint() const { return output_zero_point_; }
inline QuantizedLeakyReluTester& InputScale(float input_scale) {
input_scale_ = input_scale;
return *this;
}
inline float InputScale() const { return input_scale_; }
inline QuantizedLeakyReluTester& OutputScale(float output_scale) {
output_scale_ = output_scale;
return *this;
}
inline float OutputScale() const { return output_scale_; }
inline QuantizedLeakyReluTester& NegativeSlope(float negative_slope) {
negative_slope_ = negative_slope;
return *this;
}
inline float NegativeSlope() const { return negative_slope_; }
inline QuantizedLeakyReluTester& Unsigned(bool is_unsigned) {
unsigned_ = is_unsigned;
return *this;
}
inline bool Unsigned() const { return unsigned_; }
template <class T>
void Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel() const;
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> shape_;
int32_t size_;
int32_t input_zero_point_ = 0;
int32_t output_zero_point_ = 0;
float input_scale_ = 1.0f;
float output_scale_ = 1.0f;
float negative_slope_ = 0.5f;
bool unsigned_ = false;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_LEAKY_RELU_TESTER_H_
@@ -0,0 +1,214 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantized_pad_tester.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
std::vector<int32_t> QuantizedPadTester::OutputShape() const {
std::vector<int32_t> output_shape;
output_shape.reserve(InputShape().size());
for (size_t i = 0; i < InputShape().size(); i++) {
int32_t output_dim = InputShape()[i];
if (i < InputPrePaddings().size()) {
output_dim += InputPrePaddings()[i];
}
if (i < InputPostPaddings().size()) {
output_dim += InputPostPaddings()[i];
}
output_shape.push_back(output_dim);
}
return output_shape;
}
template <class T>
void QuantizedPadTester::Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng = std::bind(
std::uniform_int_distribution<int32_t>(std::numeric_limits<T>::min(),
std::numeric_limits<T>::max()),
std::ref(rng));
T* default_input_data = default_interpreter->typed_input_tensor<T>(0);
std::generate_n(default_input_data, ComputeSize(InputShape()),
std::ref(input_rng));
T* delegate_input_data = delegate_interpreter->typed_input_tensor<T>(0);
std::copy_n(default_input_data, ComputeSize(InputShape()),
delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
T* default_output_data = default_interpreter->typed_output_tensor<T>(0);
T* delegate_output_data = delegate_interpreter->typed_output_tensor<T>(0);
for (size_t i = 0; i < ComputeSize(OutputShape()); i++) {
ASSERT_EQ(default_output_data[i], delegate_output_data[i]);
}
}
void QuantizedPadTester::Test(TfLiteDelegate* delegate) const {
ASSERT_EQ(InputPrePaddings().size(), InputPostPaddings().size());
ASSERT_LE(InputPrePaddings().size(), InputShape().size());
std::vector<char> buffer = CreateTfLiteModel();
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (Unsigned()) {
Test<uint8_t>(delegate_interpreter.get(), default_interpreter.get());
} else {
Test<int8_t>(delegate_interpreter.get(), default_interpreter.get());
}
}
std::vector<char> QuantizedPadTester::CreateTfLiteModel() const {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<OperatorCode> operator_code =
CreateOperatorCode(builder, BuiltinOperator_PAD);
std::vector<int32_t> paddings(InputPrePaddings().size() +
InputPostPaddings().size());
for (size_t i = 0; i < InputPrePaddings().size(); i++) {
paddings[i * 2] = InputPrePaddings()[i];
paddings[i * 2 + 1] = InputPostPaddings()[i];
}
const std::array<flatbuffers::Offset<Buffer>, 2> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(paddings.data()),
sizeof(int32_t) * paddings.size())),
}};
const std::vector<int32_t> output_shape = OutputShape();
const std::array<int32_t, 2> paddings_shape{
{static_cast<int32_t>(InputPrePaddings().size()), 2}};
const std::array<flatbuffers::Offset<Tensor>, 3> tensors{{
CreateTensor(builder,
builder.CreateVector<int32_t>(InputShape().data(),
InputShape().size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({Scale()}),
builder.CreateVector<int64_t>({ZeroPoint()}))),
CreateTensor(builder,
builder.CreateVector<int32_t>(paddings_shape.data(),
paddings_shape.size()),
TensorType_INT32, /*buffer=*/1),
CreateTensor(builder,
builder.CreateVector<int32_t>(output_shape.data(),
output_shape.size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({Scale()}),
builder.CreateVector<int64_t>({ZeroPoint()}))),
}};
const std::array<int32_t, 2> op_inputs{{0, 1}};
const std::array<int32_t, 1> op_outputs{{2}};
flatbuffers::Offset<Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()));
const std::array<int32_t, 1> subgraph_inputs{{0}};
const std::array<int32_t, 1> subgraph_outputs{{2}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Quantized Pad model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t QuantizedPadTester::ComputeSize(const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,119 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_PAD_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_PAD_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class QuantizedPadTester {
public:
QuantizedPadTester() = default;
QuantizedPadTester(const QuantizedPadTester&) = delete;
QuantizedPadTester& operator=(const QuantizedPadTester&) = delete;
inline QuantizedPadTester& InputShape(std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
input_shape_ = std::vector<int32_t>(shape.begin(), shape.end());
return *this;
}
inline const std::vector<int32_t>& InputShape() const { return input_shape_; }
inline QuantizedPadTester& InputPrePaddings(
std::initializer_list<int32_t> paddings) {
for (auto it = paddings.begin(); it != paddings.end(); ++it) {
EXPECT_GE(*it, 0);
}
input_pre_paddings_ =
std::vector<int32_t>(paddings.begin(), paddings.end());
return *this;
}
inline const std::vector<int32_t> InputPrePaddings() const {
return input_pre_paddings_;
}
inline QuantizedPadTester& InputPostPaddings(
std::initializer_list<int32_t> paddings) {
for (auto it = paddings.begin(); it != paddings.end(); ++it) {
EXPECT_GE(*it, 0);
}
input_post_paddings_ =
std::vector<int32_t>(paddings.begin(), paddings.end());
return *this;
}
inline const std::vector<int32_t> InputPostPaddings() const {
return input_post_paddings_;
}
std::vector<int32_t> OutputShape() const;
inline QuantizedPadTester& ZeroPoint(int32_t zero_point) {
zero_point_ = zero_point;
return *this;
}
inline int32_t ZeroPoint() const { return zero_point_; }
inline QuantizedPadTester& Scale(float scale) {
scale_ = scale;
return *this;
}
inline float Scale() const { return scale_; }
inline QuantizedPadTester& Unsigned(bool is_unsigned) {
unsigned_ = is_unsigned;
return *this;
}
inline bool Unsigned() const { return unsigned_; }
template <class T>
void Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel() const;
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> input_shape_;
std::vector<int32_t> input_pre_paddings_;
std::vector<int32_t> input_post_paddings_;
int32_t zero_point_ = 7;
float scale_ = 0.8f;
bool unsigned_ = false;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_PAD_TESTER_H_
@@ -0,0 +1,211 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantized_pool_2d_tester.h"
#include <array>
#include <cstdint>
#include <functional>
#include <limits>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/c/c_api_types.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
template <class T>
void QuantizedPool2DTester::Test(tflite::BuiltinOperator pool_op,
Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng = std::bind(
std::uniform_int_distribution<int32_t>(std::numeric_limits<T>::min(),
std::numeric_limits<T>::max()),
std::ref(rng));
T* default_input_data = default_interpreter->typed_input_tensor<T>(0);
std::generate_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * Channels(),
std::ref(input_rng));
T* xnnpack_input_data = delegate_interpreter->typed_input_tensor<T>(0);
std::copy_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * Channels(),
xnnpack_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
T* default_output_data = default_interpreter->typed_output_tensor<T>(0);
T* xnnpack_output_data = delegate_interpreter->typed_output_tensor<T>(0);
for (int32_t i = 0; i < BatchSize(); i++) {
for (int32_t y = 0; y < OutputHeight(); y++) {
for (int32_t x = 0; x < OutputWidth(); x++) {
for (int32_t c = 0; c < Channels(); c++) {
const int32_t index =
((i * OutputHeight() + y) * OutputWidth() + x) * Channels() + c;
if (pool_op == BuiltinOperator_MAX_POOL_2D) {
// MaxPooling results must be exact
ASSERT_EQ(static_cast<int32_t>(default_output_data[index]),
static_cast<int32_t>(xnnpack_output_data[index]))
<< "batch " << i << " / " << BatchSize() << ", y position " << y
<< " / " << OutputHeight() << ", x position " << x << " / "
<< OutputWidth() << ", channel " << c << " / " << Channels();
} else {
ASSERT_NEAR(static_cast<float>(default_output_data[index]),
static_cast<float>(xnnpack_output_data[index]), 1.0f)
<< "batch " << i << " / " << BatchSize() << ", y position " << y
<< " / " << OutputHeight() << ", x position " << x << " / "
<< OutputWidth() << ", channel " << c << " / " << Channels();
}
}
}
}
}
}
void QuantizedPool2DTester::Test(tflite::BuiltinOperator pool_op,
TfLiteDelegate* delegate) const {
std::vector<char> buffer = CreateTfLiteModel(pool_op);
const tflite::Model* model = tflite::GetModel(buffer.data());
std::unique_ptr<tflite::Interpreter> delegate_interpreter;
ASSERT_EQ(
tflite::InterpreterBuilder(
model,
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<tflite::Interpreter> default_interpreter;
ASSERT_EQ(
tflite::InterpreterBuilder(
model,
tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (Unsigned()) {
Test<uint8_t>(pool_op, delegate_interpreter.get(),
default_interpreter.get());
} else {
Test<int8_t>(pool_op, delegate_interpreter.get(),
default_interpreter.get());
}
}
std::vector<char> QuantizedPool2DTester::CreateTfLiteModel(
tflite::BuiltinOperator pool_op) const {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<tflite::OperatorCode> operator_code =
CreateOperatorCode(builder, pool_op, 0);
flatbuffers::Offset<tflite::Pool2DOptions> pool_2d_options =
CreatePool2DOptions(builder, Padding(), StrideWidth(), StrideHeight(),
PoolingWidth(), PoolingHeight(), Activation());
const flatbuffers::Offset<tflite::Buffer> null_buffer =
tflite::CreateBuffer(builder, builder.CreateVector({}));
const std::array<int32_t, 4> input_shape{
{BatchSize(), InputHeight(), InputWidth(), Channels()}};
const std::array<int32_t, 4> output_shape{
{BatchSize(), OutputHeight(), OutputWidth(), Channels()}};
const std::array<flatbuffers::Offset<tflite::Tensor>, 2> tensors{{
tflite::CreateTensor(
builder,
builder.CreateVector<int32_t>(input_shape.data(), input_shape.size()),
Unsigned() ? tflite::TensorType_UINT8 : tflite::TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({Scale()}),
builder.CreateVector<int64_t>({ZeroPoint()}))),
tflite::CreateTensor(
builder,
builder.CreateVector<int32_t>(output_shape.data(),
output_shape.size()),
Unsigned() ? tflite::TensorType_UINT8 : tflite::TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({Scale()}),
builder.CreateVector<int64_t>({ZeroPoint()}))),
}};
const std::array<int32_t, 1> op_inputs{{0}};
const std::array<int32_t, 1> op_outputs{{1}};
flatbuffers::Offset<tflite::Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
tflite::BuiltinOptions_Pool2DOptions, pool_2d_options.Union());
const std::array<int32_t, 1> subgraph_inputs{{0}};
const std::array<int32_t, 1> subgraph_outputs{{1}};
flatbuffers::Offset<tflite::SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Quantized Pool2D model");
flatbuffers::Offset<tflite::Model> model_buffer = tflite::CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(&null_buffer, 1));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,206 @@
/* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_POOL_2D_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_POOL_2D_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class QuantizedPool2DTester {
public:
QuantizedPool2DTester() = default;
QuantizedPool2DTester(const QuantizedPool2DTester&) = delete;
QuantizedPool2DTester& operator=(const QuantizedPool2DTester&) = delete;
inline QuantizedPool2DTester& BatchSize(int32_t batch_size) {
EXPECT_GT(batch_size, 0);
batch_size_ = batch_size;
return *this;
}
inline int32_t BatchSize() const { return batch_size_; }
inline QuantizedPool2DTester& Channels(int32_t channels) {
EXPECT_GT(channels, 0);
channels_ = channels;
return *this;
}
inline int32_t Channels() const { return channels_; }
inline QuantizedPool2DTester& InputHeight(int32_t input_height) {
EXPECT_GT(input_height, 0);
input_height_ = input_height;
return *this;
}
inline int32_t InputHeight() const { return input_height_; }
inline QuantizedPool2DTester& InputWidth(int32_t input_width) {
EXPECT_GT(input_width, 0);
input_width_ = input_width;
return *this;
}
inline int32_t InputWidth() const { return input_width_; }
inline int32_t OutputWidth() const {
if (Padding() == ::tflite::Padding_SAME) {
return (InputWidth() - 1) / StrideWidth() + 1;
} else {
return (InputWidth() - PoolingWidth()) / StrideWidth() + 1;
}
}
inline int32_t OutputHeight() const {
if (Padding() == ::tflite::Padding_SAME) {
return (InputHeight() - 1) / StrideHeight() + 1;
} else {
return (InputHeight() - PoolingHeight()) / StrideHeight() + 1;
}
}
inline QuantizedPool2DTester& PoolingHeight(int32_t pooling_height) {
EXPECT_GT(pooling_height, 0);
pooling_height_ = pooling_height;
return *this;
}
inline int32_t PoolingHeight() const { return pooling_height_; }
inline QuantizedPool2DTester& PoolingWidth(int32_t pooling_width) {
EXPECT_GT(pooling_width, 0);
pooling_width_ = pooling_width;
return *this;
}
inline int32_t PoolingWidth() const { return pooling_width_; }
inline QuantizedPool2DTester& StrideHeight(int32_t stride_height) {
EXPECT_GT(stride_height, 0);
stride_height_ = stride_height;
return *this;
}
inline int32_t StrideHeight() const { return stride_height_; }
inline QuantizedPool2DTester& StrideWidth(int32_t stride_width) {
EXPECT_GT(stride_width, 0);
stride_width_ = stride_width;
return *this;
}
inline int32_t StrideWidth() const { return stride_width_; }
inline QuantizedPool2DTester& SamePadding() {
padding_ = ::tflite::Padding_SAME;
return *this;
}
inline QuantizedPool2DTester& ValidPadding() {
padding_ = ::tflite::Padding_VALID;
return *this;
}
inline QuantizedPool2DTester& ReluActivation() {
activation_ = ::tflite::ActivationFunctionType_RELU;
return *this;
}
inline QuantizedPool2DTester& Relu6Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU6;
return *this;
}
inline QuantizedPool2DTester& ReluMinus1To1Activation() {
activation_ = ::tflite::ActivationFunctionType_RELU_N1_TO_1;
return *this;
}
inline QuantizedPool2DTester& TanhActivation() {
activation_ = ::tflite::ActivationFunctionType_TANH;
return *this;
}
inline QuantizedPool2DTester& SignBitActivation() {
activation_ = ::tflite::ActivationFunctionType_SIGN_BIT;
return *this;
}
inline QuantizedPool2DTester& ZeroPoint(int32_t zero_point) {
zero_point_ = zero_point;
return *this;
}
inline int32_t ZeroPoint() const { return zero_point_; }
inline QuantizedPool2DTester& Scale(float scale) {
scale_ = scale;
return *this;
}
inline float Scale() const { return scale_; }
inline QuantizedPool2DTester& Unsigned(bool is_unsigned) {
unsigned_ = is_unsigned;
return *this;
}
inline bool Unsigned() const { return unsigned_; }
template <class T>
void Test(tflite::BuiltinOperator pool_op, Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(tflite::BuiltinOperator pool_op, TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel(tflite::BuiltinOperator pool_op) const;
inline ::tflite::Padding Padding() const { return padding_; }
inline ::tflite::ActivationFunctionType Activation() const {
return activation_;
}
int32_t batch_size_ = 1;
int32_t channels_ = 1;
int32_t input_height_ = 1;
int32_t input_width_ = 1;
int32_t pooling_height_ = 1;
int32_t pooling_width_ = 1;
int32_t stride_height_ = 1;
int32_t stride_width_ = 1;
::tflite::Padding padding_ = ::tflite::Padding_VALID;
::tflite::ActivationFunctionType activation_ =
::tflite::ActivationFunctionType_NONE;
int32_t zero_point_ = 7;
float scale_ = 0.5f;
bool unsigned_ = false;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_POOL_2D_TESTER_H_
@@ -0,0 +1,208 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantized_resize_bilinear_tester.h"
#include <array>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
template <class T>
void QuantizedResizeBilinearTester::Test(
Interpreter* delegate_interpreter, Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
auto input_rng = std::bind(
std::uniform_int_distribution<int32_t>(std::numeric_limits<T>::min(),
std::numeric_limits<T>::max()),
std::ref(rng));
T* default_input_data = default_interpreter->typed_input_tensor<T>(0);
std::generate_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * Channels(),
std::ref(input_rng));
T* delegate_input_data = delegate_interpreter->typed_input_tensor<T>(0);
std::copy_n(default_input_data,
BatchSize() * InputHeight() * InputWidth() * Channels(),
delegate_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
T* default_output_data = default_interpreter->typed_output_tensor<T>(0);
T* delegate_output_data = delegate_interpreter->typed_output_tensor<T>(0);
for (int i = 0; i < BatchSize(); i++) {
for (int y = 0; y < OutputHeight(); y++) {
for (int x = 0; x < OutputWidth(); x++) {
for (int c = 0; c < Channels(); c++) {
const int index =
((i * OutputHeight() + y) * OutputWidth() + x) * Channels() + c;
ASSERT_LE(std::abs(static_cast<int32_t>(default_output_data[index]) -
static_cast<int32_t>(delegate_output_data[index])),
1)
<< "batch " << i << " / " << BatchSize() << ", y position " << y
<< " / " << OutputHeight() << ", x position " << x << " / "
<< OutputWidth() << ", channel " << c << " / " << Channels();
}
}
}
}
}
void QuantizedResizeBilinearTester::Test(TfLiteDelegate* delegate) const {
std::vector<char> buffer = CreateTfLiteModel();
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (Unsigned()) {
Test<uint8_t>(delegate_interpreter.get(), default_interpreter.get());
} else {
Test<int8_t>(delegate_interpreter.get(), default_interpreter.get());
}
}
std::vector<char> QuantizedResizeBilinearTester::CreateTfLiteModel() const {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<OperatorCode> operator_code =
CreateOperatorCode(builder, BuiltinOperator_RESIZE_BILINEAR);
flatbuffers::Offset<tflite::ResizeBilinearOptions> resize_bilinear_options =
CreateResizeBilinearOptions(builder, AlignCorners(), HalfPixelCenters());
const std::array<int32_t, 2> size_data{{OutputHeight(), OutputWidth()}};
const std::array<flatbuffers::Offset<Buffer>, 2> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
CreateBuffer(builder,
builder.CreateVector(
reinterpret_cast<const uint8_t*>(size_data.data()),
size_data.size() * sizeof(int32_t))),
}};
const std::array<int32_t, 4> input_shape{
{BatchSize(), InputHeight(), InputWidth(), Channels()}};
const std::array<int32_t, 4> output_shape{
{BatchSize(), OutputHeight(), OutputWidth(), Channels()}};
const std::array<int32_t, 1> size_shape{
{static_cast<int32_t>(size_data.size())}};
const std::array<flatbuffers::Offset<Tensor>, 3> tensors{{
CreateTensor(
builder,
builder.CreateVector<int32_t>(input_shape.data(), input_shape.size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({Scale()}),
builder.CreateVector<int64_t>({ZeroPoint()}))),
CreateTensor(
builder,
builder.CreateVector<int32_t>(size_shape.data(), size_shape.size()),
TensorType_INT32, /*buffer=*/1),
CreateTensor(builder,
builder.CreateVector<int32_t>(output_shape.data(),
output_shape.size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({Scale()}),
builder.CreateVector<int64_t>({ZeroPoint()}))),
}};
const std::array<int32_t, 2> op_inputs{{0, 1}};
const std::array<int32_t, 1> op_outputs{{2}};
flatbuffers::Offset<Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()),
BuiltinOptions_ResizeBilinearOptions, resize_bilinear_options.Union());
const std::array<int32_t, 1> subgraph_inputs{{0}};
const std::array<int32_t, 1> subgraph_outputs{{2}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Quantized Resize Bilinear model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,145 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_RESIZE_BILINEAR_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_RESIZE_BILINEAR_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class QuantizedResizeBilinearTester {
public:
QuantizedResizeBilinearTester() = default;
QuantizedResizeBilinearTester(const QuantizedResizeBilinearTester&) = delete;
QuantizedResizeBilinearTester& operator=(
const QuantizedResizeBilinearTester&) = delete;
inline QuantizedResizeBilinearTester& BatchSize(int32_t batch_size) {
EXPECT_GT(batch_size, 0);
batch_size_ = batch_size;
return *this;
}
inline int32_t BatchSize() const { return batch_size_; }
inline QuantizedResizeBilinearTester& Channels(int32_t channels) {
EXPECT_GT(channels, 0);
channels_ = channels;
return *this;
}
inline int32_t Channels() const { return channels_; }
inline QuantizedResizeBilinearTester& InputHeight(int32_t input_height) {
EXPECT_GT(input_height, 0);
input_height_ = input_height;
return *this;
}
inline int32_t InputHeight() const { return input_height_; }
inline QuantizedResizeBilinearTester& InputWidth(int32_t input_width) {
EXPECT_GT(input_width, 0);
input_width_ = input_width;
return *this;
}
inline int32_t InputWidth() const { return input_width_; }
inline QuantizedResizeBilinearTester& OutputHeight(int32_t output_height) {
EXPECT_GT(output_height, 0);
output_height_ = output_height;
return *this;
}
inline int32_t OutputHeight() const { return output_height_; }
inline QuantizedResizeBilinearTester& OutputWidth(int32_t output_width) {
EXPECT_GT(output_width, 0);
output_width_ = output_width;
return *this;
}
inline int32_t OutputWidth() const { return output_width_; }
QuantizedResizeBilinearTester& AlignCorners(bool align_corners) {
align_corners_ = align_corners;
return *this;
}
bool AlignCorners() const { return align_corners_; }
QuantizedResizeBilinearTester& HalfPixelCenters(bool half_pixel_centers) {
half_pixel_centers_ = half_pixel_centers;
return *this;
}
bool HalfPixelCenters() const { return half_pixel_centers_; }
inline QuantizedResizeBilinearTester& ZeroPoint(int32_t zero_point) {
zero_point_ = zero_point;
return *this;
}
inline int32_t ZeroPoint() const { return zero_point_; }
inline QuantizedResizeBilinearTester& Scale(float scale) {
scale_ = scale;
return *this;
}
inline float Scale() const { return scale_; }
inline QuantizedResizeBilinearTester& Unsigned(bool is_unsigned) {
unsigned_ = is_unsigned;
return *this;
}
inline bool Unsigned() const { return unsigned_; }
template <class T>
void Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel() const;
int32_t batch_size_ = 1;
int32_t channels_ = 1;
int32_t input_height_ = 1;
int32_t input_width_ = 1;
int32_t output_height_ = 1;
int32_t output_width_ = 1;
bool align_corners_ = false;
bool half_pixel_centers_ = false;
int32_t zero_point_ = 2;
float scale_ = 0.75f;
bool unsigned_ = false;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_RESIZE_BILINEAR_TESTER_H_
@@ -0,0 +1,287 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantized_transpose_conv_tester.h"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
void QuantizedTransposeConvTester::Test(TfLiteDelegate* delegate) {
const Model* model = GetModel();
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (weights_cache_ != nullptr) {
TfLiteXNNPackDelegateWeightsCacheFinalizeSoft(weights_cache_);
}
std::random_device random_device;
auto rng = std::mt19937(random_device());
const int input_data_size =
BatchSize() * InputHeight() * InputWidth() * InputChannels();
// std::uniform_int_distribution<T> is undefined behavior when T is not short,
// int, long, long long, or their respective unsigned variants:
// https://en.cppreference.com/w/cpp/numeric/random/uniform_int_distribution.
auto uint8rng =
std::bind(std::uniform_int_distribution<int32_t>(0, 255), rng);
uint8_t* default_input_data = reinterpret_cast<uint8_t*>(
default_interpreter->input_tensor(0)->data.data);
std::generate_n(default_input_data, input_data_size, std::ref(uint8rng));
uint8_t* xnnpack_input_data = reinterpret_cast<uint8_t*>(
delegate_interpreter->input_tensor(0)->data.data);
std::copy_n(default_input_data, input_data_size, xnnpack_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
if (Unsigned()) {
EnsureOutputsClose<uint8_t>(default_interpreter.get(),
delegate_interpreter.get());
} else {
EnsureOutputsClose<int8_t>(default_interpreter.get(),
delegate_interpreter.get());
}
}
template <typename WeightType>
void QuantizedTransposeConvTester::EnsureOutputsClose(
const Interpreter* default_interpreter,
const Interpreter* delegate_interpreter) const {
const WeightType* default_output_data =
default_interpreter->typed_output_tensor<WeightType>(0);
const WeightType* xnnpack_output_data =
delegate_interpreter->typed_output_tensor<WeightType>(0);
const size_t output_data_size =
BatchSize() * OutputHeight() * OutputWidth() * OutputChannels();
const int kQuantizationErrorTolerance = 1;
for (size_t i = 0; i < output_data_size; i++) {
const int diff = static_cast<int>(default_output_data[i]) -
static_cast<int>(xnnpack_output_data[i]);
ASSERT_LE(std::abs(diff), kQuantizationErrorTolerance);
}
}
std::vector<char> QuantizedTransposeConvTester::CreateTfLiteModel() const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
const std::vector<int32_t> input_shape = {BatchSize(), InputHeight(),
InputWidth(), InputChannels()};
const std::vector<int32_t> output_shape = {BatchSize(), OutputHeight(),
OutputWidth(), OutputChannels()};
const std::vector<int32_t> filter_shape = {OutputChannels(), KernelHeight(),
KernelWidth(), InputChannels()};
const std::vector<int32_t> bias_shape = {OutputChannels()};
flatbuffers::FlatBufferBuilder builder;
std::vector<flatbuffers::Offset<OperatorCode>> operator_codes;
std::vector<flatbuffers::Offset<tflite::Operator>> operators;
std::vector<flatbuffers::Offset<Tensor>> tensors;
// Buffer 0 is a sentinel as required by the schema, means "no buffer".
std::vector<flatbuffers::Offset<tflite::Buffer>> buffers = {
CreateBuffer(builder, builder.CreateVector({}))};
const int kNoBuffer = 0;
// Create a tensor containing the expected output shape.
const int buffer_index_output_shape = buffers.size();
buffers.emplace_back(CreateBuffer(
builder, builder.CreateVector(
reinterpret_cast<const uint8_t*>(output_shape.data()),
sizeof(int32_t) * output_shape.size())));
std::vector<int32_t> output_shape_tensor_shape = {4};
const int tensor_index_output_shape = tensors.size();
tensors.emplace_back(
CreateTensorDirect(builder, &output_shape_tensor_shape, TensorType_INT32,
/*buffer=*/buffer_index_output_shape));
flatbuffers::Offset<::tflite::QuantizationParameters>
quantization_parameters = 0;
std::vector<uint8_t> filter_data(OutputChannels() * KernelHeight() *
KernelWidth() * InputChannels());
auto uint8rng =
std::bind(std::uniform_int_distribution<int32_t>(0, 255), rng);
std::generate(filter_data.begin(), filter_data.end(), uint8rng);
const int buffer_index_filter = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(reinterpret_cast<const uint8_t*>(filter_data.data()),
sizeof(uint8_t) * filter_data.size())));
const ::tflite::TensorType input_tensor_type =
Unsigned() ? ::tflite::TensorType_UINT8 : ::tflite::TensorType_INT8;
auto f32rng = std::bind(std::uniform_real_distribution<float>(), rng);
const float quantization_scale = f32rng();
int64_t zero_point = 0;
if (Unsigned()) {
zero_point = std::accumulate(filter_data.begin(), filter_data.end(), 0) /
filter_data.size();
}
quantization_parameters = CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({quantization_scale}),
builder.CreateVector<int64_t>({zero_point}));
tensors.emplace_back(CreateTensorDirect(
builder, &filter_shape, input_tensor_type, buffer_index_filter,
/*name=*/nullptr, quantization_parameters));
if (UseBias()) {
const int32_t kMaxAbsBias = 10000;
auto int32rng = std::bind(
std::uniform_int_distribution<int32_t>(-kMaxAbsBias, kMaxAbsBias), rng);
std::vector<int32_t> bias_data(OutputChannels());
std::generate(bias_data.begin(), bias_data.end(), int32rng);
const int buffer_index_bias = buffers.size();
buffers.emplace_back(CreateBuffer(
builder,
builder.CreateVector(reinterpret_cast<const uint8_t*>(bias_data.data()),
sizeof(int32_t) * bias_data.size())));
// TFLite checks that bias quantization scale is close to that of the
// input and filter quantization scales multiplied.
const float bias_quantization_scale =
quantization_scale * quantization_scale;
auto bias_quantization_parameters = CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
/*scale=*/builder.CreateVector<float>({bias_quantization_scale}),
/*zero_point=*/builder.CreateVector<int64_t>({0}));
tensors.emplace_back(
CreateTensorDirect(builder, &bias_shape, TensorType_INT32,
/*buffer=*/buffer_index_bias,
/*name=*/nullptr, bias_quantization_parameters));
}
const int top_tensor = tensors.size() - 1;
const int tensor_index_filter = UseBias() ? top_tensor - 1 : top_tensor;
const int tensor_index_input = tensors.size();
tensors.emplace_back(
CreateTensorDirect(builder, &input_shape, input_tensor_type, kNoBuffer,
/*name=*/nullptr, quantization_parameters));
std::vector<int32_t> op_inputs = {tensor_index_output_shape,
tensor_index_filter, tensor_index_input};
if (UseBias()) {
const int tensor_index_bias = top_tensor;
op_inputs.push_back(tensor_index_bias);
}
const int tensor_index_output = tensors.size();
tensors.emplace_back(
CreateTensorDirect(builder, &output_shape, input_tensor_type, kNoBuffer,
/*name=*/nullptr, quantization_parameters));
const std::vector<int32_t> op_outputs = {tensor_index_output};
const int opcode_index_transpose_conv = operator_codes.size();
operator_codes.emplace_back(
CreateOperatorCode(builder, BuiltinOperator_TRANSPOSE_CONV));
flatbuffers::Offset<TransposeConvOptions> transpose_conv_options =
CreateTransposeConvOptions(builder, Padding(), StrideWidth(),
StrideHeight());
operators.emplace_back(CreateOperatorDirect(
builder, /*opcode_index=*/opcode_index_transpose_conv, &op_inputs,
&op_outputs, BuiltinOptions_TransposeConvOptions,
transpose_conv_options.Union()));
const std::vector<int32_t> subgraph_inputs = {tensor_index_input};
const std::vector<int32_t> subgraph_outputs = {tensor_index_output};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraphDirect(
builder, &tensors, &subgraph_inputs, &subgraph_outputs, &operators);
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Quantized TransposeConv model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION,
builder.CreateVector(operator_codes.data(), operator_codes.size()),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,233 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_TRANSPOSE_CONV_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_TRANSPOSE_CONV_TESTER_H_
#include <cstdint>
#include <functional>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/builtin_op_data.h"
#include "tensorflow/lite/delegates/xnnpack/test_util.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class QuantizedTransposeConvTester
: public ModelCache<QuantizedTransposeConvTester> {
public:
explicit QuantizedTransposeConvTester() = default;
QuantizedTransposeConvTester(const QuantizedTransposeConvTester&) = delete;
QuantizedTransposeConvTester& operator=(const QuantizedTransposeConvTester&) =
delete;
inline QuantizedTransposeConvTester& BatchSize(int32_t batch_size) {
EXPECT_GT(batch_size, 0);
batch_size_ = batch_size;
return *this;
}
inline int32_t BatchSize() const { return batch_size_; }
inline QuantizedTransposeConvTester& InputChannels(int32_t input_channels) {
EXPECT_GT(input_channels, 0);
input_channels_ = input_channels;
return *this;
}
inline int32_t InputChannels() const { return input_channels_; }
inline QuantizedTransposeConvTester& OutputChannels(int32_t output_channels) {
EXPECT_GT(output_channels, 0);
output_channels_ = output_channels;
return *this;
}
inline int32_t OutputChannels() const { return output_channels_; }
inline QuantizedTransposeConvTester& OutputHeight(int32_t output_height) {
EXPECT_GT(output_height, 0);
output_height_ = output_height;
return *this;
}
inline int32_t OutputHeight() const { return output_height_; }
inline QuantizedTransposeConvTester& OutputWidth(int32_t output_width) {
EXPECT_GT(output_width, 0);
output_width_ = output_width;
return *this;
}
inline int32_t OutputWidth() const { return output_width_; }
inline QuantizedTransposeConvTester& KernelHeight(int32_t kernel_height) {
EXPECT_GT(kernel_height, 0);
kernel_height_ = kernel_height;
return *this;
}
inline int32_t KernelHeight() const { return kernel_height_; }
inline QuantizedTransposeConvTester& KernelWidth(int32_t kernel_width) {
EXPECT_GT(kernel_width, 0);
kernel_width_ = kernel_width;
return *this;
}
inline int32_t KernelWidth() const { return kernel_width_; }
inline QuantizedTransposeConvTester& StrideHeight(int32_t stride_height) {
EXPECT_GT(stride_height, 0);
stride_height_ = stride_height;
return *this;
}
inline int32_t StrideHeight() const { return stride_height_; }
inline QuantizedTransposeConvTester& StrideWidth(int32_t stride_width) {
EXPECT_GT(stride_width, 0);
stride_width_ = stride_width;
return *this;
}
inline int32_t StrideWidth() const { return stride_width_; }
inline QuantizedTransposeConvTester& SparseWeights() {
sparse_weights_ = true;
return *this;
}
inline bool SparseWeights() const { return sparse_weights_; }
inline QuantizedTransposeConvTester& SamePadding() {
padding_ = ::tflite::Padding_SAME;
return *this;
}
inline QuantizedTransposeConvTester& ValidPadding() {
padding_ = ::tflite::Padding_VALID;
return *this;
}
inline ::tflite::Padding Padding() const { return padding_; }
inline int32_t InputWidth() const {
return ComputeInputSize(OutputWidth(), KernelWidth(), StrideWidth());
}
inline int32_t InputHeight() const {
return ComputeInputSize(OutputHeight(), KernelHeight(), StrideHeight());
}
inline int32_t PaddingWidth() const {
return ComputePadding(OutputWidth(), KernelWidth(), StrideWidth());
}
inline int32_t PaddingHeight() const {
return ComputePadding(OutputHeight(), KernelHeight(), StrideHeight());
}
inline bool UseBias() const { return use_bias_; }
inline QuantizedTransposeConvTester& WithBias(bool use_bias = true) {
use_bias_ = use_bias;
return *this;
}
inline QuantizedTransposeConvTester& NoBias() { return WithBias(false); }
inline QuantizedTransposeConvTester& Unsigned(bool is_unsigned) {
unsigned_ = is_unsigned;
return *this;
}
inline QuantizedTransposeConvTester& Signed(bool is_signed = true) {
return Unsigned(!is_signed);
}
inline bool Unsigned() const { return unsigned_; }
inline QuantizedTransposeConvTester& WeightsCache(
TfLiteXNNPackDelegateWeightsCache* weights_cache) {
weights_cache_ = weights_cache;
return *this;
}
void Test(TfLiteDelegate* delegate);
private:
int32_t ComputeInputSize(int32_t output_size, int32_t kernel_size,
int32_t stride) const {
// Roughly follows TFLite's `ComputeOutSize`.
switch (padding_) {
case ::tflite::Padding_VALID:
return (output_size + stride - kernel_size) / stride;
break;
case ::tflite::Padding_SAME:
return (output_size + stride - 1) / stride;
break;
default:
assert(false);
}
}
int32_t ComputePadding(int32_t output_size, int32_t kernel_size,
int32_t stride) const {
// Roughly follows TFLite's `ComputePaddingWithOffset`.
if (padding_ == ::tflite::Padding_VALID) {
return 0;
}
assert(padding_ == ::tflite::Padding_SAME);
const int32_t input_size =
ComputeInputSize(output_size, kernel_size, stride);
return (output_size - 1) * stride + kernel_size - input_size;
}
private:
std::vector<char> CreateTfLiteModel() const override;
template <typename WeightType>
void EnsureOutputsClose(const Interpreter* default_interpreter,
const Interpreter* delegate_interpreter) const;
private:
int32_t batch_size_ = 1;
int32_t input_channels_ = 1;
int32_t output_channels_ = 1;
int32_t output_height_ = 1;
int32_t output_width_ = 1;
int32_t kernel_height_ = 1;
int32_t kernel_width_ = 1;
int32_t stride_height_ = 1;
int32_t stride_width_ = 1;
::tflite::Padding padding_ = ::tflite::Padding_VALID;
bool unsigned_ = true;
bool use_bias_ = true;
bool sparse_weights_ = false;
TfLiteXNNPackDelegateWeightsCache* weights_cache_ = nullptr;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_TRANSPOSE_CONV_TESTER_H_
@@ -0,0 +1,185 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow/lite/delegates/xnnpack/quantized_unary_elementwise_tester.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <random>
#include <vector>
#include <gtest/gtest.h>
#include "flatbuffers/buffer.h" // from @flatbuffers
#include "flatbuffers/flatbuffer_builder.h" // from @flatbuffers
#include "flatbuffers/string.h" // from @flatbuffers
#include "tensorflow/compiler/mlir/lite/schema/schema_conversion_utils.h"
#include "tensorflow/lite/core/interpreter_builder.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
#include "tensorflow/lite/version.h"
namespace tflite {
namespace xnnpack {
template <class T>
void QuantizedUnaryElementwiseTester::Test(
Interpreter* delegate_interpreter, Interpreter* default_interpreter) const {
std::random_device random_device;
auto rng = std::mt19937(random_device());
std::uniform_int_distribution<int32_t> input_distribution(
std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
auto input_rng = std::bind(input_distribution, std::ref(rng));
T* default_input_data = default_interpreter->typed_input_tensor<T>(0);
std::generate_n(default_input_data, ComputeSize(Shape()),
std::ref(input_rng));
T* xnnpack_input_data = delegate_interpreter->typed_input_tensor<T>(0);
std::copy_n(default_input_data, ComputeSize(Shape()), xnnpack_input_data);
ASSERT_EQ(default_interpreter->Invoke(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->Invoke(), kTfLiteOk);
T* default_output_data = default_interpreter->typed_output_tensor<T>(0);
T* delegate_output_data = delegate_interpreter->typed_output_tensor<T>(0);
for (size_t i = 0; i < ComputeSize(Shape()); i++) {
ASSERT_LE(std::abs(static_cast<int32_t>(default_output_data[i]) -
static_cast<int32_t>(delegate_output_data[i])),
1)
<< "default " << static_cast<int32_t>(default_output_data[i])
<< ", delegate " << static_cast<int32_t>(delegate_output_data[i])
<< " at index " << i << " / " << ComputeSize(Shape());
}
}
void QuantizedUnaryElementwiseTester::Test(tflite::BuiltinOperator unary_op,
TfLiteDelegate* delegate) const {
std::vector<char> buffer = CreateTfLiteModel(unary_op);
const Model* model = GetModel(buffer.data());
std::unique_ptr<Interpreter> delegate_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&delegate_interpreter),
kTfLiteOk);
std::unique_ptr<Interpreter> default_interpreter;
ASSERT_EQ(
InterpreterBuilder(
model,
::tflite::ops::builtin::BuiltinOpResolverWithoutDefaultDelegates())(
&default_interpreter),
kTfLiteOk);
ASSERT_TRUE(delegate_interpreter);
ASSERT_TRUE(default_interpreter);
ASSERT_EQ(delegate_interpreter->inputs().size(), 1);
ASSERT_EQ(default_interpreter->inputs().size(), 1);
ASSERT_EQ(delegate_interpreter->outputs().size(), 1);
ASSERT_EQ(default_interpreter->outputs().size(), 1);
ASSERT_EQ(delegate_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(default_interpreter->AllocateTensors(), kTfLiteOk);
ASSERT_EQ(delegate_interpreter->ModifyGraphWithDelegate(delegate), kTfLiteOk);
if (Unsigned()) {
Test<uint8_t>(delegate_interpreter.get(), default_interpreter.get());
} else {
Test<int8_t>(delegate_interpreter.get(), default_interpreter.get());
}
}
std::vector<char> QuantizedUnaryElementwiseTester::CreateTfLiteModel(
tflite::BuiltinOperator unary_op) const {
flatbuffers::FlatBufferBuilder builder;
flatbuffers::Offset<OperatorCode> operator_code =
CreateOperatorCode(builder, unary_op);
const std::array<flatbuffers::Offset<Buffer>, 1> buffers{{
CreateBuffer(builder, builder.CreateVector({})),
}};
const std::array<flatbuffers::Offset<Tensor>, 2> tensors{{
CreateTensor(
builder,
builder.CreateVector<int32_t>(Shape().data(), Shape().size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({InputScale()}),
builder.CreateVector<int64_t>({InputZeroPoint()}))),
CreateTensor(
builder,
builder.CreateVector<int32_t>(Shape().data(), Shape().size()),
Unsigned() ? TensorType_UINT8 : TensorType_INT8,
/*buffer=*/0, /*name=*/0,
CreateQuantizationParameters(
builder, /*min=*/0, /*max=*/0,
builder.CreateVector<float>({OutputScale()}),
builder.CreateVector<int64_t>({OutputZeroPoint()}))),
}};
const std::array<int32_t, 1> op_inputs{{0}};
const std::array<int32_t, 1> op_outputs{{1}};
flatbuffers::Offset<Operator> op = CreateOperator(
builder, /*opcode_index=*/0,
builder.CreateVector<int32_t>(op_inputs.data(), op_inputs.size()),
builder.CreateVector<int32_t>(op_outputs.data(), op_outputs.size()));
const std::array<int32_t, 1> subgraph_inputs{{0}};
const std::array<int32_t, 1> subgraph_outputs{{1}};
flatbuffers::Offset<SubGraph> subgraph = CreateSubGraph(
builder, builder.CreateVector(tensors.data(), tensors.size()),
builder.CreateVector<int32_t>(subgraph_inputs.data(),
subgraph_inputs.size()),
builder.CreateVector<int32_t>(subgraph_outputs.data(),
subgraph_outputs.size()),
builder.CreateVector(&op, 1));
flatbuffers::Offset<flatbuffers::String> description =
builder.CreateString("Quantized unary operator model");
flatbuffers::Offset<Model> model_buffer = CreateModel(
builder, TFLITE_SCHEMA_VERSION, builder.CreateVector(&operator_code, 1),
builder.CreateVector(&subgraph, 1), description,
builder.CreateVector(buffers.data(), buffers.size()));
builder.Finish(model_buffer);
return std::vector<char>(builder.GetBufferPointer(),
builder.GetBufferPointer() + builder.GetSize());
}
int32_t QuantizedUnaryElementwiseTester::ComputeSize(
const std::vector<int32_t>& shape) {
return std::accumulate(shape.cbegin(), shape.cend(), 1,
std::multiplies<int32_t>());
}
} // namespace xnnpack
} // namespace tflite
@@ -0,0 +1,112 @@
/* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_UNARY_ELEMENTWISE_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_UNARY_ELEMENTWISE_TESTER_H_
#include <cstdint>
#include <vector>
#include <gtest/gtest.h>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
class QuantizedUnaryElementwiseTester {
public:
QuantizedUnaryElementwiseTester() = default;
QuantizedUnaryElementwiseTester(const QuantizedUnaryElementwiseTester&) =
delete;
QuantizedUnaryElementwiseTester& operator=(
const QuantizedUnaryElementwiseTester&) = delete;
inline QuantizedUnaryElementwiseTester& Shape(
std::initializer_list<int32_t> shape) {
for (auto it = shape.begin(); it != shape.end(); ++it) {
EXPECT_GT(*it, 0);
}
shape_ = std::vector<int32_t>(shape.begin(), shape.end());
size_ = QuantizedUnaryElementwiseTester::ComputeSize(shape_);
return *this;
}
const std::vector<int32_t>& Shape() const { return shape_; }
int32_t Size() const { return size_; }
inline QuantizedUnaryElementwiseTester& InputZeroPoint(
int32_t input_zero_point) {
input_zero_point_ = input_zero_point;
return *this;
}
inline int32_t InputZeroPoint() const { return input_zero_point_; }
inline QuantizedUnaryElementwiseTester& OutputZeroPoint(
int32_t output_zero_point) {
output_zero_point_ = output_zero_point;
return *this;
}
inline int32_t OutputZeroPoint() const { return output_zero_point_; }
inline QuantizedUnaryElementwiseTester& InputScale(float input_scale) {
input_scale_ = input_scale;
return *this;
}
inline float InputScale() const { return input_scale_; }
inline QuantizedUnaryElementwiseTester& OutputScale(float output_scale) {
output_scale_ = output_scale;
return *this;
}
inline float OutputScale() const { return output_scale_; }
inline QuantizedUnaryElementwiseTester& Unsigned(bool is_unsigned) {
unsigned_ = is_unsigned;
return *this;
}
inline bool Unsigned() const { return unsigned_; }
template <class T>
void Test(Interpreter* delegate_interpreter,
Interpreter* default_interpreter) const;
void Test(tflite::BuiltinOperator unary_op, TfLiteDelegate* delegate) const;
private:
std::vector<char> CreateTfLiteModel(tflite::BuiltinOperator unary_op) const;
static int32_t ComputeSize(const std::vector<int32_t>& shape);
std::vector<int32_t> shape_;
int32_t size_;
int32_t input_zero_point_ = 0;
int32_t output_zero_point_ = 0;
float input_scale_ = 1.0f;
float output_scale_ = 1.0f;
bool unsigned_ = false;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_UNARY_ELEMENTWISE_TESTER_H_
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,212 @@
/* Copyright 2023 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#ifndef TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_VARIABLE_OPS_TESTER_H_
#define TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_VARIABLE_OPS_TESTER_H_
#include <functional>
#include <memory>
#include <numeric>
#include <vector>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#include "tensorflow/lite/schema/schema_generated.h"
namespace tflite {
namespace xnnpack {
std::unique_ptr<TfLiteDelegate, decltype(&TfLiteXNNPackDelegateDelete)>
NewXnnPackDelegateSupportingVariableOps();
class QuantizedVariableOpsTester {
public:
void TestAssignThenRead(TfLiteDelegate *delegate) const;
void TestAssignTwiceThenRead(TfLiteDelegate *delegate) const;
void TestAssignThenReadUsingAnotherVarHandle(TfLiteDelegate *delegate) const;
void TestTwoVarHandlesAssignThenRead(TfLiteDelegate *delegate) const;
void TestTwoSubgraphsReadAssign(TfLiteDelegate *delegate) const;
void TestTwoSubgraphsReadAssignOneVarHandle(TfLiteDelegate *delegate) const;
void TestTwoSubgraphsReadAssignOneVarHandle2(TfLiteDelegate *delegate) const;
// Creates a model with this subgraph:
// (initial_input) ----\
// \
// VAR_HANDLE ----------> AV
// \
// \---------------> RV -> (output)
std::vector<char> CreateModelAssignThenRead() const;
// Creates a model with this subgraph:
// (initial_input) -------
// \
// VAR_HANDLE ----------> AV
// \ \
// \ \---------------> RV -> (output)
// \
// \-----AV
// /
// (default_input)
std::vector<char> CreateModelAssignTwiceThenRead() const;
// Creates a model with this subgraph:
// (initial_input) ----\
// \
// VAR_HANDLE ----------> AV
//
// VAR_HANDLE ---------------> RV -> (output)
// Second VAR_HANDLE is a different operator object but refers to the same
// variable by name.
std::vector<char> CreateModelAssignThenReadUsingAnotherVarHandle() const;
// Creates a model with this subgraph:
// (default_input) -------
// \
// VAR_HANDLE ----------> AV
// \
// \---------------> RV -> (output1)
//
// (default_input) ---------------
// \
// VAR_HANDLE (different one) -----AV
// \
// \---------------> RV -> (output2)
std::vector<char> CreateModelTwoVarHandlesAssignThenRead() const;
// Creates a model with two subgraphs.
// primary subgraph:
// CALL_ONCE (secondary subgraph)
// VAR_HANDLE1 ---> RV ---> (output)
// VAR_HANDLE2 ---> RV ---> (output)
//
// secondary subgraph:
// VAR_HANDLE2 ----- AV
// buffer1 -----------/
// VAR_HANDLE1 ----- AV
// buffer2 -----------/
// The var handles are defined in different orders.
std::vector<char> CreateModelTwoSubgraphsReadAssign() const;
// Creates a model with two subgraphs.
// primary subgraph has 1 var handle.
// CALL_ONCE (secondary subgraph)
// VAR_HANDLE1 ---> RV ---> (output)
//
// secondary subgraph has 2 varhandle:
// VAR_HANDLE2 ----- AV
// buffer1 -----------/
// VAR_HANDLE1 ----- AV
// buffer2 -----------/
// The expected output is buffer2.
// The var handles are defined in different orders.
std::vector<char> CreateModelTwoSubgraphsReadAssignOneVarHandle() const;
// Similar to CreateModelTwoSubgraphsReadAssignOneVarHandle but with the first
// subgraph reading var handle 1 and flipping the order of var handles in the
// second subgraph.
// Creates a model with two subgraphs.
// primary subgraph has 1 var handle.
// CALL_ONCE (secondary subgraph)
// VAR_HANDLE2 ---> RV ---> (output)
//
// secondary subgraph has 2 varhandle:
// VAR_HANDLE1 ----- AV
// buffer1 -----------/
// VAR_HANDLE2 ----- AV
// buffer2 -----------/
// The expected output is buffer2.
// The var handles are defined in different orders.
std::vector<char> CreateModelTwoSubgraphsReadAssignOneVarHandle2() const;
inline QuantizedVariableOpsTester &NumInputs(size_t num_inputs) {
num_inputs_ = num_inputs;
return *this;
}
inline size_t NumInputs() const { return num_inputs_; }
inline QuantizedVariableOpsTester &NumOutputs(size_t num_outputs) {
num_outputs_ = num_outputs;
return *this;
}
inline size_t NumOutputs() const { return num_outputs_; }
inline size_t NumSubgraphs() const { return num_subgraphs_; }
inline QuantizedVariableOpsTester &NumSubgraphs(size_t num_subgraphs) {
num_subgraphs_ = num_subgraphs;
return *this;
}
const std::vector<int32_t> &Shape() const { return shape_; }
const std::vector<int32_t> &ResourceShape() const { return resource_shape_; }
size_t OutputSize() const {
return std::accumulate(Shape().begin(), Shape().end(), 1,
std::multiplies<int32_t>());
}
size_t InputSize() const {
return std::accumulate(Shape().begin(), Shape().cend(), 1,
std::multiplies<int32_t>());
}
inline QuantizedVariableOpsTester &ZeroPoint(int32_t zero_point) {
zero_point_ = zero_point;
return *this;
}
inline int32_t ZeroPoint() const { return zero_point_; }
inline QuantizedVariableOpsTester &Scale(float scale) {
scale_ = scale;
return *this;
}
inline float Scale() const { return scale_; }
inline QuantizedVariableOpsTester &Unsigned(bool is_unsigned) {
unsigned_ = is_unsigned;
return *this;
}
inline bool Unsigned() const { return unsigned_; }
private:
template <class T>
void Test(TfLiteDelegate *delegate, const std::vector<char> &buffer) const;
std::vector<int32_t> shape_ = {1, 2, 2, 3};
std::vector<int32_t> resource_shape_ = {1};
size_t num_inputs_ = 0;
size_t num_outputs_ = 0;
size_t num_subgraphs_ = 1;
int32_t zero_point_ = 2;
float scale_ = 0.75f;
bool unsigned_ = false;
};
} // namespace xnnpack
} // namespace tflite
#endif // TENSORFLOW_LITE_DELEGATES_XNNPACK_QUANTIZED_VARIABLE_OPS_TESTER_H_

Some files were not shown because too many files have changed in this diff Show More