• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7     http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 
16 package org.tensorflow.lite;
17 
18 import java.lang.reflect.Array;
19 import java.nio.Buffer;
20 import java.nio.ByteBuffer;
21 import java.nio.ByteOrder;
22 import java.nio.FloatBuffer;
23 import java.nio.IntBuffer;
24 import java.nio.LongBuffer;
25 import java.nio.ShortBuffer;
26 import java.util.Arrays;
27 
28 /**
29  * A typed multi-dimensional array used in Tensorflow Lite.
30  *
31  * <p>The native handle of a {@code Tensor} is managed by {@code NativeInterpreterWrapper}, and does
32  * not needed to be closed by the client. However, once the {@code NativeInterpreterWrapper} has
33  * been closed, the tensor handle will be invalidated.
34  */
35 // TODO(b/153882978): Add scalar getters similar to TF's Java API.
36 public final class Tensor {
37 
38   /**
39    * Creates a Tensor wrapper from the provided interpreter instance and tensor index.
40    *
41    * <p>The caller is responsible for closing the created wrapper, and ensuring the provided native
42    * interpreter is valid until the tensor is closed.
43    */
fromIndex(long nativeInterpreterHandle, int tensorIndex)44   static Tensor fromIndex(long nativeInterpreterHandle, int tensorIndex) {
45     return new Tensor(create(nativeInterpreterHandle, tensorIndex, /*subgraphIndex=*/ 0));
46   }
47 
48   /**
49    * Creates a Tensor wrapper from the provided interpreter instance, subgraph and tensor indices.
50    *
51    * <p>The caller is responsible for closing the created wrapper, and ensuring the provided native
52    * interpreter is valid until the tensor is closed.
53    */
fromSubgraphAndIndex( long nativeInterpreterHandle, int subgraphIndex, int tensorIndex)54   static Tensor fromSubgraphAndIndex(
55       long nativeInterpreterHandle, int subgraphIndex, int tensorIndex) {
56     return new Tensor(create(nativeInterpreterHandle, tensorIndex, subgraphIndex));
57   }
58 
59   /**
60    * Quantization parameters that corresponds to the table, {@code QuantizationParameters}, in the
61    * <a
62    * href="https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/schema/schema.fbs">TFLite
63    * Model schema file.</a>
64    *
65    * <p>Since per-channel quantization does not apply to input and output tensors, {@code scale} and
66    * {@code zero_point} are both single values instead of arrays.
67    *
68    * <p>For tensor that are not quantized, the values of scale and zero_point are both 0.
69    *
70    * <p>Given a quantized value q, the corresponding float value f should be: <br>
71    * f = scale * (q - zero_point) <br>
72    */
73   public static class QuantizationParams {
74     /** The scale value used in quantization. */
75     private final float scale;
76     /** The zero point value used in quantization. */
77     private final int zeroPoint;
78 
79     /**
80      * Creates a {@link QuantizationParams} with {@code scale} and {@code zero_point}.
81      *
82      * @param scale The scale value used in quantization.
83      * @param zeroPoint The zero point value used in quantization.
84      */
QuantizationParams(final float scale, final int zeroPoint)85     public QuantizationParams(final float scale, final int zeroPoint) {
86       this.scale = scale;
87       this.zeroPoint = zeroPoint;
88     }
89 
90     /** Returns the scale value. */
getScale()91     public float getScale() {
92       return scale;
93     }
94 
95     /** Returns the zero point value. */
getZeroPoint()96     public int getZeroPoint() {
97       return zeroPoint;
98     }
99   }
100 
101   /** Disposes of any resources used by the Tensor wrapper. */
close()102   void close() {
103     delete(nativeHandle);
104     nativeHandle = 0;
105   }
106 
107   /** Returns the {@link DataType} of elements stored in the Tensor. */
dataType()108   public DataType dataType() {
109     return dtype;
110   }
111 
112   /**
113    * Returns the number of dimensions (sometimes referred to as <a
114    * href="https://www.tensorflow.org/resources/dims_types.html#rank">rank</a>) of the Tensor.
115    *
116    * <p>Will be 0 for a scalar, 1 for a vector, 2 for a matrix, 3 for a 3-dimensional tensor etc.
117    */
numDimensions()118   public int numDimensions() {
119     return shapeCopy.length;
120   }
121 
122   /** Returns the size, in bytes, of the tensor data. */
numBytes()123   public int numBytes() {
124     return numBytes(nativeHandle);
125   }
126 
127   /** Returns the number of elements in a flattened (1-D) view of the tensor. */
numElements()128   public int numElements() {
129     return computeNumElements(shapeCopy);
130   }
131 
132   /**
133    * Returns the <a href="https://www.tensorflow.org/resources/dims_types.html#shape">shape</a> of
134    * the Tensor, i.e., the sizes of each dimension.
135    *
136    * @return an array where the i-th element is the size of the i-th dimension of the tensor.
137    */
shape()138   public int[] shape() {
139     return shapeCopy;
140   }
141 
142   /**
143    * Returns the original <a
144    * href="https://www.tensorflow.org/resources/dims_types.html#shape">shape</a> of the Tensor,
145    * i.e., the sizes of each dimension - before any resizing was performed. Unknown dimensions are
146    * designated with a value of -1.
147    *
148    * @return an array where the i-th element is the size of the i-th dimension of the tensor.
149    */
shapeSignature()150   public int[] shapeSignature() {
151     return shapeSignatureCopy;
152   }
153 
154   /**
155    * Returns the (global) index of the tensor within the subgraph of the owning {@link Interpreter}.
156    *
157    * @hide
158    */
index()159   public int index() {
160     return index(nativeHandle);
161   }
162 
163   /**
164    * Returns the subgraph index of the tensor within the owning {@link Interpreter}.
165    *
166    * @hide
167    */
subgraph()168   public int subgraph() {
169     return subgraph(nativeHandle);
170   }
171 
172   /**
173    * Returns the name of the tensor within the owning {@link Interpreter}.
174    *
175    * @hide
176    */
name()177   public String name() {
178     return name(nativeHandle);
179   }
180 
181   /**
182    * Returns the quantization parameters of the tensor within the owning {@link Interpreter}.
183    *
184    * <p>Only quantized tensors have valid {@code QuantizationParameters}. For tensor that are not
185    * quantized, the values of scale and zero_point are both 0.
186    */
quantizationParams()187   public QuantizationParams quantizationParams() {
188     return quantizationParamsCopy;
189   }
190 
191   /**
192    * Returns a read-only {@code ByteBuffer} view of the tensor data.
193    *
194    * <p>In general, this method is most useful for obtaining a read-only view of output tensor data,
195    * *after* inference has been executed (e.g., via {@link Interpreter#run(Object,Object)}). In
196    * particular, some graphs have dynamically shaped outputs, which can make feeding a predefined
197    * output buffer to the {@link Interpreter} awkward. Example usage:
198    *
199    * <pre>{@code
200    * interpreter.run(input, null);
201    * ByteBuffer outputBuffer = interpreter.getOutputTensor(0).asReadOnlyBuffer();
202    * // Copy or read from outputBuffer.
203    * }</pre>
204    *
205    * <p>WARNING: If the tensor has not yet been allocated, e.g., before inference has been executed,
206    * the result is undefined. Note that the underlying tensor pointer may also change when the
207    * tensor is invalidated in any way (e.g., if inference is executed, or the graph is resized), so
208    * it is *not* safe to hold a reference to the returned buffer beyond immediate use directly
209    * following inference. Example *bad* usage:
210    *
211    * <pre>{@code
212    * ByteBuffer outputBuffer = interpreter.getOutputTensor(0).asReadOnlyBuffer();
213    * interpreter.run(input, null);
214    * // Copy or read from outputBuffer (which may now be invalid).
215    * }</pre>
216    *
217    * <p>WARNING: This is an experimental interface that is subject to change.
218    *
219    * @throws IllegalArgumentException if the tensor data has not been allocated.
220    */
asReadOnlyBuffer()221   public ByteBuffer asReadOnlyBuffer() {
222     // Note that the ByteBuffer order is not preserved when duplicated or marked read only, so
223     // we have to repeat the call.
224     return buffer().asReadOnlyBuffer().order(ByteOrder.nativeOrder());
225   }
226 
227   /**
228    * Copies the contents of the provided {@code src} object to the Tensor.
229    *
230    * <p>The {@code src} should either be a (multi-dimensional) array with a shape matching that of
231    * this tensor, a {@link ByteByffer} of compatible primitive type with a matching flat size, or
232    * {@code null} iff the tensor has an underlying delegate buffer handle.
233    *
234    * @throws IllegalArgumentException if the tensor is a scalar or if {@code src} is not compatible
235    *     with the tensor (for example, mismatched data types or shapes).
236    */
setTo(Object src)237   void setTo(Object src) {
238     if (src == null) {
239       if (hasDelegateBufferHandle(nativeHandle)) {
240         return;
241       }
242       throw new IllegalArgumentException(
243           "Null inputs are allowed only if the Tensor is bound to a buffer handle.");
244     }
245     throwIfTypeIsIncompatible(src);
246     throwIfSrcShapeIsIncompatible(src);
247     if (isBuffer(src)) {
248       setTo((Buffer) src);
249     } else if (dtype == DataType.STRING && shapeCopy.length == 0) {
250       // Update scalar string input with 1-d byte array.
251       writeScalar(nativeHandle, src);
252     } else if (src.getClass().isArray()) {
253       writeMultiDimensionalArray(nativeHandle, src);
254     } else {
255       writeScalar(nativeHandle, src);
256     }
257   }
258 
setTo(Buffer src)259   private void setTo(Buffer src) {
260     // Note that we attempt to use a direct memcpy optimization for direct, native-ordered buffers.
261     // There are no base Buffer#order() or Buffer#put() methods, so again we have to ugly cast.
262     if (src instanceof ByteBuffer) {
263       ByteBuffer srcBuffer = (ByteBuffer) src;
264       if (srcBuffer.isDirect() && srcBuffer.order() == ByteOrder.nativeOrder()) {
265         writeDirectBuffer(nativeHandle, src);
266       } else {
267         buffer().put(srcBuffer);
268       }
269     } else if (src instanceof LongBuffer) {
270       LongBuffer srcBuffer = (LongBuffer) src;
271       if (srcBuffer.isDirect() && srcBuffer.order() == ByteOrder.nativeOrder()) {
272         writeDirectBuffer(nativeHandle, src);
273       } else {
274         buffer().asLongBuffer().put(srcBuffer);
275       }
276     } else if (src instanceof FloatBuffer) {
277       FloatBuffer srcBuffer = (FloatBuffer) src;
278       if (srcBuffer.isDirect() && srcBuffer.order() == ByteOrder.nativeOrder()) {
279         writeDirectBuffer(nativeHandle, src);
280       } else {
281         buffer().asFloatBuffer().put(srcBuffer);
282       }
283     } else if (src instanceof IntBuffer) {
284       IntBuffer srcBuffer = (IntBuffer) src;
285       if (srcBuffer.isDirect() && srcBuffer.order() == ByteOrder.nativeOrder()) {
286         writeDirectBuffer(nativeHandle, src);
287       } else {
288         buffer().asIntBuffer().put(srcBuffer);
289       }
290     } else if (src instanceof ShortBuffer) {
291       ShortBuffer srcBuffer = (ShortBuffer) src;
292       if (srcBuffer.isDirect() && srcBuffer.order() == ByteOrder.nativeOrder()) {
293         writeDirectBuffer(nativeHandle, src);
294       } else {
295         buffer().asShortBuffer().put(srcBuffer);
296       }
297     } else {
298       throw new IllegalArgumentException("Unexpected input buffer type: " + src);
299     }
300   }
301 
302   /**
303    * Copies the contents of the tensor to {@code dst} and returns {@code dst}.
304    *
305    * @param dst the destination buffer, either an explicitly-typed array, a compatible {@link
306    *     Buffer} or {@code null} iff the tensor has an underlying delegate buffer handle. If
307    *     providing a (multi-dimensional) array, its shape must match the tensor shape *exactly*. If
308    *     providing a {@link Buffer}, its capacity must be at least as large as the source tensor's
309    *     capacity.
310    * @throws IllegalArgumentException if {@code dst} is not compatible with the tensor (for example,
311    *     mismatched data types or shapes).
312    */
copyTo(Object dst)313   Object copyTo(Object dst) {
314     if (dst == null) {
315       if (hasDelegateBufferHandle(nativeHandle)) {
316         return dst;
317       }
318       throw new IllegalArgumentException(
319           "Null outputs are allowed only if the Tensor is bound to a buffer handle.");
320     }
321     throwIfTypeIsIncompatible(dst);
322     throwIfDstShapeIsIncompatible(dst);
323     if (isBuffer(dst)) {
324       copyTo((Buffer) dst);
325     } else {
326       readMultiDimensionalArray(nativeHandle, dst);
327     }
328     return dst;
329   }
330 
copyTo(Buffer dst)331   private void copyTo(Buffer dst) {
332     // There is no base Buffer#put() method, so we have to ugly cast.
333     if (dst instanceof ByteBuffer) {
334       ((ByteBuffer) dst).put(buffer());
335     } else if (dst instanceof FloatBuffer) {
336       ((FloatBuffer) dst).put(buffer().asFloatBuffer());
337     } else if (dst instanceof LongBuffer) {
338       ((LongBuffer) dst).put(buffer().asLongBuffer());
339     } else if (dst instanceof IntBuffer) {
340       ((IntBuffer) dst).put(buffer().asIntBuffer());
341     } else if (dst instanceof ShortBuffer) {
342       ((ShortBuffer) dst).put(buffer().asShortBuffer());
343     } else {
344       throw new IllegalArgumentException("Unexpected output buffer type: " + dst);
345     }
346   }
347 
348   /** Returns the provided buffer's shape if specified and different from this Tensor's shape. */
349   // TODO(b/80431971): Remove this method after deprecating multi-dimensional array inputs.
getInputShapeIfDifferent(Object input)350   int[] getInputShapeIfDifferent(Object input) {
351     if (input == null) {
352       return null;
353     }
354     // Implicit resizes based on ByteBuffer capacity isn't supported, so short-circuit that path.
355     // The Buffer's size will be validated against this Tensor's size in {@link #setTo(Object)}.
356     if (isBuffer(input)) {
357       return null;
358     }
359     throwIfTypeIsIncompatible(input);
360     int[] inputShape = computeShapeOf(input);
361     if (Arrays.equals(shapeCopy, inputShape)) {
362       return null;
363     }
364     return inputShape;
365   }
366 
367   /**
368    * Forces a refresh of the tensor's cached shape.
369    *
370    * <p>This is useful if the tensor is resized or has a dynamic shape.
371    */
refreshShape()372   void refreshShape() {
373     this.shapeCopy = shape(nativeHandle);
374   }
375 
376   /** Returns the type of the data. */
dataTypeOf(Object o)377   DataType dataTypeOf(Object o) {
378     if (o != null) {
379       Class<?> c = o.getClass();
380       // For arrays, the data elements must be a *primitive* type, e.g., an
381       // array of floats is fine, but not an array of Floats.
382       if (c.isArray()) {
383         while (c.isArray()) {
384           c = c.getComponentType();
385         }
386         if (float.class.equals(c)) {
387           return DataType.FLOAT32;
388         } else if (int.class.equals(c)) {
389           return DataType.INT32;
390         } else if (short.class.equals(c)) {
391           return DataType.INT16;
392         } else if (byte.class.equals(c)) {
393           // Byte array can be used for storing string tensors, especially for ParseExample op.
394           if (dtype == DataType.STRING) {
395             return DataType.STRING;
396           }
397           return DataType.UINT8;
398         } else if (long.class.equals(c)) {
399           return DataType.INT64;
400         } else if (boolean.class.equals(c)) {
401           return DataType.BOOL;
402         } else if (String.class.equals(c)) {
403           return DataType.STRING;
404         }
405       } else {
406         // For scalars, the type will be boxed.
407         if (Float.class.equals(c) || o instanceof FloatBuffer) {
408           return DataType.FLOAT32;
409         } else if (Integer.class.equals(c) || o instanceof IntBuffer) {
410           return DataType.INT32;
411         } else if (Short.class.equals(c) || o instanceof ShortBuffer) {
412           return DataType.INT16;
413         } else if (Byte.class.equals(c)) {
414           // Note that we don't check for ByteBuffer here; ByteBuffer payloads
415           // are allowed to map to any type, and should be handled earlier
416           // in the input/output processing pipeline.
417           return DataType.UINT8;
418         } else if (Long.class.equals(c) || o instanceof LongBuffer) {
419           return DataType.INT64;
420         } else if (Boolean.class.equals(c)) {
421           return DataType.BOOL;
422         } else if (String.class.equals(c)) {
423           return DataType.STRING;
424         }
425       }
426     }
427     throw new IllegalArgumentException(
428         "DataType error: cannot resolve DataType of " + o.getClass().getName());
429   }
430 
431   /** Returns the shape of an object as an int array. */
computeShapeOf(Object o)432   int[] computeShapeOf(Object o) {
433     int size = computeNumDimensions(o);
434     if (dtype == DataType.STRING) {
435       Class<?> c = o.getClass();
436       if (c.isArray()) {
437         while (c.isArray()) {
438           c = c.getComponentType();
439         }
440         // If the given string data is stored in byte streams, the last array dimension should be
441         // treated as a value.
442         if (byte.class.equals(c)) {
443           --size;
444         }
445       }
446     }
447     int[] dimensions = new int[size];
448     fillShape(o, 0, dimensions);
449     return dimensions;
450   }
451 
452   /** Returns the number of elements in a flattened (1-D) view of the tensor's shape. */
computeNumElements(int[] shape)453   static int computeNumElements(int[] shape) {
454     int n = 1;
455     for (int i = 0; i < shape.length; ++i) {
456       n *= shape[i];
457     }
458     return n;
459   }
460 
461   /** Returns the number of dimensions of a multi-dimensional array, otherwise 0. */
computeNumDimensions(Object o)462   static int computeNumDimensions(Object o) {
463     if (o == null || !o.getClass().isArray()) {
464       return 0;
465     }
466     if (Array.getLength(o) == 0) {
467       throw new IllegalArgumentException("Array lengths cannot be 0.");
468     }
469     return 1 + computeNumDimensions(Array.get(o, 0));
470   }
471 
472   /** Recursively populates the shape dimensions for a given (multi-dimensional) array. */
fillShape(Object o, int dim, int[] shape)473   static void fillShape(Object o, int dim, int[] shape) {
474     if (shape == null || dim == shape.length) {
475       return;
476     }
477     final int len = Array.getLength(o);
478     if (shape[dim] == 0) {
479       shape[dim] = len;
480     } else if (shape[dim] != len) {
481       throw new IllegalArgumentException(
482           String.format("Mismatched lengths (%d and %d) in dimension %d", shape[dim], len, dim));
483     }
484     for (int i = 0; i < len; ++i) {
485       fillShape(Array.get(o, i), dim + 1, shape);
486     }
487   }
488 
throwIfTypeIsIncompatible(Object o)489   private void throwIfTypeIsIncompatible(Object o) {
490     // ByteBuffer payloads can map to any type, so exempt it from the check.
491     if (isByteBuffer(o)) {
492       return;
493     }
494     DataType oType = dataTypeOf(o);
495 
496     if (oType != dtype) {
497       // INT8 and UINT8 have the same string name, "byte"
498       if (oType.toStringName().equals(dtype.toStringName())) {
499         return;
500       }
501 
502       throw new IllegalArgumentException(
503           String.format(
504               "Cannot convert between a TensorFlowLite tensor with type %s and a Java "
505                   + "object of type %s (which is compatible with the TensorFlowLite type %s).",
506               dtype, o.getClass().getName(), oType));
507     }
508   }
509 
throwIfSrcShapeIsIncompatible(Object src)510   private void throwIfSrcShapeIsIncompatible(Object src) {
511     if (isBuffer(src)) {
512       Buffer srcBuffer = (Buffer) src;
513       int bytes = numBytes();
514       // Note that we allow the client to provide a ByteBuffer even for non-byte Tensors.
515       // In such cases, we only care that the raw byte capacity matches the tensor byte capacity.
516       int srcBytes =
517           isByteBuffer(src) ? srcBuffer.capacity() : srcBuffer.capacity() * dtype.byteSize();
518       if (bytes != srcBytes) {
519         throw new IllegalArgumentException(
520             String.format(
521                 "Cannot copy to a TensorFlowLite tensor (%s) with %d bytes from a "
522                     + "Java Buffer with %d bytes.",
523                 name(), bytes, srcBytes));
524       }
525       return;
526     }
527     int[] srcShape = computeShapeOf(src);
528     if (!Arrays.equals(srcShape, shapeCopy)) {
529       throw new IllegalArgumentException(
530           String.format(
531               "Cannot copy to a TensorFlowLite tensor (%s) with shape %s from a Java object "
532                   + "with shape %s.",
533               name(), Arrays.toString(shapeCopy), Arrays.toString(srcShape)));
534     }
535   }
536 
throwIfDstShapeIsIncompatible(Object dst)537   private void throwIfDstShapeIsIncompatible(Object dst) {
538     if (isBuffer(dst)) {
539       Buffer dstBuffer = (Buffer) dst;
540       int bytes = numBytes();
541       // Note that we allow the client to provide a ByteBuffer even for non-byte Tensors.
542       // In such cases, we only care that the raw byte capacity fits the tensor byte capacity.
543       // This is subtly different than Buffer *inputs*, where the size should be exact.
544       int dstBytes =
545           isByteBuffer(dst) ? dstBuffer.capacity() : dstBuffer.capacity() * dtype.byteSize();
546       if (bytes > dstBytes) {
547         throw new IllegalArgumentException(
548             String.format(
549                 "Cannot copy from a TensorFlowLite tensor (%s) with %d bytes to a "
550                     + "Java Buffer with %d bytes.",
551                 name(), bytes, dstBytes));
552       }
553       return;
554     }
555     int[] dstShape = computeShapeOf(dst);
556     if (!Arrays.equals(dstShape, shapeCopy)) {
557       throw new IllegalArgumentException(
558           String.format(
559               "Cannot copy from a TensorFlowLite tensor (%s) with shape %s to a Java object "
560                   + "with shape %s.",
561               name(), Arrays.toString(shapeCopy), Arrays.toString(dstShape)));
562     }
563   }
564 
isBuffer(Object o)565   private static boolean isBuffer(Object o) {
566     return o instanceof Buffer;
567   }
568 
isByteBuffer(Object o)569   private static boolean isByteBuffer(Object o) {
570     return o instanceof ByteBuffer;
571   }
572 
573   private long nativeHandle;
574   private final DataType dtype;
575   private int[] shapeCopy;
576   private final int[] shapeSignatureCopy;
577   private final QuantizationParams quantizationParamsCopy;
578 
Tensor(long nativeHandle)579   private Tensor(long nativeHandle) {
580     this.nativeHandle = nativeHandle;
581     this.dtype = DataType.fromC(dtype(nativeHandle));
582     this.shapeCopy = shape(nativeHandle);
583     this.shapeSignatureCopy = shapeSignature(nativeHandle);
584     this.quantizationParamsCopy =
585         new QuantizationParams(
586             quantizationScale(nativeHandle), quantizationZeroPoint(nativeHandle));
587   }
588 
buffer()589   private ByteBuffer buffer() {
590     return buffer(nativeHandle).order(ByteOrder.nativeOrder());
591   }
592 
create(long interpreterHandle, int tensorIndex, int subgraphIndex)593   private static native long create(long interpreterHandle, int tensorIndex, int subgraphIndex);
594 
delete(long handle)595   private static native void delete(long handle);
596 
buffer(long handle)597   private static native ByteBuffer buffer(long handle);
598 
writeDirectBuffer(long handle, Buffer src)599   private static native void writeDirectBuffer(long handle, Buffer src);
600 
dtype(long handle)601   private static native int dtype(long handle);
602 
shape(long handle)603   private static native int[] shape(long handle);
604 
shapeSignature(long handle)605   private static native int[] shapeSignature(long handle);
606 
numBytes(long handle)607   private static native int numBytes(long handle);
608 
hasDelegateBufferHandle(long handle)609   private static native boolean hasDelegateBufferHandle(long handle);
610 
readMultiDimensionalArray(long handle, Object dst)611   private static native void readMultiDimensionalArray(long handle, Object dst);
612 
writeMultiDimensionalArray(long handle, Object src)613   private static native void writeMultiDimensionalArray(long handle, Object src);
614 
writeScalar(long handle, Object src)615   private static native void writeScalar(long handle, Object src);
616 
index(long handle)617   private static native int index(long handle);
618 
subgraph(long handle)619   private static native int subgraph(long handle);
620 
name(long handle)621   private static native String name(long handle);
622 
quantizationScale(long handle)623   private static native float quantizationScale(long handle);
624 
quantizationZeroPoint(long handle)625   private static native int quantizationZeroPoint(long handle);
626 }
627