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
@@ -0,0 +1,77 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
/** Represents the type of elements in a TensorFlow Lite {@link Tensor} as an enum. */
public enum DataType {
/** 32-bit single precision floating point. */
FLOAT32(1),
/** 32-bit signed integer. */
INT32(2),
/** 8-bit unsigned integer. */
UINT8(3),
/** 64-bit signed integer. */
INT64(4),
/** Strings. */
STRING(5),
/** Bool. */
BOOL(6),
/** 16-bit signed integer. */
INT16(7),
/** 8-bit signed integer. */
INT8(9);
private final int value;
DataType(int value) {
this.value = value;
}
/** Returns the size of an element of this type, in bytes, or -1 if element size is variable. */
public int byteSize() {
switch (this) {
case FLOAT32:
case INT32:
return 4;
case INT16:
return 2;
case INT8:
case UINT8:
return 1;
case INT64:
return 8;
case BOOL:
// Boolean size is JVM-dependent.
return -1;
case STRING:
return -1;
}
throw new IllegalArgumentException(
"DataType error: DataType " + this + " is not supported yet");
}
/** Corresponding value of the TfLiteType enum in the TensorFlow Lite C API. */
int c() {
return value;
}
}
@@ -0,0 +1,72 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
/**
* Utility methods for DataType.
*/
class DataTypeUtils {
private DataTypeUtils() {}
/** Gets string names of the data type. */
static String toStringName(DataType dataType) {
switch (dataType) {
case FLOAT32:
return "float";
case INT32:
return "int";
case INT16:
return "short";
case INT8:
case UINT8:
return "byte";
case INT64:
return "long";
case BOOL:
return "bool";
case STRING:
return "string";
}
throw new IllegalArgumentException(
"DataType error: DataType " + dataType + " is not supported yet");
}
/** Converts a C TfLiteType enum value to the corresponding type. */
static DataType fromC(int c) {
switch (c) {
case 1:
return DataType.FLOAT32;
case 2:
return DataType.INT32;
case 3:
return DataType.UINT8;
case 4:
return DataType.INT64;
case 5:
return DataType.STRING;
case 6:
return DataType.BOOL;
case 7:
return DataType.INT16;
case 9:
return DataType.INT8;
default: // continue below to handle unsupported C types.
}
throw new IllegalArgumentException(
"DataType error: DataType " + c + " is not recognized in Java.");
}
}
@@ -0,0 +1,56 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import java.io.Closeable;
/**
* Wrapper for a native TensorFlow Lite Delegate.
*
* <p>If a delegate implementation holds additional resources or memory that should be explicitly
* freed, then best practice is to add a {@code close()} method to the implementation and have the
* client call that explicitly when the delegate instance is no longer in use. While this approach
* technically allows sharing of a single delegate instance across multiple interpreter instances,
* the delegate implementation must explicitly support this.
*/
public interface Delegate extends Closeable {
/**
* Returns a native handle to the TensorFlow Lite delegate implementation.
*
* <p>Note: The Java {@link Delegate} maintains ownership of the native delegate instance, and
* must ensure its existence for the duration of usage with any {@link InterpreterApi} instance.
*
* <p>Note: the native delegate instance may not be created until the delegate has been attached
* to an interpreter, so this method should not be called until after an interpreter has been
* constructed with this delegate.
*
* @throws IllegalStateException if called before the native delegate instance has been
* constructed.
* @return The native delegate handle. In C/C++, this should be a pointer to
* 'TfLiteOpaqueDelegate'.
*/
long getNativeHandle();
/**
* Closes the delegate and releases any resources associated with it.
*
* <p>In contrast to the method declared in the base {@link Closeable} interface, this method
* does not throw checked exceptions.
*/
@SuppressWarnings("StaticOrDefaultInterfaceMethod")
@Override
default void close() {}
}
@@ -0,0 +1,28 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
/** Allows creating delegates for different runtime flavors. */
public interface DelegateFactory {
/**
* Create a {@link Delegate} for the given {@link RuntimeFlavor}.
*
* <p>Note for developers implementing this interface: Currently TF Lite in Google Play Services
* does not support external (developer-provided) delegates. Correspondingly, implementations of
* this method can expect to be called with {@link RuntimeFlavor#APPLICATION}.
*/
Delegate create(RuntimeFlavor runtimeFlavor);
}
@@ -0,0 +1,293 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import java.io.File;
import java.nio.ByteBuffer;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Driver class to drive model inference with TensorFlow Lite.
*
* <p>Note: If you don't need access to any of the "experimental" API features below, prefer to use
* InterpreterApi and InterpreterFactory rather than using Interpreter directly.
*
* <p>A {@code Interpreter} encapsulates a pre-trained TensorFlow Lite model, in which operations
* are executed for model inference.
*
* <p>For example, if a model takes only one input and returns only one output:
*
* <pre>{@code
* try (Interpreter interpreter = new Interpreter(file_of_a_tensorflowlite_model)) {
* interpreter.run(input, output);
* }
* }</pre>
*
* <p>If a model takes multiple inputs or outputs:
*
* <pre>{@code
* Object[] inputs = {input0, input1, ...};
* Map<Integer, Object> map_of_indices_to_outputs = new HashMap<>();
* FloatBuffer ith_output = FloatBuffer.allocateDirect(3 * 2 * 4); // Float tensor, shape 3x2x4.
* ith_output.order(ByteOrder.nativeOrder());
* map_of_indices_to_outputs.put(i, ith_output);
* try (Interpreter interpreter = new Interpreter(file_of_a_tensorflowlite_model)) {
* interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);
* }
* }</pre>
*
* <p>If a model takes or produces string tensors:
*
* <pre>{@code
* String[] input = {"foo", "bar"}; // Input tensor shape is [2].
* String[][] output = new String[3][2]; // Output tensor shape is [3, 2].
* try (Interpreter interpreter = new Interpreter(file_of_a_tensorflowlite_model)) {
* interpreter.runForMultipleInputsOutputs(input, output);
* }
* }</pre>
*
* <p>Note that there's a distinction between shape [] and shape[1]. For scalar string tensor
* outputs:
*
* <pre>{@code
* String[] input = {"foo"}; // Input tensor shape is [1].
* ByteBuffer outputBuffer = ByteBuffer.allocate(OUTPUT_BYTES_SIZE); // Output tensor shape is [].
* try (Interpreter interpreter = new Interpreter(file_of_a_tensorflowlite_model)) {
* interpreter.runForMultipleInputsOutputs(input, outputBuffer);
* }
* byte[] outputBytes = new byte[outputBuffer.remaining()];
* outputBuffer.get(outputBytes);
* // Below, the `charset` can be StandardCharsets.UTF_8.
* String output = new String(outputBytes, charset);
* }</pre>
*
* <p>Orders of inputs and outputs are determined when converting TensorFlow model to TensorFlowLite
* model with Toco, as are the default shapes of the inputs.
*
* <p>When inputs are provided as (multi-dimensional) arrays, the corresponding input tensor(s) will
* be implicitly resized according to that array's shape. When inputs are provided as {@code Buffer}
* types, no implicit resizing is done; the caller must ensure that the {@code Buffer} byte size
* either matches that of the corresponding tensor, or that they first resize the tensor via {@link
* #resizeInput(int, int[])}. Tensor shape and type information can be obtained via the {@link
* Tensor} class, available via {@link #getInputTensor(int)} and {@link #getOutputTensor(int)}.
*
* <p><b>WARNING:</b>{@code Interpreter} instances are <b>not</b> thread-safe. A {@code Interpreter}
* owns resources that <b>must</b> be explicitly freed by invoking {@link #close()}
*
* <p>The TFLite library is built against NDK API 19. It may work for Android API levels below 19,
* but is not guaranteed.
*/
public final class Interpreter extends InterpreterImpl implements InterpreterApi {
/** An options class for controlling runtime interpreter behavior. */
public static class Options extends InterpreterImpl.Options {
public Options() {}
public Options(InterpreterApi.Options options) {
super(options);
}
Options(InterpreterImpl.Options options) {
super(options);
}
@Override
public Options setUseXNNPACK(boolean useXNNPACK) {
super.setUseXNNPACK(useXNNPACK);
return this;
}
@Override
public Options setNumThreads(int numThreads) {
super.setNumThreads(numThreads);
return this;
}
@Override
public Options setUseNNAPI(boolean useNNAPI) {
super.setUseNNAPI(useNNAPI);
return this;
}
/**
* Sets whether to allow float16 precision for FP32 calculation when possible. Defaults to false
* (disallow).
*
* @deprecated Prefer using <a
* href="https://github.com/tensorflow/tensorflow/blob/5dc7f6981fdaf74c8c5be41f393df705841fb7c5/tensorflow/lite/delegates/nnapi/java/src/main/java/org/tensorflow/lite/nnapi/NnApiDelegate.java#L127">NnApiDelegate.Options#setAllowFp16(boolean
* enable)</a>.
*/
@Deprecated
public Options setAllowFp16PrecisionForFp32(boolean allow) {
this.allowFp16PrecisionForFp32 = allow;
return this;
}
@Override
public Options addDelegate(Delegate delegate) {
super.addDelegate(delegate);
return this;
}
@Override
public Options addDelegateFactory(DelegateFactory delegateFactory) {
super.addDelegateFactory(delegateFactory);
return this;
}
/**
* Advanced: Set if buffer handle output is allowed.
*
* <p>When a {@link Delegate} supports hardware acceleration, the interpreter will make the data
* of output tensors available in the CPU-allocated tensor buffers by default. If the client can
* consume the buffer handle directly (e.g. reading output from OpenGL texture), it can set this
* flag to false, avoiding the copy of data to the CPU buffer. The delegate documentation should
* indicate whether this is supported and how it can be used.
*
* <p>WARNING: This is an experimental interface that is subject to change.
*/
public Options setAllowBufferHandleOutput(boolean allow) {
this.allowBufferHandleOutput = allow;
return this;
}
/**
* Enables or disables compression of identical per-channel quantization zero-points into a
* single value to reduce memory usage.
*
* <p>value whether to enable compression of identical per-channel quantization zero-points.
*
* <p>WARNING: This is an experimental interface that is subject to change.
*/
public Options setCompressQuantizationZeroPoints(boolean value) {
this.compressQuantizationZeroPoints = value;
return this;
}
/**
* Returns whether compression of identical per-channel quantization zero-points is enabled.
*
* <p>WARNING: This is an experimental interface that is subject to change.
*/
public boolean getCompressQuantizationZeroPoints() {
return compressQuantizationZeroPoints != null && compressQuantizationZeroPoints;
}
@Override
public Options setCancellable(boolean allow) {
super.setCancellable(allow);
return this;
}
@Override
public Options setRuntime(InterpreterApi.Options.TfLiteRuntime runtime) {
super.setRuntime(runtime);
return this;
}
}
/**
* Initializes an {@code Interpreter}.
*
* @param modelFile a File of a pre-trained TF Lite model.
* @throws IllegalArgumentException if {@code modelFile} does not encode a valid TensorFlow Lite
* model.
*/
public Interpreter(@NonNull File modelFile) {
this(modelFile, /* options= */ null);
}
/**
* Initializes an {@code Interpreter} and specifies options for customizing interpreter behavior.
*
* @param modelFile a file of a pre-trained TF Lite model
* @param options a set of options for customizing interpreter behavior
* @throws IllegalArgumentException if {@code modelFile} does not encode a valid TensorFlow Lite
* model.
*/
public Interpreter(@NonNull File modelFile, Options options) {
this(new NativeInterpreterWrapperExperimental(modelFile.getAbsolutePath(), options));
}
/**
* Initializes an {@code Interpreter} with a {@code ByteBuffer} of a model file.
*
* <p>The ByteBuffer should not be modified after the construction of a {@code Interpreter}. The
* {@code ByteBuffer} can be either a {@code MappedByteBuffer} that memory-maps a model file, or a
* direct {@code ByteBuffer} of nativeOrder() that contains the bytes content of a model.
*
* @throws IllegalArgumentException if {@code byteBuffer} is not a {@code MappedByteBuffer} nor a
* direct {@code ByteBuffer} of nativeOrder.
*/
public Interpreter(@NonNull ByteBuffer byteBuffer) {
this(byteBuffer, /* options= */ null);
}
/**
* Initializes an {@code Interpreter} with a {@code ByteBuffer} of a model file and a set of
* custom {@link Interpreter.Options}.
*
* <p>The {@code ByteBuffer} should not be modified after the construction of an {@code
* Interpreter}. The {@code ByteBuffer} can be either a {@code MappedByteBuffer} that memory-maps
* a model file, or a direct {@code ByteBuffer} of nativeOrder() that contains the bytes content
* of a model.
*
* @throws IllegalArgumentException if {@code byteBuffer} is not a {@code MappedByteBuffer} nor a
* direct {@code ByteBuffer} of nativeOrder.
*/
public Interpreter(@NonNull ByteBuffer byteBuffer, Options options) {
this(new NativeInterpreterWrapperExperimental(byteBuffer, options));
}
private Interpreter(NativeInterpreterWrapperExperimental wrapper) {
super(wrapper);
wrapperExperimental = wrapper;
}
/**
* Advanced: Resets all variable tensors to the default value.
*
* <p>If a variable tensor doesn't have an associated buffer, it will be reset to zero.
*
* <p>WARNING: This is an experimental API and subject to change.
*/
public void resetVariableTensors() {
checkNotClosed();
wrapperExperimental.resetVariableTensors();
}
/**
* Advanced: Interrupts inference in the middle of a call to {@link Interpreter#run}.
*
* <p>A cancellation flag will be set to true when this function gets called. The interpreter will
* check the flag between Op invocations, and if it's {@code true}, the interpreter will stop
* execution. The interpreter will remain a cancelled state until explicitly "uncancelled" by
* {@code setCancelled(false)}.
*
* <p>WARNING: This is an experimental API and subject to change.
*
* @param cancelled {@code true} to cancel inference in a best-effort way; {@code false} to
* resume.
* @throws IllegalStateException if the interpreter is not initialized with the cancellable
* option, which is by default off.
* @see Interpreter.Options#setCancellable(boolean).
*/
public void setCancelled(boolean cancelled) {
wrapper.setCancelled(cancelled);
}
private final NativeInterpreterWrapperExperimental wrapperExperimental;
}
@@ -0,0 +1,625 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.tensorflow.lite.InterpreterApi.Options.TfLiteRuntime;
import org.tensorflow.lite.acceleration.ValidatedAccelerationConfig;
import org.tensorflow.lite.nnapi.NnApiDelegate;
/**
* Interface to TensorFlow Lite model interpreter, excluding experimental methods.
*
* <p>An {@code InterpreterApi} instance encapsulates a pre-trained TensorFlow Lite model, in which
* operations are executed for model inference.
*
* <p>For example, if a model takes only one input and returns only one output:
*
* <pre>{@code
* try (InterpreterApi interpreter =
* new InterpreterApi.create(file_of_a_tensorflowlite_model)) {
* interpreter.run(input, output);
* }
* }</pre>
*
* <p>If a model takes multiple inputs or outputs:
*
* <pre>{@code
* Object[] inputs = {input0, input1, ...};
* Map<Integer, Object> map_of_indices_to_outputs = new HashMap<>();
* FloatBuffer ith_output = FloatBuffer.allocateDirect(3 * 2 * 4); // Float tensor, shape 3x2x4.
* ith_output.order(ByteOrder.nativeOrder());
* map_of_indices_to_outputs.put(i, ith_output);
* try (InterpreterApi interpreter =
* new InterpreterApi.create(file_of_a_tensorflowlite_model)) {
* interpreter.runForMultipleInputsOutputs(inputs, map_of_indices_to_outputs);
* }
* }</pre>
*
* <p>If a model takes or produces string tensors:
*
* <pre>{@code
* String[] input = {"foo", "bar"}; // Input tensor shape is [2].
* String[][] output = new String[3][2]; // Output tensor shape is [3, 2].
* try (InterpreterApi interpreter =
* new InterpreterApi.create(file_of_a_tensorflowlite_model)) {
* interpreter.runForMultipleInputsOutputs(input, output);
* }
* }</pre>
*
* <p>Note that there's a distinction between shape [] and shape[1]. For scalar string tensor
* outputs:
*
* <pre>{@code
* String[] input = {"foo"}; // Input tensor shape is [1].
* ByteBuffer outputBuffer = ByteBuffer.allocate(OUTPUT_BYTES_SIZE); // Output tensor shape is [].
* try (Interpreter interpreter = new Interpreter(file_of_a_tensorflowlite_model)) {
* interpreter.runForMultipleInputsOutputs(input, outputBuffer);
* }
* byte[] outputBytes = new byte[outputBuffer.remaining()];
* outputBuffer.get(outputBytes);
* // Below, the `charset` can be StandardCharsets.UTF_8.
* String output = new String(outputBytes, charset);
* }</pre>
*
* <p>Orders of inputs and outputs are determined when converting TensorFlow model to TensorFlowLite
* model with Toco, as are the default shapes of the inputs.
*
* <p>When inputs are provided as (multi-dimensional) arrays, the corresponding input tensor(s) will
* be implicitly resized according to that array's shape. When inputs are provided as {@link
* java.nio.Buffer} types, no implicit resizing is done; the caller must ensure that the {@link
* java.nio.Buffer} byte size either matches that of the corresponding tensor, or that they first
* resize the tensor via {@link #resizeInput(int, int[])}. Tensor shape and type information can be
* obtained via the {@link Tensor} class, available via {@link #getInputTensor(int)} and {@link
* #getOutputTensor(int)}.
*
* <p><b>WARNING:</b>{@code InterpreterApi} instances are <b>not</b> thread-safe.
*
* <p><b>WARNING:</b>An {@code InterpreterApi} instance owns resources that <b>must</b> be
* explicitly freed by invoking {@link #close()}
*
* <p>The TFLite library is built against NDK API 19. It may work for Android API levels below 19,
* but is not guaranteed.
*/
public interface InterpreterApi extends AutoCloseable {
/** An options class for controlling runtime interpreter behavior. */
class Options {
public Options() {
this.delegates = new ArrayList<>();
this.delegateFactories = new ArrayList<>();
}
public Options(Options other) {
this.numThreads = other.numThreads;
this.useNNAPI = other.useNNAPI;
this.allowCancellation = other.allowCancellation;
this.delegates = new ArrayList<>(other.delegates);
this.delegateFactories = new ArrayList<>(other.delegateFactories);
this.runtime = other.runtime;
this.validatedAccelerationConfig = other.validatedAccelerationConfig;
this.useXNNPACK = other.useXNNPACK;
}
/**
* Sets the number of threads to be used for ops that support multi-threading.
*
* <p>{@code numThreads} should be {@code >= -1}. Setting {@code numThreads} to 0 has the effect
* of disabling multithreading, which is equivalent to setting {@code numThreads} to 1. If
* unspecified, or set to the value -1, the number of threads used will be
* implementation-defined and platform-dependent.
*/
public Options setNumThreads(int numThreads) {
this.numThreads = numThreads;
return this;
}
/**
* Returns the number of threads to be used for ops that support multi-threading.
*
* <p>{@code numThreads} should be {@code >= -1}. Values of 0 (or 1) disable multithreading.
* Default value is -1: the number of threads used will be implementation-defined and
* platform-dependent.
*/
public int getNumThreads() {
return numThreads;
}
/** Sets whether to use NN API (if available) for op execution. Defaults to false (disabled). */
public Options setUseNNAPI(boolean useNNAPI) {
this.useNNAPI = useNNAPI;
return this;
}
/**
* Returns whether to use NN API (if available) for op execution. Default value is false
* (disabled).
*/
public boolean getUseNNAPI() {
return useNNAPI != null && useNNAPI;
}
/**
* Advanced: Set if the interpreter is able to be cancelled.
*
* <p>Interpreters may have an experimental API <a
* href="https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/Interpreter#setCancelled(boolean)">setCancelled(boolean)</a>.
* If this interpreter is cancellable and such a method is invoked, a cancellation flag will be
* set to true. The interpreter will check the flag between Op invocations, and if it's {@code
* true}, the interpreter will stop execution. The interpreter will remain a cancelled state
* until explicitly "uncancelled" by {@code setCancelled(false)}.
*/
public Options setCancellable(boolean allow) {
this.allowCancellation = allow;
return this;
}
/**
* Advanced: Returns whether the interpreter is able to be cancelled.
*
* <p>Interpreters may have an experimental API <a
* href="https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/Interpreter#setCancelled(boolean)">setCancelled(boolean)</a>.
* If this interpreter is cancellable and such a method is invoked, a cancellation flag will be
* set to true. The interpreter will check the flag between Op invocations, and if it's {@code
* true}, the interpreter will stop execution. The interpreter will remain a cancelled state
* until explicitly "uncancelled" by {@code setCancelled(false)}.
*/
public boolean isCancellable() {
return allowCancellation != null && allowCancellation;
}
/**
* Adds a {@link Delegate} to be applied during interpreter creation.
*
* <p>Delegates added here are applied before any delegates created from a {@link
* DelegateFactory} that was added with {@link #addDelegateFactory}.
*
* <p>Note that TF Lite in Google Play Services (see {@link #setRuntime}) does not support
* external (developer-provided) delegates, and adding a {@link Delegate} other than {@link
* NnApiDelegate} here is not allowed when using TF Lite in Google Play Services.
*/
public Options addDelegate(Delegate delegate) {
delegates.add(delegate);
return this;
}
/**
* Returns the list of delegates intended to be applied during interpreter creation that have
* been registered via {@code addDelegate}.
*/
public List<Delegate> getDelegates() {
return Collections.unmodifiableList(delegates);
}
/**
* Adds a {@link DelegateFactory} which will be invoked to apply its created {@link Delegate}
* during interpreter creation.
*
* <p>Delegates from a delegated factory that was added here are applied after any delegates
* added with {@link #addDelegate}.
*/
public Options addDelegateFactory(DelegateFactory delegateFactory) {
delegateFactories.add(delegateFactory);
return this;
}
/**
* Returns the list of delegate factories that have been registered via {@code
* addDelegateFactory}).
*/
public List<DelegateFactory> getDelegateFactories() {
return Collections.unmodifiableList(delegateFactories);
}
/**
* Enum to represent where to get the TensorFlow Lite runtime implementation from.
*
* <p>The difference between this class and the RuntimeFlavor class: This class specifies a
* <em>preference</em> which runtime to use, whereas {@link RuntimeFlavor} specifies which exact
* runtime <em>is</em> being used.
*/
public enum TfLiteRuntime {
/**
* Use a TF Lite runtime implementation that is linked into the application. If there is no
* suitable TF Lite runtime implementation linked into the application, then attempting to
* create an InterpreterApi instance with this TfLiteRuntime setting will throw an
* IllegalStateException exception (even if the OS or system services could provide a TF Lite
* runtime implementation).
*
* <p>This is the default setting. This setting is also appropriate for apps that must run on
* systems that don't provide a TF Lite runtime implementation.
*/
FROM_APPLICATION_ONLY,
/**
* Use a TF Lite runtime implementation provided by the OS or system services. This will be
* obtained from a system library / shared object / service, such as Google Play Services. It
* may be newer than the version linked into the application (if any). If there is no suitable
* TF Lite runtime implementation provided by the system, then attempting to create an
* InterpreterApi instance with this TfLiteRuntime setting will throw an IllegalStateException
* exception (even if there is a TF Lite runtime implementation linked into the application).
*
* <p>This setting is appropriate for code that will use a system-provided TF Lite runtime,
* which can reduce app binary size and can be updated more frequently.
*/
FROM_SYSTEM_ONLY,
/**
* Use a system-provided TF Lite runtime implementation, if any, otherwise use the TF Lite
* runtime implementation linked into the application, if any. If no suitable TF Lite runtime
* can be found in any location, then attempting to create an InterpreterApi instance with
* this TFLiteRuntime setting will throw an IllegalStateException. If there is both a suitable
* TF Lite runtime linked into the application and also a suitable TF Lite runtime provided by
* the system, the one provided by the system will be used.
*
* <p>This setting is suitable for use in code that doesn't care where the TF Lite runtime is
* coming from (e.g. middleware layers).
*/
PREFER_SYSTEM_OVER_APPLICATION,
}
/** Specify where to get the TF Lite runtime implementation from. */
public Options setRuntime(TfLiteRuntime runtime) {
this.runtime = runtime;
return this;
}
/** Return where to get the TF Lite runtime implementation from. */
public TfLiteRuntime getRuntime() {
return runtime;
}
/** Specify the acceleration configuration. */
public Options setAccelerationConfig(ValidatedAccelerationConfig config) {
this.validatedAccelerationConfig = config;
return this;
}
/** Return the acceleration configuration. */
public ValidatedAccelerationConfig getAccelerationConfig() {
return this.validatedAccelerationConfig;
}
/**
* Enable or disable an optimized set of CPU kernels (provided by XNNPACK). Enabled by default.
*/
public Options setUseXNNPACK(boolean useXNNPACK) {
this.useXNNPACK = useXNNPACK;
return this;
}
public boolean getUseXNNPACK() {
// A null value indicates the default behavior, which is currently to apply the delegate.
return useXNNPACK == null || useXNNPACK.booleanValue();
}
TfLiteRuntime runtime = TfLiteRuntime.FROM_APPLICATION_ONLY;
int numThreads = -1;
Boolean useNNAPI;
/**
* Note: the initial "null" value indicates default behavior (XNNPACK delegate will be applied
* by default whenever possible).
*
* <p>Disabling this flag will disable use of a highly optimized set of CPU kernels provided via
* the XNNPACK delegate. Currently, this is restricted to a subset of floating point operations.
* See
* https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/delegates/xnnpack/README.md
* for more details.
*/
Boolean useXNNPACK;
Boolean allowCancellation;
ValidatedAccelerationConfig validatedAccelerationConfig;
// See InterpreterApi.Options#addDelegate.
final List<Delegate> delegates;
// See InterpreterApi.Options#addDelegateFactory.
private final List<DelegateFactory> delegateFactories;
}
/**
* Constructs an {@link InterpreterApi} instance, using the specified model and options. The model
* will be loaded from a file.
*
* @param modelFile A file containing a pre-trained TF Lite model.
* @param options A set of options for customizing interpreter behavior.
* @throws IllegalArgumentException if {@code modelFile} does not encode a valid TensorFlow Lite
* model.
*/
@SuppressWarnings("StaticOrDefaultInterfaceMethod")
static InterpreterApi create(@NonNull File modelFile, InterpreterApi.Options options) {
TfLiteRuntime runtime = (options == null ? null : options.getRuntime());
InterpreterFactoryApi factory = TensorFlowLite.getFactory(runtime);
return factory.create(modelFile, options);
}
/**
* Constructs an {@link InterpreterApi} instance, using the specified model and options. The model
* will be read from a {@code ByteBuffer}.
*
* @param byteBuffer A pre-trained TF Lite model, in binary serialized form. The ByteBuffer should
* not be modified after the construction of an {@link InterpreterApi} instance. The {@code
* ByteBuffer} can be either a {@code MappedByteBuffer} that memory-maps a model file, or a
* direct {@code ByteBuffer} of nativeOrder() that contains the bytes content of a model.
* @param options A set of options for customizing interpreter behavior.
* @throws IllegalArgumentException if {@code byteBuffer} is not a {@code MappedByteBuffer} nor a
* direct {@code ByteBuffer} of nativeOrder.
*/
@SuppressWarnings("StaticOrDefaultInterfaceMethod")
static InterpreterApi create(@NonNull ByteBuffer byteBuffer, InterpreterApi.Options options) {
TfLiteRuntime runtime = (options == null ? null : options.getRuntime());
InterpreterFactoryApi factory = TensorFlowLite.getFactory(runtime);
return factory.create(byteBuffer, options);
}
/**
* Runs model inference if the model takes only one input, and provides only one output.
*
* <p>Warning: The API is more efficient if a {@code Buffer} (preferably direct, but not required)
* is used as the input/output data type. Please consider using {@code Buffer} to feed and fetch
* primitive data for better performance. The following concrete {@code Buffer} types are
* supported:
*
* <ul>
* <li>{@code ByteBuffer} - compatible with any underlying primitive Tensor type.
* <li>{@code FloatBuffer} - compatible with float Tensors.
* <li>{@code IntBuffer} - compatible with int32 Tensors.
* <li>{@code LongBuffer} - compatible with int64 Tensors.
* </ul>
*
* Note that boolean types are only supported as arrays, not {@code Buffer}s, or as scalar inputs.
*
* @param input an array or multidimensional array, or a {@code Buffer} of primitive types
* including int, float, long, and byte. {@code Buffer} is the preferred way to pass large
* input data for primitive types, whereas string types require using the (multi-dimensional)
* array input path. When a {@code Buffer} is used, its content should remain unchanged until
* model inference is done, and the caller must ensure that the {@code Buffer} is at the
* appropriate read position. A {@code null} value is allowed only if the caller is using a
* {@link Delegate} that allows buffer handle interop, and such a buffer has been bound to the
* input {@link Tensor}.
* @param output a multidimensional array of output data, or a {@code Buffer} of primitive types
* including int, float, long, and byte. When a {@code Buffer} is used, the caller must ensure
* that it is set the appropriate write position. A null value is allowed, and is useful for
* certain cases, e.g., if the caller is using a {@link Delegate} that allows buffer handle
* interop, and such a buffer has been bound to the output {@link Tensor} (see also <a
* href="https://www.tensorflow.org/lite/api_docs/java/org/tensorflow/lite/Interpreter.Options#setAllowBufferHandleOutput(boolean)">Interpreter.Options#setAllowBufferHandleOutput(boolean)</a>),
* or if the graph has dynamically shaped outputs and the caller must query the output {@link
* Tensor} shape after inference has been invoked, fetching the data directly from the output
* tensor (via {@link Tensor#asReadOnlyBuffer()}).
* @throws IllegalArgumentException if {@code input} is null or empty, or if an error occurs when
* running inference.
* @throws IllegalArgumentException (EXPERIMENTAL, subject to change) if the inference is
* interrupted by {@code setCancelled(true)}.
*/
void run(Object input, Object output);
/**
* Runs model inference if the model takes multiple inputs, or returns multiple outputs.
*
* <p>Warning: The API is more efficient if {@code Buffer}s (preferably direct, but not required)
* are used as the input/output data types. Please consider using {@code Buffer} to feed and fetch
* primitive data for better performance. The following concrete {@code Buffer} types are
* supported:
*
* <ul>
* <li>{@code ByteBuffer} - compatible with any underlying primitive Tensor type.
* <li>{@code FloatBuffer} - compatible with float Tensors.
* <li>{@code IntBuffer} - compatible with int32 Tensors.
* <li>{@code LongBuffer} - compatible with int64 Tensors.
* </ul>
*
* Note that boolean types are only supported as arrays, not {@code Buffer}s, or as scalar inputs.
*
* <p>Note: {@code null} values for individual elements of {@code inputs} and {@code outputs} is
* allowed only if the caller is using a {@link Delegate} that allows buffer handle interop, and
* such a buffer has been bound to the corresponding input or output {@link Tensor}(s).
*
* @param inputs an array of input data. The inputs should be in the same order as inputs of the
* model. Each input can be an array or multidimensional array, or a {@code Buffer} of
* primitive types including int, float, long, and byte. {@code Buffer} is the preferred way
* to pass large input data, whereas string types require using the (multi-dimensional) array
* input path. When {@code Buffer} is used, its content should remain unchanged until model
* inference is done, and the caller must ensure that the {@code Buffer} is at the appropriate
* read position.
* @param outputs a map mapping output indices to multidimensional arrays of output data or {@code
* Buffer}s of primitive types including int, float, long, and byte. It only needs to keep
* entries for the outputs to be used. When a {@code Buffer} is used, the caller must ensure
* that it is set the appropriate write position. The map may be empty for cases where either
* buffer handles are used for output tensor data, or cases where the outputs are dynamically
* shaped and the caller must query the output {@link Tensor} shape after inference has been
* invoked, fetching the data directly from the output tensor (via {@link
* Tensor#asReadOnlyBuffer()}).
* @throws IllegalArgumentException if {@code inputs} is null or empty, if {@code outputs} is
* null, or if an error occurs when running inference.
*/
void runForMultipleInputsOutputs(
Object @NonNull [] inputs, @NonNull Map<Integer, Object> outputs);
/**
* Explicitly updates allocations for all tensors, if necessary.
*
* <p>This will propagate shapes and memory allocations for dependent tensors using the input
* tensor shape(s) as given.
*
* <p>Note: This call is *purely optional*. Tensor allocation will occur automatically during
* execution if any input tensors have been resized. This call is most useful in determining the
* shapes for any output tensors before executing the graph, e.g.,
*
* <pre> {@code
* interpreter.resizeInput(0, new int[]{1, 4, 4, 3}));
* interpreter.allocateTensors();
* FloatBuffer input = FloatBuffer.allocate(interpreter.getInputTensor(0).numElements());
* // Populate inputs...
* FloatBuffer output = FloatBuffer.allocate(interpreter.getOutputTensor(0).numElements());
* interpreter.run(input, output)
* // Process outputs...}</pre>
*
* <p>Note: Some graphs have dynamically shaped outputs, in which case the output shape may not
* fully propagate until inference is executed.
*
* @throws IllegalStateException if the graph's tensors could not be successfully allocated.
*/
void allocateTensors();
/**
* Resizes idx-th input of the native model to the given dims.
*
* @throws IllegalArgumentException if {@code idx} is negative or is not smaller than the number
* of model inputs; or if error occurs when resizing the idx-th input.
*/
void resizeInput(int idx, int @NonNull [] dims);
/**
* Resizes idx-th input of the native model to the given dims.
*
* <p>When `strict` is True, only unknown dimensions can be resized. Unknown dimensions are
* indicated as `-1` in the array returned by `Tensor.shapeSignature()`.
*
* @throws IllegalArgumentException if {@code idx} is negative or is not smaller than the number
* of model inputs; or if error occurs when resizing the idx-th input. Additionally, the error
* occurs when attempting to resize a tensor with fixed dimensions when `strict` is True.
*/
void resizeInput(int idx, int @NonNull [] dims, boolean strict);
/** Gets the number of input tensors. */
int getInputTensorCount();
/**
* Gets index of an input given the op name of the input.
*
* @throws IllegalArgumentException if {@code opName} does not match any input in the model used
* to initialize the interpreter.
*/
int getInputIndex(String opName);
/**
* Gets the Tensor associated with the provided input index.
*
* @throws IllegalArgumentException if {@code inputIndex} is negative or is not smaller than the
* number of model inputs.
*/
Tensor getInputTensor(int inputIndex);
/** Gets the number of output Tensors. */
int getOutputTensorCount();
/**
* Gets index of an output given the op name of the output.
*
* @throws IllegalArgumentException if {@code opName} does not match any output in the model used
* to initialize the interpreter.
*/
int getOutputIndex(String opName);
/**
* Gets the Tensor associated with the provided output index.
*
* <p>Note: Output tensor details (e.g., shape) may not be fully populated until after inference
* is executed. If you need updated details *before* running inference (e.g., after resizing an
* input tensor, which may invalidate output tensor shapes), use {@link #allocateTensors()} to
* explicitly trigger allocation and shape propagation. Note that, for graphs with output shapes
* that are dependent on input *values*, the output shape may not be fully determined until
* running inference.
*
* @throws IllegalArgumentException if {@code outputIndex} is negative or is not smaller than the
* number of model outputs.
*/
Tensor getOutputTensor(int outputIndex);
/**
* Runs model inference based on SignatureDef provided through {@code signatureKey}.
*
* <p>See {@link Interpreter#run(Object, Object)} for more details on the allowed input and output
* data types.
*
* @param inputs A map from input name in the SignatureDef to an input object.
* @param outputs A map from output name in SignatureDef to output data. This may be empty if the
* caller wishes to query the {@link Tensor} data directly after inference (e.g., if the
* output shape is dynamic, or output buffer handles are used).
* @param signatureKey Signature key identifying the SignatureDef.
* @throws IllegalArgumentException if {@code inputs} is null or empty, if {@code outputs} or
* {@code signatureKey} is null, or if an error occurs when running inference.
*/
public void runSignature(
@NonNull Map<String, Object> inputs,
@NonNull Map<String, Object> outputs,
String signatureKey);
/**
* Same as {@link #runSignature(Map, Map, String)} but doesn't require passing a signatureKey,
* assuming the model has one SignatureDef. If the model has more than one SignatureDef it will
* throw an exception.
*/
public void runSignature(
@NonNull Map<String, Object> inputs, @NonNull Map<String, Object> outputs);
/**
* Gets the Tensor associated with the provided input name and signature method name.
*
* @param inputName Input name in the signature.
* @param signatureKey Signature key identifying the SignatureDef, can be null if the model has
* one signature.
* @throws IllegalArgumentException if {@code inputName} or {@code signatureKey} is null or empty,
* or invalid name provided.
*/
public Tensor getInputTensorFromSignature(String inputName, String signatureKey);
/** Gets the list of SignatureDef exported method names available in the model. */
public String[] getSignatureKeys();
/** Gets the list of SignatureDefs inputs for method {@code signatureKey}. */
public String[] getSignatureInputs(String signatureKey);
/** Gets the list of SignatureDefs outputs for method {@code signatureKey}. */
public String[] getSignatureOutputs(String signatureKey);
/**
* Gets the Tensor associated with the provided output name in specific signature method.
*
* <p>Note: Output tensor details (e.g., shape) may not be fully populated until after inference
* is executed. If you need updated details *before* running inference (e.g., after resizing an
* input tensor, which may invalidate output tensor shapes), use {@link #allocateTensors()} to
* explicitly trigger allocation and shape propagation. Note that, for graphs with output shapes
* that are dependent on input *values*, the output shape may not be fully determined until
* running inference.
*
* @param outputName Output name in the signature.
* @param signatureKey Signature key identifying the SignatureDef, can be null if the model has
* one signature.
* @throws IllegalArgumentException if {@code outputName} or {@code signatureKey} is null or
* empty, or invalid name provided.
*/
public Tensor getOutputTensorFromSignature(String outputName, String signatureKey);
/**
* Returns native inference timing.
*
* @throws IllegalArgumentException if the model is not initialized by the interpreter.
*/
Long getLastNativeInferenceDurationNanoseconds();
/** Release resources associated with the {@code InterpreterApi} instance. */
@Override
void close();
}
@@ -0,0 +1,59 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
import java.io.File;
import java.nio.ByteBuffer;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Factory for constructing InterpreterApi instances.
*
* <p>Deprecated; please use the InterpreterApi.create method instead.
*/
@Deprecated
public class InterpreterFactory {
public InterpreterFactory() {}
/**
* Constructs an {@link InterpreterApi} instance, using the specified model and options. The model
* will be loaded from a file.
*
* @param modelFile A file containing a pre-trained TF Lite model.
* @param options A set of options for customizing interpreter behavior.
* @throws IllegalArgumentException if {@code modelFile} does not encode a valid TensorFlow Lite
* model.
*/
public InterpreterApi create(@NonNull File modelFile, InterpreterApi.Options options) {
return InterpreterApi.create(modelFile, options);
}
/**
* Constructs an {@link InterpreterApi} instance, using the specified model and options. The model
* will be read from a {@code ByteBuffer}.
*
* @param byteBuffer A pre-trained TF Lite model, in binary serialized form. The ByteBuffer should
* not be modified after the construction of an {@link InterpreterApi} instance. The {@code
* ByteBuffer} can be either a {@code MappedByteBuffer} that memory-maps a model file, or a
* direct {@code ByteBuffer} of nativeOrder() that contains the bytes content of a model.
* @param options A set of options for customizing interpreter behavior.
* @throws IllegalArgumentException if {@code byteBuffer} is not a {@code MappedByteBuffer} nor a
* direct {@code ByteBuffer} of nativeOrder.
*/
public InterpreterApi create(@NonNull ByteBuffer byteBuffer, InterpreterApi.Options options) {
return InterpreterApi.create(byteBuffer, options);
}
}
@@ -0,0 +1,72 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
import java.io.File;
import java.nio.ByteBuffer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.tensorflow.lite.nnapi.NnApiDelegate;
/**
* Private interface specifying factory for constructing InterpreterApi instances. This interface is
* an implementation detail of InterpreterFactory and should only be used from within the TensorFlow
* Lite implementation. We can't make it package-private, though, because it is used from both
* org.tensorflow.lite.InterpreterFactoryImpl and
* com.google.android.gms.tflite.InterpreterFactoryImpl.
*
* @hide
*/
public interface InterpreterFactoryApi {
/**
* Constructs an {@link InterpreterApi} instance, using the specified model and options. The model
* will be loaded from a file.
*
* @param modelFile A file containing a pre-trained TF Lite model.
* @param options A set of options for customizing interpreter behavior.
* @throws IllegalArgumentException if {@code modelFile} does not encode a valid TensorFlow Lite
* model.
*/
InterpreterApi create(@NonNull File modelFile, InterpreterApi.Options options);
/**
* Constructs an {@link InterpreterApi} instance, using the specified model and options. The model
* will be read from a {@code ByteBuffer}.
*
* @param byteBuffer A pre-trained TF Lite model, in binary serialized form. The ByteBuffer should
* not be modified after the construction of an {@link InterpreterApi} instance. The {@code
* ByteBuffer} can be either a {@code MappedByteBuffer} that memory-maps a model file, or a
* direct {@code ByteBuffer} of nativeOrder() that contains the bytes content of a model.
* @param options A set of options for customizing interpreter behavior.
* @throws IllegalArgumentException if {@code byteBuffer} is not a {@code MappedByteBuffer} nor a
* direct {@code ByteBuffer} of nativeOrder.
*/
InterpreterApi create(@NonNull ByteBuffer byteBuffer, InterpreterApi.Options options);
/** Returns the version of the underlying TensorFlowLite runtime. */
String runtimeVersion();
/**
* Returns the version of the TensorFlowLite model schema that is supported by the underlying
* TensorFlowLite runtime.
*/
String schemaVersion();
/**
* Instance method for constructing an NNAPI delegate implementation, using the TF Lite runtime
* from the InterpreterFactoryApi.
*/
NnApiDelegate.PrivateInterface createNnApiDelegateImpl(NnApiDelegate.Options options);
}
@@ -0,0 +1,63 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
import java.io.File;
import java.nio.ByteBuffer;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.tensorflow.lite.annotations.UsedByReflection;
import org.tensorflow.lite.nnapi.NnApiDelegate;
import org.tensorflow.lite.nnapi.NnApiDelegateImpl;
/** Package-private factory class for constructing InterpreterApi instances. */
@UsedByReflection("InterpreterFactory.java")
class InterpreterFactoryImpl implements InterpreterFactoryApi {
public InterpreterFactoryImpl() {}
@Override
public InterpreterApi create(@NonNull File modelFile, InterpreterApi.Options options) {
return new InterpreterImpl(
modelFile, options == null ? null : new InterpreterImpl.Options(options));
}
@Override
public InterpreterApi create(@NonNull ByteBuffer byteBuffer, InterpreterApi.Options options) {
return new InterpreterImpl(
byteBuffer, options == null ? null : new InterpreterImpl.Options(options));
}
@Override
public String runtimeVersion() {
TensorFlowLite.init();
return nativeRuntimeVersion();
}
@Override
public String schemaVersion() {
TensorFlowLite.init();
return nativeSchemaVersion();
}
@Override
public NnApiDelegate.PrivateInterface createNnApiDelegateImpl(NnApiDelegate.Options options) {
return new NnApiDelegateImpl(options);
}
private static native String nativeRuntimeVersion();
private static native String nativeSchemaVersion();
}
@@ -0,0 +1,282 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Package-private class that implements InterpreterApi. This class implements all the
* non-experimental API methods. It is used both by the public InterpreterFactory, and as a base
* class for the public Interpreter class,
*/
class InterpreterImpl implements InterpreterApi {
/**
* An options class for controlling runtime interpreter behavior. Compared to the base class
* InterpreterApi.Options, this adds fields corresponding to experimental features. But it does
* not provide accessors to set those fields -- those are only provided in the derived class
* Interpreter.Options.
*/
static class Options extends InterpreterApi.Options {
public Options() {}
public Options(InterpreterApi.Options options) {
super(options);
}
public Options(Options other) {
super(other);
allowFp16PrecisionForFp32 = other.allowFp16PrecisionForFp32;
allowBufferHandleOutput = other.allowBufferHandleOutput;
compressQuantizationZeroPoints = other.compressQuantizationZeroPoints;
}
// See Interpreter.Options#setAllowFp16PrecisionForFp32(boolean).
Boolean allowFp16PrecisionForFp32;
// See Interpreter.Options#setAllowBufferHandleOutput(boolean).
Boolean allowBufferHandleOutput;
// See Interpreter.Options#setCompressQuantizationZeroPoints(boolean).
Boolean compressQuantizationZeroPoints;
// See Interpreter.Options#getCompressQuantizationZeroPoints().
boolean getCompressQuantizationZeroPoints() {
return compressQuantizationZeroPoints != null && compressQuantizationZeroPoints;
}
}
/**
* Initializes an {@code InterpreterImpl} and specifies options for customizing interpreter
* behavior.
*
* @param modelFile a file of a pre-trained TF Lite model
* @param options a set of options for customizing interpreter behavior
* @throws IllegalArgumentException if {@code modelFile} does not encode a valid TensorFlow Lite
* model.
*/
InterpreterImpl(@NonNull File modelFile, Options options) {
wrapper = new NativeInterpreterWrapper(modelFile.getAbsolutePath(), options);
signatureKeyList = getSignatureKeys();
}
/**
* Initializes an {@code InterpreterImpl} with a {@code ByteBuffer} of a model file and a set of
* custom {@link Interpreter.Options}.
*
* <p>The {@code ByteBuffer} should not be modified after the construction of an {@code
* InterpreterImpl}. The {@code ByteBuffer} can be either a {@code MappedByteBuffer} that
* memory-maps a model file, or a direct {@code ByteBuffer} of nativeOrder() that contains the
* bytes content of a model.
*
* @throws IllegalArgumentException if {@code byteBuffer} is not a {@code MappedByteBuffer} nor a
* direct {@code ByteBuffer} of nativeOrder.
*/
InterpreterImpl(@NonNull ByteBuffer byteBuffer, Options options) {
wrapper = new NativeInterpreterWrapper(byteBuffer, options);
signatureKeyList = getSignatureKeys();
}
InterpreterImpl(NativeInterpreterWrapper wrapper) {
this.wrapper = wrapper;
signatureKeyList = getSignatureKeys();
}
@Override
public void run(Object input, Object output) {
Object[] inputs = {input};
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, output);
runForMultipleInputsOutputs(inputs, outputs);
}
@Override
public void runForMultipleInputsOutputs(
Object @NonNull [] inputs, @NonNull Map<Integer, Object> outputs) {
checkNotClosed();
wrapper.run(inputs, outputs);
}
@Override
public void allocateTensors() {
checkNotClosed();
wrapper.allocateTensors();
}
@Override
public void resizeInput(int idx, int @NonNull [] dims) {
checkNotClosed();
wrapper.resizeInput(idx, dims, false);
}
@Override
public void resizeInput(int idx, int @NonNull [] dims, boolean strict) {
checkNotClosed();
wrapper.resizeInput(idx, dims, strict);
}
@Override
public int getInputTensorCount() {
checkNotClosed();
return wrapper.getInputTensorCount();
}
@Override
public int getInputIndex(String opName) {
checkNotClosed();
return wrapper.getInputIndex(opName);
}
@Override
public Tensor getInputTensor(int inputIndex) {
checkNotClosed();
return wrapper.getInputTensor(inputIndex);
}
/** Gets the number of output Tensors. */
@Override
public int getOutputTensorCount() {
checkNotClosed();
return wrapper.getOutputTensorCount();
}
@Override
public int getOutputIndex(String opName) {
checkNotClosed();
return wrapper.getOutputIndex(opName);
}
@Override
public Tensor getOutputTensor(int outputIndex) {
checkNotClosed();
return wrapper.getOutputTensor(outputIndex);
}
@Override
public void runSignature(
@NonNull Map<String, Object> inputs,
@NonNull Map<String, Object> outputs,
String signatureKey) {
checkNotClosed();
if (signatureKey == null && signatureKeyList.length == 1) {
signatureKey = signatureKeyList[0];
}
if (signatureKey == null) {
throw new IllegalArgumentException(
"Input error: SignatureDef signatureKey should not be null. null is only allowed if the"
+ " model has a single Signature. Available Signatures: "
+ Arrays.toString(signatureKeyList));
}
wrapper.runSignature(inputs, outputs, signatureKey);
}
@Override
public void runSignature(
@NonNull Map<String, Object> inputs, @NonNull Map<String, Object> outputs) {
checkNotClosed();
runSignature(inputs, outputs, null);
}
@Override
public Tensor getInputTensorFromSignature(String inputName, String signatureKey) {
checkNotClosed();
if (signatureKey == null && signatureKeyList.length == 1) {
signatureKey = signatureKeyList[0];
}
if (signatureKey == null) {
throw new IllegalArgumentException(
"Input error: SignatureDef signatureKey should not be null. null is only allowed if the"
+ " model has a single Signature. Available Signatures: "
+ Arrays.toString(signatureKeyList));
}
return wrapper.getInputTensor(inputName, signatureKey);
}
@Override
public String[] getSignatureKeys() {
checkNotClosed();
return wrapper.getSignatureKeys();
}
@Override
public String[] getSignatureInputs(String signatureKey) {
checkNotClosed();
return wrapper.getSignatureInputs(signatureKey);
}
@Override
public String[] getSignatureOutputs(String signatureKey) {
checkNotClosed();
return wrapper.getSignatureOutputs(signatureKey);
}
@Override
public Tensor getOutputTensorFromSignature(String outputName, String signatureKey) {
checkNotClosed();
if (signatureKey == null && signatureKeyList.length == 1) {
signatureKey = signatureKeyList[0];
}
if (signatureKey == null) {
throw new IllegalArgumentException(
"Input error: SignatureDef signatureKey should not be null. null is only allowed if the"
+ " model has a single Signature. Available Signatures: "
+ Arrays.toString(signatureKeyList));
}
return wrapper.getOutputTensor(outputName, signatureKey);
}
@Override
public Long getLastNativeInferenceDurationNanoseconds() {
checkNotClosed();
return wrapper.getLastNativeInferenceDurationNanoseconds();
}
int getExecutionPlanLength() {
checkNotClosed();
return wrapper.getExecutionPlanLength();
}
@Override
public void close() {
if (wrapper != null) {
wrapper.close();
wrapper = null;
}
}
@SuppressWarnings("deprecation")
@Override
protected void finalize() throws Throwable {
try {
close();
} finally {
super.finalize();
}
}
void checkNotClosed() {
if (wrapper == null) {
throw new IllegalStateException("Internal error: The Interpreter has already been closed.");
}
}
NativeInterpreterWrapper wrapper;
private final String[] signatureKeyList;
}
@@ -0,0 +1,672 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.tensorflow.lite.InterpreterApi.Options.TfLiteRuntime;
import org.tensorflow.lite.InterpreterImpl.Options;
import org.tensorflow.lite.annotations.UsedByReflection;
import org.tensorflow.lite.nnapi.NnApiDelegate;
/**
* An internal wrapper that wraps native interpreter and controls model execution.
*
* <p><b>WARNING:</b> Resources consumed by the {@code NativeInterpreterWrapper} object must be
* explicitly freed by invoking the {@link #close()} method when the {@code
* NativeInterpreterWrapper} object is no longer needed.
*
* <p>Note: This class is not thread safe.
*/
class NativeInterpreterWrapper implements AutoCloseable {
// This is changed to RuntimeFlavor.SYSTEM for TF Lite in Google Play Services.
private static final RuntimeFlavor RUNTIME_FLAVOR = RuntimeFlavor.APPLICATION;
NativeInterpreterWrapper(String modelPath) {
this(modelPath, /* options= */ null);
}
NativeInterpreterWrapper(ByteBuffer byteBuffer) {
this(byteBuffer, /* options= */ null);
}
NativeInterpreterWrapper(String modelPath, InterpreterImpl.Options options) {
TensorFlowLite.init();
long errorHandle = createErrorReporter(ERROR_BUFFER_SIZE);
long modelHandle = createModel(modelPath, errorHandle);
init(errorHandle, modelHandle, options);
}
NativeInterpreterWrapper(ByteBuffer buffer, InterpreterImpl.Options options) {
TensorFlowLite.init();
if (buffer == null
|| (!(buffer instanceof MappedByteBuffer)
&& (!buffer.isDirect() || buffer.order() != ByteOrder.nativeOrder()))) {
throw new IllegalArgumentException(
"Model ByteBuffer should be either a MappedByteBuffer of the model file, or a direct "
+ "ByteBuffer using ByteOrder.nativeOrder() which contains bytes of model content.");
}
this.modelByteBuffer = buffer;
long errorHandle = createErrorReporter(ERROR_BUFFER_SIZE);
long modelHandle = createModelWithBuffer(modelByteBuffer, errorHandle);
init(errorHandle, modelHandle, options);
}
private void init(long errorHandle, long modelHandle, InterpreterImpl.Options options) {
if (options == null) {
options = new InterpreterImpl.Options();
}
if (options.getAccelerationConfig() != null) {
// Apply the validated acceleration config
options.getAccelerationConfig().apply(options);
}
this.errorHandle = errorHandle;
this.modelHandle = modelHandle;
// First create the interpreter without delegates. We need an interpreter in order to figure
// out whether the model contains any unresolved flex ops, and creating the interpreter with
// delegates might fail if there are any unresolved flex ops.
// (Alternatively, we could determine this without needing to recreate the interpreter
// by passing the tflite::Model in to here, and then traversing that?)
ArrayList<Long> delegateHandles = new ArrayList<>();
this.interpreterHandle =
createInterpreter(
modelHandle,
errorHandle,
options.getNumThreads(),
options.getUseXNNPACK(),
options.getCompressQuantizationZeroPoints(),
delegateHandles);
this.originalGraphHasUnresolvedFlexOp = hasUnresolvedFlexOp(interpreterHandle);
addDelegates(options);
initDelegatesWithInterpreterFactory();
delegateHandles.ensureCapacity(delegates.size());
for (Delegate delegate : delegates) {
delegateHandles.add(delegate.getNativeHandle());
}
if (!delegateHandles.isEmpty()) {
// If there are any delegates enabled, recreate the interpreter with those delegates.
delete(/* errorHandle= */ 0, /* modelHandle= */ 0, this.interpreterHandle);
this.interpreterHandle =
createInterpreter(
modelHandle,
errorHandle,
options.getNumThreads(),
options.getUseXNNPACK(),
options.getCompressQuantizationZeroPoints(),
delegateHandles);
}
if (options.allowFp16PrecisionForFp32 != null) {
allowFp16PrecisionForFp32(interpreterHandle, options.allowFp16PrecisionForFp32);
}
if (options.allowBufferHandleOutput != null) {
allowBufferHandleOutput(interpreterHandle, options.allowBufferHandleOutput);
}
if (options.isCancellable()) {
this.cancellationFlagHandle = createCancellationFlag(interpreterHandle);
}
this.inputTensors = new TensorImpl[getInputCount(interpreterHandle)];
this.outputTensors = new TensorImpl[getOutputCount(interpreterHandle)];
if (options.allowFp16PrecisionForFp32 != null) {
allowFp16PrecisionForFp32(interpreterHandle, options.allowFp16PrecisionForFp32);
}
if (options.allowBufferHandleOutput != null) {
allowBufferHandleOutput(interpreterHandle, options.allowBufferHandleOutput);
}
allocateTensors(interpreterHandle, errorHandle);
this.isMemoryAllocated = true;
}
/** Releases resources associated with this {@code NativeInterpreterWrapper}. */
@Override
public void close() {
// Close the tensors first as they may reference the native interpreter.
for (int i = 0; i < inputTensors.length; ++i) {
if (inputTensors[i] != null) {
inputTensors[i].close();
inputTensors[i] = null;
}
}
for (int i = 0; i < outputTensors.length; ++i) {
if (outputTensors[i] != null) {
outputTensors[i].close();
outputTensors[i] = null;
}
}
// Close the delegates first as they may reference the model.
delegates.clear();
for (Delegate ownedDelegate : ownedDelegates) {
ownedDelegate.close();
}
ownedDelegates.clear();
delete(errorHandle, modelHandle, interpreterHandle);
deleteCancellationFlag(cancellationFlagHandle);
errorHandle = 0;
modelHandle = 0;
interpreterHandle = 0;
cancellationFlagHandle = 0;
modelByteBuffer = null;
inputsIndexes = null;
outputsIndexes = null;
isMemoryAllocated = false;
}
/** Runs model inference based on SignatureDef provided through {@code signatureKey}. */
public void runSignature(
Map<String, Object> inputs, Map<String, Object> outputs, String signatureKey) {
inferenceDurationNanoseconds = -1;
if (inputs == null || inputs.isEmpty()) {
throw new IllegalArgumentException("Input error: Inputs should not be null or empty.");
}
if (outputs == null) {
throw new IllegalArgumentException("Input error: Outputs should not be null.");
}
NativeSignatureRunnerWrapper signatureRunnerWrapper = getSignatureRunnerWrapper(signatureKey);
int subgraphIndex = signatureRunnerWrapper.getSubgraphIndex();
if (subgraphIndex == 0) {
// Map inputs/output to input indexes.
Object[] inputsList = new Object[inputs.size()];
for (Map.Entry<String, Object> input : inputs.entrySet()) {
inputsList[signatureRunnerWrapper.getInputIndex(input.getKey())] = input.getValue();
}
Map<Integer, Object> outputsWithOutputIndex = new TreeMap<>();
for (Map.Entry<String, Object> output : outputs.entrySet()) {
outputsWithOutputIndex.put(
signatureRunnerWrapper.getOutputIndex(output.getKey()), output.getValue());
}
run(inputsList, outputsWithOutputIndex);
return;
}
for (Map.Entry<String, Object> input : inputs.entrySet()) {
TensorImpl tensor = getInputTensor(input.getKey(), signatureKey);
int[] newShape = tensor.getInputShapeIfDifferent(input.getValue());
if (newShape != null) {
try {
signatureRunnerWrapper.resizeInput(input.getKey(), newShape);
} catch (IllegalArgumentException e) {
throw (IllegalArgumentException)
new IllegalArgumentException(
String.format(
"Tensor passed for input '%s' of signature '%s' has different "
+ "shape than expected",
input.getKey(), signatureKey))
.initCause(e);
}
}
}
signatureRunnerWrapper.allocateTensorsIfNeeded();
for (Map.Entry<String, Object> input : inputs.entrySet()) {
signatureRunnerWrapper.getInputTensor(input.getKey()).setTo(input.getValue());
}
long inferenceStartNanos = System.nanoTime();
signatureRunnerWrapper.invoke();
long inferenceDurationNanoseconds = System.nanoTime() - inferenceStartNanos;
for (Map.Entry<String, Object> output : outputs.entrySet()) {
// Null output placeholders are allowed and ignored.
if (output.getValue() != null) {
signatureRunnerWrapper.getOutputTensor(output.getKey()).copyTo(output.getValue());
}
}
// Only set if the entire operation succeeds.
this.inferenceDurationNanoseconds = inferenceDurationNanoseconds;
}
/** Sets inputs, runs model inference and returns outputs. */
void run(Object[] inputs, Map<Integer, Object> outputs) {
inferenceDurationNanoseconds = -1;
if (inputs == null || inputs.length == 0) {
throw new IllegalArgumentException("Input error: Inputs should not be null or empty.");
}
if (outputs == null) {
throw new IllegalArgumentException("Input error: Outputs should not be null.");
}
// TODO(b/80431971): Remove implicit resize after deprecating multi-dimensional array inputs.
// Rather than forcing an immediate resize + allocation if an input's shape differs, we first
// flush all resizes, avoiding redundant allocations.
for (int i = 0; i < inputs.length; ++i) {
TensorImpl tensor = getInputTensor(i);
int[] newShape = tensor.getInputShapeIfDifferent(inputs[i]);
if (newShape != null) {
resizeInput(i, newShape);
}
}
boolean allocatedTensors = allocateTensorsIfNeeded();
for (int i = 0; i < inputs.length; ++i) {
getInputTensor(i).setTo(inputs[i]);
}
long inferenceStartNanos = System.nanoTime();
run(interpreterHandle, errorHandle);
long inferenceDurationNanoseconds = System.nanoTime() - inferenceStartNanos;
// Allocation can trigger dynamic resizing of output tensors, so refresh all output shapes.
if (allocatedTensors) {
for (TensorImpl outputTensor : outputTensors) {
if (outputTensor != null) {
outputTensor.refreshShape();
}
}
}
for (Map.Entry<Integer, Object> output : outputs.entrySet()) {
// Null output placeholders are allowed and ignored.
if (output.getValue() != null) {
getOutputTensor(output.getKey()).copyTo(output.getValue());
}
}
// Only set if the entire operation succeeds.
this.inferenceDurationNanoseconds = inferenceDurationNanoseconds;
}
/** Resizes dimensions of a specific input. */
void resizeInput(int idx, int[] dims) {
resizeInput(idx, dims, false);
}
/** Resizes dimensions of a specific input. */
void resizeInput(int idx, int[] dims, boolean strict) {
if (resizeInput(interpreterHandle, errorHandle, idx, dims, strict)) {
// Tensor allocation is deferred until either an explicit `allocateTensors()` call or
// `invoke()` avoiding redundant allocations if multiple tensors are simultaneosly resized.
isMemoryAllocated = false;
if (inputTensors[idx] != null) {
inputTensors[idx].refreshShape();
}
}
}
/** Triggers explicit allocation of tensors. */
void allocateTensors() {
allocateTensorsIfNeeded();
}
/**
* Allocates tensor memory space in the given subgraph and returns true when allocation happens
*/
private boolean allocateTensorsIfNeeded() {
if (isMemoryAllocated) {
return false;
}
isMemoryAllocated = true;
allocateTensors(interpreterHandle, errorHandle);
for (TensorImpl outputTensor : outputTensors) {
if (outputTensor != null) {
outputTensor.refreshShape();
}
}
return true;
}
/** Gets index of an input given its name. */
int getInputIndex(String name) {
if (inputsIndexes == null) {
String[] names = getInputNames(interpreterHandle);
inputsIndexes = new HashMap<>();
if (names != null) {
for (int i = 0; i < names.length; ++i) {
inputsIndexes.put(names[i], i);
}
}
}
if (inputsIndexes.containsKey(name)) {
return inputsIndexes.get(name);
} else {
throw new IllegalArgumentException(
String.format(
"Input error: '%s' is not a valid name for any input. Names of inputs and their "
+ "indexes are %s",
name, inputsIndexes));
}
}
/** Gets index of an output given its name. */
int getOutputIndex(String name) {
if (outputsIndexes == null) {
String[] names = getOutputNames(interpreterHandle);
outputsIndexes = new HashMap<>();
if (names != null) {
for (int i = 0; i < names.length; ++i) {
outputsIndexes.put(names[i], i);
}
}
}
if (outputsIndexes.containsKey(name)) {
return outputsIndexes.get(name);
} else {
throw new IllegalArgumentException(
String.format(
"Input error: '%s' is not a valid name for any output. Names of outputs and their "
+ "indexes are %s",
name, outputsIndexes));
}
}
/**
* Gets the last inference duration in nanoseconds. It returns null if there is no previous
* inference run or the last inference run failed.
*/
Long getLastNativeInferenceDurationNanoseconds() {
return (inferenceDurationNanoseconds < 0) ? null : inferenceDurationNanoseconds;
}
/** Gets the number of input tensors. */
int getInputTensorCount() {
return inputTensors.length;
}
/**
* Gets the input {@link TensorImpl} for the provided input index.
*
* @throws IllegalArgumentException if the input index is invalid.
*/
TensorImpl getInputTensor(int index) {
if (index < 0 || index >= inputTensors.length) {
throw new IllegalArgumentException("Invalid input Tensor index: " + index);
}
TensorImpl inputTensor = inputTensors[index];
if (inputTensor == null) {
inputTensor =
inputTensors[index] =
TensorImpl.fromIndex(
interpreterHandle, getInputTensorIndex(interpreterHandle, index));
}
return inputTensor;
}
/**
* Gets the input {@link TensorImpl} given the tensor name and method in the signature.
*
* @throws IllegalArgumentException if the input name is invalid.
*/
TensorImpl getInputTensor(String inputName, String signatureKey) {
if (inputName == null) {
throw new IllegalArgumentException("Invalid input tensor name provided (null)");
}
NativeSignatureRunnerWrapper signatureRunnerWrapper = getSignatureRunnerWrapper(signatureKey);
int subgraphIndex = signatureRunnerWrapper.getSubgraphIndex();
if (subgraphIndex == 0) {
int inputIndex = signatureRunnerWrapper.getInputIndex(inputName);
return getInputTensor(inputIndex);
}
return signatureRunnerWrapper.getInputTensor(inputName);
}
/** Gets the keys of SignatureDefs available in the model, if any. */
public String[] getSignatureKeys() {
return getSignatureKeys(interpreterHandle);
}
/** Gets the list of SignatureDefs inputs for method {@code signatureKey} */
String[] getSignatureInputs(String signatureKey) {
return getSignatureRunnerWrapper(signatureKey).inputNames();
}
/** Gets the list of SignatureDefs outputs for method {@code signatureKey} */
String[] getSignatureOutputs(String signatureKey) {
return getSignatureRunnerWrapper(signatureKey).outputNames();
}
/** Gets the number of output tensors. */
int getOutputTensorCount() {
return outputTensors.length;
}
/**
* Gets the output {@link TensorImpl} for the provided output index.
*
* @throws IllegalArgumentException if the output index is invalid.
*/
TensorImpl getOutputTensor(int index) {
if (index < 0 || index >= outputTensors.length) {
throw new IllegalArgumentException("Invalid output Tensor index: " + index);
}
TensorImpl outputTensor = outputTensors[index];
if (outputTensor == null) {
outputTensor =
outputTensors[index] =
TensorImpl.fromIndex(
interpreterHandle, getOutputTensorIndex(interpreterHandle, index));
}
return outputTensor;
}
/**
* Gets the output {@link TensorImpl} given the tensor name and method in the signature.
*
* @throws IllegalArgumentException if the output name is invalid.
*/
TensorImpl getOutputTensor(String outputName, String signatureKey) {
if (outputName == null) {
throw new IllegalArgumentException("Invalid output tensor name provided (null)");
}
NativeSignatureRunnerWrapper signatureRunnerWrapper = getSignatureRunnerWrapper(signatureKey);
int subgraphIndex = signatureRunnerWrapper.getSubgraphIndex();
if (subgraphIndex == 0) {
int outputIndex = signatureRunnerWrapper.getOutputIndex(outputName);
return getOutputTensor(outputIndex);
}
return signatureRunnerWrapper.getOutputTensor(outputName);
}
/** Gets the number of ops in the execution plan. */
int getExecutionPlanLength() {
return getExecutionPlanLength(interpreterHandle);
}
/**
* Sets internal cancellation flag. If it's true, the interpreter will try to interrupt any
* invocation between ops.
*/
void setCancelled(boolean value) {
if (cancellationFlagHandle == 0) {
throw new IllegalStateException(
"Cannot cancel the inference. Have you called InterpreterApi.Options.setCancellable?");
}
setCancelled(interpreterHandle, cancellationFlagHandle, value);
}
// Add all the delegates specified in the options (other than XNNPACK) to this.delegates.
private void addDelegates(InterpreterImpl.Options options) {
// First add the flex delegate if necessary. This ensures the graph is fully resolved before
// applying other delegates.
if (originalGraphHasUnresolvedFlexOp) {
Delegate optionalFlexDelegate = maybeCreateFlexDelegate(options.getDelegates());
if (optionalFlexDelegate != null) {
ownedDelegates.add(optionalFlexDelegate);
delegates.add(optionalFlexDelegate);
}
}
// Now add the user-supplied delegates.
addUserProvidedDelegates(options);
for (DelegateFactory delegateFactory : options.getDelegateFactories()) {
Delegate delegate = delegateFactory.create(RUNTIME_FLAVOR);
ownedDelegates.add(delegate);
delegates.add(delegate);
}
if (options.getUseNNAPI()) {
NnApiDelegate optionalNnApiDelegate = new NnApiDelegate();
ownedDelegates.add(optionalNnApiDelegate);
delegates.add(optionalNnApiDelegate);
}
}
private void addUserProvidedDelegates(Options options) {
for (Delegate delegate : options.getDelegates()) {
// NnApiDelegate is compatible with both the system and built-in runtimes and therefore can be
// added directly even when using TF Lite from the system.
if (options.getRuntime() != TfLiteRuntime.FROM_APPLICATION_ONLY
&& !(delegate instanceof NnApiDelegate)) {
throw new IllegalArgumentException(
"Instantiated delegates (other than NnApiDelegate) are not allowed when using TF Lite"
+ " from Google Play Services. Please use"
+ " InterpreterApi.Options.addDelegateFactory() with an appropriate DelegateFactory"
+ " instead.");
}
delegates.add(delegate);
}
}
// Complete the initialization of any delegates that require an InterpreterFactoryApi instance.
private void initDelegatesWithInterpreterFactory() {
InterpreterFactoryApi interpreterFactoryApi = new InterpreterFactoryImpl();
for (Delegate delegate : delegates) {
if (delegate instanceof NnApiDelegate) {
((NnApiDelegate) delegate).initWithInterpreterFactoryApi(interpreterFactoryApi);
}
}
}
private NativeSignatureRunnerWrapper getSignatureRunnerWrapper(String signatureKey) {
if (signatureRunnerMap == null) {
signatureRunnerMap = new HashMap<>();
}
if (!signatureRunnerMap.containsKey(signatureKey)) {
signatureRunnerMap.put(
signatureKey,
new NativeSignatureRunnerWrapper(interpreterHandle, errorHandle, signatureKey));
}
return signatureRunnerMap.get(signatureKey);
}
private static Delegate maybeCreateFlexDelegate(List<Delegate> delegates) {
try {
Class<?> clazz = Class.forName("org.tensorflow.lite.flex.FlexDelegate");
// No need to create the Flex delegate if one has already been provided.
for (Delegate delegate : delegates) {
if (clazz.isInstance(delegate)) {
return null;
}
}
return (Delegate) clazz.getConstructor().newInstance();
} catch (ClassNotFoundException
| IllegalAccessException
| IllegalArgumentException
| InstantiationException
| InvocationTargetException
| NoSuchMethodException
| SecurityException e) {
// The error will propagate when tensors are allocated.
return null;
}
}
private static final int ERROR_BUFFER_SIZE = 512;
long errorHandle;
long interpreterHandle;
private long modelHandle;
private long cancellationFlagHandle = 0;
@UsedByReflection("nativeinterpreterwrapper_jni.cc")
private long inferenceDurationNanoseconds = -1;
private ByteBuffer modelByteBuffer;
// Lazily constructed maps of input and output names to input and output Tensor indexes.
private Map<String, Integer> inputsIndexes;
private Map<String, Integer> outputsIndexes;
// A map from signature key to its native wrapper object.
private Map<String, NativeSignatureRunnerWrapper> signatureRunnerMap;
// Lazily constructed and populated arrays of input and output Tensor wrappers.
private TensorImpl[] inputTensors;
private TensorImpl[] outputTensors;
// Whether subgraph's tensor memory space is allocated.
private boolean isMemoryAllocated = false;
// Whether the model has any Flex custom ops that can't be resolved by the OpResolver.
private boolean originalGraphHasUnresolvedFlexOp = false;
// As the Java Delegate owns the native delegate instance, we keep a strong ref to any injected
// delegates for safety.
private final List<Delegate> delegates = new ArrayList<>();
// List of owned delegates that must be closed when the interpreter is closed.
private final List<Delegate> ownedDelegates = new ArrayList<>();
private static native void run(long interpreterHandle, long errorHandle);
private static native boolean resizeInput(
long interpreterHandle, long errorHandle, int inputIdx, int[] dims, boolean strict);
private static native long allocateTensors(long interpreterHandle, long errorHandle);
private static native String[] getSignatureKeys(long interpreterHandle);
private static native void setCancelled(
long interpreterHandle, long cancellationFlagHandle, boolean value);
private static native boolean hasUnresolvedFlexOp(long interpreterHandle);
private static native int getInputTensorIndex(long interpreterHandle, int inputIdx);
private static native int getOutputTensorIndex(long interpreterHandle, int outputIdx);
private static native int getInputCount(long interpreterHandle);
private static native int getOutputCount(long interpreterHandle);
private static native int getExecutionPlanLength(long interpreterHandle);
private static native String[] getInputNames(long interpreterHandle);
private static native String[] getOutputNames(long interpreterHandle);
private static native void allowFp16PrecisionForFp32(long interpreterHandle, boolean allow);
private static native void allowBufferHandleOutput(long interpreterHandle, boolean allow);
private static native long createErrorReporter(int size);
private static native long createModel(String modelPathOrBuffer, long errorHandle);
private static native long createModelWithBuffer(ByteBuffer modelBuffer, long errorHandle);
private static native long createInterpreter(
long modelHandle,
long errorHandle,
int numThreads,
boolean useXnnpack,
boolean compressQuantizationZeroPoints,
List<Long> delegateHandles);
private static native long createCancellationFlag(long interpreterHandle);
private static native long deleteCancellationFlag(long cancellationFlagHandle);
private static native void delete(long errorHandle, long modelHandle, long interpreterHandle);
}
@@ -0,0 +1,44 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import java.nio.ByteBuffer;
/**
* Extension of NativeInterpreterWrapper that adds support for experimental methods.
*
* <p><b>WARNING:</b> Resources consumed by the {@code NativeInterpreterWrapperExperimental} object
* must be explicitly freed by invoking the {@link #close()} method when the {@code
* NativeInterpreterWrapperExperimental} object is no longer needed.
*
* <p>Note: This class is not thread safe.
*/
final class NativeInterpreterWrapperExperimental extends NativeInterpreterWrapper {
NativeInterpreterWrapperExperimental(String modelPath, InterpreterImpl.Options options) {
super(modelPath, options);
}
NativeInterpreterWrapperExperimental(ByteBuffer buffer, InterpreterImpl.Options options) {
super(buffer, options);
}
void resetVariableTensors() {
resetVariableTensors(interpreterHandle, errorHandle);
}
private static native void resetVariableTensors(long interpreterHandle, long errorHandle);
}
@@ -0,0 +1,123 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
/**
* An internal wrapper that wraps native SignatureRunner.
*
* <p>Note: This class is not thread safe.
*/
final class NativeSignatureRunnerWrapper {
NativeSignatureRunnerWrapper(long interpreterHandle, long errorHandle, String signatureKey) {
this.errorHandle = errorHandle;
signatureRunnerHandle = nativeGetSignatureRunner(interpreterHandle, signatureKey);
if (signatureRunnerHandle == -1) {
throw new IllegalArgumentException("Input error: Signature " + signatureKey + " not found.");
}
}
/**
* Attempts to get the subgraph index associated with this Signature. Returns the subgraph index,
* or -1 on error.
*/
public int getSubgraphIndex() {
return nativeGetSubgraphIndex(signatureRunnerHandle);
}
/** Gets the inputs of this Signature. */
public String[] inputNames() {
return nativeInputNames(signatureRunnerHandle);
}
/** Gets the outputs of this Signature. */
public String[] outputNames() {
return nativeOutputNames(signatureRunnerHandle);
}
/** Gets the input tensor specified by {@code inputName}. */
public TensorImpl getInputTensor(String inputName) {
return TensorImpl.fromSignatureInput(signatureRunnerHandle, inputName);
}
/** Gets the output tensor specified by {@code outputName}. */
public TensorImpl getOutputTensor(String outputName) {
return TensorImpl.fromSignatureOutput(signatureRunnerHandle, outputName);
}
/** Gets the index of the input specified by {@code inputName}. */
public int getInputIndex(String inputName) {
int inputIndex = nativeGetInputIndex(signatureRunnerHandle, inputName);
if (inputIndex == -1) {
throw new IllegalArgumentException("Input error: input " + inputName + " not found.");
}
return inputIndex;
}
/** Gets the index of the output specified by {@code outputName}. */
public int getOutputIndex(String outputName) {
int outputIndex = nativeGetOutputIndex(signatureRunnerHandle, outputName);
if (outputIndex == -1) {
throw new IllegalArgumentException("Input error: output " + outputName + " not found.");
}
return outputIndex;
}
/** Resizes dimensions of a specific input. */
public boolean resizeInput(String inputName, int[] dims) {
isMemoryAllocated = false;
return nativeResizeInput(signatureRunnerHandle, errorHandle, inputName, dims);
}
/** Allocates tensor memory space. */
public void allocateTensorsIfNeeded() {
if (isMemoryAllocated) {
return;
}
nativeAllocateTensors(signatureRunnerHandle, errorHandle);
isMemoryAllocated = true;
}
/** Runs inference for this Signature. */
public void invoke() {
nativeInvoke(signatureRunnerHandle, errorHandle);
}
private final long signatureRunnerHandle;
private final long errorHandle;
private boolean isMemoryAllocated = false;
private static native long nativeGetSignatureRunner(long interpreterHandle, String signatureKey);
private static native int nativeGetSubgraphIndex(long signatureRunnerHandle);
private static native String[] nativeInputNames(long signatureRunnerHandle);
private static native String[] nativeOutputNames(long signatureRunnerHandle);
private static native int nativeGetInputIndex(long signatureRunnerHandle, String inputName);
private static native int nativeGetOutputIndex(long signatureRunnerHandle, String outputName);
private static native boolean nativeResizeInput(
long signatureRunnerHandle, long errorHandle, String inputName, int[] dims);
private static native void nativeAllocateTensors(long signatureRunnerHandle, long errorHandle);
private static native void nativeInvoke(long signatureRunnerHandle, long errorHandle);
}
@@ -0,0 +1,30 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
import org.tensorflow.lite.InterpreterApi.Options.TfLiteRuntime;
/**
* Represents a TFLite runtime. In contrast to {@link TfLiteRuntime}, this enum represents the
* actual runtime that is being used, whereas the latter represents a preference for which runtime
* should be used.
*/
public enum RuntimeFlavor {
/** A TFLite runtime built directly into the application. */
APPLICATION,
/** A TFLite runtime provided by the system (TFLite in Google Play Services). */
SYSTEM,
}
@@ -0,0 +1,155 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import java.nio.ByteBuffer;
/**
* A typed multi-dimensional array used in Tensorflow Lite.
*
* <p>The native handle of a {@code Tensor} is managed by {@code NativeInterpreterWrapper}, and does
* not needed to be closed by the client. However, once the {@code NativeInterpreterWrapper} has
* been closed, the tensor handle will be invalidated.
*/
public interface Tensor {
/**
* Quantization parameters that corresponds to the table, {@code QuantizationParameters}, in the
* <a
* href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/schema/schema.fbs">TFLite
* Model schema file.</a>
*
* <p>Since per-channel quantization does not apply to input and output tensors, {@code scale} and
* {@code zero_point} are both single values instead of arrays.
*
* <p>For tensor that are not quantized, the values of scale and zero_point are both 0.
*
* <p>Given a quantized value q, the corresponding float value f should be: <br>
* f = scale * (q - zero_point) <br>
*/
class QuantizationParams {
/** The scale value used in quantization. */
private final float scale;
/** The zero point value used in quantization. */
private final int zeroPoint;
/**
* Creates a {@link QuantizationParams} with {@code scale} and {@code zero_point}.
*
* @param scale The scale value used in quantization.
* @param zeroPoint The zero point value used in quantization.
*/
public QuantizationParams(final float scale, final int zeroPoint) {
this.scale = scale;
this.zeroPoint = zeroPoint;
}
/** Returns the scale value. */
public float getScale() {
return scale;
}
/** Returns the zero point value. */
public int getZeroPoint() {
return zeroPoint;
}
}
/** Returns the {@link DataType} of elements stored in the Tensor. */
DataType dataType();
/**
* Returns the number of dimensions (sometimes referred to as <a
* href="https://www.tensorflow.org/resources/dims_types.html#rank">rank</a>) of the Tensor.
*
* <p>Will be 0 for a scalar, 1 for a vector, 2 for a matrix, 3 for a 3-dimensional tensor etc.
*/
int numDimensions();
/** Returns the size, in bytes, of the tensor data. */
int numBytes();
/** Returns the number of elements in a flattened (1-D) view of the tensor. */
int numElements();
/**
* Returns the <a href="https://www.tensorflow.org/resources/dims_types.html#shape">shape</a> of
* the Tensor, i.e., the sizes of each dimension.
*
* @return an array where the i-th element is the size of the i-th dimension of the tensor.
*/
int[] shape();
/**
* Returns the original <a
* href="https://www.tensorflow.org/resources/dims_types.html#shape">shape</a> of the Tensor,
* i.e., the sizes of each dimension - before any resizing was performed. Unknown dimensions are
* designated with a value of -1.
*
* @return an array where the i-th element is the size of the i-th dimension of the tensor.
*/
int[] shapeSignature();
/**
* Returns the (global) index of the tensor within the subgraph of the owning interpreter.
*
* @hide
*/
int index();
/**
* Returns the name of the tensor within the owning interpreter.
*
* @hide
*/
String name();
/**
* Returns the quantization parameters of the tensor within the owning interpreter.
*
* <p>Only quantized tensors have valid {@code QuantizationParameters}. For tensor that are not
* quantized, the values of scale and zero_point are both 0.
*/
QuantizationParams quantizationParams();
/**
* Returns a read-only {@code ByteBuffer} view of the tensor data.
*
* <p>In general, this method is most useful for obtaining a read-only view of output tensor data,
* *after* inference has been executed (e.g., via {@link InterpreterApi#run(Object,Object)}). In
* particular, some graphs have dynamically shaped outputs, which can make feeding a predefined
* output buffer to the interpreter awkward. Example usage:
*
* <pre> {@code
* interpreter.run(input, null);
* ByteBuffer outputBuffer = interpreter.getOutputTensor(0).asReadOnlyBuffer();
* // Copy or read from outputBuffer.}</pre>
*
* <p>WARNING: If the tensor has not yet been allocated, e.g., before inference has been executed,
* the result is undefined. Note that the underlying tensor pointer may also change when the
* tensor is invalidated in any way (e.g., if inference is executed, or the graph is resized), so
* it is *not* safe to hold a reference to the returned buffer beyond immediate use directly
* following inference. Example *bad* usage:
*
* <pre> {@code
* ByteBuffer outputBuffer = interpreter.getOutputTensor(0).asReadOnlyBuffer();
* interpreter.run(input, null);
* // Copy or read from outputBuffer (which may now be invalid).}</pre>
*
* @throws IllegalArgumentException if the tensor data has not been allocated.
*/
ByteBuffer asReadOnlyBuffer();
}
@@ -0,0 +1,311 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;
import org.tensorflow.lite.InterpreterApi.Options.TfLiteRuntime;
/** Static utility methods for loading the TensorFlowLite runtime and native code. */
public final class TensorFlowLite {
// We use Java logging here (java.util.logging), rather than Android logging (android.util.Log),
// to avoid unnecessary platform dependencies. This also makes unit testing simpler and faster,
// since we can use plain Java tests rather than needing to use Robolectric (android_local_test).
//
// WARNING: some care is required when using Java logging on Android. In particular, avoid
// logging with severity levels lower than "INFO", since the default Java log handler on Android
// will discard those, and avoid logging messages with parameters (call String.format instead),
// since the default Java log handler on Android only logs the raw message string and doesn't
// apply the parameters.
private static final Logger logger = Logger.getLogger(TensorFlowLite.class.getName());
private static final String[][] TFLITE_RUNTIME_LIBNAMES =
new String[][] {
// We load the first library that we find in each group.
new String[] {
// Regular TF Lite.
"tensorflowlite_jni", // Full library, including experimental features.
"tensorflowlite_jni_stable", // Subset excluding experimental features.
},
new String[] {
// TF Lite from system.
"tensorflowlite_jni_gms_client"
}
};
private static final Throwable LOAD_LIBRARY_EXCEPTION;
private static volatile boolean isInit = false;
static {
// Attempt to load the TF Lite runtime's JNI library, trying each alternative name in turn.
// If unavailable, catch and save the exception(s); the client may choose to link the native
// deps into their own custom native library, so it's not an error if the default library names
// can't be loaded.
Throwable loadLibraryException = null;
for (String[] group : TFLITE_RUNTIME_LIBNAMES) {
for (String libName : group) {
try {
System.loadLibrary(libName);
logger.info("Loaded native library: " + libName);
break;
} catch (UnsatisfiedLinkError e) {
logger.info("Didn't load native library: " + libName);
if (loadLibraryException == null) {
loadLibraryException = e;
} else {
loadLibraryException.addSuppressed(e);
}
}
}
}
LOAD_LIBRARY_EXCEPTION = loadLibraryException;
}
private TensorFlowLite() {}
/**
* Returns the version of the underlying TensorFlowLite model schema.
*
* @deprecated Prefer using {@link #runtimeVersion() or #schemaVersion()}.
*/
@Deprecated
public static String version() {
return schemaVersion();
}
/** Returns the version of the specified TensorFlowLite runtime. */
public static String runtimeVersion(TfLiteRuntime runtime) {
return getFactory(runtime, "org.tensorflow.lite.TensorFlowLite", "runtimeVersion")
.runtimeVersion();
}
/** Returns the version of the default TensorFlowLite runtime. */
public static String runtimeVersion() {
return runtimeVersion(null);
}
/**
* Returns the version of the TensorFlowLite model schema that is supported by the specified
* TensorFlowLite runtime.
*/
public static String schemaVersion(TfLiteRuntime runtime) {
return getFactory(runtime, "org.tensorflow.lite.TensorFlowLite", "schemaVersion")
.schemaVersion();
}
/**
* Returns the version of the TensorFlowLite model schema that is supported by the default
* TensorFlowLite runtime.
*/
public static String schemaVersion() {
return schemaVersion(null);
}
/**
* Ensure the TensorFlowLite native library has been loaded.
*
* <p>If unsuccessful, throws an UnsatisfiedLinkError with the appropriate error message.
*/
public static void init() {
if (isInit) {
return;
}
try {
// Try to invoke a native method (which itself does nothing) to ensure that native libs are
// available.
nativeDoNothing();
isInit = true;
} catch (UnsatisfiedLinkError e) {
// Prefer logging the original library loading exception if native methods are unavailable.
Throwable exceptionToLog = LOAD_LIBRARY_EXCEPTION != null ? LOAD_LIBRARY_EXCEPTION : e;
UnsatisfiedLinkError exceptionToThrow =
new UnsatisfiedLinkError(
"Failed to load native TensorFlow Lite methods. Check that the correct native"
+ " libraries are present, and, if using a custom native library, have been"
+ " properly loaded via System.loadLibrary():\n"
+ " "
+ exceptionToLog);
exceptionToThrow.initCause(e);
throw exceptionToThrow;
}
}
private static native void nativeDoNothing();
/** Encapsulates the use of reflection to find an available TF Lite runtime. */
private static class PossiblyAvailableRuntime {
private final InterpreterFactoryApi factory;
private final Exception exception;
/**
* @param namespace: "org.tensorflow.lite" or "com.google.android.gms.tflite".
* @param category: "application" or "system".
*/
PossiblyAvailableRuntime(String namespace, String category) {
InterpreterFactoryApi factory = null;
Exception exception = null;
try {
Class<?> clazz = Class.forName(namespace + ".InterpreterFactoryImpl");
Constructor<?> factoryConstructor = clazz.getDeclaredConstructor();
factoryConstructor.setAccessible(true);
factory = (InterpreterFactoryApi) factoryConstructor.newInstance();
if (factory != null) {
logger.info(String.format("Found %s TF Lite runtime client in %s", category, namespace));
} else {
logger.warning(
String.format("Failed to construct TF Lite runtime client from %s", namespace));
}
} catch (ClassNotFoundException
| IllegalAccessException
| IllegalArgumentException
| InstantiationException
| InvocationTargetException
| NoSuchMethodException
| SecurityException e) {
logger.info(
String.format("Didn't find %s TF Lite runtime client in %s", category, namespace));
exception = e;
}
this.exception = exception;
this.factory = factory;
}
/**
* @return the InterpreterFactoryApi for this runtime, or null if this runtime wasn't found.
*/
public InterpreterFactoryApi getFactory() {
return factory;
}
/**
* @return The exception that occurred when trying to find this runtime, if any, or null.
*/
public Exception getException() {
return exception;
}
}
// We use static members here for caching, to ensure that we only do the reflective lookups once
// and then afterwards re-use the previously computed results.
//
// We put these static members in nested static classes to ensure that Java will
// delay the initialization of these static members until their respective first use;
// that's needed to ensure that we only log messages about TF Lite runtime not found
// for TF Lite runtimes that the application actually tries to use.
private static class RuntimeFromSystem {
static final PossiblyAvailableRuntime TFLITE =
new PossiblyAvailableRuntime("com.google.android.gms.tflite", "system");
}
private static class RuntimeFromApplication {
static final PossiblyAvailableRuntime TFLITE =
new PossiblyAvailableRuntime("org.tensorflow.lite", "application");
}
// We log at most once for each different options.runtime value.
private static final AtomicBoolean[] haveLogged =
new AtomicBoolean[TfLiteRuntime.values().length];
static {
for (int i = 0; i < TfLiteRuntime.values().length; i++) {
haveLogged[i] = new AtomicBoolean();
}
}
static InterpreterFactoryApi getFactory(TfLiteRuntime runtime) {
return getFactory(runtime, "org.tensorflow.lite.InterpreterApi.Options", "setRuntime");
}
/**
* Internal method for finding the TF Lite runtime implementation.
*
* @param className Class name for method to mention in exception messages.
* @param methodName Method name for method to mention in exception messages.
*/
private static InterpreterFactoryApi getFactory(
TfLiteRuntime runtime, String className, String methodName) {
Exception exception = null;
if (runtime == null) {
runtime = TfLiteRuntime.FROM_APPLICATION_ONLY;
}
if (runtime == TfLiteRuntime.PREFER_SYSTEM_OVER_APPLICATION
|| runtime == TfLiteRuntime.FROM_SYSTEM_ONLY) {
if (RuntimeFromSystem.TFLITE.getFactory() != null) {
if (!haveLogged[runtime.ordinal()].getAndSet(true)) {
logger.info(
String.format(
"TfLiteRuntime.%s: "
+ "Using system TF Lite runtime client from com.google.android.gms",
runtime.name()));
}
return RuntimeFromSystem.TFLITE.getFactory();
} else {
exception = RuntimeFromSystem.TFLITE.getException();
}
}
if (runtime == TfLiteRuntime.PREFER_SYSTEM_OVER_APPLICATION
|| runtime == TfLiteRuntime.FROM_APPLICATION_ONLY) {
if (RuntimeFromApplication.TFLITE.getFactory() != null) {
if (!haveLogged[runtime.ordinal()].getAndSet(true)) {
logger.info(
String.format(
"TfLiteRuntime.%s: "
+ "Using application TF Lite runtime client from org.tensorflow.lite",
runtime.name()));
}
return RuntimeFromApplication.TFLITE.getFactory();
} else {
if (exception == null) {
exception = RuntimeFromApplication.TFLITE.getException();
} else if (exception.getSuppressed().length == 0) {
exception.addSuppressed(RuntimeFromApplication.TFLITE.getException());
}
}
}
String message;
switch (runtime) {
case FROM_APPLICATION_ONLY:
message =
String.format(
"You should declare a build dependency on com.google.ai.edge.litert:litert,"
+ " or call .%s with a value other than TfLiteRuntime.FROM_APPLICATION_ONLY"
+ " (see docs for %s#%s(TfLiteRuntime)).",
methodName, className, methodName);
break;
case FROM_SYSTEM_ONLY:
message =
String.format(
"You should declare a build dependency on"
+ " com.google.android.gms:play-services-tflite-java,"
+ " or call .%s with a value other than TfLiteRuntime.FROM_SYSTEM_ONLY "
+ " (see docs for %s#%s).",
methodName, className, methodName);
break;
default:
message =
"You should declare a build dependency on"
+ " com.google.ai.edge.litert:litert or"
+ " com.google.android.gms:play-services-tflite-java";
break;
}
throw new IllegalStateException(
"Couldn't find TensorFlow Lite runtime's InterpreterFactoryImpl class --"
+ " make sure your app links in the right TensorFlow Lite runtime. "
+ message,
exception);
}
}
@@ -0,0 +1,536 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
import java.lang.reflect.Array;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.util.Arrays;
import org.checkerframework.checker.nullness.qual.NonNull;
/** Implementation of {@link Tensor}. */
// TODO(b/153882978): Add scalar getters similar to TF's Java API.
final class TensorImpl implements Tensor {
/**
* Creates a Tensor wrapper from the provided interpreter instance and tensor index.
*
* <p>The caller is responsible for closing the created wrapper, and ensuring the provided native
* interpreter is valid until the tensor is closed.
*/
static TensorImpl fromIndex(long nativeInterpreterHandle, int tensorIndex) {
return new TensorImpl(create(nativeInterpreterHandle, tensorIndex, /*subgraphIndex=*/ 0));
}
/**
* Creates a Tensor wrapper for a Signature input.
*
* <p>The caller is responsible for closing the created wrapper, and ensuring the provided native
* SignatureRunner is valid until the tensor is closed.
*/
static TensorImpl fromSignatureInput(long signatureRunnerHandle, String inputName) {
long tensorHandle = createSignatureInputTensor(signatureRunnerHandle, inputName);
if (tensorHandle == -1) {
throw new IllegalArgumentException("Input error: input " + inputName + " not found.");
} else {
return new TensorImpl(tensorHandle);
}
}
/**
* Creates a Tensor wrapper for a Signature output.
*
* <p>The caller is responsible for closing the created wrapper, and ensuring the provided native
* SignatureRunner is valid until the tensor is closed.
*/
static TensorImpl fromSignatureOutput(long signatureRunnerHandle, String outputName) {
long tensorHandle = createSignatureOutputTensor(signatureRunnerHandle, outputName);
if (tensorHandle == -1) {
throw new IllegalArgumentException("Input error: output " + outputName + " not found.");
} else {
return new TensorImpl(tensorHandle);
}
}
/** Disposes of any resources used by the Tensor wrapper. */
void close() {
delete(nativeHandle);
nativeHandle = 0;
}
@Override
public DataType dataType() {
return dtype;
}
@Override
public int numDimensions() {
return shapeCopy.length;
}
@Override
public int numBytes() {
return numBytes(nativeHandle);
}
@Override
public int numElements() {
return computeNumElements(shapeCopy);
}
@Override
public int[] shape() {
return shapeCopy;
}
@Override
public int[] shapeSignature() {
return shapeSignatureCopy;
}
@Override
public int index() {
return index(nativeHandle);
}
@Override
public String name() {
return name(nativeHandle);
}
@Override
public QuantizationParams quantizationParams() {
return quantizationParamsCopy;
}
@Override
public ByteBuffer asReadOnlyBuffer() {
// Note that the ByteBuffer order is not preserved when duplicated or marked read only, so
// we have to repeat the call.
return buffer().asReadOnlyBuffer().order(ByteOrder.nativeOrder());
}
/**
* Copies the contents of the provided {@code src} object to the Tensor.
*
* <p>The {@code src} should either be a (multi-dimensional) array with a shape matching that of
* this tensor, a {@link ByteBuffer} of compatible primitive type with a matching flat size, or
* {@code null} iff the tensor has an underlying delegate buffer handle.
*
* @throws IllegalArgumentException if the tensor is a scalar or if {@code src} is not compatible
* with the tensor (for example, mismatched data types or shapes).
*/
void setTo(Object src) {
if (src == null) {
if (hasDelegateBufferHandle(nativeHandle)) {
return;
}
throw new IllegalArgumentException(
"Null inputs are allowed only if the Tensor is bound to a buffer handle.");
}
throwIfTypeIsIncompatible(src);
throwIfSrcShapeIsIncompatible(src);
if (isBuffer(src)) {
setTo((Buffer) src);
} else if (dtype == DataType.STRING && shapeCopy.length == 0) {
// Update scalar string input with 1-d byte array.
writeScalar(nativeHandle, src);
} else if (src.getClass().isArray()) {
writeMultiDimensionalArray(nativeHandle, src);
} else {
writeScalar(nativeHandle, src);
}
}
private void setTo(Buffer src) {
// Note that we attempt to use a direct memcpy optimization for direct, native-ordered buffers.
// There are no base Buffer#order() or Buffer#put() methods, so again we have to ugly cast.
if (src instanceof ByteBuffer) {
ByteBuffer srcBuffer = (ByteBuffer) src;
if (srcBuffer.isDirect() && srcBuffer.order() == ByteOrder.nativeOrder()) {
writeDirectBuffer(nativeHandle, src);
} else {
buffer().put(srcBuffer);
}
} else if (src instanceof LongBuffer) {
LongBuffer srcBuffer = (LongBuffer) src;
if (srcBuffer.isDirect() && srcBuffer.order() == ByteOrder.nativeOrder()) {
writeDirectBuffer(nativeHandle, src);
} else {
buffer().asLongBuffer().put(srcBuffer);
}
} else if (src instanceof FloatBuffer) {
FloatBuffer srcBuffer = (FloatBuffer) src;
if (srcBuffer.isDirect() && srcBuffer.order() == ByteOrder.nativeOrder()) {
writeDirectBuffer(nativeHandle, src);
} else {
buffer().asFloatBuffer().put(srcBuffer);
}
} else if (src instanceof IntBuffer) {
IntBuffer srcBuffer = (IntBuffer) src;
if (srcBuffer.isDirect() && srcBuffer.order() == ByteOrder.nativeOrder()) {
writeDirectBuffer(nativeHandle, src);
} else {
buffer().asIntBuffer().put(srcBuffer);
}
} else if (src instanceof ShortBuffer) {
ShortBuffer srcBuffer = (ShortBuffer) src;
if (srcBuffer.isDirect() && srcBuffer.order() == ByteOrder.nativeOrder()) {
writeDirectBuffer(nativeHandle, src);
} else {
buffer().asShortBuffer().put(srcBuffer);
}
} else {
throw new IllegalArgumentException("Unexpected input buffer type: " + src);
}
}
/**
* Copies the contents of the tensor to {@code dst}.
*
* @param dst the destination buffer, either an explicitly-typed array, a compatible {@link
* Buffer} or {@code null} iff the tensor has an underlying delegate buffer handle. If
* providing a (multi-dimensional) array, its shape must match the tensor shape *exactly*. If
* providing a {@link Buffer}, its capacity must be at least as large as the source tensor's
* capacity.
* @throws IllegalArgumentException if {@code dst} is not compatible with the tensor (for example,
* mismatched data types or shapes).
*/
void copyTo(Object dst) {
if (dst == null) {
if (hasDelegateBufferHandle(nativeHandle)) {
return;
}
throw new IllegalArgumentException(
"Null outputs are allowed only if the Tensor is bound to a buffer handle.");
}
throwIfTypeIsIncompatible(dst);
throwIfDstShapeIsIncompatible(dst);
if (isBuffer(dst)) {
copyTo((Buffer) dst);
} else {
readMultiDimensionalArray(nativeHandle, dst);
}
}
private void copyTo(Buffer dst) {
// There is no base Buffer#put() method, so we have to ugly cast.
if (dst instanceof ByteBuffer) {
((ByteBuffer) dst).put(buffer());
} else if (dst instanceof FloatBuffer) {
((FloatBuffer) dst).put(buffer().asFloatBuffer());
} else if (dst instanceof LongBuffer) {
((LongBuffer) dst).put(buffer().asLongBuffer());
} else if (dst instanceof IntBuffer) {
((IntBuffer) dst).put(buffer().asIntBuffer());
} else if (dst instanceof ShortBuffer) {
((ShortBuffer) dst).put(buffer().asShortBuffer());
} else {
throw new IllegalArgumentException("Unexpected output buffer type: " + dst);
}
}
/** Returns the provided buffer's shape if specified and different from this Tensor's shape. */
// TODO(b/80431971): Remove this method after deprecating multi-dimensional array inputs.
int[] getInputShapeIfDifferent(Object input) {
if (input == null) {
return null;
}
// Implicit resizes based on ByteBuffer capacity isn't supported, so short-circuit that path.
// The Buffer's size will be validated against this Tensor's size in {@link #setTo(Object)}.
if (isBuffer(input)) {
return null;
}
throwIfTypeIsIncompatible(input);
int[] inputShape = computeShapeOf(input);
if (Arrays.equals(shapeCopy, inputShape)) {
return null;
}
return inputShape;
}
/**
* Forces a refresh of the tensor's cached shape.
*
* <p>This is useful if the tensor is resized or has a dynamic shape.
*/
void refreshShape() {
this.shapeCopy = shape(nativeHandle);
}
/** Returns the type of the data. */
DataType dataTypeOf(@NonNull Object o) {
Class<?> c = o.getClass();
// For arrays, the data elements must be a *primitive* type, e.g., an
// array of floats is fine, but not an array of Floats.
if (c.isArray()) {
while (c.isArray()) {
c = c.getComponentType();
}
if (float.class.equals(c)) {
return DataType.FLOAT32;
} else if (int.class.equals(c)) {
return DataType.INT32;
} else if (short.class.equals(c)) {
return DataType.INT16;
} else if (byte.class.equals(c)) {
// Byte array can be used for storing string tensors, especially for ParseExample op.
if (dtype == DataType.STRING) {
return DataType.STRING;
}
return DataType.UINT8;
} else if (long.class.equals(c)) {
return DataType.INT64;
} else if (boolean.class.equals(c)) {
return DataType.BOOL;
} else if (String.class.equals(c)) {
return DataType.STRING;
}
} else {
// For scalars, the type will be boxed.
if (Float.class.equals(c) || o instanceof FloatBuffer) {
return DataType.FLOAT32;
} else if (Integer.class.equals(c) || o instanceof IntBuffer) {
return DataType.INT32;
} else if (Short.class.equals(c) || o instanceof ShortBuffer) {
return DataType.INT16;
} else if (Byte.class.equals(c)) {
// Note that we don't check for ByteBuffer here; ByteBuffer payloads
// are allowed to map to any type, and should be handled earlier
// in the input/output processing pipeline.
return DataType.UINT8;
} else if (Long.class.equals(c) || o instanceof LongBuffer) {
return DataType.INT64;
} else if (Boolean.class.equals(c)) {
return DataType.BOOL;
} else if (String.class.equals(c)) {
return DataType.STRING;
}
}
throw new IllegalArgumentException(
"DataType error: cannot resolve DataType of " + o.getClass().getName());
}
/** Returns the shape of an object as an int array. */
private int[] computeShapeOf(Object o) {
int size = computeNumDimensions(o);
if (dtype == DataType.STRING) {
Class<?> c = o.getClass();
if (c.isArray()) {
while (c.isArray()) {
c = c.getComponentType();
}
// If the given string data is stored in byte streams, the last array dimension should be
// treated as a value.
if (byte.class.equals(c)) {
--size;
}
}
}
int[] dimensions = new int[size];
fillShape(o, 0, dimensions);
return dimensions;
}
/** Returns the number of elements in a flattened (1-D) view of the tensor's shape. */
static int computeNumElements(int[] shape) {
int n = 1;
for (int j : shape) {
n *= j;
}
return n;
}
/** Returns the number of dimensions of a multi-dimensional array, otherwise 0. */
static int computeNumDimensions(Object o) {
if (o == null || !o.getClass().isArray()) {
return 0;
}
if (Array.getLength(o) == 0) {
throw new IllegalArgumentException("Array lengths cannot be 0.");
}
return 1 + computeNumDimensions(Array.get(o, 0));
}
/** Recursively populates the shape dimensions for a given (multi-dimensional) array. */
static void fillShape(Object o, int dim, int[] shape) {
if (shape == null || dim == shape.length) {
return;
}
final int len = Array.getLength(o);
if (shape[dim] == 0) {
shape[dim] = len;
} else if (shape[dim] != len) {
throw new IllegalArgumentException(
String.format("Mismatched lengths (%d and %d) in dimension %d", shape[dim], len, dim));
}
final int nextDim = dim + 1;
// Short-circuit the innermost dimension to avoid unnecessary Array.get() reflection overhead.
if (nextDim == shape.length) {
return;
}
for (int i = 0; i < len; ++i) {
fillShape(Array.get(o, i), nextDim, shape);
}
}
private void throwIfTypeIsIncompatible(@NonNull Object o) {
// ByteBuffer payloads can map to any type, so exempt it from the check.
if (isByteBuffer(o)) {
return;
}
DataType oType = dataTypeOf(o);
if (oType != dtype) {
// INT8 and UINT8 have the same string name, "byte"
if (DataTypeUtils.toStringName(oType).equals(DataTypeUtils.toStringName(dtype))) {
return;
}
throw new IllegalArgumentException(
String.format(
"Cannot convert between a TensorFlowLite tensor with type %s and a Java "
+ "object of type %s (which is compatible with the TensorFlowLite type %s).",
dtype, o.getClass().getName(), oType));
}
}
private void throwIfSrcShapeIsIncompatible(Object src) {
if (isBuffer(src)) {
Buffer srcBuffer = (Buffer) src;
int bytes = numBytes();
// Note that we allow the client to provide a ByteBuffer even for non-byte Tensors.
// In such cases, we only care that the raw byte capacity matches the tensor byte capacity.
int srcBytes =
isByteBuffer(src) ? srcBuffer.capacity() : srcBuffer.capacity() * dtype.byteSize();
if (bytes != srcBytes) {
throw new IllegalArgumentException(
String.format(
"Cannot copy to a TensorFlowLite tensor (%s) with %d bytes from a "
+ "Java Buffer with %d bytes.",
name(), bytes, srcBytes));
}
return;
}
int[] srcShape = computeShapeOf(src);
if (!Arrays.equals(srcShape, shapeCopy)) {
throw new IllegalArgumentException(
String.format(
"Cannot copy to a TensorFlowLite tensor (%s) with shape %s from a Java object "
+ "with shape %s.",
name(), Arrays.toString(shapeCopy), Arrays.toString(srcShape)));
}
}
private void throwIfDstShapeIsIncompatible(Object dst) {
if (isBuffer(dst)) {
Buffer dstBuffer = (Buffer) dst;
int bytes = numBytes();
// Note that we allow the client to provide a ByteBuffer even for non-byte Tensors.
// In such cases, we only care that the raw byte capacity fits the tensor byte capacity.
// This is subtly different than Buffer *inputs*, where the size should be exact.
int dstBytes =
isByteBuffer(dst) ? dstBuffer.capacity() : dstBuffer.capacity() * dtype.byteSize();
if (bytes > dstBytes) {
throw new IllegalArgumentException(
String.format(
"Cannot copy from a TensorFlowLite tensor (%s) with %d bytes to a "
+ "Java Buffer with %d bytes.",
name(), bytes, dstBytes));
}
return;
}
int[] dstShape = computeShapeOf(dst);
if (!Arrays.equals(dstShape, shapeCopy)) {
throw new IllegalArgumentException(
String.format(
"Cannot copy from a TensorFlowLite tensor (%s) with shape %s to a Java object "
+ "with shape %s.",
name(), Arrays.toString(shapeCopy), Arrays.toString(dstShape)));
}
}
private static boolean isBuffer(Object o) {
return o instanceof Buffer;
}
private static boolean isByteBuffer(Object o) {
return o instanceof ByteBuffer;
}
private long nativeHandle;
private final DataType dtype;
private int[] shapeCopy;
private final int[] shapeSignatureCopy;
private final QuantizationParams quantizationParamsCopy;
private TensorImpl(long nativeHandle) {
this.nativeHandle = nativeHandle;
this.dtype = DataTypeUtils.fromC(dtype(nativeHandle));
this.shapeCopy = shape(nativeHandle);
this.shapeSignatureCopy = shapeSignature(nativeHandle);
this.quantizationParamsCopy =
new QuantizationParams(
quantizationScale(nativeHandle), quantizationZeroPoint(nativeHandle));
}
private ByteBuffer buffer() {
return buffer(nativeHandle).order(ByteOrder.nativeOrder());
}
private static native long create(long interpreterHandle, int tensorIndex, int subgraphIndex);
private static native long createSignatureInputTensor(
long signatureRunnerHandle, String inputName);
private static native long createSignatureOutputTensor(
long signatureRunnerHandle, String outputName);
private static native void delete(long handle);
private static native ByteBuffer buffer(long handle);
private static native void writeDirectBuffer(long handle, Buffer src);
private static native int dtype(long handle);
private static native int[] shape(long handle);
private static native int[] shapeSignature(long handle);
private static native int numBytes(long handle);
private static native boolean hasDelegateBufferHandle(long handle);
private static native void readMultiDimensionalArray(long handle, Object dst);
private static native void writeMultiDimensionalArray(long handle, Object src);
private static native void writeScalar(long handle, Object src);
private static native int index(long handle);
private static native String name(long handle);
private static native float quantizationScale(long handle);
private static native int quantizationZeroPoint(long handle);
}
@@ -0,0 +1,37 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite.acceleration;
import org.tensorflow.lite.InterpreterApi.Options;
/**
* Interface specifying validated acceleration configuration. Developers should not implement this
* interface directly as it is only supported through the Acceleration service SDK.
*/
public interface ValidatedAccelerationConfig {
/**
* Returns the serialized validated acceleration config as bytes.
*/
byte[] serialize();
/**
* Applies the validated acceleration config to the interpreter options.
*
* @hide
*/
void apply(Options options);
}
@@ -0,0 +1,31 @@
/* Copyright 2019 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.
==============================================================================*/
package org.tensorflow.lite.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
/**
* Annotation used for marking methods and fields that are called by reflection. Useful for keeping
* components that would otherwise be removed by Proguard. Use the value parameter to mention a file
* that calls this method.
*
* @hide
*/
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, ElementType.CONSTRUCTOR})
public @interface UsedByReflection {
String value();
}
@@ -0,0 +1,17 @@
/* Copyright 2017 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.
==============================================================================*/
/** Defines classes to load and execute TensorFlowLite models. */
package org.tensorflow.lite;
+173
View File
@@ -0,0 +1,173 @@
# Description:
# Java Native Interface (JNI) library intended for implementing the
# TensorFlow Lite Java API using the TensorFlow Lite CC library.
load("//tensorflow/lite:build_def.bzl", "tflite_copts")
load("//tensorflow/lite:special_rules.bzl", "jni_utils_visibility_allowlist")
load("//tensorflow/lite/core/shims:cc_library_with_tflite.bzl", "alias_with_tflite", "cc_library_with_tflite")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"], # Apache 2.0
)
cc_library_with_tflite(
name = "jni_utils",
srcs = ["jni_utils.cc"],
hdrs = ["jni_utils.h"],
tflite_deps = [
"//tensorflow/lite/c/jni:jni_utils",
],
visibility = jni_utils_visibility_allowlist(),
deps = [
"//tensorflow/lite:error_reporter",
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/java/jni",
],
)
alias_with_tflite(
name = "native_framework_only",
actual = ":native_experimental_framework_only",
)
# Native code needed for TF Lite Java API, including experimental APIs;
# but only the framework code, no ops.
cc_library_with_tflite(
name = "native_experimental_framework_only",
srcs = [
":native_experimental_only_srcs",
],
hdrs = [":native_hdrs"],
tflite_deps = [
":jni_utils",
":native_stable_framework_only",
"//tensorflow/lite:framework_experimental",
],
deps = [
"//tensorflow/lite/java/jni",
],
alwayslink = 1,
)
filegroup(
name = "native_experimental_only_srcs",
srcs = [
"nativeinterpreterwrapperexperimental_jni.cc",
],
)
filegroup(
name = "native_hdrs",
srcs = [
"op_resolver_lazy_delegate_proxy.h",
],
)
# Native code needed for TF Lite Java API, excluding experimental APIs;
# but only the framework code, no ops.
filegroup(
name = "native_stable_srcs",
srcs = [
"interpreter_factory_impl_jni.cc",
"nativeinterpreterwrapper_jni.cc",
"nativesignaturerunner_jni.cc",
"op_resolver_lazy_delegate_proxy.cc",
"tensor_jni.cc",
"tensorflow_lite_jni.cc",
],
)
filegroup(
name = "native_api_srcs",
srcs = [
"tensorflow_lite_jni.cc",
],
)
cc_library_with_tflite(
name = "native_stable_framework_only",
srcs = [":native_stable_srcs"],
hdrs = [":native_hdrs"],
copts = tflite_copts(),
linkopts = [
"-lm",
"-ldl",
"-Wl,-z,max-page-size=16384",
],
tflite_deps = [
":jni_utils",
"//tensorflow/lite:create_op_resolver_header",
"//tensorflow/lite:framework_stable",
"//tensorflow/lite:signature_runner",
"//tensorflow/lite/c/jni:jni_utils",
"//tensorflow/lite/c:c_api_without_op_resolver",
"//tensorflow/lite/c:common",
"//tensorflow/lite/acceleration/configuration/c:delegate_plugin",
"//tensorflow/lite/acceleration/configuration/c:xnnpack_plugin",
"//tensorflow/lite/tools:verifier_internal",
],
deps = [
"//tensorflow/lite:interpreter_options_header",
"//tensorflow/lite:minimal_logging",
"//tensorflow/lite:op_resolver",
"//tensorflow/lite:schema_fbs_version",
"//tensorflow/lite:string_util",
"//tensorflow/lite:util",
"//tensorflow/lite/core:framework",
"//tensorflow/lite/core/api:op_resolver_internal",
"//tensorflow/lite/delegates/xnnpack:xnnpack_delegate_hdrs_only",
"//tensorflow/lite/java/jni",
],
alwayslink = 1,
)
alias_with_tflite(
name = "native",
actual = ":native_experimental",
)
# Native code and ops needed for the TF Lite Java API, including experimental
# features. This includes all ops. If you want a smaller binary, you should use
# tflite_custom_cc_library or tflite_custom_android_library rules.
cc_library_with_tflite(
name = "native_experimental",
copts = tflite_copts(),
tflite_deps = [
":native_experimental_framework_only",
"//tensorflow/lite:create_op_resolver_with_builtin_ops",
"//tensorflow/lite:framework",
"//tensorflow/lite/kernels:builtin_ops",
],
deps = [
"//tensorflow/lite/core/api",
],
alwayslink = 1,
)
# Native code and ops needed for the TF Lite Java API, excluding experimental
# features. This includes all ops. If you want a smaller binary, you should use
# tflite_custom_cc_library or tflite_custom_android_library rules.
cc_library_with_tflite(
name = "native_stable",
copts = tflite_copts(),
tflite_deps = [
":native_stable_framework_only",
"//tensorflow/lite:create_op_resolver_with_builtin_ops",
"//tensorflow/lite:framework_stable",
"//tensorflow/lite/kernels:builtin_ops",
],
deps = [
"//tensorflow/lite/core/api",
"//tensorflow/lite/kernels:deprecated_backends",
],
alwayslink = 1,
)
exports_files(
[
"exported_symbols.lds",
"version_script.lds",
],
)
@@ -0,0 +1,3 @@
*Java_*
*JNI_OnLoad
*JNI_OnUnload
@@ -0,0 +1,44 @@
/* Copyright 2017 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 <jni.h>
#include <stdio.h>
#include "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/java/src/main/native/jni_utils.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
JNIEXPORT jstring JNICALL
Java_org_tensorflow_lite_InterpreterFactoryImpl_nativeRuntimeVersion(
JNIEnv* env, jclass /*clazz*/) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return nullptr;
return env->NewStringUTF(TfLiteVersion());
}
JNIEXPORT jstring JNICALL
Java_org_tensorflow_lite_InterpreterFactoryImpl_nativeSchemaVersion(
JNIEnv* env, jclass /*clazz*/) {
char buf[64];
snprintf(buf, sizeof(buf), "%d", TfLiteSchemaVersion());
return env->NewStringUTF(buf);
}
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
@@ -0,0 +1,143 @@
/* Copyright 2017 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/java/src/main/native/jni_utils.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include "tensorflow/lite/c/jni/jni_utils.h"
namespace tflite {
namespace jni {
const char kIllegalArgumentException[] = "java/lang/IllegalArgumentException";
const char kIllegalStateException[] = "java/lang/IllegalStateException";
const char kNullPointerException[] = "java/lang/NullPointerException";
const char kUnsupportedOperationException[] =
"java/lang/UnsupportedOperationException";
void ThrowException(JNIEnv* env, const char* clazz, const char* fmt, ...) {
va_list args;
va_start(args, fmt);
const size_t max_msg_len = 512;
auto* message = static_cast<char*>(malloc(max_msg_len));
if (message && (vsnprintf(message, max_msg_len, fmt, args) >= 0)) {
env->ThrowNew(env->FindClass(clazz), message);
} else {
env->ThrowNew(env->FindClass(clazz), "");
}
free(message);
va_end(args);
}
bool CheckJniInitializedOrThrow(JNIEnv* env) {
return TfLiteCheckInitializedOrThrow(env);
}
BufferErrorReporter::BufferErrorReporter(JNIEnv* env, int limit) {
buffer_ = new char[limit];
if (!buffer_) {
ThrowException(env, kNullPointerException,
"Internal error: Malloc of BufferErrorReporter to hold %d "
"char failed.",
limit);
return;
}
buffer_[0] = '\0';
start_idx_ = 0;
end_idx_ = limit - 1;
}
BufferErrorReporter::~BufferErrorReporter() { delete[] buffer_; }
int BufferErrorReporter::Report(const char* format, va_list args) {
int size = 0;
// If an error has already been logged, insert a newline.
if (start_idx_ > 0 && start_idx_ < end_idx_) {
buffer_[start_idx_++] = '\n';
++size;
}
if (start_idx_ < end_idx_) {
size = vsnprintf(buffer_ + start_idx_, end_idx_ - start_idx_, format, args);
}
start_idx_ += size;
return size;
}
const char* BufferErrorReporter::CachedErrorMessage() { return buffer_; }
jobjectArray CreateStringArray(const std::vector<const char*>& values,
JNIEnv* env) {
jclass string_class = env->FindClass("java/lang/String");
if (string_class == nullptr) {
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Internal error: Can not find java/lang/String class.");
return nullptr;
}
jobjectArray results =
env->NewObjectArray(values.size(), string_class, env->NewStringUTF(""));
int i = 0;
for (const char* value : values) {
env->SetObjectArrayElement(results, i++, env->NewStringUTF(value));
}
return results;
}
bool AreDimsDifferent(JNIEnv* env, TfLiteTensor* tensor, const jintArray dims) {
int num_dims = static_cast<int>(env->GetArrayLength(dims));
jint* ptr = env->GetIntArrayElements(dims, nullptr);
if (ptr == nullptr) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Empty dimensions of input array.");
return true;
}
bool is_different = false;
if (tensor->dims->size != num_dims) {
is_different = true;
} else {
for (int i = 0; i < num_dims; ++i) {
if (ptr[i] != tensor->dims->data[i]) {
is_different = true;
break;
}
}
}
env->ReleaseIntArrayElements(dims, ptr, JNI_ABORT);
return is_different;
}
std::vector<int> ConvertJIntArrayToVector(JNIEnv* env, const jintArray inputs) {
int size = static_cast<int>(env->GetArrayLength(inputs));
std::vector<int> outputs(size, 0);
jint* ptr = env->GetIntArrayElements(inputs, nullptr);
if (ptr == nullptr) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Array has empty dimensions.");
return {};
}
for (int i = 0; i < size; ++i) {
outputs[i] = ptr[i];
}
env->ReleaseIntArrayElements(inputs, ptr, JNI_ABORT);
return outputs;
}
} // namespace jni
} // namespace tflite
@@ -0,0 +1,96 @@
/* Copyright 2019 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_JAVA_SRC_MAIN_NATIVE_JNI_UTILS_H_
#define TENSORFLOW_LITE_JAVA_SRC_MAIN_NATIVE_JNI_UTILS_H_
#include <jni.h>
#include <stdarg.h>
#include <vector>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/error_reporter.h"
namespace tflite {
namespace jni {
extern const char kIllegalArgumentException[];
extern const char kIllegalStateException[];
extern const char kNullPointerException[];
extern const char kUnsupportedOperationException[];
/**
* Thin wrapper around env->ThrowNew(...) that constructs the message using
* printf-style formatting.
*
* Beware that if there is an exception already pending, then throwing
* another exception may result in program termination, so it is good
* practice to ensure that there is no pending exception before calling
* this function.
*/
void ThrowException(JNIEnv* env, const char* clazz, const char* fmt, ...);
/**
* Checks whether the necessary JNI infra has been initialized, throwing a Java
* exception otherwise.
*
* @param env The JNIEnv for the current thread (which has to be attached to the
* JVM).
* @return Whether or not the JNI infra has been initialized. If this method
* returns false, no other JNI method should be called until the pending
* exception has been handled (typically by returning to Java).
*/
bool CheckJniInitializedOrThrow(JNIEnv* env);
class BufferErrorReporter : public ErrorReporter {
public:
BufferErrorReporter(JNIEnv* env, int limit);
~BufferErrorReporter() override;
int Report(const char* format, va_list args) override;
const char* CachedErrorMessage();
using ErrorReporter::Report;
private:
char* buffer_;
int start_idx_ = 0;
int end_idx_ = 0;
};
// Creates a Java string array from a C++ string vector.
jobjectArray CreateStringArray(const std::vector<const char*>& values,
JNIEnv* env);
// Checks the difference between tensor dimensions and given dimensions. Returns
// true if there is a difference, else false.
bool AreDimsDifferent(JNIEnv* env, TfLiteTensor* tensor, jintArray dims);
// Creates a C++ integer vector from a jintArray.
std::vector<int> ConvertJIntArrayToVector(JNIEnv* env, jintArray inputs);
// Converts a handle to a pointer of expected type.
template <typename T>
T* CastLongToPointer(JNIEnv* env, jlong handle) {
if (handle == 0 || handle == -1) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Found invalid handle");
return nullptr;
}
return reinterpret_cast<T*>(handle);
}
} // namespace jni
} // namespace tflite
#endif // TENSORFLOW_LITE_JAVA_SRC_MAIN_NATIVE_JNI_UTILS_H_
@@ -0,0 +1,673 @@
/* Copyright 2017 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 <dlfcn.h>
#include <jni.h>
#include <stdio.h>
#include <time.h>
#include <atomic>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/create_op_resolver.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/interpreter_builder.h"
#if !TFLITE_IN_GMSCORE && TFLITE_WITH_STABLE_ABI
#include "tensorflow/lite/interpreter_options.h"
#endif
#include "tensorflow/lite/model_builder.h"
#include "tensorflow/lite/tools/verifier_internal.h"
#if TFLITE_DISABLE_SELECT_JAVA_APIS
#include "tensorflow/lite/acceleration/configuration/c/delegate_plugin.h"
#include "tensorflow/lite/acceleration/configuration/c/xnnpack_plugin.h"
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#else
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#endif
#include "tensorflow/lite/java/src/main/native/jni_utils.h"
#include "tensorflow/lite/java/src/main/native/op_resolver_lazy_delegate_proxy.h"
#include "tensorflow/lite/minimal_logging.h"
#include "tensorflow/lite/util.h"
using tflite::FlatBufferModel;
using tflite::Interpreter;
using tflite::InterpreterBuilder;
using tflite::OpResolver;
using tflite::jni::AreDimsDifferent;
using tflite::jni::BufferErrorReporter;
using tflite::jni::CastLongToPointer;
using tflite::jni::ConvertJIntArrayToVector;
using tflite::jni::ThrowException;
namespace {
Interpreter* convertLongToInterpreter(JNIEnv* env, jlong handle) {
return CastLongToPointer<Interpreter>(env, handle);
}
FlatBufferModel* convertLongToModel(JNIEnv* env, jlong handle) {
return CastLongToPointer<FlatBufferModel>(env, handle);
}
BufferErrorReporter* convertLongToErrorReporter(JNIEnv* env, jlong handle) {
return CastLongToPointer<BufferErrorReporter>(env, handle);
}
int getDataType(TfLiteType data_type) {
switch (data_type) {
case kTfLiteFloat32:
return 1;
case kTfLiteInt32:
return 2;
case kTfLiteUInt8:
return 3;
case kTfLiteInt64:
return 4;
case kTfLiteString:
return 5;
case kTfLiteBool:
return 6;
default:
return -1;
}
}
// TODO(yichengfan): evaluate the benefit to use tflite verifier.
bool VerifyModel(const void* buf, size_t length) {
return tflite::internal::VerifyFlatBufferAndGetModel(buf, length);
}
// Verifies whether the model is a flatbuffer file.
class JNIFlatBufferVerifier : public tflite::TfLiteVerifier {
public:
bool Verify(const char* data, int length,
tflite::ErrorReporter* reporter) override {
if (!VerifyModel(data, length)) {
TF_LITE_REPORT_ERROR(reporter,
"The model is not a valid Flatbuffer file");
return false;
}
return true;
}
};
// Like JNIEnv's FindClass method, but converts the result to a
// JNI global reference rather than returning a local reference.
jclass FindClassAndMakeGlobalRef(JNIEnv* env, const char* class_name) {
jclass local_ref = env->FindClass(class_name);
jclass global_ref = static_cast<jclass>(env->NewGlobalRef(local_ref));
env->DeleteLocalRef(local_ref);
return global_ref;
}
} // namespace
extern "C" {
JNIEXPORT jobjectArray JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_getInputNames(JNIEnv* env,
jclass clazz,
jlong handle) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return nullptr;
Interpreter* interpreter = convertLongToInterpreter(env, handle);
if (interpreter == nullptr) return nullptr;
static jclass string_class =
FindClassAndMakeGlobalRef(env, "java/lang/String");
if (string_class == nullptr) {
if (!env->ExceptionCheck()) {
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Internal error: Can not find java/lang/String class to "
"get input names.");
}
return nullptr;
}
size_t size = interpreter->inputs().size();
jobjectArray names = static_cast<jobjectArray>(
env->NewObjectArray(size, string_class, env->NewStringUTF("")));
for (int i = 0; i < size; ++i) {
env->SetObjectArrayElement(names, i,
env->NewStringUTF(interpreter->GetInputName(i)));
}
return names;
}
JNIEXPORT void JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_allocateTensors(
JNIEnv* env, jclass clazz, jlong handle, jlong error_handle) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return;
Interpreter* interpreter = convertLongToInterpreter(env, handle);
if (interpreter == nullptr) return;
BufferErrorReporter* error_reporter =
convertLongToErrorReporter(env, error_handle);
if (error_reporter == nullptr) return;
if (interpreter->AllocateTensors() != kTfLiteOk) {
ThrowException(env, tflite::jni::kIllegalStateException,
"Internal error: Unexpected failure when preparing tensor "
"allocations: %s",
error_reporter->CachedErrorMessage());
}
}
JNIEXPORT jboolean JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_hasUnresolvedFlexOp(
JNIEnv* env, jclass clazz, jlong handle) {
#if TFLITE_DISABLE_SELECT_JAVA_APIS
TFLITE_LOG(tflite::TFLITE_LOG_WARNING, "Not supported: hasUnresolvedFlexOp");
return JNI_FALSE;
#else
Interpreter* interpreter = convertLongToInterpreter(env, handle);
if (interpreter == nullptr) return JNI_FALSE;
// TODO(b/132995737): Remove this logic by caching whether an unresolved
// Flex op is present during Interpreter creation.
for (size_t subgraph_i = 0; subgraph_i < interpreter->subgraphs_size();
++subgraph_i) {
const auto* subgraph = interpreter->subgraph(static_cast<int>(subgraph_i));
for (int node_i : subgraph->execution_plan()) {
const auto& registration =
subgraph->node_and_registration(node_i)->second;
if (tflite::IsUnresolvedCustomOp(registration) &&
tflite::IsFlexOp(registration.custom_name)) {
return JNI_TRUE;
}
}
}
return JNI_FALSE;
#endif // TFLITE_DISABLE_SELECT_JAVA_APIS
}
JNIEXPORT jobjectArray JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_getSignatureKeys(
JNIEnv* env, jclass clazz, jlong handle) {
Interpreter* interpreter = convertLongToInterpreter(env, handle);
if (interpreter == nullptr) return nullptr;
static jclass string_class =
FindClassAndMakeGlobalRef(env, "java/lang/String");
if (string_class == nullptr) {
if (!env->ExceptionCheck()) {
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Internal error: Can not find java/lang/String class to "
"get SignatureDef keys.");
}
return nullptr;
}
const auto& signature_keys = interpreter->signature_keys();
jobjectArray keys = static_cast<jobjectArray>(env->NewObjectArray(
signature_keys.size(), string_class, env->NewStringUTF("")));
for (int i = 0; i < signature_keys.size(); ++i) {
env->SetObjectArrayElement(keys, i,
env->NewStringUTF(signature_keys[i]->c_str()));
}
return keys;
}
JNIEXPORT jint JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_getInputTensorIndex(
JNIEnv* env, jclass clazz, jlong handle, jint input_index) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return 0;
Interpreter* interpreter = convertLongToInterpreter(env, handle);
if (interpreter == nullptr) return 0;
return interpreter->inputs()[input_index];
}
JNIEXPORT jint JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_getOutputTensorIndex(
JNIEnv* env, jclass clazz, jlong handle, jint output_index) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return 0;
Interpreter* interpreter = convertLongToInterpreter(env, handle);
if (interpreter == nullptr) return 0;
return interpreter->outputs()[output_index];
}
JNIEXPORT jint JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_getExecutionPlanLength(
JNIEnv* env, jclass clazz, jlong handle) {
#if TFLITE_DISABLE_SELECT_JAVA_APIS
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Not supported: getExecutionPlanLength");
return -1;
#else
Interpreter* interpreter = convertLongToInterpreter(env, handle);
if (interpreter == nullptr) return 0;
return static_cast<jint>(interpreter->execution_plan().size());
#endif // TFLITE_DISABLE_SELECT_JAVA_APIS
}
JNIEXPORT jint JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_getInputCount(JNIEnv* env,
jclass clazz,
jlong handle) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return 0;
Interpreter* interpreter = convertLongToInterpreter(env, handle);
if (interpreter == nullptr) return 0;
return static_cast<jint>(interpreter->inputs().size());
}
JNIEXPORT jint JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_getOutputCount(JNIEnv* env,
jclass clazz,
jlong handle) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return 0;
Interpreter* interpreter = convertLongToInterpreter(env, handle);
if (interpreter == nullptr) return 0;
return static_cast<jint>(interpreter->outputs().size());
}
JNIEXPORT jobjectArray JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_getOutputNames(JNIEnv* env,
jclass clazz,
jlong handle) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return nullptr;
Interpreter* interpreter = convertLongToInterpreter(env, handle);
if (interpreter == nullptr) return nullptr;
static jclass string_class =
FindClassAndMakeGlobalRef(env, "java/lang/String");
if (string_class == nullptr) {
if (!env->ExceptionCheck()) {
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Internal error: Can not find java/lang/String class to "
"get output names.");
}
return nullptr;
}
size_t size = interpreter->outputs().size();
jobjectArray names = static_cast<jobjectArray>(
env->NewObjectArray(size, string_class, env->NewStringUTF("")));
for (int i = 0; i < size; ++i) {
env->SetObjectArrayElement(
names, i, env->NewStringUTF(interpreter->GetOutputName(i)));
}
return names;
}
JNIEXPORT void JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_allowFp16PrecisionForFp32(
JNIEnv* env, jclass clazz, jlong handle, jboolean allow) {
Interpreter* interpreter = convertLongToInterpreter(env, handle);
if (interpreter == nullptr) return;
#if TFLITE_DISABLE_SELECT_JAVA_APIS
if (allow) {
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Not supported: SetAllowFp16PrecisionForFp32(true)");
}
#else
interpreter->SetAllowFp16PrecisionForFp32(static_cast<bool>(allow));
#endif // TFLITE_DISABLE_SELECT_JAVA_APIS
}
JNIEXPORT void JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_allowBufferHandleOutput(
JNIEnv* env, jclass clazz, jlong handle, jboolean allow) {
#if TFLITE_DISABLE_SELECT_JAVA_APIS
if (allow) {
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Not supported: allowBufferHandleOutput(true)");
}
#else
Interpreter* interpreter = convertLongToInterpreter(env, handle);
if (interpreter == nullptr) return;
interpreter->SetAllowBufferHandleOutput(allow);
#endif // TFLITE_DISABLE_SELECT_JAVA_APIS
}
JNIEXPORT jlong JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_createErrorReporter(
JNIEnv* env, jclass clazz, jint size) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return 0;
BufferErrorReporter* error_reporter =
new BufferErrorReporter(env, static_cast<int>(size));
return reinterpret_cast<jlong>(error_reporter);
}
JNIEXPORT jlong JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_createModel(
JNIEnv* env, jclass clazz, jstring model_file, jlong error_handle) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return 0;
BufferErrorReporter* error_reporter =
convertLongToErrorReporter(env, error_handle);
if (error_reporter == nullptr) return 0;
const char* path = env->GetStringUTFChars(model_file, nullptr);
std::unique_ptr<tflite::TfLiteVerifier> verifier;
verifier = std::make_unique<JNIFlatBufferVerifier>();
auto model = FlatBufferModel::VerifyAndBuildFromFile(path, verifier.get(),
error_reporter);
if (!model) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Contents of %s does not encode a valid "
"TensorFlow Lite model: %s",
path, error_reporter->CachedErrorMessage());
env->ReleaseStringUTFChars(model_file, path);
return 0;
}
env->ReleaseStringUTFChars(model_file, path);
return reinterpret_cast<jlong>(model.release());
}
JNIEXPORT jlong JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_createModelWithBuffer(
JNIEnv* env, jclass /*clazz*/, jobject model_buffer, jlong error_handle) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return 0;
BufferErrorReporter* error_reporter =
convertLongToErrorReporter(env, error_handle);
if (error_reporter == nullptr) return 0;
const char* buf =
static_cast<char*>(env->GetDirectBufferAddress(model_buffer));
jlong capacity = env->GetDirectBufferCapacity(model_buffer);
if (!VerifyModel(buf, capacity)) {
ThrowException(
env, tflite::jni::kIllegalArgumentException,
"ByteBuffer is not a valid TensorFlow Lite model flatbuffer");
return 0;
}
auto model = FlatBufferModel::BuildFromBuffer(
buf, static_cast<size_t>(capacity), error_reporter);
if (!model) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"ByteBuffer does not encode a valid model: %s",
error_reporter->CachedErrorMessage());
return 0;
}
return reinterpret_cast<jlong>(model.release());
}
JNIEXPORT jlong JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_createInterpreter(
JNIEnv* env, jclass clazz, jlong model_handle, jlong error_handle,
jint num_threads, jboolean useXnnpack,
jboolean compress_quantization_zero_points, jobject delegate_handle_list) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return 0;
static jclass list_class = FindClassAndMakeGlobalRef(env, "java/util/List");
if (list_class == nullptr) {
if (!env->ExceptionCheck()) {
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Internal error: Can't find java.util.List class.");
}
return 0;
}
static jmethodID list_size_method =
env->GetMethodID(list_class, "size", "()I");
if (list_size_method == nullptr) {
if (!env->ExceptionCheck()) {
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Internal error: Can't find java.util.List.size method.");
}
return 0;
}
static jmethodID list_get_method =
env->GetMethodID(list_class, "get", "(I)Ljava/lang/Object;");
if (list_get_method == nullptr) {
if (!env->ExceptionCheck()) {
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Internal error: Can't find java.util.List.get method.");
}
return 0;
}
static jclass long_class = FindClassAndMakeGlobalRef(env, "java/lang/Long");
if (long_class == nullptr) {
if (!env->ExceptionCheck()) {
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Internal error: "
"Can't find java.lang.Long class.");
}
return 0;
}
static jmethodID long_value_method =
env->GetMethodID(long_class, "longValue", "()J");
if (long_value_method == nullptr) {
if (!env->ExceptionCheck()) {
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Internal error: "
"Can't find java.lang.Long longValue method.");
}
return 0;
}
FlatBufferModel* model = convertLongToModel(env, model_handle);
if (model == nullptr) return 0;
BufferErrorReporter* error_reporter =
convertLongToErrorReporter(env, error_handle);
if (error_reporter == nullptr) return 0;
std::unique_ptr<OpResolver> resolver =
std::make_unique<tflite::jni::OpResolverLazyDelegateProxy>(
tflite::CreateOpResolver(), useXnnpack != JNI_FALSE);
#if TFLITE_IN_GMSCORE || !TFLITE_WITH_STABLE_ABI
if (compress_quantization_zero_points != JNI_FALSE) {
TFLITE_LOG(tflite::TFLITE_LOG_WARNING,
"Not supported in internal service binding: "
"compress_quantization_zero_points=true");
}
InterpreterBuilder interpreter_builder(*model, *resolver);
#else
tflite::InterpreterOptions options;
options.SetCompressQuantizationZeroPoints(compress_quantization_zero_points !=
JNI_FALSE);
InterpreterBuilder interpreter_builder(*model, *resolver, &options);
#endif
interpreter_builder.SetNumThreads(static_cast<int>(num_threads));
// Add delegate_list to interpreter_builder.
// Java: int size = delegate_list.size();
jint size = env->CallIntMethod(delegate_handle_list, list_size_method);
for (jint i = 0; i < size; ++i) {
// Java: Long jdelegate_handle = delegate_handle_list->get(i);
jobject jdelegate_handle =
env->CallObjectMethod(delegate_handle_list, list_get_method, i);
if (jdelegate_handle == nullptr) {
if (!env->ExceptionCheck()) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: null object in Delegate handle list");
}
return 0;
}
// Java: long delegate_handle = jdelegate_handle.longValue();
jlong delegate_handle =
env->CallLongMethod(jdelegate_handle, long_value_method);
if (delegate_handle == 0) {
if (!env->ExceptionCheck()) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Found invalid handle");
}
return 0;
}
auto delegate = reinterpret_cast<TfLiteOpaqueDelegate*>(delegate_handle);
interpreter_builder.AddDelegate(delegate);
}
// Create the Interpreter.
std::unique_ptr<Interpreter> interpreter;
TfLiteStatus status = interpreter_builder(&interpreter);
if (status != kTfLiteOk) {
if (status == kTfLiteDelegateError) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Failed to apply delegate: %s",
error_reporter->CachedErrorMessage());
} else if (status == kTfLiteApplicationError) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Error applying delegate: %s",
error_reporter->CachedErrorMessage());
} else {
const char* error_message = error_reporter->CachedErrorMessage();
if (std::strcmp(
error_message,
"Restored original execution plan after delegate application "
"failure.") == 0 ||
std::strcmp(
error_message,
"Restored original execution plan after delegate application "
"failure.\n"
"Restored original execution plan after delegate application "
"failure.") == 0) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Failed to apply delegate.");
} else {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Cannot create interpreter: %s",
error_message);
}
}
return 0;
}
// Note that tensor allocation is performed explicitly by the owning Java
// NativeInterpreterWrapper instance.
return reinterpret_cast<jlong>(interpreter.release());
}
// Sets inputs, runs inference, and returns outputs as long handles.
JNIEXPORT void JNICALL Java_org_tensorflow_lite_NativeInterpreterWrapper_run(
JNIEnv* env, jclass clazz, jlong interpreter_handle, jlong error_handle) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return;
Interpreter* interpreter = convertLongToInterpreter(env, interpreter_handle);
if (interpreter == nullptr) return;
BufferErrorReporter* error_reporter =
convertLongToErrorReporter(env, error_handle);
if (error_reporter == nullptr) return;
if (interpreter->Invoke() != kTfLiteOk) {
// TODO(b/168266570): Return InterruptedException.
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Failed to run on the given Interpreter: %s",
error_reporter->CachedErrorMessage());
return;
}
}
JNIEXPORT jboolean JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_resizeInput(
JNIEnv* env, jclass clazz, jlong interpreter_handle, jlong error_handle,
jint input_idx, jintArray dims, jboolean strict) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return JNI_FALSE;
BufferErrorReporter* error_reporter =
convertLongToErrorReporter(env, error_handle);
if (error_reporter == nullptr) return JNI_FALSE;
Interpreter* interpreter = convertLongToInterpreter(env, interpreter_handle);
if (interpreter == nullptr) return JNI_FALSE;
if (input_idx < 0 || input_idx >= interpreter->inputs().size()) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Input error: Can not resize %d-th input for a model having "
"%d inputs.",
input_idx, interpreter->inputs().size());
return JNI_FALSE;
}
const int tensor_idx = interpreter->inputs()[input_idx];
// check whether it is resizing with the same dimensions.
TfLiteTensor* target = interpreter->tensor(tensor_idx);
bool is_changed = AreDimsDifferent(env, target, dims);
if (is_changed) {
TfLiteStatus status;
if (strict) {
status = interpreter->ResizeInputTensorStrict(
tensor_idx, ConvertJIntArrayToVector(env, dims));
} else {
status = interpreter->ResizeInputTensor(
tensor_idx, ConvertJIntArrayToVector(env, dims));
}
if (status != kTfLiteOk) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Failed to resize %d-th input: %s",
input_idx, error_reporter->CachedErrorMessage());
return JNI_FALSE;
}
}
return is_changed ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jlong JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_createCancellationFlag(
JNIEnv* env, jclass clazz, jlong interpreter_handle) {
Interpreter* interpreter = convertLongToInterpreter(env, interpreter_handle);
if (interpreter == nullptr) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Invalid handle to interpreter.");
return 0;
}
std::atomic_bool* cancellation_flag = new std::atomic_bool(false);
#if TFLITE_DISABLE_SELECT_JAVA_APIS
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Not supported: cancellation");
#else
interpreter->SetCancellationFunction(cancellation_flag, [](void* payload) {
std::atomic_bool* cancellation_flag =
reinterpret_cast<std::atomic_bool*>(payload);
return cancellation_flag->load() == true;
});
#endif // TFLITE_DISABLE_SELECT_JAVA_APIS
return reinterpret_cast<jlong>(cancellation_flag);
}
JNIEXPORT void JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_deleteCancellationFlag(
JNIEnv* env, jclass clazz, jlong flag_handle) {
std::atomic_bool* cancellation_flag =
reinterpret_cast<std::atomic_bool*>(flag_handle);
delete cancellation_flag;
}
JNIEXPORT void JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapper_setCancelled(
JNIEnv* env, jclass clazz, jlong interpreter_handle, jlong flag_handle,
jboolean value) {
#if TFLITE_DISABLE_SELECT_JAVA_APIS
ThrowException(env, tflite::jni::kUnsupportedOperationException,
"Not supported: cancellation");
#else
std::atomic_bool* cancellation_flag =
reinterpret_cast<std::atomic_bool*>(flag_handle);
if (cancellation_flag != nullptr) {
cancellation_flag->store(static_cast<bool>(value));
}
#endif // TFLITE_DISABLE_SELECT_JAVA_APIS
}
JNIEXPORT void JNICALL Java_org_tensorflow_lite_NativeInterpreterWrapper_delete(
JNIEnv* env, jclass clazz, jlong error_handle, jlong model_handle,
jlong interpreter_handle) {
if (interpreter_handle != 0) {
delete convertLongToInterpreter(env, interpreter_handle);
}
if (model_handle != 0) {
delete convertLongToModel(env, model_handle);
}
if (error_handle != 0) {
delete convertLongToErrorReporter(env, error_handle);
}
}
} // extern "C"
@@ -0,0 +1,72 @@
/* Copyright 2017 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 <dlfcn.h>
#include <jni.h>
#include <stdio.h>
#include <time.h>
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/java/src/main/native/jni_utils.h"
using tflite::Interpreter;
using tflite::jni::BufferErrorReporter;
using tflite::jni::ThrowException;
namespace {
Interpreter* convertLongToInterpreter(JNIEnv* env, jlong handle) {
if (handle == 0) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Invalid handle to Interpreter.");
return nullptr;
}
return reinterpret_cast<Interpreter*>(handle);
}
BufferErrorReporter* convertLongToErrorReporter(JNIEnv* env, jlong handle) {
if (handle == 0) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Invalid handle to ErrorReporter.");
return nullptr;
}
return reinterpret_cast<BufferErrorReporter*>(handle);
}
} // namespace
extern "C" {
JNIEXPORT void JNICALL
Java_org_tensorflow_lite_NativeInterpreterWrapperExperimental_resetVariableTensors(
JNIEnv* env, jclass clazz, jlong interpreter_handle, jlong error_handle) {
if (!tflite::jni::CheckJniInitializedOrThrow(env)) return;
Interpreter* interpreter = convertLongToInterpreter(env, interpreter_handle);
if (interpreter == nullptr) return;
BufferErrorReporter* error_reporter =
convertLongToErrorReporter(env, error_handle);
if (error_reporter == nullptr) return;
TfLiteStatus status = interpreter->ResetVariableTensors();
if (status != kTfLiteOk) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Failed to reset variable tensors: %s",
error_reporter->CachedErrorMessage());
}
}
} // extern "C"
@@ -0,0 +1,275 @@
/* 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 <jni.h>
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/java/src/main/native/jni_utils.h"
#include "tensorflow/lite/signature_runner.h"
#ifdef TFLITE_DISABLE_SELECT_JAVA_APIS
#include <algorithm>
#include "tensorflow/lite/logger.h"
#include "tensorflow/lite/minimal_logging.h"
#endif
using tflite::Interpreter;
using tflite::jni::ThrowException;
namespace tflite {
// A helper class to access private information of SignatureRunner class.
class SignatureRunnerJNIHelper {
public:
explicit SignatureRunnerJNIHelper(SignatureRunner* runner)
: signature_runner_(runner) {}
#ifndef TFLITE_DISABLE_SELECT_JAVA_APIS
// Attempts to get the subgraph index associated with this SignatureRunner.
// Returns the subgraph index, if available, or -1 on error.
int GetSubgraphIndex() {
if (!signature_runner_) return -1;
return signature_runner_->signature_def_->subgraph_index;
}
#endif
public:
// Gets the index of the input specified by `input_name`.
int GetInputIndex(const char* input_name) {
#ifndef TFLITE_DISABLE_SELECT_JAVA_APIS
int input_tensor_index = GetInputTensorIndex(input_name);
if (input_tensor_index == -1) return -1;
int count = 0;
for (int tensor_idx : signature_runner_->subgraph_->inputs()) {
if (input_tensor_index == tensor_idx) return count;
++count;
}
return -1;
#else
const auto& inputs = signature_runner_->input_names();
const auto& it = std::find(inputs.begin(), inputs.end(), input_name);
if (it == inputs.end()) {
return -1;
}
return it - inputs.begin();
#endif
}
// Gets the index of the output specified by `output_name`.
int GetOutputIndex(const char* output_name) {
#ifndef TFLITE_DISABLE_SELECT_JAVA_APIS
int output_tensor_index = GetOutputTensorIndex(output_name);
if (output_tensor_index == -1) return -1;
int count = 0;
for (int tensor_idx : signature_runner_->subgraph_->outputs()) {
if (output_tensor_index == tensor_idx) return count;
++count;
}
return -1;
#else
const auto& outputs = signature_runner_->output_names();
const auto& it = std::find(outputs.begin(), outputs.end(), output_name);
if (it == outputs.end()) {
return -1;
}
return it - outputs.begin();
#endif
}
private:
#ifndef TFLITE_DISABLE_SELECT_JAVA_APIS
// Gets the tensor index of a given input.
int GetInputTensorIndex(const char* input_name) {
const auto& inputs = signature_runner_->signature_def_->inputs;
const auto& it = inputs.find(input_name);
if (it == inputs.end()) {
return -1;
}
return it->second;
}
// Gets the tensor index of a given output.
int GetOutputTensorIndex(const char* output_name) {
const auto& it =
signature_runner_->signature_def_->outputs.find(output_name);
if (it == signature_runner_->signature_def_->outputs.end()) {
return -1;
}
return it->second;
}
#endif // ndef TFLITE_DISABLE_SELECT_JAVA_APIS
SignatureRunner* signature_runner_;
};
} // namespace tflite
using tflite::Interpreter;
using tflite::SignatureRunner;
using tflite::SignatureRunnerJNIHelper;
using tflite::jni::BufferErrorReporter;
using tflite::jni::CastLongToPointer;
extern "C" {
JNIEXPORT jlong JNICALL
Java_org_tensorflow_lite_NativeSignatureRunnerWrapper_nativeGetSignatureRunner(
JNIEnv* env, jclass clazz, jlong handle, jstring signature_key) {
Interpreter* interpreter = CastLongToPointer<Interpreter>(env, handle);
if (interpreter == nullptr) return -1;
const char* signature_key_ptr =
env->GetStringUTFChars(signature_key, nullptr);
SignatureRunner* runner = interpreter->GetSignatureRunner(signature_key_ptr);
if (runner == nullptr) {
// Release the memory before returning.
env->ReleaseStringUTFChars(signature_key, signature_key_ptr);
return -1;
}
// Release the memory before returning.
env->ReleaseStringUTFChars(signature_key, signature_key_ptr);
return reinterpret_cast<jlong>(runner);
}
JNIEXPORT jint JNICALL
Java_org_tensorflow_lite_NativeSignatureRunnerWrapper_nativeGetSubgraphIndex(
JNIEnv* env, jclass clazz, jlong handle) {
#if TFLITE_DISABLE_SELECT_JAVA_APIS
TFLITE_LOG(tflite::TFLITE_LOG_WARNING,
"Not supported: nativeGetSubgraphIndex");
return -1;
#else
SignatureRunner* runner = CastLongToPointer<SignatureRunner>(env, handle);
if (runner == nullptr) return -1;
return SignatureRunnerJNIHelper(runner).GetSubgraphIndex();
#endif // TFLITE_DISABLE_SELECT_JAVA_APIS
}
JNIEXPORT jobjectArray JNICALL
Java_org_tensorflow_lite_NativeSignatureRunnerWrapper_nativeInputNames(
JNIEnv* env, jclass clazz, jlong handle) {
SignatureRunner* runner = CastLongToPointer<SignatureRunner>(env, handle);
if (runner == nullptr) return nullptr;
return tflite::jni::CreateStringArray(runner->input_names(), env);
}
JNIEXPORT jobjectArray JNICALL
Java_org_tensorflow_lite_NativeSignatureRunnerWrapper_nativeOutputNames(
JNIEnv* env, jclass clazz, jlong handle) {
SignatureRunner* runner = CastLongToPointer<SignatureRunner>(env, handle);
if (runner == nullptr) return nullptr;
return tflite::jni::CreateStringArray(runner->output_names(), env);
}
JNIEXPORT jint JNICALL
Java_org_tensorflow_lite_NativeSignatureRunnerWrapper_nativeGetInputIndex(
JNIEnv* env, jclass clazz, jlong handle, jstring input_name) {
SignatureRunner* runner = CastLongToPointer<SignatureRunner>(env, handle);
if (runner == nullptr) return -1;
const char* input_name_ptr = env->GetStringUTFChars(input_name, nullptr);
int index = SignatureRunnerJNIHelper(runner).GetInputIndex(input_name_ptr);
// Release the memory before returning.
env->ReleaseStringUTFChars(input_name, input_name_ptr);
return index;
}
JNIEXPORT jint JNICALL
Java_org_tensorflow_lite_NativeSignatureRunnerWrapper_nativeGetOutputIndex(
JNIEnv* env, jclass clazz, jlong handle, jstring output_name) {
SignatureRunner* runner = CastLongToPointer<SignatureRunner>(env, handle);
if (runner == nullptr) return -1;
const char* output_name_ptr = env->GetStringUTFChars(output_name, nullptr);
int index = SignatureRunnerJNIHelper(runner).GetOutputIndex(output_name_ptr);
// Release the memory before returning.
env->ReleaseStringUTFChars(output_name, output_name_ptr);
return index;
}
JNIEXPORT jboolean JNICALL
Java_org_tensorflow_lite_NativeSignatureRunnerWrapper_nativeResizeInput(
JNIEnv* env, jclass clazz, jlong handle, jlong error_handle,
jstring input_name, jintArray dims) {
SignatureRunner* runner = CastLongToPointer<SignatureRunner>(env, handle);
BufferErrorReporter* error_reporter =
CastLongToPointer<BufferErrorReporter>(env, error_handle);
if (runner == nullptr || error_reporter == nullptr) return JNI_FALSE;
// Check whether it is resizing with the same dimensions.
const char* input_name_ptr = env->GetStringUTFChars(input_name, nullptr);
TfLiteTensor* target = runner->input_tensor(input_name_ptr);
if (target == nullptr) {
// Release the memory before returning.
env->ReleaseStringUTFChars(input_name, input_name_ptr);
return JNI_FALSE;
}
bool is_changed = tflite::jni::AreDimsDifferent(env, target, dims);
if (is_changed) {
#if TFLITE_DISABLE_SELECT_JAVA_APIS
// In this case, only unknown dimensions (those with size = -1)
// can be resized.
TfLiteStatus status = runner->ResizeInputTensorStrict(
input_name_ptr, tflite::jni::ConvertJIntArrayToVector(env, dims));
#else
TfLiteStatus status = runner->ResizeInputTensor(
input_name_ptr, tflite::jni::ConvertJIntArrayToVector(env, dims));
#endif
if (status != kTfLiteOk) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Error: Failed to resize input %s: %s", input_name_ptr,
error_reporter->CachedErrorMessage());
// Release the memory before returning.
env->ReleaseStringUTFChars(input_name, input_name_ptr);
return JNI_FALSE;
}
}
// Release the memory before returning.
env->ReleaseStringUTFChars(input_name, input_name_ptr);
return is_changed ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT void JNICALL
Java_org_tensorflow_lite_NativeSignatureRunnerWrapper_nativeAllocateTensors(
JNIEnv* env, jclass clazz, jlong handle, jlong error_handle) {
SignatureRunner* runner = CastLongToPointer<SignatureRunner>(env, handle);
BufferErrorReporter* error_reporter =
CastLongToPointer<BufferErrorReporter>(env, error_handle);
if (runner == nullptr || error_reporter == nullptr) return;
if (runner->AllocateTensors() != kTfLiteOk) {
ThrowException(
env, tflite::jni::kIllegalStateException,
"Internal error: Unexpected failure when preparing tensor allocations:"
" %s",
error_reporter->CachedErrorMessage());
}
}
JNIEXPORT void JNICALL
Java_org_tensorflow_lite_NativeSignatureRunnerWrapper_nativeInvoke(
JNIEnv* env, jclass clazz, jlong handle, jlong error_handle) {
SignatureRunner* runner = CastLongToPointer<SignatureRunner>(env, handle);
BufferErrorReporter* error_reporter =
CastLongToPointer<BufferErrorReporter>(env, error_handle);
if (runner == nullptr || error_reporter == nullptr) return;
if (runner->Invoke() != kTfLiteOk) {
ThrowException(env, tflite::jni::kIllegalStateException,
"Internal error: Failed to run on the given Interpreter: %s",
error_reporter->CachedErrorMessage());
}
}
} // extern "C"
@@ -0,0 +1,178 @@
/* 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.
==============================================================================*/
#if !TFLITE_DISABLE_SELECT_JAVA_APIS
#include <dlfcn.h>
#ifdef __ANDROID__
#include <sys/system_properties.h>
#endif
#endif
#include <cstdlib>
#include <cstring>
#include <functional>
#include <memory>
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/core/api/op_resolver_internal.h"
#include "tensorflow/lite/model_builder.h"
#if TFLITE_DISABLE_SELECT_JAVA_APIS
#include "tensorflow/lite/acceleration/configuration/c/delegate_plugin.h"
#include "tensorflow/lite/acceleration/configuration/c/xnnpack_plugin.h"
#include "tensorflow/lite/acceleration/configuration/configuration_generated.h"
#else
#include "tensorflow/lite/delegates/xnnpack/xnnpack_delegate.h"
#endif
#include "tensorflow/lite/java/src/main/native/op_resolver_lazy_delegate_proxy.h"
namespace tflite {
namespace jni {
namespace {
#if !TFLITE_DISABLE_SELECT_JAVA_APIS
// Indicates if it is safe to call dlsym to check if a symbol is present
bool IsDlsymSafeForSymbolCheck() {
#ifdef __ANDROID__
// On Android 4.4 (API 19) and earlier it is not safe to call dlsym to check
// if a symbol if present, as dlsym will crash if the symbol isn't present.
// The underlying bug is already fixed in the platform long ago
// <https://android-review.googlesource.com/c/platform/bionic/+/69033>
// but as Android 4.4 systems still exist in the wild, we check API version
// as a work-around.
char sdk_version_str[PROP_VALUE_MAX + 1];
std::memset(sdk_version_str, 0, sizeof(sdk_version_str));
if (!__system_property_get("ro.build.version.sdk", sdk_version_str)) {
return false;
}
char* sdk_version_end = sdk_version_str;
const auto sdk_version = strtol(sdk_version_str, &sdk_version_end, 10);
return sdk_version_end != sdk_version_str && sdk_version > 19;
#else
return true;
#endif
}
#endif
} // namespace
const TfLiteRegistration* OpResolverLazyDelegateProxy::FindOp(
tflite::BuiltinOperator op, int version) const {
return op_resolver_->FindOp(op, version);
}
const TfLiteRegistration* OpResolverLazyDelegateProxy::FindOp(
const char* op, int version) const {
return op_resolver_->FindOp(op, version);
}
OpResolver::TfLiteDelegateCreators
OpResolverLazyDelegateProxy::GetDelegateCreators() const {
// Early exit if not using the XNNPack delegate by default
if (!use_xnnpack_) {
return OpResolver::TfLiteDelegateCreators();
}
return OpResolver::TfLiteDelegateCreators{
{&OpResolverLazyDelegateProxy::createXNNPackDelegate}};
}
OpResolver::TfLiteOpaqueDelegateCreators
OpResolverLazyDelegateProxy::GetOpaqueDelegateCreators() const {
// Early exit if not using the XNNPack delegate by default
if (!use_xnnpack_) {
return OpResolver::TfLiteOpaqueDelegateCreators();
}
return OpResolver::TfLiteOpaqueDelegateCreators{
{&OpResolverLazyDelegateProxy::createXNNPackOpaqueDelegate}};
}
bool OpResolverLazyDelegateProxy::MayContainUserDefinedOps() const {
return OpResolverInternal::MayContainUserDefinedOps(*op_resolver_);
}
std::unique_ptr<TfLiteDelegate, void (*)(TfLiteDelegate*)>
OpResolverLazyDelegateProxy::createXNNPackDelegate(TfLiteContext* context) {
TfLiteDelegate* delegate = nullptr;
void (*delegate_deleter)(TfLiteDelegate*) = nullptr;
#if !TFLITE_DISABLE_SELECT_JAVA_APIS
// We use dynamic loading to avoid taking a hard dependency on XNNPack.
// This allows clients that use trimmed builds to save on binary size.
if (IsDlsymSafeForSymbolCheck()) {
auto xnnpack_options_default =
reinterpret_cast<decltype(TfLiteXNNPackDelegateOptionsDefault)*>(
dlsym(RTLD_DEFAULT, "TfLiteXNNPackDelegateOptionsDefault"));
auto xnnpack_create =
reinterpret_cast<decltype(TfLiteXNNPackDelegateCreate)*>(
dlsym(RTLD_DEFAULT, "TfLiteXNNPackDelegateCreate"));
auto xnnpack_delete =
reinterpret_cast<decltype(TfLiteXNNPackDelegateDelete)*>(
dlsym(RTLD_DEFAULT, "TfLiteXNNPackDelegateDelete"));
if (xnnpack_options_default && xnnpack_create && xnnpack_delete) {
TfLiteXNNPackDelegateOptions options = xnnpack_options_default();
delegate = xnnpack_create(&options);
delegate_deleter = xnnpack_delete;
}
}
#endif // !TFLITE_DISABLE_SELECT_JAVA_APIS
return std::unique_ptr<TfLiteDelegate, void (*)(TfLiteDelegate*)>(
delegate, delegate_deleter);
}
OpResolver::TfLiteOpaqueDelegatePtr
OpResolverLazyDelegateProxy::createXNNPackOpaqueDelegate(int num_threads) {
TfLiteOpaqueDelegate* delegate = nullptr;
void (*delegate_deleter)(TfLiteOpaqueDelegate*) = nullptr;
#if TFLITE_DISABLE_SELECT_JAVA_APIS
// Construct a FlatBuffer containing
// TFLiteSettings {
// delegate: Delegate.XNNPack
// XNNPackSettings {
// num_threads: <num_threads>
// }
// }
flatbuffers::FlatBufferBuilder flatbuffer_builder;
tflite::XNNPackSettingsBuilder xnnpack_settings_builder(flatbuffer_builder);
if (num_threads >= 0) {
xnnpack_settings_builder.add_num_threads(num_threads);
}
flatbuffers::Offset<tflite::XNNPackSettings> xnnpack_settings =
xnnpack_settings_builder.Finish();
tflite::TFLiteSettingsBuilder tflite_settings_builder(flatbuffer_builder);
tflite_settings_builder.add_xnnpack_settings(xnnpack_settings);
tflite_settings_builder.add_delegate(tflite::Delegate_XNNPACK);
flatbuffers::Offset<tflite::TFLiteSettings> tflite_settings =
tflite_settings_builder.Finish();
flatbuffer_builder.Finish(tflite_settings);
const tflite::TFLiteSettings* tflite_settings_flatbuffer =
flatbuffers::GetRoot<tflite::TFLiteSettings>(
flatbuffer_builder.GetBufferPointer());
// Create an XNNPack delegate plugin using the settings from the flatbuffer.
const TfLiteOpaqueDelegatePlugin* delegate_plugin =
TfLiteXnnpackDelegatePluginCApi();
delegate = delegate_plugin->create(tflite_settings_flatbuffer);
delegate_deleter = delegate_plugin->destroy;
#endif
return OpResolver::TfLiteOpaqueDelegatePtr(delegate, delegate_deleter);
}
} // namespace jni
} // namespace tflite
@@ -0,0 +1,57 @@
/* 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_JAVA_SRC_MAIN_NATIVE_OP_RESOLVER_LAZY_DELEGATE_PROXY_H_
#define TENSORFLOW_LITE_JAVA_SRC_MAIN_NATIVE_OP_RESOLVER_LAZY_DELEGATE_PROXY_H_
#include <memory>
#include <utility>
#include "tensorflow/lite/op_resolver.h"
namespace tflite {
namespace jni {
class OpResolverLazyDelegateProxy : public OpResolver {
public:
OpResolverLazyDelegateProxy(std::unique_ptr<tflite::OpResolver>&& op_resolver,
bool use_xnnpack)
: op_resolver_(std::move(op_resolver)), use_xnnpack_(use_xnnpack) {}
const TfLiteRegistration* FindOp(tflite::BuiltinOperator op,
int version) const override;
const TfLiteRegistration* FindOp(const char* op, int version) const override;
OpResolver::TfLiteDelegateCreators GetDelegateCreators() const override;
OpResolver::TfLiteOpaqueDelegateCreators GetOpaqueDelegateCreators()
const override;
private:
bool MayContainUserDefinedOps() const override;
static std::unique_ptr<TfLiteDelegate, void (*)(TfLiteDelegate*)>
createXNNPackDelegate(TfLiteContext* context);
static OpResolver::TfLiteOpaqueDelegatePtr createXNNPackOpaqueDelegate(
int num_threads);
std::unique_ptr<tflite::OpResolver> op_resolver_;
bool use_xnnpack_ = false;
};
} // namespace jni
} // namespace tflite
#endif // TENSORFLOW_LITE_JAVA_SRC_MAIN_NATIVE_OP_RESOLVER_LAZY_DELEGATE_PROXY_H_
@@ -0,0 +1,714 @@
/* Copyright 2017 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 <jni.h>
#include <cstring>
#include <memory>
#include <string>
#include "tensorflow/lite/c/common.h"
#include "tensorflow/lite/interpreter.h"
#include "tensorflow/lite/java/src/main/native/jni_utils.h"
#include "tensorflow/lite/signature_runner.h"
#include "tensorflow/lite/string_util.h"
using tflite::Interpreter;
using tflite::jni::ThrowException;
namespace tflite {
// Convenience handle for obtaining a TfLiteTensor given an interpreter and
// tensor index.
//
// Historically, the Java Tensor class used a TfLiteTensor pointer as its native
// handle. However, this approach isn't generally safe, as the interpreter may
// invalidate all TfLiteTensor* handles during inference or allocation.
class TensorHandleImpl {
public:
virtual ~TensorHandleImpl() = default;
virtual TfLiteTensor* tensor() const = 0;
virtual int index() const { return -1; }
};
class InterpreterTensorHandle : public TensorHandleImpl {
public:
InterpreterTensorHandle(Interpreter* interpreter, int tensor_index)
: interpreter_(interpreter), tensor_index_(tensor_index) {}
TfLiteTensor* tensor() const override {
return interpreter_->tensor(tensor_index_);
}
int index() const override { return tensor_index_; }
private:
Interpreter* const interpreter_;
const int tensor_index_;
};
class SignatureRunnerTensorHandle : public TensorHandleImpl {
public:
SignatureRunnerTensorHandle(SignatureRunner* runner, const char* name,
bool is_input)
: signature_runner_(runner), name_(name), is_input_(is_input) {}
TfLiteTensor* tensor() const override {
if (is_input_) {
return signature_runner_->input_tensor(name_.c_str());
}
return const_cast<TfLiteTensor*>(
signature_runner_->output_tensor(name_.c_str()));
}
private:
SignatureRunner* signature_runner_;
std::string name_;
bool is_input_;
};
class TensorHandle {
public:
TensorHandle(Interpreter* interpreter, int tensor_index) {
impl_ =
std::make_unique<InterpreterTensorHandle>(interpreter, tensor_index);
}
TensorHandle(SignatureRunner* runner, const char* name, bool is_input) {
impl_ =
std::make_unique<SignatureRunnerTensorHandle>(runner, name, is_input);
}
TfLiteTensor* tensor() const { return impl_->tensor(); }
int index() const { return impl_->index(); }
private:
std::unique_ptr<TensorHandleImpl> impl_;
};
} // namespace tflite
namespace {
using tflite::TensorHandle;
static const char* kByteArrayClassPath = "[B";
static const char* kStringClassPath = "java/lang/String";
TfLiteTensor* GetTensorFromHandle(JNIEnv* env, jlong handle) {
if (handle == 0) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Invalid handle to TfLiteTensor.");
return nullptr;
}
return reinterpret_cast<TensorHandle*>(handle)->tensor();
}
int GetTensorIndexFromHandle(JNIEnv* env, jlong handle) {
if (handle == 0) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Invalid handle to TfLiteTensor.");
return -1;
}
return reinterpret_cast<TensorHandle*>(handle)->index();
}
size_t ElementByteSize(TfLiteType data_type) {
// The code in this file makes the assumption that the
// TensorFlow TF_DataTypes and the Java primitive types
// have the same byte sizes. Validate that:
switch (data_type) {
case kTfLiteFloat32:
static_assert(sizeof(jfloat) == 4,
"Interal error: Java float not compatible with "
"kTfLiteFloat");
return 4;
case kTfLiteInt32:
static_assert(sizeof(jint) == 4,
"Interal error: Java int not compatible with kTfLiteInt");
return 4;
case kTfLiteInt16:
static_assert(sizeof(jshort) == 2,
"Interal error: Java int not compatible with kTfLiteShort");
return 2;
case kTfLiteUInt8:
case kTfLiteInt8:
static_assert(sizeof(jbyte) == 1,
"Interal error: Java byte not compatible with "
"kTfLiteUInt8");
return 1;
case kTfLiteBool:
static_assert(sizeof(jboolean) == 1,
"Interal error: Java boolean not compatible with "
"kTfLiteBool");
return 1;
case kTfLiteInt64:
static_assert(sizeof(jlong) == 8,
"Interal error: Java long not compatible with "
"kTfLiteInt64");
return 8;
default:
return 0;
}
}
size_t WriteOneDimensionalArray(JNIEnv* env, jobject object, TfLiteType type,
void* dst, size_t dst_size) {
jarray array = static_cast<jarray>(object);
const int num_elements = env->GetArrayLength(array);
size_t to_copy = num_elements * ElementByteSize(type);
if (to_copy > dst_size) {
ThrowException(env, tflite::jni::kIllegalStateException,
"Internal error: cannot write Java array of %d bytes to "
"Tensor of %d bytes",
to_copy, dst_size);
return 0;
}
switch (type) {
case kTfLiteFloat32: {
jfloatArray float_array = static_cast<jfloatArray>(array);
jfloat* float_dst = static_cast<jfloat*>(dst);
env->GetFloatArrayRegion(float_array, 0, num_elements, float_dst);
return to_copy;
}
case kTfLiteInt32: {
jintArray int_array = static_cast<jintArray>(array);
jint* int_dst = static_cast<jint*>(dst);
env->GetIntArrayRegion(int_array, 0, num_elements, int_dst);
return to_copy;
}
case kTfLiteInt16: {
jshortArray short_array = static_cast<jshortArray>(array);
jshort* short_dst = static_cast<jshort*>(dst);
env->GetShortArrayRegion(short_array, 0, num_elements, short_dst);
return to_copy;
}
case kTfLiteInt64: {
jlongArray long_array = static_cast<jlongArray>(array);
jlong* long_dst = static_cast<jlong*>(dst);
env->GetLongArrayRegion(long_array, 0, num_elements, long_dst);
return to_copy;
}
case kTfLiteInt8:
case kTfLiteUInt8: {
jbyteArray byte_array = static_cast<jbyteArray>(array);
jbyte* byte_dst = static_cast<jbyte*>(dst);
env->GetByteArrayRegion(byte_array, 0, num_elements, byte_dst);
return to_copy;
}
case kTfLiteBool: {
jbooleanArray bool_array = static_cast<jbooleanArray>(array);
jboolean* bool_dst = static_cast<jboolean*>(dst);
env->GetBooleanArrayRegion(bool_array, 0, num_elements, bool_dst);
return to_copy;
}
default: {
ThrowException(
env, tflite::jni::kUnsupportedOperationException,
"DataType error: TensorFlowLite currently supports float "
"(32 bits), int (32 bits), byte (8 bits), bool (8 bits), and long "
"(64 bits), support for other types (DataType %d in this "
"case) will be added in the future",
kTfLiteFloat32, type);
return 0;
}
}
}
size_t ReadOneDimensionalArray(JNIEnv* env, TfLiteType data_type,
const void* src, size_t src_size, jarray dst) {
const int len = env->GetArrayLength(dst);
const size_t size = len * ElementByteSize(data_type);
if (size > src_size) {
ThrowException(
env, tflite::jni::kIllegalStateException,
"Internal error: cannot fill a Java array of %d bytes with a Tensor of "
"%d bytes",
size, src_size);
return 0;
}
switch (data_type) {
case kTfLiteFloat32: {
jfloatArray float_array = static_cast<jfloatArray>(dst);
env->SetFloatArrayRegion(float_array, 0, len,
static_cast<const jfloat*>(src));
return size;
}
case kTfLiteInt32: {
jintArray int_array = static_cast<jintArray>(dst);
env->SetIntArrayRegion(int_array, 0, len, static_cast<const jint*>(src));
return size;
}
case kTfLiteInt16: {
jshortArray short_array = static_cast<jshortArray>(dst);
env->SetShortArrayRegion(short_array, 0, len,
static_cast<const jshort*>(src));
return size;
}
case kTfLiteInt64: {
jlongArray long_array = static_cast<jlongArray>(dst);
env->SetLongArrayRegion(long_array, 0, len,
static_cast<const jlong*>(src));
return size;
}
case kTfLiteInt8:
case kTfLiteUInt8: {
jbyteArray byte_array = static_cast<jbyteArray>(dst);
env->SetByteArrayRegion(byte_array, 0, len,
static_cast<const jbyte*>(src));
return size;
}
case kTfLiteBool: {
jbooleanArray bool_array = static_cast<jbooleanArray>(dst);
env->SetBooleanArrayRegion(bool_array, 0, len,
static_cast<const jboolean*>(src));
return size;
}
default: {
ThrowException(env, tflite::jni::kIllegalStateException,
"DataType error: invalid DataType(%d)", data_type);
}
}
return 0;
}
size_t ReadMultiDimensionalArray(JNIEnv* env, TfLiteType data_type, char* src,
size_t src_size, int dims_left, jarray dst) {
if (dims_left == 1) {
return ReadOneDimensionalArray(env, data_type, src, src_size, dst);
} else {
jobjectArray ndarray = static_cast<jobjectArray>(dst);
int len = env->GetArrayLength(ndarray);
size_t size = 0;
for (int i = 0; i < len; ++i) {
jarray row = static_cast<jarray>(env->GetObjectArrayElement(ndarray, i));
size += ReadMultiDimensionalArray(env, data_type, src + size,
src_size - size, dims_left - 1, row);
env->DeleteLocalRef(row);
if (env->ExceptionCheck()) return size;
}
return size;
}
}
// Returns the total number of strings read.
int ReadMultiDimensionalStringArray(JNIEnv* env, TfLiteTensor* tensor,
int dims_left, int start_str_index,
jarray dst) {
jobjectArray object_array = static_cast<jobjectArray>(dst);
int len = env->GetArrayLength(object_array);
int num_strings_read = 0;
// If dst is a 1-dimensional array, copy the strings into it. Else
// recursively call ReadMultiDimensionalStringArray over sub-dimensions.
if (dims_left == 1) {
for (int i = 0; i < len; ++i) {
const tflite::StringRef strref =
tflite::GetString(tensor, start_str_index + num_strings_read);
// Makes sure the string is null terminated before passing to
// NewStringUTF.
std::string str(strref.str, strref.len);
jstring string_dest = env->NewStringUTF(str.data());
env->SetObjectArrayElement(object_array, i, string_dest);
env->DeleteLocalRef(string_dest);
++num_strings_read;
}
} else {
for (int i = 0; i < len; ++i) {
jarray row =
static_cast<jarray>(env->GetObjectArrayElement(object_array, i));
num_strings_read += ReadMultiDimensionalStringArray(
env, tensor, dims_left - 1, start_str_index + num_strings_read, row);
env->DeleteLocalRef(row);
if (env->ExceptionCheck()) return num_strings_read;
}
}
return num_strings_read;
}
size_t WriteMultiDimensionalArray(JNIEnv* env, jobject src, TfLiteType type,
int dims_left, char** dst, int dst_size) {
if (dims_left <= 1) {
return WriteOneDimensionalArray(env, src, type, *dst, dst_size);
} else {
jobjectArray ndarray = static_cast<jobjectArray>(src);
int len = env->GetArrayLength(ndarray);
size_t sz = 0;
for (int i = 0; i < len; ++i) {
jobject row = env->GetObjectArrayElement(ndarray, i);
char* next_dst = *dst + sz;
sz += WriteMultiDimensionalArray(env, row, type, dims_left - 1, &next_dst,
dst_size - sz);
env->DeleteLocalRef(row);
if (env->ExceptionCheck()) return sz;
}
return sz;
}
}
void AddStringDynamicBuffer(JNIEnv* env, jobject src,
tflite::DynamicBuffer* dst_buffer) {
if (env->IsInstanceOf(src, env->FindClass(kStringClassPath))) {
jstring str = static_cast<jstring>(src);
const char* chars = env->GetStringUTFChars(str, nullptr);
// + 1 for terminating character.
const int byte_len = env->GetStringUTFLength(str) + 1;
dst_buffer->AddString(chars, byte_len);
env->ReleaseStringUTFChars(str, chars);
}
if (env->IsInstanceOf(src, env->FindClass(kByteArrayClassPath))) {
jbyteArray byte_array = static_cast<jbyteArray>(src);
jsize byte_array_length = env->GetArrayLength(byte_array);
jbyte* bytes = env->GetByteArrayElements(byte_array, nullptr);
dst_buffer->AddString(reinterpret_cast<const char*>(bytes),
byte_array_length);
env->ReleaseByteArrayElements(byte_array, bytes, JNI_ABORT);
}
}
void PopulateStringDynamicBuffer(JNIEnv* env, jobject src,
tflite::DynamicBuffer* dst_buffer,
int dims_left) {
jobjectArray object_array = static_cast<jobjectArray>(src);
const int num_elements = env->GetArrayLength(object_array);
// If src is a 1-dimensional array, add the strings into dst_buffer. Else
// recursively call populateStringDynamicBuffer over sub-dimensions.
if (dims_left <= 1) {
for (int i = 0; i < num_elements; ++i) {
jobject obj = env->GetObjectArrayElement(object_array, i);
AddStringDynamicBuffer(env, obj, dst_buffer);
env->DeleteLocalRef(obj);
}
} else {
for (int i = 0; i < num_elements; ++i) {
jobject row = env->GetObjectArrayElement(object_array, i);
PopulateStringDynamicBuffer(env, row, dst_buffer, dims_left - 1);
env->DeleteLocalRef(row);
if (env->ExceptionCheck()) return;
}
}
}
void WriteMultiDimensionalStringArray(JNIEnv* env, jobject src,
TfLiteTensor* tensor) {
tflite::DynamicBuffer dst_buffer;
PopulateStringDynamicBuffer(env, src, &dst_buffer, tensor->dims->size);
if (!env->ExceptionCheck()) {
dst_buffer.WriteToTensor(tensor, /*new_shape=*/nullptr);
}
}
void WriteScalar(JNIEnv* env, jobject src, TfLiteType type, void* dst,
int dst_size) {
size_t src_size = ElementByteSize(type);
if (src_size != dst_size) {
ThrowException(
env, tflite::jni::kIllegalStateException,
"Scalar (%d bytes) not compatible with allocated tensor (%d bytes)",
src_size, dst_size);
return;
}
switch (type) {
// env->FindClass and env->GetMethodID are expensive and JNI best practices
// suggest that they should be cached. However, until the creation of scalar
// valued tensors seems to become a noticeable fraction of program execution,
// ignore that cost.
#define CASE(type, jtype, method_name, method_signature, call_type) \
case type: { \
jclass clazz = env->FindClass("java/lang/Number"); \
jmethodID method = env->GetMethodID(clazz, method_name, method_signature); \
jtype v = env->Call##call_type##Method(src, method); \
memcpy(dst, &v, src_size); \
return; \
}
CASE(kTfLiteFloat32, jfloat, "floatValue", "()F", Float);
CASE(kTfLiteInt32, jint, "intValue", "()I", Int);
CASE(kTfLiteInt16, jshort, "shortValue", "()S", Short);
CASE(kTfLiteInt64, jlong, "longValue", "()J", Long);
CASE(kTfLiteInt8, jbyte, "byteValue", "()B", Byte);
CASE(kTfLiteUInt8, jbyte, "byteValue", "()B", Byte);
#undef CASE
case kTfLiteBool: {
jclass clazz = env->FindClass("java/lang/Boolean");
jmethodID method = env->GetMethodID(clazz, "booleanValue", "()Z");
jboolean v = env->CallBooleanMethod(src, method);
*(static_cast<unsigned char*>(dst)) = v ? 1 : 0;
return;
}
default:
ThrowException(env, tflite::jni::kIllegalStateException,
"Invalid DataType(%d)", type);
return;
}
}
void WriteScalarString(JNIEnv* env, jobject src, TfLiteTensor* tensor) {
tflite::DynamicBuffer dst_buffer;
AddStringDynamicBuffer(env, src, &dst_buffer);
if (!env->ExceptionCheck()) {
dst_buffer.WriteToTensor(tensor, /*new_shape=*/nullptr);
}
}
} // namespace
extern "C" {
JNIEXPORT jlong JNICALL Java_org_tensorflow_lite_TensorImpl_create(
JNIEnv* env, jclass clazz, jlong interpreter_handle, jint tensor_index) {
Interpreter* interpreter = reinterpret_cast<Interpreter*>(interpreter_handle);
return reinterpret_cast<jlong>(new TensorHandle(interpreter, tensor_index));
}
JNIEXPORT jlong JNICALL
Java_org_tensorflow_lite_TensorImpl_createSignatureInputTensor(
JNIEnv* env, jclass clazz, jlong signature_runner_handle,
jstring input_name) {
tflite::SignatureRunner* runner =
reinterpret_cast<tflite::SignatureRunner*>(signature_runner_handle);
if (runner == nullptr) return -1;
const char* input_name_ptr = env->GetStringUTFChars(input_name, nullptr);
TensorHandle* handle =
new TensorHandle(runner, input_name_ptr, /*is_input=*/true);
// Release the memory before returning.
env->ReleaseStringUTFChars(input_name, input_name_ptr);
if (handle->tensor() == nullptr) {
delete handle;
return -1;
}
return reinterpret_cast<jlong>(handle);
}
JNIEXPORT jlong JNICALL
Java_org_tensorflow_lite_TensorImpl_createSignatureOutputTensor(
JNIEnv* env, jclass clazz, jlong signature_runner_handle,
jstring output_name) {
tflite::SignatureRunner* runner =
reinterpret_cast<tflite::SignatureRunner*>(signature_runner_handle);
if (runner == nullptr) return -1;
const char* output_name_ptr = env->GetStringUTFChars(output_name, nullptr);
TensorHandle* handle =
new TensorHandle(runner, output_name_ptr, /*is_input=*/false);
// Release the memory before returning.
env->ReleaseStringUTFChars(output_name, output_name_ptr);
if (handle->tensor() == nullptr) {
delete handle;
return -1;
}
return reinterpret_cast<jlong>(handle);
}
JNIEXPORT void JNICALL Java_org_tensorflow_lite_TensorImpl_delete(
JNIEnv* env, jclass clazz, jlong handle) {
delete reinterpret_cast<TensorHandle*>(handle);
}
JNIEXPORT jobject JNICALL Java_org_tensorflow_lite_TensorImpl_buffer(
JNIEnv* env, jclass clazz, jlong handle) {
TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
if (tensor == nullptr) return nullptr;
if (tensor->data.raw == nullptr) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Tensor hasn't been allocated.");
return nullptr;
}
return env->NewDirectByteBuffer(static_cast<void*>(tensor->data.raw),
static_cast<jlong>(tensor->bytes));
}
JNIEXPORT void JNICALL Java_org_tensorflow_lite_TensorImpl_writeDirectBuffer(
JNIEnv* env, jclass clazz, jlong handle, jobject src) {
TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
if (tensor == nullptr) return;
void* src_data_raw = env->GetDirectBufferAddress(src);
if (!src_data_raw) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Input ByteBuffer is not a direct buffer");
return;
}
if (!tensor->data.data) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Tensor hasn't been allocated.");
return;
}
// Historically, we would simply overwrite the tensor buffer pointer with
// the direct Buffer address. However, that is generally unsafe, and
// specifically wrong if the graph happens to have dynamic shapes where
// arena-allocated input buffers will be refreshed during invocation.
// TODO(b/156094015): Explore whether this is actually faster than
// using ByteBuffer.put(ByteBuffer).
memcpy(tensor->data.data, src_data_raw, tensor->bytes);
}
JNIEXPORT void JNICALL
Java_org_tensorflow_lite_TensorImpl_readMultiDimensionalArray(JNIEnv* env,
jclass clazz,
jlong handle,
jobject value) {
TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
if (tensor == nullptr) return;
int num_dims = tensor->dims->size;
if (num_dims == 0) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Cannot copy empty/scalar Tensors.");
return;
}
if (tensor->type == kTfLiteString) {
ReadMultiDimensionalStringArray(env, tensor, num_dims, 0,
static_cast<jarray>(value));
} else {
ReadMultiDimensionalArray(env, tensor->type, tensor->data.raw,
tensor->bytes, num_dims,
static_cast<jarray>(value));
}
}
JNIEXPORT void JNICALL
Java_org_tensorflow_lite_TensorImpl_writeMultiDimensionalArray(JNIEnv* env,
jclass clazz,
jlong handle,
jobject src) {
TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
if (tensor == nullptr) return;
if (tensor->type != kTfLiteString && tensor->data.raw == nullptr) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Target Tensor hasn't been allocated.");
return;
}
if (tensor->dims->size == 0) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Cannot copy empty/scalar Tensors.");
return;
}
if (tensor->type == kTfLiteString) {
WriteMultiDimensionalStringArray(env, src, tensor);
} else {
WriteMultiDimensionalArray(env, src, tensor->type, tensor->dims->size,
&tensor->data.raw, tensor->bytes);
}
}
JNIEXPORT void JNICALL Java_org_tensorflow_lite_TensorImpl_writeScalar(
JNIEnv* env, jclass clazz, jlong handle, jobject src) {
TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
if (tensor == nullptr) return;
if ((tensor->type != kTfLiteString) && (tensor->data.raw == nullptr)) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Target Tensor hasn't been allocated.");
return;
}
if ((tensor->dims->size != 0) && (tensor->dims->data[0] != 1)) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Internal error: Cannot write Java scalar to non-scalar "
"Tensor.");
return;
}
if (tensor->type == kTfLiteString) {
WriteScalarString(env, src, tensor);
} else {
WriteScalar(env, src, tensor->type, tensor->data.data, tensor->bytes);
}
}
JNIEXPORT jint JNICALL Java_org_tensorflow_lite_TensorImpl_dtype(JNIEnv* env,
jclass clazz,
jlong handle) {
TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
if (tensor == nullptr) return 0;
return static_cast<jint>(tensor->type);
}
JNIEXPORT jstring JNICALL Java_org_tensorflow_lite_TensorImpl_name(
JNIEnv* env, jclass clazz, jlong handle) {
TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
if (tensor == nullptr) {
ThrowException(env, tflite::jni::kIllegalArgumentException,
"Target Tensor doesn't exist.");
return nullptr;
}
if (tensor->name == nullptr) {
return env->NewStringUTF("");
}
jstring tensor_name = env->NewStringUTF(tensor->name);
if (tensor_name == nullptr) {
return env->NewStringUTF("");
}
return tensor_name;
}
JNIEXPORT jintArray JNICALL Java_org_tensorflow_lite_TensorImpl_shape(
JNIEnv* env, jclass clazz, jlong handle) {
TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
if (tensor == nullptr) return nullptr;
int num_dims = tensor->dims->size;
jintArray result = env->NewIntArray(num_dims);
env->SetIntArrayRegion(result, 0, num_dims, tensor->dims->data);
return result;
}
JNIEXPORT jintArray JNICALL Java_org_tensorflow_lite_TensorImpl_shapeSignature(
JNIEnv* env, jclass clazz, jlong handle) {
TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
if (tensor == nullptr) return nullptr;
const TfLiteIntArray* dims_signature = TfLiteTensorGetDimsSignature(tensor);
jintArray result = env->NewIntArray(dims_signature->size);
env->SetIntArrayRegion(result, 0, dims_signature->size, dims_signature->data);
return result;
}
JNIEXPORT jint JNICALL Java_org_tensorflow_lite_TensorImpl_numBytes(
JNIEnv* env, jclass clazz, jlong handle) {
const TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
if (tensor == nullptr) return 0;
return static_cast<jint>(tensor->bytes);
}
JNIEXPORT jboolean JNICALL
Java_org_tensorflow_lite_TensorImpl_hasDelegateBufferHandle(JNIEnv* env,
jclass clazz,
jlong handle) {
const TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
if (tensor == nullptr) return false;
return tensor->delegate && (tensor->buffer_handle != kTfLiteNullBufferHandle)
? JNI_TRUE
: JNI_FALSE;
}
JNIEXPORT jint JNICALL Java_org_tensorflow_lite_TensorImpl_index(JNIEnv* env,
jclass clazz,
jlong handle) {
return GetTensorIndexFromHandle(env, handle);
}
JNIEXPORT jfloat JNICALL Java_org_tensorflow_lite_TensorImpl_quantizationScale(
JNIEnv* env, jclass clazz, jlong handle) {
const TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
return static_cast<jfloat>(tensor ? tensor->params.scale : 0.f);
}
JNIEXPORT jint JNICALL
Java_org_tensorflow_lite_TensorImpl_quantizationZeroPoint(JNIEnv* env,
jclass clazz,
jlong handle) {
const TfLiteTensor* tensor = GetTensorFromHandle(env, handle);
return static_cast<jint>(tensor ? tensor->params.zero_point : 0);
}
} // extern "C"
@@ -0,0 +1,32 @@
/* Copyright 2017 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 <jni.h>
#include <stdio.h>
#include "tensorflow/lite/java/src/main/native/jni_utils.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
JNIEXPORT JNICALL void Java_org_tensorflow_lite_TensorFlowLite_nativeDoNothing(
JNIEnv* env, jclass /*clazz*/) {
// Do nothing.
}
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
@@ -0,0 +1,11 @@
VERS_1.0 {
# Export JNI symbols.
global:
Java_*;
JNI_OnLoad*;
JNI_OnUnload*;
# Hide everything else.
local:
*;
};
@@ -0,0 +1,50 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link org.tensorflow.lite.DataType}. */
@RunWith(JUnit4.class)
public final class DataTypeTest {
@Test
public void testElemByteSize() {
assertThat(DataType.FLOAT32.byteSize()).isEqualTo(4);
assertThat(DataType.INT32.byteSize()).isEqualTo(4);
assertThat(DataType.UINT8.byteSize()).isEqualTo(1);
assertThat(DataType.INT64.byteSize()).isEqualTo(8);
assertThat(DataType.STRING.byteSize()).isEqualTo(-1);
}
@Test
public void testConversion() {
for (DataType dataType : DataType.values()) {
assertThat(DataTypeUtils.fromC(dataType.c())).isEqualTo(dataType);
}
}
@Test
public void testINT8AndUINT8() {
assertThat(DataTypeUtils.toStringName(DataType.INT8)).isEqualTo("byte");
assertThat(DataTypeUtils.toStringName(DataType.UINT8)).isEqualTo("byte");
assertThat(DataTypeUtils.toStringName(DataType.INT8))
.isEqualTo(DataTypeUtils.toStringName(DataType.UINT8));
}
}
@@ -0,0 +1,116 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import java.nio.ByteBuffer;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.tensorflow.lite.InterpreterApi.Options.TfLiteRuntime;
/**
* Tests of InterpreterApi that are NOT linked against any TF Lite runtime. These tests check that
* we throw exceptions in those cases and that the errors have appropriate error messages.
*/
@RunWith(JUnit4.class)
public final class InterpreterApiNoRuntimeTest {
private static final String MODEL_PATH = "tensorflow/lite/java/src/testdata/add.bin";
private static final ByteBuffer MODEL_BUFFER = TestUtils.getTestFileAsBuffer(MODEL_PATH);
@Before
public void setUp() {
TestInit.init();
}
@Test
public void testInterpreterWithNullOptions() throws Exception {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, null)) {
fail();
} catch (IllegalStateException e) {
// Verify that the error message has some hints about how to link
// against the runtime ("com.google.ai.edge.litert:litert:<version>").
assertThat(e).hasMessageThat().contains("com.google.ai.edge.litert");
assertThat(e).hasMessageThat().contains("litert");
}
}
@Test
public void testInterpreterWithOptions() throws Exception {
InterpreterApi.Options options = new InterpreterApi.Options();
try (InterpreterApi interpreter =
InterpreterApi.create(MODEL_BUFFER, options.setNumThreads(3).setUseNNAPI(true))) {
fail();
} catch (IllegalStateException e) {
// Verify that the error message has some hints about how to link
// against the runtime ("com.google.ai.edge.litert:litert:<version>").
assertThat(e).hasMessageThat().contains("com.google.ai.edge.litert");
assertThat(e).hasMessageThat().contains("litert");
}
}
@Test
public void testRuntimeFromApplicationOnly() throws Exception {
InterpreterApi.Options options =
new InterpreterApi.Options().setRuntime(TfLiteRuntime.FROM_APPLICATION_ONLY);
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, options)) {
fail();
} catch (IllegalStateException e) {
// Verify that the error message has some hints about how to link
// against the runtime ("com.google.ai.edge.litert:litert:<version>").
assertThat(e).hasMessageThat().contains("com.google.ai.edge.litert");
assertThat(e).hasMessageThat().contains("litert");
}
}
@Test
public void testRuntimeFromSystemOnly() throws Exception {
InterpreterApi.Options options =
new InterpreterApi.Options().setRuntime(TfLiteRuntime.FROM_SYSTEM_ONLY);
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, options)) {
fail();
} catch (IllegalStateException e) {
// This can occur when this code is not linked against the right TF Lite runtime client.
// Verify that the error message has some hints about how to link in the
// client library for TF Lite in Google Play Services
// ("com.google.android.gms:play-services-tflite-java:<version>").
assertThat(e).hasMessageThat().contains("com.google.android.gms");
assertThat(e).hasMessageThat().contains("play-services-tflite-java");
}
}
@Test
public void testRuntimePreferSystemOverApplication() throws Exception {
InterpreterApi.Options options =
new InterpreterApi.Options().setRuntime(TfLiteRuntime.PREFER_SYSTEM_OVER_APPLICATION);
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, options)) {
fail();
} catch (IllegalStateException e) {
// Verify that the error message has some hints about how to link in EITHER client library
// (app should link against either "org.tensorflow:tensorflow-lite:<version>" or
// "com.google.android.gms:play-services-tflite-java:<version>").
assertThat(e).hasMessageThat().contains("com.google.android.gms");
assertThat(e).hasMessageThat().contains("play-services-tflite-java");
assertThat(e).hasMessageThat().contains("com.google.ai.edge.litert");
assertThat(e).hasMessageThat().contains("litert");
}
}
}
@@ -0,0 +1,927 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.tensorflow.lite.InterpreterApi.Options.TfLiteRuntime;
import org.tensorflow.lite.acceleration.ValidatedAccelerationConfig;
/** Unit tests for {@link org.tensorflow.lite.InterpreterApi}. */
@RunWith(JUnit4.class)
public final class InterpreterApiTest {
private static final String MODEL_PATH = "tensorflow/lite/java/src/testdata/add.bin";
private static final String MULTIPLE_INPUTS_MODEL_PATH =
"tensorflow/lite/testdata/multi_add.bin";
private static final String FLEX_MODEL_PATH =
"tensorflow/lite/testdata/multi_add_flex.bin";
private static final String UNKNOWN_DIMS_MODEL_PATH =
"tensorflow/lite/java/src/testdata/add_unknown_dimensions.bin";
private static final String DYNAMIC_SHAPES_MODEL_PATH =
"tensorflow/lite/testdata/dynamic_shapes.bin";
private static final String BOOL_MODEL =
"tensorflow/lite/java/src/testdata/tile_with_bool_input.bin";
private static final String MODEL_WITH_SIGNATURE_PATH =
"tensorflow/lite/java/src/testdata/mul_add_signature_def.bin";
private static final String MODEL_WITH_MULTI_SIGNATURE_PATH =
"tensorflow/lite/java/src/testdata/multi_signature_def.bin";
private static final ByteBuffer MODEL_BUFFER = TestUtils.getTestFileAsBuffer(MODEL_PATH);
private static final ByteBuffer MULTIPLE_INPUTS_MODEL_BUFFER =
TestUtils.getTestFileAsBuffer(MULTIPLE_INPUTS_MODEL_PATH);
private static final ByteBuffer FLEX_MODEL_BUFFER =
TestUtils.getTestFileAsBuffer(FLEX_MODEL_PATH);
private static final ByteBuffer UNKNOWN_DIMS_MODEL_PATH_BUFFER =
TestUtils.getTestFileAsBuffer(UNKNOWN_DIMS_MODEL_PATH);
private static final ByteBuffer DYNAMIC_SHAPES_MODEL_BUFFER =
TestUtils.getTestFileAsBuffer(DYNAMIC_SHAPES_MODEL_PATH);
private static final ByteBuffer BOOL_MODEL_BUFFER = TestUtils.getTestFileAsBuffer(BOOL_MODEL);
private static final ByteBuffer MODEL_WITH_SIGNATURE_BUFFER =
TestUtils.getTestFileAsBuffer(MODEL_WITH_SIGNATURE_PATH);
private static final ByteBuffer MODEL_WITH_MULTI_SIGNATURE_BUFFER =
TestUtils.getTestFileAsBuffer(MODEL_WITH_MULTI_SIGNATURE_PATH);
// We want to run these tests both with the TF Lite runtime library linked in,
// and also using the system TF Lite runtime library if the client for that is linked in.
// So we need to use a runtime setting that will work with both scenarios.
private static final InterpreterApi.Options TEST_OPTIONS =
new InterpreterApi.Options().setRuntime(TfLiteRuntime.PREFER_SYSTEM_OVER_APPLICATION);
@Before
public void setUp() {
TestInit.init();
}
@Test
public void testInterpreter() throws Exception {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
assertThat(interpreter).isNotNull();
assertThat(interpreter.getInputTensorCount()).isEqualTo(1);
assertThat(interpreter.getInputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getOutputTensorCount()).isEqualTo(1);
assertThat(interpreter.getOutputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
}
}
@Test
public void testInterpreterWithOptions() throws Exception {
InterpreterApi.Options options = new InterpreterApi.Options(TEST_OPTIONS);
try (InterpreterApi interpreter =
InterpreterApi.create(MODEL_BUFFER, options.setNumThreads(2).setUseNNAPI(true))) {
assertThat(interpreter).isNotNull();
assertThat(interpreter.getInputTensorCount()).isEqualTo(1);
assertThat(interpreter.getInputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getOutputTensorCount()).isEqualTo(1);
assertThat(interpreter.getOutputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
}
}
@Test
public void testInterpreterWithoutAccelerationConfig() throws Exception {
FloatBuffer parsedOutput = FloatBuffer.allocate(1);
InterpreterApi.Options options = new InterpreterApi.Options(TEST_OPTIONS);
assertThat(options.getAccelerationConfig()).isNull();
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, options)) {
// Not setting acceleration config has no effect on an interpreter.
assertThat(interpreter).isNotNull();
interpreter.run(2.37f, parsedOutput);
assertThat(parsedOutput.get(0)).isWithin(0.1f).of(7.11f);
}
}
@Test
public void testInterpreterWithAccelerationConfig() throws Exception {
InterpreterApi.Options options = new InterpreterApi.Options(TEST_OPTIONS);
// Mock the acceleration config interface.
ValidatedAccelerationConfig accelerationConfig = mock(ValidatedAccelerationConfig.class);
// Set the acceleration config
options.setAccelerationConfig(accelerationConfig);
// Verify that the config was set
assertThat(options.getAccelerationConfig()).isEqualTo(accelerationConfig);
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, options)) {
assertThat(interpreter).isNotNull();
// Verify that the apply method was invoked
verify(accelerationConfig).apply(any());
}
}
@Test
public void testInterpreterWithNullOptions() throws Exception {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, null)) {
assertThat(interpreter).isNotNull();
assertThat(interpreter.getInputTensorCount()).isEqualTo(1);
assertThat(interpreter.getInputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getOutputTensorCount()).isEqualTo(1);
assertThat(interpreter.getOutputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getClass().getCanonicalName()).contains("org.tensorflow.lite");
} catch (IllegalStateException e) {
// This can occur when this code is not linked against the TF Lite runtime.
// Verify that the error message has some hints about how to link
// against the runtime ("com.google.ai.edge.litert:litert:<version>").
assertThat(e).hasMessageThat().contains("com.google.ai.edge.litert");
assertThat(e).hasMessageThat().contains("litert");
assertThat(e).hasMessageThat().doesNotContain("com.google.android.gms");
assertThat(e).hasMessageThat().doesNotContain("play-services-tflite-java");
}
}
@Test
public void testRuntimeFromApplicationOnly() throws Exception {
InterpreterApi.Options options =
new InterpreterApi.Options().setRuntime(TfLiteRuntime.FROM_APPLICATION_ONLY);
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, options)) {
assertThat(interpreter).isNotNull();
assertThat(interpreter.getInputTensorCount()).isEqualTo(1);
assertThat(interpreter.getInputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getOutputTensorCount()).isEqualTo(1);
assertThat(interpreter.getOutputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getClass().getCanonicalName()).contains("org.tensorflow.lite");
} catch (IllegalStateException e) {
// This can occur when this code is not linked against the TF Lite runtime.
// Verify that the error message has some hints about how to link
// against the runtime ("com.google.ai.edge.litert:litert:<version>").
assertThat(e).hasMessageThat().contains("com.google.ai.edge.litert");
assertThat(e).hasMessageThat().contains("litert");
assertThat(e).hasMessageThat().doesNotContain("com.google.android.gms");
assertThat(e).hasMessageThat().doesNotContain("play-services-tflite-java");
}
}
@Test
public void testRuntimeFromSystemOnly() throws Exception {
InterpreterApi.Options options =
new InterpreterApi.Options().setRuntime(TfLiteRuntime.FROM_SYSTEM_ONLY);
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, options)) {
assertThat(interpreter).isNotNull();
assertThat(interpreter.getInputTensorCount()).isEqualTo(1);
assertThat(interpreter.getInputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getOutputTensorCount()).isEqualTo(1);
assertThat(interpreter.getOutputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getClass().getCanonicalName()).contains("com.google.android.gms");
} catch (IllegalStateException e) {
// This can occur when this code is not linked against the right TF Lite runtime client.
// Verify that the error message has some hints about how to link in the
// client library for TF Lite in Google Play Services
// ("com.google.android.gms:play-services-tflite-java:<version>").
assertThat(e).hasMessageThat().contains("com.google.android.gms");
assertThat(e).hasMessageThat().contains("play-services-tflite-java");
assertThat(e).hasMessageThat().doesNotContain("org.tensorflow:tensorflow-lite");
assertThat(e).hasMessageThat().doesNotContain("com.google.ai.edge.litert:litert");
}
}
@Test
public void testRuntimePreferSystemOverApplication() throws Exception {
InterpreterApi.Options options =
new InterpreterApi.Options().setRuntime(TfLiteRuntime.PREFER_SYSTEM_OVER_APPLICATION);
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, options)) {
assertThat(interpreter).isNotNull();
assertThat(interpreter.getInputTensorCount()).isEqualTo(1);
assertThat(interpreter.getInputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getOutputTensorCount()).isEqualTo(1);
assertThat(interpreter.getOutputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
}
}
@Test
public void testRunWithFileModel() throws Exception {
if (!TestUtils.supportsFilePaths()) {
System.err.println("Not testing with file model, since file paths aren't supported.");
return;
}
try (InterpreterApi interpreter = InterpreterApi.create(new File(MODEL_PATH), TEST_OPTIONS)) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
float[][][][] parsedOutputs = new float[2][8][8][3];
interpreter.run(fourD, parsedOutputs);
float[] outputOneD = parsedOutputs[0][0][0];
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
}
@Test
public void testRunWithDirectByteBufferModel() throws Exception {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(MODEL_BUFFER.capacity());
byteBuffer.order(ByteOrder.nativeOrder());
byteBuffer.put(MODEL_BUFFER.duplicate()); // Use duplicate to avoid updating MODEL_BUFFER.
try (InterpreterApi interpreter = InterpreterApi.create(byteBuffer, TEST_OPTIONS)) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
float[][][][] parsedOutputs = new float[2][8][8][3];
interpreter.run(fourD, parsedOutputs);
float[] outputOneD = parsedOutputs[0][0][0];
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
}
@Test
public void testRunWithInvalidByteBufferModel() throws Exception {
ByteBuffer byteBuffer = ByteBuffer.allocate(MODEL_BUFFER.capacity());
byteBuffer.order(ByteOrder.nativeOrder());
byteBuffer.put(MODEL_BUFFER.duplicate()); // Use duplicate to avoid updating MODEL_BUFFER.
try {
InterpreterApi.create(byteBuffer, TEST_OPTIONS);
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"Model ByteBuffer should be either a MappedByteBuffer"
+ " of the model file, or a direct ByteBuffer using ByteOrder.nativeOrder()");
}
}
@Test
public void testRun() {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
Float[] oneD = {1.23f, 6.54f, 7.81f};
Float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
Float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
Float[][][][] fourD = {threeD, threeD};
Float[][][][] parsedOutputs = new Float[2][8][8][3];
try {
interpreter.run(fourD, parsedOutputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().contains("cannot resolve DataType of [[[[Ljava.lang.Float;");
}
}
}
@Test
public void testRunWithBoxedInputs() {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
float[][][][] parsedOutputs = new float[2][8][8][3];
interpreter.run(fourD, parsedOutputs);
float[] outputOneD = parsedOutputs[0][0][0];
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
}
@Test
public void testRunForMultipleInputsOutputs() {
try (InterpreterApi interpreter =
InterpreterApi.create(MULTIPLE_INPUTS_MODEL_BUFFER, TEST_OPTIONS)) {
assertThat(interpreter.getInputTensorCount()).isEqualTo(4);
assertThat(interpreter.getInputTensor(0).index()).isGreaterThan(-1);
assertThat(interpreter.getInputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getInputTensor(1).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getInputTensor(2).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getInputTensor(3).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getOutputTensorCount()).isEqualTo(2);
assertThat(interpreter.getOutputTensor(0).index()).isGreaterThan(-1);
assertThat(interpreter.getOutputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getOutputTensor(1).dataType()).isEqualTo(DataType.FLOAT32);
float[] input0 = {1.23f};
float[] input1 = {2.43f};
Object[] inputs = {input0, input1, input0, input1};
float[] parsedOutput0 = new float[1];
float[] parsedOutput1 = new float[1];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutput0);
outputs.put(1, parsedOutput1);
interpreter.runForMultipleInputsOutputs(inputs, outputs);
float[] expected0 = {4.89f};
float[] expected1 = {6.09f};
assertThat(parsedOutput0).usingTolerance(0.1f).containsExactly(expected0).inOrder();
assertThat(parsedOutput1).usingTolerance(0.1f).containsExactly(expected1).inOrder();
}
}
@Test
public void testRunWithByteBufferOutput() {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
ByteBuffer parsedOutput =
ByteBuffer.allocateDirect(2 * 8 * 8 * 3 * 4).order(ByteOrder.nativeOrder());
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
interpreter.run(fourD, parsedOutput);
}
float[] outputOneD = {
parsedOutput.getFloat(0), parsedOutput.getFloat(4), parsedOutput.getFloat(8)
};
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
@Test
public void testRunWithScalarInput() {
FloatBuffer parsedOutput = FloatBuffer.allocate(1);
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
interpreter.run(2.37f, parsedOutput);
}
assertThat(parsedOutput.get(0)).isWithin(0.1f).of(7.11f);
}
@Test
public void testResizeInput() {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
int[] inputDims = {1};
interpreter.resizeInput(0, inputDims);
assertThat(interpreter.getInputTensor(0).shape()).isEqualTo(inputDims);
ByteBuffer input = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder());
ByteBuffer output = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder());
interpreter.run(input, output);
assertThat(interpreter.getOutputTensor(0).shape()).isEqualTo(inputDims);
}
}
@Test
public void testAllocateTensors() {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
// Redundant allocateTensors() should have no effect.
interpreter.allocateTensors();
// allocateTensors() should propagate resizes.
int[] inputDims = {1};
assertThat(interpreter.getOutputTensor(0).shape()).isNotEqualTo(inputDims);
interpreter.resizeInput(0, inputDims);
assertThat(interpreter.getOutputTensor(0).shape()).isNotEqualTo(inputDims);
interpreter.allocateTensors();
assertThat(interpreter.getOutputTensor(0).shape()).isEqualTo(inputDims);
// Additional redundant calls should have no effect.
interpreter.allocateTensors();
assertThat(interpreter.getOutputTensor(0).shape()).isEqualTo(inputDims);
// Execution should succeed as expected.
ByteBuffer input = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder());
ByteBuffer output = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder());
interpreter.run(input, output);
assertThat(interpreter.getOutputTensor(0).shape()).isEqualTo(inputDims);
}
}
@Test
public void testUnknownDims() {
try (InterpreterApi interpreter =
InterpreterApi.create(UNKNOWN_DIMS_MODEL_PATH_BUFFER, TEST_OPTIONS)) {
int[] inputDims = {1, 1, 3, 3};
int[] inputDimsSignature = {1, -1, 3, 3};
assertThat(interpreter.getInputTensor(0).shape()).isEqualTo(inputDims);
assertThat(interpreter.getInputTensor(0).shapeSignature()).isEqualTo(inputDimsSignature);
// Resize tensor with strict checking. Try invalid resize.
inputDims[2] = 5;
try {
interpreter.resizeInput(0, inputDims, true);
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"ResizeInputTensorStrict only allows mutating unknown dimensions identified by -1");
}
inputDims[2] = 3;
// Set the dimension of the unknown dimension to the expected dimension and ensure shape
// signature doesn't change.
inputDims[1] = 3;
interpreter.resizeInput(0, inputDims, true);
assertThat(interpreter.getInputTensor(0).shape()).isEqualTo(inputDims);
assertThat(interpreter.getInputTensor(0).shapeSignature()).isEqualTo(inputDimsSignature);
ByteBuffer input =
ByteBuffer.allocateDirect(1 * 3 * 3 * 3 * 4).order(ByteOrder.nativeOrder());
ByteBuffer output =
ByteBuffer.allocateDirect(1 * 3 * 3 * 3 * 4).order(ByteOrder.nativeOrder());
interpreter.run(input, output);
assertThat(interpreter.getOutputTensor(0).shape()).isEqualTo(inputDims);
}
}
@Test
public void testRunWithWrongInputType() {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
int[] oneD = {4, 3, 9};
int[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
int[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
int[][][][] fourD = {threeD, threeD};
float[][][][] parsedOutputs = new float[2][8][8][3];
try {
interpreter.run(fourD, parsedOutputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"Cannot convert between a TensorFlowLite tensor with type "
+ "FLOAT32 and a Java object of type [[[[I (which is compatible with the"
+ " TensorFlowLite type INT32)");
}
}
}
@Test
public void testRunWithUnsupportedInputType() {
DoubleBuffer doubleBuffer = DoubleBuffer.allocate(10);
float[][][][] parsedOutputs = new float[2][8][8][3];
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
interpreter.run(doubleBuffer, parsedOutputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().contains("DataType error: cannot resolve DataType of");
}
}
@Test
public void testRunWithWrongOutputType() {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
int[][][][] parsedOutputs = new int[2][8][8][3];
try {
interpreter.run(fourD, parsedOutputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"Cannot convert between a TensorFlowLite tensor with type "
+ "FLOAT32 and a Java object of type [[[[I (which is compatible with the"
+ " TensorFlowLite type INT32)");
}
}
}
@Test
public void testGetInputIndex() {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
try {
interpreter.getInputIndex("WrongInputName");
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"'WrongInputName' is not a valid name for any input. Names of inputs and their "
+ "indexes are {input=0}");
}
int index = interpreter.getInputIndex("input");
assertThat(index).isEqualTo(0);
}
}
@Test
public void testGetOutputIndex() {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
try {
interpreter.getOutputIndex("WrongOutputName");
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"'WrongOutputName' is not a valid name for any output. Names of outputs and their"
+ " indexes are {output=0}");
}
int index = interpreter.getOutputIndex("output");
assertThat(index).isEqualTo(0);
}
}
@Test
public void testTurnOnNNAPI() throws Exception {
InterpreterApi.Options options = new InterpreterApi.Options(TEST_OPTIONS).setUseNNAPI(true);
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, options)) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
float[][][][] parsedOutputs = new float[2][8][8][3];
interpreter.run(fourD, parsedOutputs);
float[] outputOneD = parsedOutputs[0][0][0];
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
}
@Test
public void testRedundantClose() throws Exception {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
interpreter.close();
interpreter.close();
} // Implicitly calls interpreter.close() for a third time.
}
@Test
public void testNullInputs() throws Exception {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
try {
interpreter.run(null, new float[2][8][8][3]);
fail();
} catch (IllegalArgumentException e) {
// Expected failure.
}
}
}
@Test
public void testNullOutputs() throws Exception {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
float[] input = {1.f};
interpreter.run(input, null);
float output = interpreter.getOutputTensor(0).asReadOnlyBuffer().getFloat(0);
assertThat(output).isEqualTo(3.f);
}
}
// Smoke test validating that flex model loading fails when the flex delegate is not linked.
@Test
public void testFlexModel() throws Exception {
try {
InterpreterApi.create(FLEX_MODEL_BUFFER, TEST_OPTIONS);
fail();
} catch (IllegalStateException e) {
// Expected failure.
} catch (IllegalArgumentException e) {
// As we could apply some TfLite delegate by default, the flex ops preparation could fail if
// the flex delegate isn't applied first, in which case this type of exception is thrown.
// Expected failure.
}
}
@Test
public void testBoolModel() throws Exception {
boolean[][][] inputs = {{{true, false}, {false, true}}, {{true, true}, {false, true}}};
int[] multipliers = {1, 1, 2};
boolean[][][] parsedOutputs = new boolean[2][2][4];
try (InterpreterApi interpreter = InterpreterApi.create(BOOL_MODEL_BUFFER, TEST_OPTIONS)) {
assertThat(interpreter.getInputTensor(0).dataType()).isEqualTo(DataType.BOOL);
Object[] inputsArray = {inputs, multipliers};
Map<Integer, Object> outputsMap = new HashMap<>();
outputsMap.put(0, parsedOutputs);
interpreter.runForMultipleInputsOutputs(inputsArray, outputsMap);
boolean[][][] expectedOutputs = {
{{true, false, true, false}, {false, true, false, true}},
{{true, true, true, true}, {false, true, false, true}}
};
assertThat(parsedOutputs).isEqualTo(expectedOutputs);
}
}
private static FloatBuffer fill(FloatBuffer buffer, float value) {
while (buffer.hasRemaining()) {
buffer.put(value);
}
buffer.rewind();
return buffer;
}
// Regression test case to ensure that graphs with dynamically computed shapes work properly.
// Historically, direct ByteBuffer addresses would overwrite the arena-allocated tensor input
// pointers. Normally this works fine, but for dynamic graphs, the original input tensor pointers
// may be "restored" at invocation time by the arena allocator, resetting the direct ByteBuffer
// address and leading to stale input data being used.
@Test
public void testDynamicShapesWithDirectBufferInputs() {
try (InterpreterApi interpreter =
InterpreterApi.create(DYNAMIC_SHAPES_MODEL_BUFFER, TEST_OPTIONS)) {
ByteBuffer input0 =
ByteBuffer.allocateDirect(8 * 42 * 1024 * 4).order(ByteOrder.nativeOrder());
ByteBuffer input1 =
ByteBuffer.allocateDirect(1 * 90 * 1024 * 4).order(ByteOrder.nativeOrder());
ByteBuffer input2 = ByteBuffer.allocateDirect(1 * 4).order(ByteOrder.nativeOrder());
Object[] inputs = {input0, input1, input2};
fill(input0.asFloatBuffer(), 2.0f);
fill(input1.asFloatBuffer(), 0.5f);
// Note that the value of this input dictates the shape of the output.
fill(input2.asFloatBuffer(), 1.0f);
FloatBuffer output = FloatBuffer.allocate(8 * 1 * 1024);
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, output);
interpreter.runForMultipleInputsOutputs(inputs, outputs);
FloatBuffer expected = fill(FloatBuffer.allocate(8 * 1 * 1024), 2.0f);
assertThat(output.array()).usingTolerance(0.1f).containsExactly(expected.array()).inOrder();
}
}
@Test
public void testDynamicShapesWithEmptyOutputs() {
try (InterpreterApi interpreter =
InterpreterApi.create(DYNAMIC_SHAPES_MODEL_BUFFER, TEST_OPTIONS)) {
ByteBuffer input0 =
ByteBuffer.allocateDirect(8 * 42 * 1024 * 4).order(ByteOrder.nativeOrder());
ByteBuffer input1 =
ByteBuffer.allocateDirect(1 * 90 * 1024 * 4).order(ByteOrder.nativeOrder());
ByteBuffer input2 = ByteBuffer.allocateDirect(1 * 4).order(ByteOrder.nativeOrder());
Object[] inputs = {input0, input1, input2};
fill(input0.asFloatBuffer(), 2.0f);
fill(input1.asFloatBuffer(), 0.5f);
fill(input2.asFloatBuffer(), 1.0f);
// Use an empty output map; the output data will be retrieved directly from the tensor.
Map<Integer, Object> outputs = new HashMap<>();
interpreter.runForMultipleInputsOutputs(inputs, outputs);
FloatBuffer output = FloatBuffer.allocate(8 * 1 * 1024);
output.put(interpreter.getOutputTensor(0).asReadOnlyBuffer().asFloatBuffer());
FloatBuffer expected = fill(FloatBuffer.allocate(8 * 1 * 1024), 2.0f);
assertThat(output.array()).usingTolerance(0.1f).containsExactly(expected.array()).inOrder();
}
}
@Test
public void testModelWithSignatureDef() {
try (InterpreterApi interpreter =
InterpreterApi.create(MODEL_WITH_SIGNATURE_BUFFER, TEST_OPTIONS)) {
String[] signatureKeys = interpreter.getSignatureKeys();
String[] expectedSignatureKeys = {"mul_add"};
assertThat(signatureKeys).isEqualTo(expectedSignatureKeys);
String[] signatureInputs = interpreter.getSignatureInputs(expectedSignatureKeys[0]);
String[] expectedSignatureInputs = {"x", "y"};
assertThat(signatureInputs).isEqualTo(expectedSignatureInputs);
String[] signatureOutputs = interpreter.getSignatureOutputs(expectedSignatureKeys[0]);
String[] expectedSignatureOutputs = {"output_0"};
assertThat(signatureOutputs).isEqualTo(expectedSignatureOutputs);
Tensor outputTensor = interpreter.getOutputTensorFromSignature("output_0", "mul_add");
Tensor inputTensor = interpreter.getInputTensorFromSignature("x", "mul_add");
assertThat(outputTensor.numElements()).isEqualTo(1);
assertThat(inputTensor.numElements()).isEqualTo(1);
// Test null input name.
try {
inputTensor = interpreter.getInputTensorFromSignature(null, "mul_add");
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().contains("Invalid input tensor name provided (null)");
}
// Test invalid input name.
try {
inputTensor = interpreter.getInputTensorFromSignature("xx", "mul_add");
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().contains("Input error: input xx not found.");
}
// Test null output name.
try {
outputTensor = interpreter.getOutputTensorFromSignature(null, "mul_add");
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().contains("Invalid output tensor name provided (null)");
}
// Test invalid output name.
try {
outputTensor = interpreter.getOutputTensorFromSignature("yy", "mul_add");
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().contains("Input error: output yy not found.");
}
FloatBuffer output = FloatBuffer.allocate(1);
float[] inputX = {2.0f};
float[] inputY = {4.0f};
Map<String, Object> inputs = new HashMap<>();
inputs.put("x", inputX);
inputs.put("y", inputY);
Map<String, Object> outputs = new HashMap<>();
outputs.put("output_0", output);
interpreter.runSignature(inputs, outputs, "mul_add");
// Result should be x * 3.0 + y
FloatBuffer expected = fill(FloatBuffer.allocate(1), 10.0f);
assertThat(output.array()).usingTolerance(0.1f).containsExactly(expected.array()).inOrder();
}
}
@Test
public void testModelWithSignatureDefNullMethodName() {
try (InterpreterApi interpreter =
InterpreterApi.create(MODEL_WITH_SIGNATURE_BUFFER, TEST_OPTIONS)) {
String[] signatureKeys = interpreter.getSignatureKeys();
String[] expectedSignatureKeys = {"mul_add"};
assertThat(signatureKeys).isEqualTo(expectedSignatureKeys);
String[] signatureInputs = interpreter.getSignatureInputs(expectedSignatureKeys[0]);
String[] expectedSignatureInputs = {"x", "y"};
assertThat(signatureInputs).isEqualTo(expectedSignatureInputs);
String[] signatureOutputs = interpreter.getSignatureOutputs(expectedSignatureKeys[0]);
String[] expectedSignatureOutputs = {"output_0"};
assertThat(signatureOutputs).isEqualTo(expectedSignatureOutputs);
FloatBuffer output = FloatBuffer.allocate(1);
float[] inputX = {2.0f};
float[] inputY = {4.0f};
Map<String, Object> inputs = new HashMap<>();
inputs.put("x", inputX);
inputs.put("y", inputY);
Map<String, Object> outputs = new HashMap<>();
outputs.put("output_0", output);
interpreter.runSignature(inputs, outputs, null);
// Result should be x * 3.0 + y
FloatBuffer expected = fill(FloatBuffer.allocate(1), 10.0f);
assertThat(output.array()).usingTolerance(0.1f).containsExactly(expected.array()).inOrder();
output = FloatBuffer.allocate(1);
outputs.put("output_0", output);
interpreter.runSignature(inputs, outputs);
assertThat(output.array()).usingTolerance(0.1f).containsExactly(expected.array()).inOrder();
}
}
@Test
public void testModelWithSignatureDefNoSignatures() {
try (InterpreterApi interpreter = InterpreterApi.create(MODEL_BUFFER, TEST_OPTIONS)) {
String[] signatureKeys = interpreter.getSignatureKeys();
String[] expectedSignatureKeys = {};
assertThat(signatureKeys).isEqualTo(expectedSignatureKeys);
Map<String, Object> inputs = new HashMap<>();
Map<String, Object> outputs = new HashMap<>();
try {
interpreter.runSignature(inputs, outputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"Input error: SignatureDef signatureKey should not be null. null is only allowed if"
+ " the model has a single Signature");
}
}
}
@Test
public void testModelWithMultiSignatureDef() {
try (InterpreterApi interpreter =
InterpreterApi.create(MODEL_WITH_MULTI_SIGNATURE_BUFFER, TEST_OPTIONS)) {
String[] signatureKeys = interpreter.getSignatureKeys();
String[] expectedSignatureKeys = {"add", "sub"};
assertThat(signatureKeys).isEqualTo(expectedSignatureKeys);
String[] addSignatureInputs = interpreter.getSignatureInputs(expectedSignatureKeys[0]);
String[] expectedAddSignatureInputs = {"a", "b"};
assertThat(addSignatureInputs).isEqualTo(expectedAddSignatureInputs);
String[] addSignatureOutputs = interpreter.getSignatureOutputs(expectedSignatureKeys[0]);
String[] expectedAddSignatureOutputs = {"add_result"};
assertThat(addSignatureOutputs).isEqualTo(expectedAddSignatureOutputs);
String[] subSignatureInputs = interpreter.getSignatureInputs(expectedSignatureKeys[1]);
String[] expectedSubSignatureInputs = {"x", "y"};
assertThat(subSignatureInputs).isEqualTo(expectedSubSignatureInputs);
String[] subSignatureOutputs = interpreter.getSignatureOutputs(expectedSignatureKeys[1]);
String[] expectedSubSignatureOutputs = {"sub_result"};
assertThat(subSignatureOutputs).isEqualTo(expectedSubSignatureOutputs);
// Test "add" signature.
FloatBuffer output = FloatBuffer.allocate(1);
float inputA = 2.0f;
float inputB = 4.0f;
Map<String, Object> inputs = new HashMap<>();
inputs.put("a", inputA);
inputs.put("b", inputB);
Map<String, Object> outputs = new HashMap<>();
outputs.put("add_result", output);
interpreter.runSignature(inputs, outputs, "add");
// Result should be a + b
FloatBuffer expected = fill(FloatBuffer.allocate(1), 6.0f);
assertThat(output.array()).usingTolerance(0.1f).containsExactly(expected.array()).inOrder();
// Test "sub" signature.
output = FloatBuffer.allocate(1);
float inputX = 4.0f;
float inputY = 2.0f;
inputs = new HashMap<>();
inputs.put("x", inputX);
inputs.put("y", inputY);
outputs = new HashMap<>();
outputs.put("sub_result", output);
interpreter.runSignature(inputs, outputs, "sub");
// Result should be x - y
expected = fill(FloatBuffer.allocate(1), 2.0f);
assertThat(output.array()).usingTolerance(0.1f).containsExactly(expected.array()).inOrder();
}
}
// This test is the same as testModelWithMultiSignatureDef above,
// except that it passes in vectors rather than scalars.
@Test
public void testImplicitNonstrictResize() {
try (InterpreterApi interpreter =
InterpreterApi.create(MODEL_WITH_MULTI_SIGNATURE_BUFFER, TEST_OPTIONS)) {
String[] signatureKeys = interpreter.getSignatureKeys();
String[] expectedSignatureKeys = {"add", "sub"};
assertThat(signatureKeys).isEqualTo(expectedSignatureKeys);
String[] addSignatureInputs = interpreter.getSignatureInputs(expectedSignatureKeys[0]);
String[] expectedAddSignatureInputs = {"a", "b"};
assertThat(addSignatureInputs).isEqualTo(expectedAddSignatureInputs);
String[] addSignatureOutputs = interpreter.getSignatureOutputs(expectedSignatureKeys[0]);
String[] expectedAddSignatureOutputs = {"add_result"};
assertThat(addSignatureOutputs).isEqualTo(expectedAddSignatureOutputs);
String[] subSignatureInputs = interpreter.getSignatureInputs(expectedSignatureKeys[1]);
String[] expectedSubSignatureInputs = {"x", "y"};
assertThat(subSignatureInputs).isEqualTo(expectedSubSignatureInputs);
String[] subSignatureOutputs = interpreter.getSignatureOutputs(expectedSignatureKeys[1]);
String[] expectedSubSignatureOutputs = {"sub_result"};
assertThat(subSignatureOutputs).isEqualTo(expectedSubSignatureOutputs);
// Test "add" signature.
FloatBuffer output = FloatBuffer.allocate(1);
float[] inputA = {2.0f};
float[] inputB = {4.0f};
Map<String, Object> inputs = new HashMap<>();
inputs.put("a", inputA);
inputs.put("b", inputB);
Map<String, Object> outputs = new HashMap<>();
outputs.put("add_result", output);
if (SupportedFeatures.supportsNonstrictResize()) {
interpreter.runSignature(inputs, outputs, "add");
// Result should be a + b
FloatBuffer expected = fill(FloatBuffer.allocate(1), 6.0f);
assertThat(output.array()).usingTolerance(0.1f).containsExactly(expected.array()).inOrder();
// Test "sub" signature.
output = FloatBuffer.allocate(1);
float[] inputX = {4.0f};
float[] inputY = {2.0f};
inputs = new HashMap<>();
inputs.put("x", inputX);
inputs.put("y", inputY);
outputs = new HashMap<>();
outputs.put("sub_result", output);
interpreter.runSignature(inputs, outputs, "sub");
// Result should be x - y
expected = fill(FloatBuffer.allocate(1), 2.0f);
assertThat(output.array()).usingTolerance(0.1f).containsExactly(expected.array()).inOrder();
} else {
try {
interpreter.runSignature(inputs, outputs, "add");
fail();
} catch (IllegalArgumentException e) {
// Could report error message for either input 'a' or input 'b'; both have wrong
// shape, and we don't want the test to depend on the order of error checking.
assertThat(e).hasMessageThat().contains("Tensor passed for input '");
assertThat(e)
.hasMessageThat()
.contains("' of signature 'add' has different shape than expected");
}
}
}
}
}
@@ -0,0 +1,63 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import java.nio.ByteBuffer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link org.tensorflow.lite.Interpreter} with selective registration. */
@RunWith(JUnit4.class)
public final class InterpreterCustomizedAndroidBuildTest {
// Supported model.
private static final String SUPPORTED_MODEL_PATH = "tensorflow/lite/testdata/add.bin";
private static final ByteBuffer SUPPORTED_MODEL_BUFFER =
TestUtils.getTestFileAsBuffer(SUPPORTED_MODEL_PATH);
// Model with unregistered operator.
private static final String UNSUPPORTED_MODEL_PATH =
"tensorflow/lite/testdata/test_model.bin";
private static final ByteBuffer UNSUPPORTED_MODEL_BUFFER =
TestUtils.getTestFileAsBuffer(UNSUPPORTED_MODEL_PATH);
@Test
public void testSupportedModel() throws Exception {
try (Interpreter interpreter = new Interpreter(SUPPORTED_MODEL_BUFFER)) {
assertThat(interpreter).isNotNull();
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
float[][][][] parsedOutputs = new float[2][8][8][3];
interpreter.run(fourD, parsedOutputs);
}
}
@Test
public void testUnsupportedModel() throws Exception {
try (Interpreter interpreter = new Interpreter(UNSUPPORTED_MODEL_BUFFER)) {
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains("Cannot create interpreter: Didn't find op for builtin opcode 'CONV_2D'");
}
}
}
@@ -0,0 +1,103 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.tensorflow.lite.flex.FlexDelegate;
import org.tensorflow.lite.nnapi.NnApiDelegate;
/**
* Unit tests for {@link org.tensorflow.lite.Interpreter} that validate execution with models that
* have TensorFlow ops.
*/
@RunWith(JUnit4.class)
public final class InterpreterFlexTest {
private static final ByteBuffer FLEX_MODEL_BUFFER =
TestUtils.getTestFileAsBuffer("tensorflow/lite/testdata/multi_add_flex.bin");
/** Smoke test validating that flex model loading works when the flex delegate is used. */
@Test
public void testFlexModel() throws Exception {
FlexDelegate delegate = new FlexDelegate();
Interpreter.Options options = new Interpreter.Options().addDelegate(delegate);
try (Interpreter interpreter = new Interpreter(FLEX_MODEL_BUFFER, options)) {
testCommon(interpreter);
} finally {
delegate.close();
}
}
/** Smoke test validating that flex model loading works when the flex delegate is linked. */
@Test
public void testFlexModelDelegateAutomaticallyApplied() throws Exception {
try (Interpreter interpreter = new Interpreter(FLEX_MODEL_BUFFER)) {
testCommon(interpreter);
}
}
/** Smoke test validating that flex model loading works when the flex delegate is linked. */
@Test
public void testFlexModelDelegateAutomaticallyAppliedBeforeOtherDelegates() throws Exception {
Interpreter.Options options = new Interpreter.Options();
try (NnApiDelegate delegate = new NnApiDelegate();
Interpreter interpreter =
new Interpreter(FLEX_MODEL_BUFFER, options.addDelegate(delegate))) {
testCommon(interpreter);
}
}
private static void testCommon(Interpreter interpreter) {
assertThat(interpreter.getInputTensorCount()).isEqualTo(4);
assertThat(interpreter.getInputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getInputTensor(1).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getInputTensor(2).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getInputTensor(3).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getOutputTensorCount()).isEqualTo(2);
assertThat(interpreter.getOutputTensor(0).dataType()).isEqualTo(DataType.FLOAT32);
assertThat(interpreter.getOutputTensor(1).dataType()).isEqualTo(DataType.FLOAT32);
float[] input1 = {1};
float[] input2 = {2};
float[] input3 = {3};
float[] input4 = {5};
Object[] inputs = new Object[] {input1, input2, input3, input4};
float[] parsedOutput1 = new float[1];
float[] parsedOutput2 = new float[1];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutput1);
outputs.put(1, parsedOutput2);
interpreter.runForMultipleInputsOutputs(inputs, outputs);
float[] expectedOutput1 = {6};
float[] expectedOutput2 = {10};
assertThat(parsedOutput1).usingTolerance(0.1f).containsExactly(expectedOutput1).inOrder();
assertThat(parsedOutput2).usingTolerance(0.1f).containsExactly(expectedOutput2).inOrder();
}
static {
FlexDelegate.initTensorFlowForTesting();
}
}
@@ -0,0 +1,53 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import java.nio.ByteBuffer;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.tensorflow.lite.flex.FlexDelegate;
/**
* Unit tests for {@link org.tensorflow.lite.Interpreter} that validate execution with models that
* have user's defined TensorFlow ops.
*/
@RunWith(JUnit4.class)
public final class InterpreterFlexWithCustomOpsTest {
private static final ByteBuffer DOUBLE_MODEL_BUFFER =
TestUtils.getTestFileAsBuffer("tensorflow/lite/testdata/double_flex.bin");
/** Smoke test validating that flex model with a user's defined TF op. */
@Test
public void testFlexModelWithUsersDefinedOp() throws Exception {
try (Interpreter interpreter = new Interpreter(DOUBLE_MODEL_BUFFER)) {
int[] oneD = {1, 2, 3, 4};
int[][] twoD = {oneD};
int[][] parsedOutputs = new int[1][4];
interpreter.run(twoD, parsedOutputs);
int[] outputOneD = parsedOutputs[0];
int[] expected = {2, 4, 6, 8};
assertThat(outputOneD).isEqualTo(expected);
}
}
static {
FlexDelegate.initTensorFlowForTesting();
}
}
@@ -0,0 +1,145 @@
/* Copyright 2018 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import java.nio.ByteBuffer;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Map;
import java.util.PriorityQueue;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Smoke tests for {@link org.tensorflow.lite.Interpreter} agains MobileNet models.
*
* <p>Note that these tests are not intended to validate accuracy, rather, they serve to exercise
* end-to-end inference, against a meaningful model, to tease out any stability/runtime issues.
*/
@RunWith(JUnit4.class)
public final class InterpreterMobileNetTest {
private static final ByteBuffer MOBILENET_FLOAT_MODEL_BUFFER =
TestUtils.getTestFileAsBuffer(
"tensorflow/lite/java/demo/app/src/main/assets/mobilenet_v1_1.0_224.tflite");
private static final ByteBuffer MOBILENET_QUANTIZED_MODEL_BUFFER =
TestUtils.getTestFileAsBuffer(
"tensorflow/lite/java/demo/app/src/main/assets/mobilenet_v1_1.0_224_quant.tflite");
@Test
public void testMobileNet() {
runMobileNetFloatTest(new Interpreter.Options());
}
@Test
public void testMobileNetMultithreaded() {
runMobileNetFloatTest(new Interpreter.Options().setNumThreads(2));
}
@Test
public void testMobileNetEnhancedCpuKernels() {
runMobileNetFloatTest(new Interpreter.Options().setUseXNNPACK(true));
}
@Test
public void testMobileNetEnhancedCpuKernelsMultithreaded() {
runMobileNetFloatTest(new Interpreter.Options().setUseXNNPACK(true).setNumThreads(2));
}
@Test
public void testMobileNetQuantized() {
runMobileNetQuantizedTest(new Interpreter.Options());
}
@Test
public void testMobileNetQuantizedMultithreaded() {
runMobileNetQuantizedTest(new Interpreter.Options().setNumThreads(2));
}
@Test
public void testMobileNetQuantizedEnhancedCpu() {
// The "enhanced CPU flag" should only impact float models, this is a sanity test to confirm.
runMobileNetQuantizedTest(new Interpreter.Options().setUseXNNPACK(true));
}
private static void runMobileNetFloatTest(Interpreter.Options options) {
ByteBuffer img =
TestUtils.getTestImageAsFloatByteBuffer(
"tensorflow/lite/java/src/testdata/grace_hopper_224.jpg");
float[][] labels = new float[1][1001];
try (Interpreter interpreter = new Interpreter(MOBILENET_FLOAT_MODEL_BUFFER, options)) {
interpreter.run(img, labels);
assertThat(interpreter.getInputTensor(0).shape()).isEqualTo(new int[] {1, 224, 224, 3});
assertThat(interpreter.getOutputTensor(0).shape()).isEqualTo(new int[] {1, 1001});
}
assertThat(labels[0])
.usingExactEquality()
.containsNoneOf(new float[] {Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY});
// 653 == "military uniform"
assertThat(getTopKLabels(labels, 3)).contains(653);
}
private static void runMobileNetQuantizedTest(Interpreter.Options options) {
ByteBuffer img =
TestUtils.getTestImageAsByteBuffer(
"tensorflow/lite/java/src/testdata/grace_hopper_224.jpg");
byte[][] labels = new byte[1][1001];
try (Interpreter interpreter = new Interpreter(MOBILENET_QUANTIZED_MODEL_BUFFER, options)) {
interpreter.run(img, labels);
assertThat(interpreter.getInputTensor(0).shape()).isEqualTo(new int[] {1, 224, 224, 3});
assertThat(interpreter.getOutputTensor(0).shape()).isEqualTo(new int[] {1, 1001});
}
// 653 == "military uniform"
assertThat(getTopKLabels(labels, 3)).contains(653);
}
private static ArrayList<Integer> getTopKLabels(byte[][] byteLabels, int k) {
float[][] labels = new float[1][1001];
for (int i = 0; i < byteLabels[0].length; ++i) {
labels[0][i] = (byteLabels[0][i] & 0xff) / 255.0f;
}
return getTopKLabels(labels, k);
}
private static ArrayList<Integer> getTopKLabels(float[][] labels, int k) {
PriorityQueue<Map.Entry<Integer, Float>> pq =
new PriorityQueue<>(
k,
new Comparator<Map.Entry<Integer, Float>>() {
@Override
public int compare(Map.Entry<Integer, Float> o1, Map.Entry<Integer, Float> o2) {
// Intentionally reversed to put high confidence at the head of the queue.
return o1.getValue().compareTo(o2.getValue()) * -1;
}
});
for (int i = 0; i < labels[0].length; ++i) {
pq.add(new AbstractMap.SimpleEntry<>(i, labels[0][i]));
}
final ArrayList<Integer> topKLabels = new ArrayList<>();
int topKLabelsSize = Math.min(pq.size(), k);
for (int i = 0; i < topKLabelsSize; ++i) {
topKLabels.add(pq.poll().getKey());
}
return topKLabels;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,29 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
/** Utility for interacting with Interpreter in delegate tests. */
public abstract class InterpreterTestHelper {
/**
* Returns the number of nodes in the execution plan that are invoked per inference.
*
* <p>WARNING: This is an experimental API and subject to change.
*/
public static int executionPlanLength(Interpreter interpreter) {
return interpreter.getExecutionPlanLength();
}
}
@@ -0,0 +1,622 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link org.tensorflow.lite.NativeInterpreterWrapper}. */
// TODO(b/71818425): Generates model files dynamically.
@RunWith(JUnit4.class)
public final class NativeInterpreterWrapperTest {
private static final String FLOAT_MODEL_PATH =
"tensorflow/lite/java/src/testdata/add.bin";
private static final String INT_MODEL_PATH =
"tensorflow/lite/java/src/testdata/int32.bin";
private static final String LONG_MODEL_PATH =
"tensorflow/lite/java/src/testdata/int64.bin";
private static final String BYTE_MODEL_PATH =
"tensorflow/lite/java/src/testdata/uint8.bin";
private static final String STRING_MODEL_PATH =
"tensorflow/lite/java/src/testdata/string.bin";
private static final String STRING_SCALAR_MODEL_PATH =
"tensorflow/lite/java/src/testdata/string_scalar.bin";
private static final String INVALID_MODEL_PATH =
"tensorflow/lite/java/src/testdata/invalid_model.bin";
private static final String MODEL_WITH_CUSTOM_OP_PATH =
"tensorflow/lite/java/src/testdata/with_custom_op.lite";
private static final String NONEXISTING_MODEL_PATH =
"tensorflow/lite/java/src/testdata/nonexisting_model.bin";
@Before
public void setUp() {
TestInit.init();
}
@Test
public void testConstructor() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
assertThat(wrapper).isNotNull();
}
}
@Test
public void testConstructorWithOptions() {
InterpreterImpl.Options options = new InterpreterImpl.Options();
options.setNumThreads(2).setUseNNAPI(true);
try (NativeInterpreterWrapper wrapper =
new NativeInterpreterWrapper(FLOAT_MODEL_PATH, options)) {
assertThat(wrapper).isNotNull();
}
}
@Test
public void testConstructorWithInvalidModel() {
try {
@SuppressWarnings("unused")
NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(INVALID_MODEL_PATH);
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.containsMatch(
String.join(
"|",
"The model is not a valid Flatbuffer",
"does not encode a valid TensorFlow Lite model"));
}
}
@Test
public void testConstructorWithNonexistingModel() {
try {
@SuppressWarnings("unused")
NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(NONEXISTING_MODEL_PATH);
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().contains("Could not open");
}
}
@Test
public void testConstructorWithUnresolableCustomOp() {
try {
@SuppressWarnings("unused")
NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(MODEL_WITH_CUSTOM_OP_PATH);
fail();
} catch (IllegalStateException e) {
assertThat(e)
.hasMessageThat()
.contains("preparing tensor allocations: Encountered unresolved custom op: Assign");
} catch (IllegalArgumentException e) {
// As we could apply TfLite delegate by default, during which the prepration of this
// unresolved custom op could fail and this type of exception is thrown.
assertThat(e)
.hasMessageThat()
.containsMatch("Failed to apply .* delegate: Encountered unresolved custom op: Assign");
}
}
@Test
public void testRunWithFloat() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
float[] oneD = {1.23f, -6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
float[][][][] parsedOutputs = new float[2][8][8][3];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
wrapper.run(inputs, outputs);
float[] outputOneD = parsedOutputs[0][0][0];
float[] expected = {3.69f, -19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
}
@Test
public void testRunWithBufferOutput() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
float[] oneD = {1.23f, -6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
ByteBuffer parsedOutput =
ByteBuffer.allocateDirect(2 * 8 * 8 * 3 * 4).order(ByteOrder.nativeOrder());
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutput);
wrapper.run(inputs, outputs);
float[] outputOneD = {
parsedOutput.getFloat(0), parsedOutput.getFloat(4), parsedOutput.getFloat(8)
};
float[] expected = {3.69f, -19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
}
@Test
public void testRunWithInputsOfSameDims() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
float[] oneD = {1.23f, -6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
float[][][][] parsedOutputs = new float[2][8][8][3];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
wrapper.run(inputs, outputs);
float[] outputOneD = parsedOutputs[0][0][0];
float[] expected = {3.69f, -19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
parsedOutputs = new float[2][8][8][3];
outputs.put(0, parsedOutputs);
wrapper.run(inputs, outputs);
outputOneD = parsedOutputs[0][0][0];
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
}
@Test
public void testRunWithInt() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(INT_MODEL_PATH)) {
int[] oneD = {3, 7, -4};
int[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
int[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
int[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
int[][][][] parsedOutputs = new int[2][4][4][12];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
wrapper.run(inputs, outputs);
int[] outputOneD = parsedOutputs[0][0][0];
int[] expected = {3, 7, -4, 3, 7, -4, 3, 7, -4, 3, 7, -4};
assertThat(outputOneD).isEqualTo(expected);
}
}
@Test
public void testRunWithLong() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(LONG_MODEL_PATH)) {
long[] oneD = {-892834092L, 923423L, 2123918239018L};
long[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
long[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
long[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
long[][][][] parsedOutputs = new long[2][4][4][12];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
wrapper.run(inputs, outputs);
long[] outputOneD = parsedOutputs[0][0][0];
long[] expected = {
-892834092L,
923423L,
2123918239018L,
-892834092L,
923423L,
2123918239018L,
-892834092L,
923423L,
2123918239018L,
-892834092L,
923423L,
2123918239018L
};
assertThat(outputOneD).isEqualTo(expected);
}
}
@Test
public void testRunWithByte() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(BYTE_MODEL_PATH)) {
byte[] oneD = {(byte) 0xe0, 0x4f, (byte) 0xd0};
byte[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
byte[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
byte[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
int[] inputDims = {2, 8, 8, 3};
wrapper.resizeInput(0, inputDims);
byte[][][][] parsedOutputs = new byte[2][4][4][12];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
wrapper.run(inputs, outputs);
byte[] outputOneD = parsedOutputs[0][0][0];
byte[] expected = {
(byte) 0xe0,
0x4f,
(byte) 0xd0,
(byte) 0xe0,
0x4f,
(byte) 0xd0,
(byte) 0xe0,
0x4f,
(byte) 0xd0,
(byte) 0xe0,
0x4f,
(byte) 0xd0
};
assertThat(outputOneD).isEqualTo(expected);
}
}
@Test
public void testRunWithString() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(STRING_MODEL_PATH)) {
String[] oneD = {"s1", "s22", "s333"};
String[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
String[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
String[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
String[][][][] parsedOutputs = new String[2][4][4][12];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
wrapper.run(inputs, outputs);
String[] outputOneD = parsedOutputs[0][0][0];
String[] expected = {
"s1", "s22", "s333", "s1", "s22", "s333", "s1", "s22", "s333", "s1", "s22", "s333"
};
assertThat(outputOneD).isEqualTo(expected);
}
}
@Test
public void testRunWithScalarString() {
try (NativeInterpreterWrapper wrapper =
new NativeInterpreterWrapper(STRING_SCALAR_MODEL_PATH)) {
String[] parsedOutputs = new String[1];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
Object[] inputs = {"s1"};
wrapper.run(inputs, outputs);
String[] expected = {"s1"};
assertThat(parsedOutputs).isEqualTo(expected);
}
}
@Test
public void testRunWithString_supplementaryUnicodeCharacters() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(STRING_MODEL_PATH)) {
String[] oneD = {"\uD800\uDC01", "s22", "\ud841\udf0e"};
String[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
String[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
String[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
String[][][][] parsedOutputs = new String[2][4][4][12];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
wrapper.run(inputs, outputs);
String[] outputOneD = parsedOutputs[0][0][0];
String[] expected = {
"\uD800\uDC01", "s22", "\ud841\udf0e", "\uD800\uDC01", "s22", "\ud841\udf0e",
"\uD800\uDC01", "s22", "\ud841\udf0e", "\uD800\uDC01", "s22", "\ud841\udf0e"
};
assertThat(outputOneD).isEqualTo(expected);
}
}
@Test
public void testRunWithString_wrongShapeError() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(STRING_MODEL_PATH)) {
String[] oneD = {"s1", "s22", "s333"};
String[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
String[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
String[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
String[][][][] parsedOutputs = new String[2][4][4][10];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
try {
wrapper.run(inputs, outputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"Cannot copy from a TensorFlowLite tensor (output_tensor) with shape [2, 4, 4, 12] "
+ "to a Java object with shape [2, 4, 4, 10]");
}
}
}
@Test
public void testRunWithByteBufferHavingBytes() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(BYTE_MODEL_PATH)) {
ByteBuffer bbuf = ByteBuffer.allocateDirect(2 * 8 * 8 * 3);
bbuf.order(ByteOrder.nativeOrder());
bbuf.rewind();
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 8; ++j) {
for (int k = 0; k < 8; ++k) {
bbuf.put((byte) 0xe0);
bbuf.put((byte) 0x4f);
bbuf.put((byte) 0xd0);
}
}
}
bbuf.rewind();
Object[] inputs = {bbuf};
int[] inputDims = {2, 8, 8, 3};
wrapper.resizeInput(0, inputDims);
byte[][][][] parsedOutputs = new byte[2][4][4][12];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
wrapper.run(inputs, outputs);
byte[] outputOneD = parsedOutputs[0][0][0];
byte[] expected = {
(byte) 0xe0, 0x4f, (byte) 0xd0, (byte) 0xe0, 0x4f, (byte) 0xd0,
(byte) 0xe0, 0x4f, (byte) 0xd0, (byte) 0xe0, 0x4f, (byte) 0xd0
};
assertThat(outputOneD).isEqualTo(expected);
}
}
@Test
public void testRunWithByteBufferHavingFloats() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
ByteBuffer bbuf = ByteBuffer.allocateDirect(4 * 8 * 8 * 3 * 4);
bbuf.order(ByteOrder.nativeOrder());
bbuf.rewind();
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 8; ++j) {
for (int k = 0; k < 8; ++k) {
bbuf.putFloat(1.23f);
bbuf.putFloat(-6.54f);
bbuf.putFloat(7.81f);
}
}
}
Object[] inputs = {bbuf};
float[][][][] parsedOutputs = new float[4][8][8][3];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
try {
wrapper.run(inputs, outputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"Cannot copy to a TensorFlowLite tensor (input) with 768 bytes from a "
+ "Java Buffer with 3072 bytes.");
}
int[] inputDims = {4, 8, 8, 3};
wrapper.resizeInput(0, inputDims);
wrapper.run(inputs, outputs);
float[] outputOneD = parsedOutputs[0][0][0];
float[] expected = {3.69f, -19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
}
@Test
public void testRunWithByteBufferHavingWrongSize() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(BYTE_MODEL_PATH)) {
ByteBuffer bbuf = ByteBuffer.allocateDirect(2 * 7 * 8 * 3);
bbuf.order(ByteOrder.nativeOrder());
Object[] inputs = {bbuf};
Map<Integer, Object> outputs = new HashMap<>();
ByteBuffer parsedOutput = ByteBuffer.allocateDirect(2 * 7 * 8 * 3);
outputs.put(0, parsedOutput);
try {
wrapper.run(inputs, outputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"Cannot copy to a TensorFlowLite tensor (input) with 192 bytes from a "
+ "Java Buffer with 336 bytes.");
}
}
}
@Test
public void testRunWithWrongInputType() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
int[] oneD = {4, 3, 9};
int[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
int[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
int[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
int[][][][] parsedOutputs = new int[2][8][8][3];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
try {
wrapper.run(inputs, outputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"Cannot convert between a TensorFlowLite tensor with type FLOAT32 and a Java "
+ "object of type [[[[I (which is compatible with the TensorFlowLite type "
+ "INT32)");
}
}
}
@Test
public void testRunAfterClose() {
NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH);
wrapper.close();
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
float[][][][] parsedOutputs = new float[2][8][8][3];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
try {
wrapper.run(inputs, outputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().contains("Internal error: Found invalid handle");
}
}
@Test
public void testRunWithEmptyInputs() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
try {
Object[] inputs = {};
wrapper.run(inputs, null);
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().contains("Inputs should not be null or empty.");
}
}
}
@Test
public void testRunWithWrongInputSize() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD, fourD};
float[][][][] parsedOutputs = new float[2][8][8][3];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
try {
wrapper.run(inputs, outputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().contains("Invalid input Tensor index: 1");
}
}
}
@Test
public void testRunWithWrongInputNumOfDims() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
Object[] inputs = {threeD};
float[][][][] parsedOutputs = new float[2][8][8][3];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
try {
wrapper.run(inputs, outputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"Cannot copy from a TensorFlowLite tensor (output) with shape [8, 7, 3] to a "
+ "Java object with shape [2, 8, 8, 3].");
}
}
}
@Test
public void testRunWithWrongInputDims() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
float[][][][] parsedOutputs = new float[2][8][8][3];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
try {
wrapper.run(inputs, outputs);
fail();
} catch (IllegalArgumentException e) {
assertThat(e)
.hasMessageThat()
.contains(
"Cannot copy from a TensorFlowLite tensor (output) with shape [2, 8, 7, 3] to a "
+ "Java object with shape [2, 8, 8, 3].");
}
}
}
@Test
public void testGetInferenceLatency() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
float[][][][] parsedOutputs = new float[2][8][8][3];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
wrapper.run(inputs, outputs);
assertThat(wrapper.getLastNativeInferenceDurationNanoseconds()).isGreaterThan(0L);
}
}
@Test
public void testGetInferenceLatencyWithNewWrapper() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
assertThat(wrapper.getLastNativeInferenceDurationNanoseconds()).isNull();
}
}
@Test
public void testGetLatencyAfterFailedInference() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
float[][][][] parsedOutputs = new float[2][8][8][3];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutputs);
try {
wrapper.run(inputs, outputs);
fail();
} catch (IllegalArgumentException e) {
// Expected.
}
assertThat(wrapper.getLastNativeInferenceDurationNanoseconds()).isNull();
}
}
@Test
public void testGetInputDims() {
try (NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(FLOAT_MODEL_PATH)) {
int[] expectedDims = {1, 8, 8, 3};
assertThat(wrapper.getInputTensor(0).shape()).isEqualTo(expectedDims);
}
}
}
@@ -0,0 +1,69 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.tensorflow.lite.nnapi.NnApiDelegate;
import org.tensorflow.lite.nnapi.NnApiDelegateImpl;
/** Unit tests for {@link org.tensorflow.lite.nnapi.NnApiDelegate}. */
@RunWith(JUnit4.class)
public final class NnApiDelegateNativeTest {
@Before
public void setUp() throws Exception {
TestInit.init();
}
private static native boolean getAllowFp16Option(long delegateHandle);
private static native boolean getDisallowNnapiCpuOption(long delegateHandle);
private static native String getModelTokenOption(long delegateHandle);
@Test
public void testCorrectOptions() {
// Create NNAPI delegate and DelegateImpl.
NnApiDelegate.Options options = new NnApiDelegate.Options();
options.setAllowFp16(false).setUseNnapiCpu(false).setModelToken("ABC");
NnApiDelegateImpl delegateImpl = new NnApiDelegateImpl(options);
NnApiDelegate nnapiDelegate = new NnApiDelegate(options);
// Mock the Interpreter to return specific DelegateImpl.
InterpreterFactoryApi mockInterpreterFactory = mock(InterpreterFactoryApi.class);
when(mockInterpreterFactory.createNnApiDelegateImpl(eq(options))).thenReturn(delegateImpl);
nnapiDelegate.initWithInterpreterFactoryApi(mockInterpreterFactory);
// Verify mocking called exactly once.
verify(mockInterpreterFactory, times(1)).createNnApiDelegateImpl(options);
// Verify constructed delegate options is as expected with no errors.
assertThat(nnapiDelegate.getNnapiErrno()).isEqualTo(0);
assertThat(getAllowFp16Option(delegateImpl.getNativeHandle())).isFalse();
assertThat(getDisallowNnapiCpuOption(delegateImpl.getNativeHandle())).isTrue();
assertThat(getModelTokenOption(delegateImpl.getNativeHandle())).isEqualTo("ABC");
}
}
@@ -0,0 +1,57 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
/**
* This class is used to conditionally enable certain tests that depend on experimental or
* deprecated features of TF Lite that may not be univerally portable to all TF Lite
* implementations.
*/
public final class SupportedFeatures {
private SupportedFeatures() {}
/**
* True if the TF Lite implementation supports `setCanceled`.
*
* @see Interpreter#setCanceled
*/
public static native boolean supportsCancellation();
/**
* True if the TF Lite implementation supports the XNNPACK delegate.
*
* @see Interpreter#setUseXNNPACK
*/
public static native boolean supportsXnnpack();
/**
* True if the TF Lite implementation supports using reduced 16-bit floating point precision for
* operations that are specified as 32-bit in the model.
*
* @see Interpreter#setAllowFp16PrecisionForFp32
*/
public static native boolean supportsAllowFp16PrecisionForFp32();
/**
* True if the TF Lite implementation supports resizing tensor dimensions whose size is not -1,
* which is used to indicate dynamic sizes. This affects whether you can call `runSignature` with
* inputs/outputs that are a different shape than the model expects.
*
* @see Interpreter#runSignature
*/
public static native boolean supportsNonstrictResize();
}
@@ -0,0 +1,72 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import java.io.File;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link org.tensorflow.lite.TensorFlowLite} when the native lib is loaded but the
* necessary native methods are unavailable.
*/
@RunWith(JUnit4.class)
public final class TensorFlowLiteInvalidNativeLibTest {
@Test
public void testInit() {
try {
TensorFlowLite.init();
fail();
} catch (UnsatisfiedLinkError e) {
assertThat(e).hasMessageThat().contains("Failed to load native TensorFlow Lite methods");
}
}
@Test
public void testInterpreter() {
try {
new Interpreter(new File("path/does/not/matter.tflite"));
fail();
} catch (UnsatisfiedLinkError e) {
assertThat(e).hasMessageThat().contains("Failed to load native TensorFlow Lite methods");
}
}
@Test
public void testInterpreterApi() {
try {
InterpreterApi.create(new File("path/does/not/matter.tflite"), null);
fail();
} catch (UnsatisfiedLinkError e) {
assertThat(e).hasMessageThat().contains("Failed to load native TensorFlow Lite methods");
}
}
@Test
@SuppressWarnings("deprecation") // This is a test of the deprecated InterpreterFactory class.
public void testInterpreterFactory() {
try {
new InterpreterFactory().create(new File("path/does/not/matter.tflite"), null);
fail();
} catch (UnsatisfiedLinkError e) {
assertThat(e).hasMessageThat().contains("Failed to load native TensorFlow Lite methods");
}
}
}
@@ -0,0 +1,77 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import java.io.File;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link org.tensorflow.lite.TensorFlowLite} when no native lib is available. */
@RunWith(JUnit4.class)
public final class TensorFlowLiteNoNativeLibTest {
@Test
public void testCheckInit() {
try {
TensorFlowLite.init();
fail();
} catch (UnsatisfiedLinkError e) {
assertThat(e).hasMessageThat().contains("Failed to load native TensorFlow Lite methods");
assertThat(e).hasMessageThat().contains("no tensorflowlite_jni");
assertThat(e).hasMessageThat().contains("in java.library.path");
}
}
@Test
public void testInterpreter() {
try {
new Interpreter(new File("path/does/not/matter.tflite"));
fail();
} catch (UnsatisfiedLinkError e) {
assertThat(e).hasMessageThat().contains("Failed to load native TensorFlow Lite methods");
assertThat(e).hasMessageThat().contains("no tensorflowlite_jni");
assertThat(e).hasMessageThat().contains("in java.library.path");
}
}
@Test
public void testInterpreterApi() {
try {
InterpreterApi.create(new File("path/does/not/matter.tflite"), null);
fail();
} catch (UnsatisfiedLinkError e) {
assertThat(e).hasMessageThat().contains("Failed to load native TensorFlow Lite methods");
assertThat(e).hasMessageThat().contains("no tensorflowlite_jni");
assertThat(e).hasMessageThat().contains("in java.library.path");
}
}
@Test
@SuppressWarnings("deprecation") // This is a test of the deprecated InterpreterFactory class.
public void testInterpreterFactory() {
try {
new InterpreterFactory().create(new File("path/does/not/matter.tflite"), null);
fail();
} catch (UnsatisfiedLinkError e) {
assertThat(e).hasMessageThat().contains("Failed to load native TensorFlow Lite methods");
assertThat(e).hasMessageThat().contains("no tensorflowlite_jni");
assertThat(e).hasMessageThat().contains("in java.library.path");
}
}
}
@@ -0,0 +1,121 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.tensorflow.lite.InterpreterApi.Options.TfLiteRuntime;
// LINT.IfChange
/** Unit tests for {@link org.tensorflow.lite.TensorFlowLite}. */
@RunWith(JUnit4.class)
public final class TensorFlowLiteTest {
@Before
public void setUp() {
TestInit.init();
}
@Test
@SuppressWarnings("deprecation")
public void testVersion() {
assertThat(TensorFlowLite.version()).isEqualTo("3");
}
@Test
public void testSchemaVersionForDefaultRuntime() {
try {
assertThat(TensorFlowLite.schemaVersion()).isEqualTo("3");
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("org.tensorflow");
assertThat(e).hasMessageThat().contains("tensorflow-lite");
assertThat(e).hasMessageThat().doesNotContain("com.google.android.gms");
assertThat(e).hasMessageThat().doesNotContain("play-services-tflite-java");
}
}
@Test
public void testSchemaVersionForSpecifiedRuntime() {
assertThat(TensorFlowLite.schemaVersion(TfLiteRuntime.PREFER_SYSTEM_OVER_APPLICATION))
.isEqualTo("3");
try {
assertThat(TensorFlowLite.schemaVersion(TfLiteRuntime.FROM_APPLICATION_ONLY)).isEqualTo("3");
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("org.tensorflow");
assertThat(e).hasMessageThat().contains("tensorflow-lite");
assertThat(e).hasMessageThat().doesNotContain("com.google.android.gms");
assertThat(e).hasMessageThat().doesNotContain("play-services-tflite-java");
assertThat(e).hasMessageThat().contains("org.tensorflow.lite.TensorFlowLite#schemaVersion");
}
try {
assertThat(TensorFlowLite.schemaVersion(TfLiteRuntime.FROM_SYSTEM_ONLY)).isEqualTo("3");
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("com.google.android.gms");
assertThat(e).hasMessageThat().contains("play-services-tflite-java");
assertThat(e).hasMessageThat().doesNotContain("org.tensorflow:tensorflow-lite");
assertThat(e).hasMessageThat().contains("org.tensorflow.lite.TensorFlowLite#schemaVersion");
}
}
@Test
public void testRuntimeVersionForDefaultRuntime() {
// Unlike the schema version, which should almost never change, the runtime version can change
// with some frequency, so simply ensure that it's non-empty and doesn't fail.
try {
assertThat(TensorFlowLite.runtimeVersion()).isNotEmpty();
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("org.tensorflow");
assertThat(e).hasMessageThat().contains("tensorflow-lite");
assertThat(e).hasMessageThat().doesNotContain("com.google.android.gms");
assertThat(e).hasMessageThat().doesNotContain("play-services-tflite-java");
assertThat(e).hasMessageThat().contains("org.tensorflow.lite.TensorFlowLite#runtimeVersion");
}
}
@Test
public void testRuntimeVersionForSpecifiedRuntime() {
// Unlike the schema version, which should almost never change, the runtime version can change
// with some frequency, so simply ensure that it's non-empty and doesn't fail,
// or that if it fails due to not having the appropriate runtime linked in,
// it reports an appropriate error message.
assertThat(TensorFlowLite.runtimeVersion(TfLiteRuntime.PREFER_SYSTEM_OVER_APPLICATION))
.isNotEmpty();
try {
assertThat(TensorFlowLite.runtimeVersion(TfLiteRuntime.FROM_APPLICATION_ONLY)).isNotEmpty();
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("org.tensorflow");
assertThat(e).hasMessageThat().contains("tensorflow-lite");
assertThat(e).hasMessageThat().doesNotContain("com.google.android.gms");
assertThat(e).hasMessageThat().doesNotContain("play-services-tflite-java");
assertThat(e).hasMessageThat().contains("org.tensorflow.lite.TensorFlowLite#runtimeVersion");
}
try {
assertThat(TensorFlowLite.runtimeVersion(TfLiteRuntime.FROM_SYSTEM_ONLY)).isNotEmpty();
} catch (IllegalStateException e) {
assertThat(e).hasMessageThat().contains("com.google.android.gms");
assertThat(e).hasMessageThat().contains("play-services-tflite-java");
assertThat(e).hasMessageThat().doesNotContain("org.tensorflow:tensorflow-lite");
assertThat(e).hasMessageThat().contains("org.tensorflow.lite.TensorFlowLite#runtimeVersion");
}
}
}
// LINT.ThenChange(../../../../../../BUILD:TensorFlowLiteTestShardCount)
@@ -0,0 +1,512 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ReadOnlyBufferException;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.tensorflow.lite.Tensor.QuantizationParams;
/** Unit tests for {@link TensorImpl}. */
@RunWith(JUnit4.class)
public final class TensorTest {
private static final String MODEL_PATH = "tensorflow/lite/java/src/testdata/add.bin";
private static final String INT_MODEL_PATH =
"tensorflow/lite/java/src/testdata/int32.bin";
private static final String LONG_MODEL_PATH =
"tensorflow/lite/java/src/testdata/int64.bin";
private static final String STRING_MODEL_PATH =
"tensorflow/lite/java/src/testdata/string.bin";
private static final String QUANTIZED_MODEL_PATH =
"tensorflow/lite/java/src/testdata/quantized.bin";
private NativeInterpreterWrapper wrapper;
private TensorImpl tensor;
@Before
public void setUp() {
TestInit.init();
wrapper = new NativeInterpreterWrapper(MODEL_PATH);
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
Object[] inputs = {fourD};
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, new float[2][8][8][3]);
wrapper.run(inputs, outputs);
tensor = wrapper.getOutputTensor(0);
assertThat(tensor.index()).isGreaterThan(-1);
}
@After
public void tearDown() {
wrapper.close();
}
@Test
public void testBasic() throws Exception {
assertThat(tensor).isNotNull();
int[] expectedShape = {2, 8, 8, 3};
assertThat(tensor.shape()).isEqualTo(expectedShape);
assertThat(tensor.shapeSignature()).isEqualTo(expectedShape);
assertThat(tensor.dataType()).isEqualTo(DataType.FLOAT32);
assertThat(tensor.numBytes()).isEqualTo(2 * 8 * 8 * 3 * 4);
assertThat(tensor.numElements()).isEqualTo(2 * 8 * 8 * 3);
assertThat(tensor.numDimensions()).isEqualTo(4);
assertThat(tensor.name()).isEqualTo("output");
assertThat(tensor.asReadOnlyBuffer().capacity()).isEqualTo(tensor.numBytes());
}
@Test
public void testCopyTo() {
float[][][][] parsedOutputs = new float[2][8][8][3];
tensor.copyTo(parsedOutputs);
float[] outputOneD = parsedOutputs[0][0][0];
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
@Test
public void testCopyToNull() {
assertThrows(IllegalArgumentException.class, () -> tensor.copyTo(null));
}
@Test
public void testModifyReadOnlyBuffer() {
assertThrows(ReadOnlyBufferException.class, () -> tensor.asReadOnlyBuffer().putFloat(0.f));
}
@Test
public void testCopyToByteBuffer() {
ByteBuffer parsedOutput =
ByteBuffer.allocateDirect(2 * 8 * 8 * 3 * 4).order(ByteOrder.nativeOrder());
tensor.copyTo(parsedOutput);
assertThat(parsedOutput.position()).isEqualTo(2 * 8 * 8 * 3 * 4);
float[] outputOneD = {
parsedOutput.getFloat(0), parsedOutput.getFloat(4), parsedOutput.getFloat(8)
};
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
@Test
public void testCopyToLargerByteBuffer() {
// Allocate a ByteBuffer that is larger than the Tensor, and ensure we can copy to it.
ByteBuffer parsedOutput =
ByteBuffer.allocateDirect(10 * 2 * 8 * 8 * 3 * 4).order(ByteOrder.nativeOrder());
tensor.copyTo(parsedOutput);
assertThat(parsedOutput.position()).isEqualTo(2 * 8 * 8 * 3 * 4);
float[] outputOneD = {
parsedOutput.getFloat(0), parsedOutput.getFloat(4), parsedOutput.getFloat(8)
};
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
@Test
public void testCopyToByteBufferAsFloatBuffer() {
FloatBuffer parsedOutput =
ByteBuffer.allocateDirect(2 * 8 * 8 * 3 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
tensor.copyTo(parsedOutput);
assertThat(parsedOutput.position()).isEqualTo(2 * 8 * 8 * 3);
float[] outputOneD = {parsedOutput.get(0), parsedOutput.get(1), parsedOutput.get(2)};
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
@Test
public void testCopyToFloatBuffer() {
FloatBuffer parsedOutput = FloatBuffer.allocate(2 * 8 * 8 * 3);
tensor.copyTo(parsedOutput);
assertThat(parsedOutput.position()).isEqualTo(2 * 8 * 8 * 3);
float[] outputOneD = {parsedOutput.get(0), parsedOutput.get(1), parsedOutput.get(2)};
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
@Test
public void testCopyToIntBuffer() {
wrapper = new NativeInterpreterWrapper(INT_MODEL_PATH);
tensor = wrapper.getOutputTensor(0);
IntBuffer parsedOutput = IntBuffer.allocate(1 * 4 * 4 * 12);
tensor.copyTo(parsedOutput);
assertThat(parsedOutput.position()).isEqualTo(1 * 4 * 4 * 12);
}
@Test
public void testCopyToLongBuffer() {
wrapper = new NativeInterpreterWrapper(LONG_MODEL_PATH);
tensor = wrapper.getOutputTensor(0);
LongBuffer parsedOutput = LongBuffer.allocate(1 * 4 * 4 * 12);
tensor.copyTo(parsedOutput);
assertThat(parsedOutput.position()).isEqualTo(1 * 4 * 4 * 12);
}
@Test
public void testCopyToInvalidByteBuffer() {
ByteBuffer parsedOutput = ByteBuffer.allocateDirect(3 * 4).order(ByteOrder.nativeOrder());
assertThrows(IllegalArgumentException.class, () -> tensor.copyTo(parsedOutput));
}
@Test
public void testCopyToInvalidTypedBuffer() {
IntBuffer parsedOutput = IntBuffer.allocate(2 * 8 * 8 * 3);
assertThrows(IllegalArgumentException.class, () -> tensor.copyTo(parsedOutput));
}
@Test
public void testCopyToWrongType() {
int[][][][] parsedOutputs = new int[2][8][8][3];
IllegalArgumentException e =
assertThrows(IllegalArgumentException.class, () -> tensor.copyTo(parsedOutputs));
assertThat(e)
.hasMessageThat()
.contains(
"Cannot convert between a TensorFlowLite tensor with type FLOAT32 and a Java object "
+ "of type [[[[I (which is compatible with the TensorFlowLite type INT32)");
}
@Test
public void testCopyToWrongShape() {
float[][][][] parsedOutputs = new float[1][8][8][3];
IllegalArgumentException e =
assertThrows(IllegalArgumentException.class, () -> tensor.copyTo(parsedOutputs));
assertThat(e)
.hasMessageThat()
.contains(
"Cannot copy from a TensorFlowLite tensor (output) with shape [2, 8, 8, 3] "
+ "to a Java object with shape [1, 8, 8, 3].");
}
@Test
public void testSetTo() {
float[][][][] input = new float[2][8][8][3];
float[][][][] output = new float[2][8][8][3];
// Assign from array.
input[0][0][0][0] = 2.0f;
tensor.setTo(input);
tensor.copyTo(output);
assertThat(output[0][0][0][0]).isEqualTo(2.0f);
// Assign from direct ByteBuffer.
ByteBuffer inputByteBuffer =
ByteBuffer.allocateDirect(2 * 8 * 8 * 3 * 4).order(ByteOrder.nativeOrder());
inputByteBuffer.putFloat(0, 3.0f);
tensor.setTo(inputByteBuffer);
tensor.copyTo(output);
assertThat(output[0][0][0][0]).isEqualTo(3.0f);
// Assign from FloatBuffer view of ByteBuffer.
inputByteBuffer.rewind();
FloatBuffer inputFloatBuffer = inputByteBuffer.asFloatBuffer();
inputFloatBuffer.put(0, 5.0f);
tensor.setTo(inputFloatBuffer);
tensor.copyTo(output);
assertThat(output[0][0][0][0]).isEqualTo(5.0f);
// Assign from (non-direct) FloatBuffer.
inputFloatBuffer = FloatBuffer.allocate(2 * 8 * 8 * 3);
inputFloatBuffer.put(0, 5.0f);
inputFloatBuffer.rewind();
tensor.setTo(inputFloatBuffer);
tensor.copyTo(output);
assertThat(output[0][0][0][0]).isEqualTo(5.0f);
// Assign from scalar float.
wrapper.resizeInput(0, new int[0]);
wrapper.allocateTensors();
float scalar = 5.0f;
tensor.setTo(scalar);
FloatBuffer outputScalar = FloatBuffer.allocate(1);
tensor.copyTo(outputScalar);
assertThat(outputScalar.get(0)).isEqualTo(5.0f);
// Assign from boxed scalar Float.
Float boxedScalar = 9.0f;
tensor.setTo(boxedScalar);
outputScalar = FloatBuffer.allocate(1);
tensor.copyTo(outputScalar);
assertThat(outputScalar.get(0)).isEqualTo(9.0f);
}
@Test
public void testSetToInt() {
wrapper = new NativeInterpreterWrapper(INT_MODEL_PATH);
tensor = wrapper.getOutputTensor(0);
int[][][][] input = new int[1][4][4][12];
int[][][][] output = new int[1][4][4][12];
// Assign from array.
input[0][0][0][0] = 2;
tensor.setTo(input);
tensor.copyTo(output);
assertThat(output[0][0][0][0]).isEqualTo(2);
// Assign from direct ByteBuffer.
ByteBuffer inputByteBuffer =
ByteBuffer.allocateDirect(1 * 4 * 4 * 12 * 4).order(ByteOrder.nativeOrder());
inputByteBuffer.putInt(0, 3);
tensor.setTo(inputByteBuffer);
tensor.copyTo(output);
assertThat(output[0][0][0][0]).isEqualTo(3);
// Assign from IntBuffer view of ByteBuffer.
inputByteBuffer.rewind();
IntBuffer inputIntBuffer = inputByteBuffer.asIntBuffer();
inputIntBuffer.put(0, 5);
tensor.setTo(inputIntBuffer);
tensor.copyTo(output);
assertThat(output[0][0][0][0]).isEqualTo(5);
// Assign from (non-direct) IntBuffer.
inputIntBuffer = IntBuffer.allocate(1 * 4 * 4 * 12);
inputIntBuffer.put(0, 5);
tensor.setTo(inputIntBuffer);
tensor.copyTo(output);
assertThat(output[0][0][0][0]).isEqualTo(5);
}
@Test
public void testSetToLong() {
wrapper = new NativeInterpreterWrapper(LONG_MODEL_PATH);
tensor = wrapper.getOutputTensor(0);
long[][][][] input = new long[1][4][4][12];
long[][][][] output = new long[1][4][4][12];
// Assign from array.
input[0][0][0][0] = 2;
tensor.setTo(input);
tensor.copyTo(output);
assertThat(output[0][0][0][0]).isEqualTo(2);
// Assign from direct ByteBuffer.
ByteBuffer inputByteBuffer =
ByteBuffer.allocateDirect(1 * 4 * 4 * 12 * 8).order(ByteOrder.nativeOrder());
inputByteBuffer.putLong(0, 3);
tensor.setTo(inputByteBuffer);
tensor.copyTo(output);
assertThat(output[0][0][0][0]).isEqualTo(3);
// Assign from LongBuffer view of ByteBuffer.
inputByteBuffer.rewind();
LongBuffer inputLongBuffer = inputByteBuffer.asLongBuffer();
inputLongBuffer.put(0, 5);
tensor.setTo(inputLongBuffer);
tensor.copyTo(output);
assertThat(output[0][0][0][0]).isEqualTo(5);
// Assign from (non-direct) LongBuffer.
inputLongBuffer = LongBuffer.allocate(1 * 4 * 4 * 12);
inputLongBuffer.put(0, 5);
tensor.setTo(inputLongBuffer);
tensor.copyTo(output);
assertThat(output[0][0][0][0]).isEqualTo(5);
}
@Test
public void testSetToNull() {
assertThrows(IllegalArgumentException.class, () -> tensor.setTo(null));
}
@Test
public void testSetToFloatBuffer() {
float[] input = new float[2 * 8 * 8 * 3];
float[] output = new float[2 * 8 * 8 * 3];
FloatBuffer inputFloatBuffer = FloatBuffer.wrap(input);
FloatBuffer outputFloatBuffer = FloatBuffer.wrap(output);
input[0] = 2.0f;
input[2 * 8 * 8 * 3 - 1] = 7.0f;
tensor.setTo(inputFloatBuffer);
tensor.copyTo(outputFloatBuffer);
assertThat(output[0]).isEqualTo(2.0f);
assertThat(output[2 * 8 * 8 * 3 - 1]).isEqualTo(7.0f);
}
@Test
public void testSetToInvalidBuffer() {
Buffer[] inputs = {
ByteBuffer.allocateDirect(3 * 4).order(ByteOrder.nativeOrder()),
FloatBuffer.allocate(3),
IntBuffer.allocate(3),
LongBuffer.allocate(3)
};
for (Buffer input : inputs) {
assertThrows(IllegalArgumentException.class, () -> tensor.setTo(input));
}
}
@Test
public void testGetInputShapeIfDifferent() {
ByteBuffer bytBufferInput = ByteBuffer.allocateDirect(3 * 4).order(ByteOrder.nativeOrder());
assertThat(tensor.getInputShapeIfDifferent(bytBufferInput)).isNull();
float[][][][] sameShapeInput = new float[2][8][8][3];
assertThat(tensor.getInputShapeIfDifferent(sameShapeInput)).isNull();
float[][][][] differentShapeInput = new float[1][8][8][3];
assertThat(tensor.getInputShapeIfDifferent(differentShapeInput))
.isEqualTo(new int[] {1, 8, 8, 3});
Float differentShapeInputScalar = 5.0f;
assertThat(tensor.getInputShapeIfDifferent(differentShapeInputScalar)).isEqualTo(new int[] {});
}
@Test
public void testDataTypeOf() {
float[] testEmptyArray = {};
DataType dataType = tensor.dataTypeOf(testEmptyArray);
assertThat(dataType).isEqualTo(DataType.FLOAT32);
float[] testFloatArray = {0.783f, 0.251f};
dataType = tensor.dataTypeOf(testFloatArray);
assertThat(dataType).isEqualTo(DataType.FLOAT32);
float[][] testMultiDimArray = {testFloatArray, testFloatArray, testFloatArray};
dataType = tensor.dataTypeOf(testMultiDimArray);
assertThat(dataType).isEqualTo(DataType.FLOAT32);
FloatBuffer testFloatBuffer = FloatBuffer.allocate(1);
dataType = tensor.dataTypeOf(testFloatBuffer);
assertThat(dataType).isEqualTo(DataType.FLOAT32);
float testFloat = 1.0f;
dataType = tensor.dataTypeOf(testFloat);
assertThat(dataType).isEqualTo(DataType.FLOAT32);
double[] testDoubleArray = {0.783, 0.251};
IllegalArgumentException e =
assertThrows(IllegalArgumentException.class, () -> tensor.dataTypeOf(testDoubleArray));
assertThat(e).hasMessageThat().contains("cannot resolve DataType of");
Float[] testBoxedArray = {0.783f, 0.251f};
e = assertThrows(IllegalArgumentException.class, () -> tensor.dataTypeOf(testBoxedArray));
assertThat(e).hasMessageThat().contains("cannot resolve DataType of [Ljava.lang.Float;");
}
@Test
public void testNumDimensions() {
int scalar = 1;
assertThat(TensorImpl.computeNumDimensions(scalar)).isEqualTo(0);
int[][] array = {{2, 4}, {1, 9}};
assertThat(TensorImpl.computeNumDimensions(array)).isEqualTo(2);
int[] emptyArray = {};
IllegalArgumentException e =
assertThrows(
IllegalArgumentException.class, () -> TensorImpl.computeNumDimensions(emptyArray));
assertThat(e).hasMessageThat().contains("Array lengths cannot be 0.");
}
@Test
public void testNumElements() {
int[] scalarShape = {};
assertThat(TensorImpl.computeNumElements(scalarShape)).isEqualTo(1);
int[] vectorShape = {3};
assertThat(TensorImpl.computeNumElements(vectorShape)).isEqualTo(3);
int[] matrixShape = {3, 4};
assertThat(TensorImpl.computeNumElements(matrixShape)).isEqualTo(12);
int[] degenerateShape = {3, 4, 0};
assertThat(TensorImpl.computeNumElements(degenerateShape)).isEqualTo(0);
}
@Test
public void testFillShape() {
int[][][] array = {{{23}, {14}, {87}}, {{12}, {42}, {31}}};
int num = TensorImpl.computeNumDimensions(array);
int[] shape = new int[num];
TensorImpl.fillShape(array, 0, shape);
assertThat(num).isEqualTo(3);
assertThat(shape[0]).isEqualTo(2);
assertThat(shape[1]).isEqualTo(3);
assertThat(shape[2]).isEqualTo(1);
}
@Test
public void testCopyToScalarUnsupported() {
wrapper.resizeInput(0, new int[0]);
wrapper.allocateTensors();
tensor.setTo(5.0f);
Float outputScalar = 7.0f;
assertThrows(IllegalArgumentException.class, () -> tensor.copyTo(outputScalar));
}
@Test
public void testUseAfterClose() {
tensor.close();
assertThrows(IllegalArgumentException.class, () -> tensor.numBytes());
}
@Test
public void testQuantizationParameters_floatModel() {
QuantizationParams quantizationParams = tensor.quantizationParams();
float scale = quantizationParams.getScale();
long zeroPoint = quantizationParams.getZeroPoint();
assertThat(scale).isWithin(1e-6f).of(0.0f);
assertThat(zeroPoint).isEqualTo(0);
}
@Test
public void testQuantizationParameters_quantizedModel() {
wrapper = new NativeInterpreterWrapper(QUANTIZED_MODEL_PATH);
tensor = wrapper.getOutputTensor(0);
QuantizationParams quantizationParams = tensor.quantizationParams();
float scale = quantizationParams.getScale();
long zeroPoint = quantizationParams.getZeroPoint();
assertThat(scale).isWithin(1e-6f).of(0.25f);
assertThat(zeroPoint).isEqualTo(127);
}
@Test
public void testByteArrayStringTensorInput() {
NativeInterpreterWrapper wrapper = new NativeInterpreterWrapper(STRING_MODEL_PATH);
// Test input of string[1]
wrapper.resizeInput(0, new int[] {1});
TensorImpl stringTensor = wrapper.getInputTensor(0);
byte[][] bytes1DStringData = new byte[][] {{0x00, 0x01, 0x02, 0x03}};
stringTensor.setTo(bytes1DStringData);
byte[][] byteArray = new byte[][] {new byte[1]};
assertThat(stringTensor.dataTypeOf(byteArray)).isEqualTo(DataType.STRING);
assertThat(stringTensor.shape()).isEqualTo(new int[] {1});
// Test input of scalar string
wrapper.resizeInput(0, new int[] {});
byte[] bytesStringData = new byte[] {0x00, 0x01, 0x02, 0x03};
stringTensor.setTo(bytesStringData);
assertThat(stringTensor.shape()).isEqualTo(new int[] {});
}
}
@@ -0,0 +1,51 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite;
import java.util.logging.Logger;
/** Utilities for initializing TF Lite for tests. */
public final class TestInit {
private static final Logger logger = Logger.getLogger(TestInit.class.getName());
private TestInit() {}
private static boolean initialized;
/**
* Initialize TF Lite for tests. In tests, this should be called before executing any native code
* that uses TF Lite. It may, for example, dynamically load the TF Lite library.
*/
public static void init() {
if (!initialized) {
try {
System.loadLibrary("tensorflowlite_test_jni");
logger.info("Loaded native library for tests: tensorflowlite_test_jni");
} catch (UnsatisfiedLinkError e) {
logger.info("Didn't load native library for tests: tensorflowlite_test_jni");
try {
System.loadLibrary("tensorflowlite_stable_test_jni");
logger.info("Loaded native library for tests: tensorflowlite_stable_test_jni");
} catch (UnsatisfiedLinkError e2) {
logger.info("Didn't load native library for tests: tensorflowlite_stable_test_jni");
}
}
initTfLiteForTest();
initialized = true;
}
}
private static native void initTfLiteForTest();
}
@@ -0,0 +1,106 @@
/* Copyright 2019 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.
==============================================================================*/
package org.tensorflow.lite;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.EnumSet;
import javax.imageio.ImageIO;
/** Utility for interacting with test-specific data. */
public abstract class TestUtils {
private static final float DEFAULT_IMAGE_MEAN = 127.5f;
private static final float DEFAULT_IMAGE_STD = 127.5f;
public static MappedByteBuffer getTestFileAsBuffer(String path) {
try (FileChannel fileChannel =
(FileChannel)
Files.newByteChannel(new File(path).toPath(), EnumSet.of(StandardOpenOption.READ))) {
return fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static boolean supportsFilePaths() {
return true;
}
public static ByteBuffer getTestImageAsByteBuffer(String path) {
File imageFile = new File(path);
try {
BufferedImage image = ImageIO.read(imageFile);
return toByteBuffer(image);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static ByteBuffer getTestImageAsFloatByteBuffer(String path) {
File imageFile = new File(path);
try {
BufferedImage image = ImageIO.read(imageFile);
return toFloatByteBuffer(image);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static ByteBuffer toByteBuffer(BufferedImage image) {
ByteBuffer imgData =
ByteBuffer.allocateDirect(image.getHeight() * image.getWidth() * 3)
.order(ByteOrder.nativeOrder());
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int val = image.getRGB(x, y);
imgData.put((byte) ((val >> 16) & 0xFF));
imgData.put((byte) ((val >> 8) & 0xFF));
imgData.put((byte) (val & 0xFF));
}
}
return imgData;
}
private static ByteBuffer toFloatByteBuffer(BufferedImage image) {
return toFloatByteBuffer(image, DEFAULT_IMAGE_MEAN, DEFAULT_IMAGE_STD);
}
private static ByteBuffer toFloatByteBuffer(
BufferedImage image, float imageMean, float imageStd) {
ByteBuffer imgData =
ByteBuffer.allocateDirect(image.getHeight() * image.getWidth() * 3 * 4)
.order(ByteOrder.nativeOrder());
for (int y = 0; y < image.getHeight(); y++) {
for (int x = 0; x < image.getWidth(); x++) {
int pixelValue = image.getRGB(x, y);
imgData.putFloat((((pixelValue >> 16) & 0xFF) - imageMean) / imageStd);
imgData.putFloat((((pixelValue >> 8) & 0xFF) - imageMean) / imageStd);
imgData.putFloat(((pixelValue & 0xFF) - imageMean) / imageStd);
}
}
return imgData;
}
private TestUtils() {}
}
@@ -0,0 +1,34 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite.gpu;
import static com.google.common.truth.Truth.assertThat;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link org.tensorflow.lite.gpu.CompatibilityList}. */
@RunWith(JUnit4.class)
public final class CompatibilityListTest {
@Test
public void testBasic() throws Exception {
try (CompatibilityList allowlist = new CompatibilityList()) {
assertThat(allowlist.isDelegateSupportedOnThisDevice()).isTrue();
}
}
}
@@ -0,0 +1,232 @@
/* 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.
==============================================================================*/
package org.tensorflow.lite.gpu;
import static com.google.common.truth.Truth.assertThat;
import static java.util.concurrent.TimeUnit.MICROSECONDS;
import static org.tensorflow.lite.gpu.GpuDelegateFactory.Options.GpuBackend.OPENCL;
import com.google.common.base.Stopwatch;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.tensorflow.lite.Interpreter;
import org.tensorflow.lite.InterpreterTestHelper;
import org.tensorflow.lite.TestUtils;
/** Unit tests for {@link org.tensorflow.lite.gpu.GpuDelegate}. */
@RunWith(JUnit4.class)
public final class GpuDelegateTest {
private static final String MODEL_PATH = "tensorflow/lite/testdata/multi_add.bin";
private static final ByteBuffer MODEL_BUFFER = TestUtils.getTestFileAsBuffer(MODEL_PATH);
private static final ByteBuffer MOBILENET_QUANTIZED_MODEL_BUFFER =
TestUtils.getTestFileAsBuffer(
"tensorflow/lite/java/demo/app/src/main/assets/mobilenet_v1_1.0_224_quant.tflite");
@Rule public final TemporaryFolder tempDir = new TemporaryFolder();
@Test
public void testBasic() throws Exception {
try (GpuDelegate delegate = new GpuDelegate()) {
assertThat(delegate.getNativeHandle()).isNotEqualTo(0);
}
}
@Test
public void testInterpreterWithGpu_FloatModel() throws Exception {
Interpreter.Options options = new Interpreter.Options();
try (GpuDelegate delegate = new GpuDelegate();
Interpreter interpreter = new Interpreter(MODEL_BUFFER, options.addDelegate(delegate))) {
float[] input0 = {1.23f};
float[] input1 = {2.43f};
Object[] inputs = {input0, input1, input0, input1};
float[] parsedOutput0 = new float[1];
float[] parsedOutput1 = new float[1];
Map<Integer, Object> outputs = new HashMap<>();
outputs.put(0, parsedOutput0);
outputs.put(1, parsedOutput1);
interpreter.runForMultipleInputsOutputs(inputs, outputs);
float[] expected0 = {4.89f};
float[] expected1 = {6.09f};
assertThat(parsedOutput0).usingTolerance(0.1f).containsExactly(expected0).inOrder();
assertThat(parsedOutput1).usingTolerance(0.1f).containsExactly(expected1).inOrder();
}
}
@Test
public void testInterpreterWithGpu_QuantModelRunWithDelegate() throws Exception {
ByteBuffer img =
TestUtils.getTestImageAsByteBuffer(
"tensorflow/lite/java/src/testdata/grace_hopper_224.jpg");
Interpreter.Options options = new Interpreter.Options();
// Default behavior allows quantized models.
try (GpuDelegate delegate = new GpuDelegate();
Interpreter interpreter =
new Interpreter(MOBILENET_QUANTIZED_MODEL_BUFFER, options.addDelegate(delegate))) {
byte[][] output = new byte[1][1001];
interpreter.run(img, output);
// Should be only 1 node (Delegate) in the execution plan.
assertThat(InterpreterTestHelper.executionPlanLength(interpreter)).isEqualTo(1);
assertThat(interpreter.getInputTensor(0).shape()).isEqualTo(new int[] {1, 224, 224, 3});
assertThat(interpreter.getOutputTensor(0).shape()).isEqualTo(new int[] {1, 1001});
// 653 == "military uniform"
assertThat(getTopKLabels(output, 3)).contains(653);
}
}
@Test
public void testInterpreterWithGpu_QuantModelRunOnCPU() throws Exception {
ByteBuffer img =
TestUtils.getTestImageAsByteBuffer(
"tensorflow/lite/java/src/testdata/grace_hopper_224.jpg");
Interpreter.Options options = new Interpreter.Options();
try (GpuDelegate delegate =
new GpuDelegate(new GpuDelegateFactory.Options().setQuantizedModelsAllowed(false));
Interpreter interpreter =
new Interpreter(MOBILENET_QUANTIZED_MODEL_BUFFER, options.addDelegate(delegate))) {
byte[][] output = new byte[1][1001];
interpreter.run(img, output);
// Model is now delegated to XNNPACK as the GPU delegate was deactivated.
// Don't hard code the execution plan length as this may change as more operators are added to
// XNNPACK.
assertThat(InterpreterTestHelper.executionPlanLength(interpreter)).isLessThan(31);
assertThat(interpreter.getInputTensor(0).shape()).isEqualTo(new int[] {1, 224, 224, 3});
assertThat(interpreter.getOutputTensor(0).shape()).isEqualTo(new int[] {1, 1001});
// 653 == "military uniform"
assertThat(getTopKLabels(output, 3)).contains(653);
}
}
@Test
public void testInterpreterWithGpu_forceOpenCl_worksOrThrowsException() {
Interpreter.Options options = new Interpreter.Options();
try (GpuDelegate delegate =
new GpuDelegate(new GpuDelegateFactory.Options().setForceBackend(OPENCL));
Interpreter interpreter =
new Interpreter(MOBILENET_QUANTIZED_MODEL_BUFFER, options.addDelegate(delegate))) {
ByteBuffer img =
TestUtils.getTestImageAsByteBuffer(
"tensorflow/lite/java/src/testdata/grace_hopper_224.jpg");
byte[][] output = new byte[1][1001];
interpreter.run(img, output);
assertThat(interpreter.getInputTensor(0).shape()).isEqualTo(new int[] {1, 224, 224, 3});
assertThat(interpreter.getOutputTensor(0).shape()).isEqualTo(new int[] {1, 1001});
// 653 == "military uniform"
assertThat(getTopKLabels(output, 3)).contains(653);
} catch (IllegalArgumentException e) {
// May fail if OpenCL is not available on the device.
assertThat(e).hasMessageThat().contains("Can not open OpenCL library");
}
}
@Test
public void testDelegateSerialization() throws Exception {
ByteBuffer img =
TestUtils.getTestImageAsByteBuffer(
"tensorflow/lite/java/src/testdata/grace_hopper_224.jpg");
File serializationFolder = tempDir.newFolder();
String serializationDir = serializationFolder.getPath();
// Create the interpreter with serialization enabled delegate.
createInterpreterWithDelegate(/*enableSerialization=*/ true, serializationFolder.getPath());
// In the second interpreter initialization, delegate reuses the serialization data.
Stopwatch stopWatch = Stopwatch.createStarted();
Interpreter interpreter =
createInterpreterWithDelegate(/*enableSerialization=*/ true, serializationFolder.getPath());
stopWatch.stop();
long serializedInitTime = stopWatch.elapsed(MICROSECONDS);
// Check on the model.
byte[][] output = new byte[1][1001];
interpreter.run(img, output);
// 653 == "military uniform"
assertThat(getTopKLabels(output, 3)).contains(653);
// If OpenCL is available, serialized data will be written to serializationDir and
// initialization time improvement shall be observed.
// Otherwise, this testcase performs a check that enabling the option won't crash.
if (serializationFolder.list().length > 0) {
stopWatch.reset();
stopWatch.start();
// Initialze interpreter with GpuDelegate serialization not enabled.
createInterpreterWithDelegate(/*enableSerialization=*/ false, /*serializationDir=*/ null);
long notserializedInitTime = stopWatch.elapsed(MICROSECONDS);
assertThat(serializedInitTime).isLessThan(notserializedInitTime);
}
}
private Interpreter createInterpreterWithDelegate(
boolean enableSerialization, String serializationDir) {
Interpreter.Options options = new Interpreter.Options();
if (enableSerialization) {
options.addDelegate(
new GpuDelegate(
new GpuDelegateFactory.Options()
.setSerializationParams(serializationDir, "GpuDelegateTest.testModelToken")));
} else {
options.addDelegate(new GpuDelegate());
}
Interpreter interpreter = new Interpreter(MOBILENET_QUANTIZED_MODEL_BUFFER, options);
return interpreter;
}
private static ArrayList<Integer> getTopKLabels(byte[][] byteLabels, int k) {
float[][] labels = new float[1][1001];
for (int i = 0; i < byteLabels[0].length; ++i) {
labels[0][i] = (byteLabels[0][i] & 0xff) / 255.0f;
}
return getTopKLabels(labels, k);
}
private static ArrayList<Integer> getTopKLabels(float[][] labels, int k) {
PriorityQueue<Map.Entry<Integer, Float>> pq =
new PriorityQueue<>(
k,
new Comparator<Map.Entry<Integer, Float>>() {
@Override
public int compare(Map.Entry<Integer, Float> o1, Map.Entry<Integer, Float> o2) {
// Intentionally reversed to put high confidence at the head of the queue.
return o1.getValue().compareTo(o2.getValue()) * -1;
}
});
for (int i = 0; i < labels[0].length; ++i) {
pq.add(new AbstractMap.SimpleEntry<>(i, labels[0][i]));
}
final ArrayList<Integer> topKLabels = new ArrayList<>();
int topKLabelsSize = Math.min(pq.size(), k);
for (int i = 0; i < topKLabelsSize; ++i) {
topKLabels.add(pq.poll().getKey());
}
return topKLabels;
}
}
@@ -0,0 +1,239 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite.nnapi;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import java.nio.ByteBuffer;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.tensorflow.lite.Interpreter;
import org.tensorflow.lite.InterpreterApi.Options.TfLiteRuntime;
import org.tensorflow.lite.SupportedFeatures;
import org.tensorflow.lite.TestInit;
import org.tensorflow.lite.TestUtils;
/** Unit tests for {@link org.tensorflow.lite.nnapi.NnApiDelegate}. */
@RunWith(JUnit4.class)
public final class NnApiDelegateTest {
private static final String MODEL_PATH = "tensorflow/lite/java/src/testdata/add.bin";
private static final ByteBuffer MODEL_BUFFER = TestUtils.getTestFileAsBuffer(MODEL_PATH);
private static final Interpreter.Options INTERPRETER_OPTIONS =
new Interpreter.Options().setRuntime(TfLiteRuntime.PREFER_SYSTEM_OVER_APPLICATION);
@Before
public void setUp() throws Exception {
TestInit.init();
}
@Test
public void testBasic() throws Exception {
Interpreter.Options options = new Interpreter.Options(INTERPRETER_OPTIONS);
try (NnApiDelegate delegate = new NnApiDelegate(); // Without options.
Interpreter interpreter = new Interpreter(MODEL_BUFFER, options.addDelegate(delegate))) {
assertThat(delegate.getNativeHandle()).isNotEqualTo(0);
}
}
@Test
public void testAccessBeforeInterpreterInitialized() throws Exception {
try (NnApiDelegate delegate = new NnApiDelegate()) {
try {
delegate.getNativeHandle();
fail("Expected IllegalStateException to be thrown");
} catch (IllegalStateException e) {
assertThat(e)
.hasMessageThat()
.contains("Should not access delegate before interpreter has been constructed");
}
// Merely adding the delegate to the options isn't enough either.
Interpreter.Options options =
new Interpreter.Options(INTERPRETER_OPTIONS).addDelegate(delegate);
try {
delegate.getNativeHandle();
fail("Expected IllegalStateException to be thrown");
} catch (IllegalStateException e) {
assertThat(e)
.hasMessageThat()
.contains("Should not access delegate before interpreter has been constructed");
}
}
}
@Test
public void testWithoutNnApiDelegateOptions() throws Exception {
Interpreter.Options options = new Interpreter.Options(INTERPRETER_OPTIONS);
try (NnApiDelegate delegate = new NnApiDelegate(); // Without options.
Interpreter interpreter = new Interpreter(MODEL_BUFFER, options.addDelegate(delegate))) {
assertThat(delegate.getNativeHandle()).isNotEqualTo(0);
} catch (IllegalStateException e) {
// This can happen if the TF Lite runtime isn't linked into the test.
assertThat(e)
.hasMessageThat()
.contains(
"Couldn't find TensorFlow Lite runtime's InterpreterFactoryImpl class -- make sure"
+ " your app links in the right TensorFlow Lite runtime. You should declare a"
+ " build dependency on");
assertThat(e)
.hasMessageThat()
.contains(" , or call"
+ " setTfLiteRuntime with a value other than TfLiteRuntime.FROM_APPLICATION_ONLY"
+ " (see docs for"
+ " org.tensorflow.lite.nnapi.NnApiDelegate.Options#setTfLiteRuntime).");
}
}
@Test
public void testInterpreterWithNnApi() throws Exception {
Interpreter.Options options = new Interpreter.Options(INTERPRETER_OPTIONS);
try (NnApiDelegate delegate = new NnApiDelegate();
Interpreter interpreter = new Interpreter(MODEL_BUFFER, options.addDelegate(delegate))) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
float[][][][] parsedOutputs = new float[2][8][8][3];
interpreter.run(fourD, parsedOutputs);
float[] outputOneD = parsedOutputs[0][0][0];
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
}
@Test
public void testInterpreterWithNnApiAndXNNPack() throws Exception {
if (!SupportedFeatures.supportsXnnpack()) {
System.err.println("Not testing NNAPI with XNNPACK, since XNNPACK isn't supported.");
return;
}
Interpreter.Options options = new Interpreter.Options(INTERPRETER_OPTIONS);
options.setUseXNNPACK(true);
try (NnApiDelegate delegate = new NnApiDelegate();
Interpreter interpreter = new Interpreter(MODEL_BUFFER, options.addDelegate(delegate))) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
float[][][][] parsedOutputs = new float[2][8][8][3];
interpreter.run(fourD, parsedOutputs);
float[] outputOneD = parsedOutputs[0][0][0];
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
}
@Test
public void testInterpreterWithNnApiAllowFp16() throws Exception {
NnApiDelegate.Options nnApiOptions = new NnApiDelegate.Options();
Interpreter.Options options = new Interpreter.Options(INTERPRETER_OPTIONS);
nnApiOptions.setAllowFp16(true);
try (NnApiDelegate delegate = new NnApiDelegate(nnApiOptions);
Interpreter interpreter = new Interpreter(MODEL_BUFFER, options.addDelegate(delegate))) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
float[][][][] parsedOutputs = new float[2][8][8][3];
interpreter.run(fourD, parsedOutputs);
float[] outputOneD = parsedOutputs[0][0][0];
float[] expected = {3.69f, 19.62f, 23.43f};
assertThat(outputOneD).usingTolerance(0.1f).containsExactly(expected).inOrder();
}
}
@Test
public void testGetNnApiErrnoReturnsZeroIfNoNnapiCallFailed() throws Exception {
Interpreter.Options options = new Interpreter.Options(INTERPRETER_OPTIONS);
try (NnApiDelegate delegate = new NnApiDelegate();
Interpreter interpreter = new Interpreter(MODEL_BUFFER, options.addDelegate(delegate))) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
float[][][][] parsedOutputs = new float[2][8][8][3];
interpreter.run(fourD, parsedOutputs);
assertThat(delegate.getNnapiErrno()).isEqualTo(0);
assertThat(delegate.hasErrors()).isFalse();
}
}
@Test
public void testGetNnApiErrnoBeforeInitializingInterpreterReturnsZero() {
NnApiDelegate delegate = new NnApiDelegate();
assertThat(delegate.getNnapiErrno()).isEqualTo(0);
}
@Test
public void testGetNnApiErrnoThrowsExceptionAfterClosingDelegate() {
NnApiDelegate delegate = new NnApiDelegate();
Interpreter interpreter =
new Interpreter(
MODEL_BUFFER, new Interpreter.Options(INTERPRETER_OPTIONS).addDelegate(delegate));
assertThat(delegate.getNnapiErrno()).isEqualTo(0);
delegate.close();
try {
delegate.getNnapiErrno();
fail("Expected IllegalStateException to be thrown.");
} catch (IllegalStateException expected) {
assertThat(expected)
.hasMessageThat()
.contains("Should not access delegate after delegate has been closed");
}
}
@Test
public void testSupportLibraryIsSetFromHandle() {
NnApiDelegate.Options nnApiOptions = new NnApiDelegate.Options();
Interpreter.Options options = new Interpreter.Options(INTERPRETER_OPTIONS);
long mockSlHandle = getMockSlHandle();
try (NnApiDelegate delegate =
new NnApiDelegate(
nnApiOptions.setNnApiSupportLibraryHandle(mockSlHandle).setUseNnapiCpu(true));
Interpreter interpreter =
new Interpreter(MODEL_BUFFER, options.addDelegate(delegate).setUseXNNPACK(false))) {
float[] oneD = {1.23f, 6.54f, 7.81f};
float[][] twoD = {oneD, oneD, oneD, oneD, oneD, oneD, oneD, oneD};
float[][][] threeD = {twoD, twoD, twoD, twoD, twoD, twoD, twoD, twoD};
float[][][][] fourD = {threeD, threeD};
float[][][][] parsedOutputs = new float[2][8][8][3];
interpreter.run(fourD, parsedOutputs);
// Not checking outputs since we're using mock NNAPI support library
assertThat(hasNnApiSlBeenCalled()).isTrue();
}
closeMockSl(mockSlHandle);
}
/**
* Allocates a mock NNAPI Support Library object. The mock library does nothing but remembers if
* any of its functions were called at least once.
*/
private static native long getMockSlHandle();
/** Returns true if any function from the mock Support Library has been called at least once. */
private static native boolean hasNnApiSlBeenCalled();
/** Deallocates the Support Library object. */
private static native void closeMockSl(long handle);
}
+111
View File
@@ -0,0 +1,111 @@
# Description:
# Java Native Interface (JNI) library for testing the TensorFlow Lite Java API.
load(
"//tensorflow/lite/core/shims:cc_library_with_tflite.bzl",
"cc_library_with_tflite",
"jni_binary_with_tflite",
)
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
filegroup(
name = "native_srcs",
srcs = [
"interpreter_test_jni.cc",
"nnapi_delegate_test_jni.cc",
"supported_features_jni.cc",
"test_init_jni.cc",
],
)
cc_library_with_tflite(
name = "native",
testonly = 1,
srcs = [
"interpreter_test_jni.cc",
"nnapi_delegate_test_jni.cc",
"supported_features_jni.cc",
],
tflite_deps = [
":test_init_jni",
"//tensorflow/lite/delegates/nnapi/java/src/main/native",
"//tensorflow/lite/java/src/main/native",
"//tensorflow/lite/java/src/main/native:jni_utils",
"//tensorflow/lite/java/src/main/native:native_framework_only",
],
deps = [
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/delegates/nnapi:nnapi_delegate",
"//tensorflow/lite/delegates/xnnpack:xnnpack_delegate",
"//tensorflow/lite/java/jni",
"//tensorflow/lite/kernels:kernel_util",
"//tensorflow/lite/nnapi:nnapi_lib",
"//tensorflow/lite/nnapi/sl:nnapi_support_library_headers",
],
alwayslink = 1,
)
cc_library_with_tflite(
name = "test_init_jni",
testonly = 1,
srcs = [
"test_init_jni.cc",
],
tflite_deps = [
"//tensorflow/lite/java/src/main/native:jni_utils",
"//tensorflow/lite/c:test_util",
],
deps = [
"//tensorflow/lite/java/jni",
],
alwayslink = 1,
)
# Same as "native", but excluding dependencies on experimental features.
cc_library_with_tflite(
name = "native_stable",
testonly = 1,
srcs = [
"supported_features_jni.cc",
"test_init_jni.cc",
],
tflite_deps = [
"//tensorflow/lite/c:test_util",
"//tensorflow/lite/delegates/nnapi/java/src/main/native",
"//tensorflow/lite/java/src/main/native:jni_utils",
"//tensorflow/lite/java/src/main/native:native_stable",
"//tensorflow/lite/java/src/main/native:native_stable_framework_only",
],
deps = [
"//tensorflow/lite/core/c:common",
"//tensorflow/lite/delegates/xnnpack:xnnpack_delegate",
"//tensorflow/lite/java/jni",
"//tensorflow/lite/kernels:kernel_util",
],
alwayslink = 1,
)
jni_binary_with_tflite(
name = "libtensorflowlite_test_jni.so",
testonly = 1,
linkscript = "//tensorflow/lite/java:tflite_version_script.lds",
tflite_deps = [":native"],
)
jni_binary_with_tflite(
name = "libtensorflowlite_stable_test_jni.so",
testonly = 1,
linkscript = "//tensorflow/lite/java:tflite_version_script.lds",
tflite_deps = [":native_stable"],
)
# Dummy native library which doesn't actually contain the TFLite implementation.
jni_binary_with_tflite(
name = "libtensorflowlite_jni.so",
testonly = 1,
)
@@ -0,0 +1,99 @@
/* Copyright 2018 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 <jni.h>
#include <algorithm>
#include <cstddef>
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/kernels/kernel_util.h"
extern "C" {
JNIEXPORT jlong JNICALL
Java_org_tensorflow_lite_InterpreterTest_getNativeHandleForDelegate(
JNIEnv* env, jclass clazz) {
// A simple op which outputs a tensor with values of 7.
static TfLiteRegistration registration = {
.init = nullptr,
.free = nullptr,
.prepare =
[](TfLiteContext* context, TfLiteNode* node) {
const TfLiteTensor* input;
TF_LITE_ENSURE_OK(context,
tflite::GetInputSafe(context, node, 0, &input));
TfLiteTensor* output;
TF_LITE_ENSURE_OK(context,
tflite::GetOutputSafe(context, node, 0, &output));
TfLiteIntArray* output_dims = TfLiteIntArrayCopy(input->dims);
output->type = kTfLiteFloat32;
return context->ResizeTensor(context, output, output_dims);
},
.invoke =
[](TfLiteContext* context, TfLiteNode* node) {
TfLiteTensor* output;
TF_LITE_ENSURE_OK(context,
tflite::GetOutputSafe(context, node, 0, &output));
std::fill(output->data.f,
output->data.f + tflite::NumElements(output), 7.0f);
return kTfLiteOk;
},
.profiling_string = nullptr,
.builtin_code = 0,
.custom_name = "",
.version = 1,
};
static TfLiteDelegate delegate = {
.data_ = nullptr,
.Prepare = [](TfLiteContext* context,
TfLiteDelegate* delegate) -> TfLiteStatus {
TfLiteIntArray* execution_plan;
TF_LITE_ENSURE_STATUS(
context->GetExecutionPlan(context, &execution_plan));
context->ReplaceNodeSubsetsWithDelegateKernels(
context, registration, execution_plan, delegate);
// Now bind delegate buffer handles for all tensors.
for (size_t i = 0; i < context->tensors_size; ++i) {
context->tensors[i].delegate = delegate;
context->tensors[i].buffer_handle = static_cast<int>(i);
}
return kTfLiteOk;
},
.CopyFromBufferHandle = nullptr,
.CopyToBufferHandle = nullptr,
.FreeBufferHandle = nullptr,
.flags = kTfLiteDelegateFlagsAllowDynamicTensors,
};
return reinterpret_cast<jlong>(&delegate);
}
JNIEXPORT jlong JNICALL
Java_org_tensorflow_lite_InterpreterTest_getNativeHandleForInvalidDelegate(
JNIEnv* env, jclass clazz) {
// A simple delegate that fails during preparation.
static TfLiteDelegate delegate = {
.data_ = nullptr,
.Prepare = [](TfLiteContext* context, TfLiteDelegate* delegate)
-> TfLiteStatus { return kTfLiteError; },
.CopyFromBufferHandle = nullptr,
.CopyToBufferHandle = nullptr,
.FreeBufferHandle = nullptr,
.flags = kTfLiteDelegateFlagsNone,
};
return reinterpret_cast<jlong>(&delegate);
}
} // extern "C"
@@ -0,0 +1,306 @@
/* 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 <jni.h>
#include <cstdint>
#include "tensorflow/lite/delegates/nnapi/nnapi_delegate.h"
#include "tensorflow/lite/nnapi/NeuralNetworksTypes.h"
#include "tensorflow/lite/nnapi/sl/public/NeuralNetworksSupportLibraryImpl.h"
namespace {
bool g_sl_has_been_called = false;
template <int return_value, typename... Types>
int DoNothingAndReturn(Types... args) {
g_sl_has_been_called = true;
return return_value;
}
template <typename... Types>
void DoNothing(Types... args) {
g_sl_has_been_called = true;
}
const uint32_t kDefaultMemoryPaddingAndAlignment = 64;
} // namespace
extern "C" {
JNIEXPORT jlong JNICALL
Java_org_tensorflow_lite_nnapi_NnApiDelegateTest_getMockSlHandle(JNIEnv* env,
jclass clazz) {
g_sl_has_been_called = false;
NnApiSLDriverImplFL5* supportLibraryImplementation =
new NnApiSLDriverImplFL5();
supportLibraryImplementation->base.implFeatureLevel =
ANEURALNETWORKS_FEATURE_LEVEL_5;
// Most calls do nothing and return NO_ERROR as result. Errors are returned in
// get* calls that are not trivial to mock.
supportLibraryImplementation->ANeuralNetworksBurst_create =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksBurst_free = DoNothing;
supportLibraryImplementation->ANeuralNetworksCompilation_createForDevices =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksCompilation_finish =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksCompilation_free = DoNothing;
supportLibraryImplementation
->ANeuralNetworksCompilation_getPreferredMemoryAlignmentForInput =
[](const ANeuralNetworksCompilation* compilation, uint32_t index,
uint32_t* alignment) -> int {
g_sl_has_been_called = true;
*alignment = kDefaultMemoryPaddingAndAlignment;
return ANEURALNETWORKS_NO_ERROR;
};
supportLibraryImplementation
->ANeuralNetworksCompilation_getPreferredMemoryAlignmentForOutput =
[](const ANeuralNetworksCompilation* compilation, uint32_t index,
uint32_t* alignment) -> int {
g_sl_has_been_called = true;
*alignment = kDefaultMemoryPaddingAndAlignment;
return ANEURALNETWORKS_NO_ERROR;
};
supportLibraryImplementation
->ANeuralNetworksCompilation_getPreferredMemoryPaddingForInput =
[](const ANeuralNetworksCompilation* compilation, uint32_t index,
uint32_t* padding) -> int {
g_sl_has_been_called = true;
*padding = kDefaultMemoryPaddingAndAlignment;
return ANEURALNETWORKS_NO_ERROR;
};
supportLibraryImplementation
->ANeuralNetworksCompilation_getPreferredMemoryPaddingForOutput =
[](const ANeuralNetworksCompilation* compilation, uint32_t index,
uint32_t* padding) -> int {
g_sl_has_been_called = true;
*padding = kDefaultMemoryPaddingAndAlignment;
return ANEURALNETWORKS_NO_ERROR;
};
supportLibraryImplementation->ANeuralNetworksCompilation_setCaching =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksCompilation_setPreference =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksCompilation_setPriority =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksCompilation_setTimeout =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksDevice_getExtensionSupport =
[](const ANeuralNetworksDevice* device, const char* extensionName,
bool* isExtensionSupported) -> int {
g_sl_has_been_called = true;
*isExtensionSupported = false;
return ANEURALNETWORKS_NO_ERROR;
};
supportLibraryImplementation->ANeuralNetworksDevice_getFeatureLevel =
[](const ANeuralNetworksDevice* device, int64_t* featureLevel) -> int {
g_sl_has_been_called = true;
*featureLevel = ANEURALNETWORKS_FEATURE_LEVEL_5;
return ANEURALNETWORKS_NO_ERROR;
};
supportLibraryImplementation->ANeuralNetworksDevice_getName =
[](const ANeuralNetworksDevice* device, const char** name) -> int {
g_sl_has_been_called = true;
*name = "mockDevice";
return ANEURALNETWORKS_NO_ERROR;
};
supportLibraryImplementation->ANeuralNetworksDevice_getType =
[](const ANeuralNetworksDevice* device, int32_t* type) -> int {
g_sl_has_been_called = true;
*type = ANEURALNETWORKS_DEVICE_CPU;
return ANEURALNETWORKS_NO_ERROR;
};
supportLibraryImplementation->ANeuralNetworksDevice_getVersion =
[](const ANeuralNetworksDevice* device, const char** version) -> int {
g_sl_has_been_called = true;
*version = "mock";
return ANEURALNETWORKS_NO_ERROR;
};
supportLibraryImplementation->ANeuralNetworksDevice_wait =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksEvent_createFromSyncFenceFd =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksEvent_free = DoNothing;
supportLibraryImplementation->ANeuralNetworksEvent_getSyncFenceFd =
DoNothingAndReturn<ANEURALNETWORKS_BAD_DATA>;
supportLibraryImplementation->ANeuralNetworksEvent_wait =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksExecution_burstCompute =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksExecution_compute =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksExecution_create =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation
->ANeuralNetworksExecution_enableInputAndOutputPadding =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksExecution_free = DoNothing;
supportLibraryImplementation->ANeuralNetworksExecution_getDuration =
[](const ANeuralNetworksExecution* execution, int32_t durationCode,
uint64_t* duration) -> int {
g_sl_has_been_called = true;
*duration = UINT64_MAX;
return ANEURALNETWORKS_NO_ERROR;
};
supportLibraryImplementation
->ANeuralNetworksExecution_getOutputOperandDimensions =
DoNothingAndReturn<ANEURALNETWORKS_BAD_DATA>;
supportLibraryImplementation->ANeuralNetworksExecution_getOutputOperandRank =
DoNothingAndReturn<ANEURALNETWORKS_BAD_DATA>;
supportLibraryImplementation->ANeuralNetworksExecution_setInput =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksExecution_setInputFromMemory =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksExecution_setLoopTimeout =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksExecution_setMeasureTiming =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksExecution_setOutput =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksExecution_setOutputFromMemory =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksExecution_setReusable =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksExecution_setTimeout =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation
->ANeuralNetworksExecution_startComputeWithDependencies =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksMemoryDesc_addInputRole =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksMemoryDesc_addOutputRole =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksMemoryDesc_create =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksMemoryDesc_finish =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksMemoryDesc_free = DoNothing;
supportLibraryImplementation->ANeuralNetworksMemoryDesc_setDimensions =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksMemory_copy =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation
->ANeuralNetworksMemory_createFromAHardwareBuffer =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksMemory_createFromDesc =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksMemory_createFromFd =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksMemory_free = DoNothing;
supportLibraryImplementation->ANeuralNetworksModel_addOperand =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksModel_addOperation =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksModel_create =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksModel_finish =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksModel_free = DoNothing;
supportLibraryImplementation->ANeuralNetworksModel_getExtensionOperandType =
DoNothingAndReturn<ANEURALNETWORKS_BAD_DATA>;
supportLibraryImplementation->ANeuralNetworksModel_getExtensionOperationType =
DoNothingAndReturn<ANEURALNETWORKS_BAD_DATA>;
supportLibraryImplementation
->ANeuralNetworksModel_getSupportedOperationsForDevices =
DoNothingAndReturn<ANEURALNETWORKS_BAD_DATA>;
supportLibraryImplementation->ANeuralNetworksModel_identifyInputsAndOutputs =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation
->ANeuralNetworksModel_relaxComputationFloat32toFloat16 =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksModel_setOperandExtensionData =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation
->ANeuralNetworksModel_setOperandSymmPerChannelQuantParams =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksModel_setOperandValue =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksModel_setOperandValueFromMemory =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworksModel_setOperandValueFromModel =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworks_getDefaultLoopTimeout =
[]() -> uint64_t {
g_sl_has_been_called = true;
return UINT64_MAX;
};
supportLibraryImplementation->ANeuralNetworks_getDevice =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworks_getDeviceCount =
DoNothingAndReturn<ANEURALNETWORKS_NO_ERROR>;
supportLibraryImplementation->ANeuralNetworks_getMaximumLoopTimeout =
[]() -> uint64_t {
g_sl_has_been_called = true;
return UINT64_MAX;
};
supportLibraryImplementation->ANeuralNetworks_getRuntimeFeatureLevel =
[]() -> int64_t {
g_sl_has_been_called = true;
return ANEURALNETWORKS_FEATURE_LEVEL_5;
};
return reinterpret_cast<jlong>(supportLibraryImplementation);
}
JNIEXPORT jboolean JNICALL
Java_org_tensorflow_lite_nnapi_NnApiDelegateTest_hasNnApiSlBeenCalled(
JNIEnv* env, jclass clazz) {
return g_sl_has_been_called;
}
JNIEXPORT void JNICALL
Java_org_tensorflow_lite_nnapi_NnApiDelegateTest_closeMockSl(JNIEnv* env,
jclass clazz,
jlong handle) {
delete reinterpret_cast<NnApiSLDriverImplFL5*>(handle);
}
JNIEXPORT jboolean JNICALL
Java_org_tensorflow_lite_NnApiDelegateNativeTest_getAllowFp16Option(
JNIEnv* env, jclass clazz, jlong delegate_handle) {
auto* delegate =
reinterpret_cast<tflite::StatefulNnApiDelegate*>(delegate_handle);
tflite::StatefulNnApiDelegate::Options options =
delegate->GetOptions(delegate);
return options.allow_fp16;
}
JNIEXPORT jboolean JNICALL
Java_org_tensorflow_lite_NnApiDelegateNativeTest_getDisallowNnapiCpuOption(
JNIEnv* env, jclass clazz, jlong delegate_handle) {
auto* delegate =
reinterpret_cast<tflite::StatefulNnApiDelegate*>(delegate_handle);
tflite::StatefulNnApiDelegate::Options options =
delegate->GetOptions(delegate);
return options.disallow_nnapi_cpu;
}
JNIEXPORT jstring JNICALL
Java_org_tensorflow_lite_NnApiDelegateNativeTest_getModelTokenOption(
JNIEnv* env, jclass clazz, jlong delegate_handle) {
auto* delegate =
reinterpret_cast<tflite::StatefulNnApiDelegate*>(delegate_handle);
tflite::StatefulNnApiDelegate::Options options =
delegate->GetOptions(delegate);
if (options.model_token == nullptr) return nullptr;
return env->NewStringUTF(options.model_token);
}
} // extern "C"
@@ -0,0 +1,58 @@
/* 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 <jni.h>
extern "C" {
JNIEXPORT jboolean JNICALL
Java_org_tensorflow_lite_SupportedFeatures_supportsCancellation(JNIEnv*,
jclass) {
#if TFLITE_DISABLE_SELECT_JAVA_APIS
return JNI_FALSE;
#else
return JNI_TRUE;
#endif
}
JNIEXPORT jboolean JNICALL
Java_org_tensorflow_lite_SupportedFeatures_supportsXnnpack(JNIEnv*, jclass) {
#if TFLITE_DISABLE_SELECT_JAVA_APIS
return JNI_FALSE;
#else
return JNI_TRUE;
#endif
}
JNIEXPORT jboolean JNICALL
Java_org_tensorflow_lite_SupportedFeatures_supportsAllowFp16PrecisionForFp32(
JNIEnv*, jclass) {
#if TFLITE_DISABLE_SELECT_JAVA_APIS
return JNI_FALSE;
#else
return JNI_TRUE;
#endif
}
JNIEXPORT jboolean JNICALL
Java_org_tensorflow_lite_SupportedFeatures_supportsNonstrictResize(JNIEnv*,
jclass) {
#if TFLITE_DISABLE_SELECT_JAVA_APIS
return JNI_FALSE;
#else
return JNI_TRUE;
#endif
}
} // extern "C"
@@ -0,0 +1,30 @@
/* 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 <jni.h>
#include "tensorflow/lite/c/test_util.h"
#include "tensorflow/lite/java/src/main/native/jni_utils.h"
extern "C" {
JNIEXPORT void JNICALL
Java_org_tensorflow_lite_TestInit_initTfLiteForTest(JNIEnv* env, jclass clazz) {
if (TfLiteInitializeShimsForTest() != 0) {
tflite::jni::ThrowException(env, tflite::jni::kIllegalStateException,
"TfLiteInitializeShimsForTest() failed.");
}
}
} // extern "C"
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
This is an invalid model.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,35 @@
# Description:
# Internal helper function to test TF Lite API.
load("@build_bazel_rules_android//android:rules.bzl", "android_library")
load("@rules_java//java:defs.bzl", "java_library")
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
android_library(
name = "testhelper",
srcs = glob(
[
"*.java",
],
),
deps = [
"//tensorflow/lite/java:tensorflowlite_java",
],
)
java_library(
name = "testhelper_javalib",
srcs = glob(
[
"*.java",
],
),
deps = [
"//tensorflow/lite/java:tensorflowlite_javalib",
],
)
@@ -0,0 +1,90 @@
/* Copyright 2017 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.
==============================================================================*/
package org.tensorflow.lite;
/** A helper class for internal tests. */
public class TestHelper {
/**
* Gets the last inference duration in nanoseconds. It returns null if there is no previous
* inference run or the last inference run failed.
*
* @param interpreter an instance of {@code Interpreter}. If it is not initialized, an {@code
* IllegalArgumentException} will be thrown.
*/
public static Long getLastNativeInferenceDurationNanoseconds(Interpreter interpreter) {
if (interpreter != null && interpreter.wrapper != null) {
return interpreter.wrapper.getLastNativeInferenceDurationNanoseconds();
} else {
throw new IllegalArgumentException("Interpreter has not initialized; Failed to get latency.");
}
}
/**
* Gets the dimensions of an input.
*
* @param interpreter an instance of {@code Interpreter}. If it is not initialized, an {@code
* IllegalArgumentException} will be thrown.
* @param index an integer index of the input. If it is invalid, an {@code
* IllegalArgumentException} will be thrown.
*/
public static int[] getInputDims(Interpreter interpreter, int index) {
if (interpreter != null && interpreter.wrapper != null) {
return interpreter.wrapper.getInputTensor(index).shape();
} else {
throw new IllegalArgumentException(
"Interpreter has not initialized;" + " Failed to get input dimensions.");
}
}
/**
* Gets the string name of the data type of an input.
*
* @param interpreter an instance of {@code Interpreter}. If it is not initialized, an {@code
* IllegalArgumentException} will be thrown.
* @param index an integer index of the input. If it is invalid, an {@code
* IllegalArgumentException} will be thrown.
* @return string name of the data type. Possible values include "float", "int", "byte", and
* "long".
*/
public static String getInputDataType(Interpreter interpreter, int index) {
if (interpreter != null && interpreter.wrapper != null) {
return DataTypeUtils.toStringName(interpreter.wrapper.getInputTensor(index).dataType());
} else {
throw new IllegalArgumentException(
"Interpreter has not initialized;" + " Failed to get input data type.");
}
}
/**
* Gets the string name of the data type of an output.
*
* @param interpreter an instance of {@code Interpreter}. If it is not initialized, an {@code
* IllegalArgumentException} will be thrown.
* @param index an integer index of the output. If it is invalid, an {@code
* IllegalArgumentException} will be thrown.
* @return string name of the data type. Possible values include "float", "int", "byte", and
* "long".
*/
public static String getOutputDataType(Interpreter interpreter, int index) {
if (interpreter != null && interpreter.wrapper != null) {
return DataTypeUtils.toStringName(interpreter.wrapper.getOutputTensor(index).dataType());
} else {
throw new IllegalArgumentException(
"Interpreter has not initialized;" + " Failed to get output data type.");
}
}
}