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