Melange
API ReferenceAndroid

Tensor

Complete API reference for the Tensor class on Android.

The Tensor class is the unified data container passed to and returned from ZeticMLangeModel.run(). It wraps a direct ByteBuffer together with its DataType and shape, and provides typed accessors for reading and writing the underlying data.

Package

com.zeticai.mlange.core.tensor

Import

import com.zeticai.mlange.core.tensor.Tensor
import com.zeticai.mlange.core.tensor.DataType

Companion Factories

The Tensor.of(...) factories are the recommended way to create tensors from typed arrays or NIO buffers. Each overload infers a sensible default DataType matching the source type.

Tensor.of(
    data: FloatArray,
    dataType: DataType = DataType.Float32,
    shape: IntArray = intArrayOf(data.size),
    immediateRelease: Boolean = false,
): Tensor

Overloads are provided for the following source types:

SourceDefault DataType
ByteBufferDataType.Int8
ByteArrayDataType.Int8
FloatArray, FloatBufferDataType.Float32
DoubleArray, DoubleBufferDataType.Float64
IntArray, IntBufferDataType.Int32
LongArray, LongBufferDataType.Int64
ShortArray, ShortBufferDataType.Int16
CharArray, CharBufferDataType.Int16

Each overload also has a variant that accepts shape: Array<Int> in addition to shape: IntArray.

val input = Tensor.of(
    data = floatArrayOf(/* ... */),
    dataType = DataType.Float32,
    shape = intArrayOf(1, 3, 640, 640),
)

Tensor.of(data, dataType, shape) (generic array)

inline fun <reified T> of(
    data: Array<T>,
    dataType: DataType,
    shape: Array<Int>,
    immediateRelease: Boolean = false,
): Tensor

Supported element types: Int, Float, Long, Double, Char, Short.

Tensor.random(dataType, shape, immediateRelease)

Creates a tensor filled with random bytes. Handy for smoke-testing a model graph.

fun random(
    dataType: DataType,
    shape: IntArray,
    immediateRelease: Boolean = false,
): Tensor
val fakeInput = Tensor.random(DataType.Float32, intArrayOf(1, 3, 640, 640))

Constructor

Tensor(data, dataType, shape, immediateRelease)

Creates a tensor directly from a direct ByteBuffer. Prefer the Tensor.of(...) factories above; use this constructor only when you already hold a direct ByteBuffer (for example, wrapping a native allocation).

Tensor(
    data: ByteBuffer,
    dataType: DataType = DataType.Int8,
    shape: IntArray = intArrayOf(data.capacity()),
    immediateRelease: Boolean = false,
)
ParameterTypeDescription
dataByteBufferA direct ByteBuffer holding the raw tensor bytes. Non-direct buffers will throw.
dataTypeDataTypeElement type of the tensor. See DataType. Defaults to DataType.Int8.
shapeIntArrayTensor shape. The product of its dimensions times dataType.size must equal data.capacity().
immediateReleaseBooleanWhen true, registers the tensor with TensorCleaner so its native memory is released as soon as it becomes unreachable.

Throws: IllegalArgumentException if data is not a direct ByteBuffer, or if shape does not match data.capacity().

val buffer = ByteBuffer.allocateDirect(4 * 3 * 4).order(ByteOrder.nativeOrder())
val tensor = Tensor(buffer, DataType.Float32, intArrayOf(1, 3, 2, 2))

Methods

count()

Returns the number of elements in the tensor (total bytes divided by dataType.size).

fun count(): Int

size()

Returns the size of the tensor in bytes (equivalent to data.capacity()).

fun size(): Int

data<T>()

Reifies the underlying ByteBuffer as a typed view or array. Useful for reading inference outputs as floats, ints, etc.

inline fun <reified T> data(): T

Supported T:

TypeReturns
ByteBufferThe underlying buffer itself.
IntBuffer, LongBuffer, FloatBuffer, DoubleBuffer, CharBuffer, ShortBufferA typed view on the buffer.
ByteArray, IntArray, LongArray, FloatArray, DoubleArray, CharArray, ShortArrayA backing array copy.

Throws: IllegalArgumentException if T is not one of the supported types.

val floats: FloatArray = tensor.data()
val floatView: FloatBuffer = tensor.data()

get<T>(index)

Reads a single element at index as the requested numeric type.

inline fun <reified T : Number> get(index: Int): T

Supported T: Int, Long, Float, Double, Char, Short, Byte.

val first: Float = tensor.get<Float>(0)

set(index, value)

Writes value into the tensor at index. The write type is inferred from the runtime type of value.

fun <T> set(index: Int, value: T)

Supported value types: Int, Long, Float, Double, Char, Short.

tensor.set(0, 1.5f)

order(order)

Changes the byte order of the underlying buffer.

fun order(order: ByteOrder)
tensor.order(ByteOrder.LITTLE_ENDIAN)

from(tensor)

Copies another tensor's bytes into this tensor. The source tensor must have the same shape.

fun from(tensor: Tensor)

Throws: IllegalArgumentException if the source tensor's shape does not match.

output.from(reusedOutputTensor)

copy(data, shape, dataType, immediateRelease)

Copies bytes from data into this tensor in place. The total byte size implied by shape and dataType must equal this tensor's current size.

fun copy(
    data: ByteBuffer = this.data,
    shape: IntArray = this.shape,
    dataType: DataType = this.dataType,
    immediateRelease: Boolean = this.immediateRelease,
)

Throws: IllegalArgumentException if the source or destination buffer is smaller than the expected byte count.


DataType

DataType is a sealed interface describing the element type and byte size of a tensor.

import com.zeticai.mlange.core.tensor.DataType
VariantElement size (bytes)
DataType.Float324
DataType.Float648
DataType.Float16, DataType.BFloat162
DataType.UInt8, DataType.Int8, DataType.QInt8, DataType.QInt41
DataType.UInt16, DataType.Int16, DataType.QInt162
DataType.UInt32, DataType.Int32, DataType.QInt324
DataType.UInt64, DataType.Int648
DataType.Boolean1
DataType.Unknown(size)Caller-provided

Use DataType.from(name) to look up a type by its lowercase name (e.g. "float32", "int8", "bool"), and DataType.toName(type) for the reverse.


Properties

data

The underlying direct ByteBuffer backing the tensor.

val data: ByteBuffer

Prefer the typed data<T>() accessor when you need a typed view.


Full Working Example

The fastest path is to fill the model's own input buffers in place and call run() with no arguments — skipping the byte copy that run(inputs) would otherwise perform from each user tensor into getInputBufferAt(i).

import com.zeticai.mlange.core.model.ZeticMLangeModel
import com.zeticai.mlange.core.tensor.Tensor

class MainActivity : AppCompatActivity() {
    private lateinit var model: ZeticMLangeModel
    private lateinit var inputBuffers: Array<Tensor>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        model = ZeticMLangeModel(this, PERSONAL_KEY, "Steve/YOLOv11_comparison")

        // (1) Grab the model-owned input buffers once
        inputBuffers = model.getInputBuffers()
    }

    private fun runFrame(sourceTensors: Array<Tensor>) {
        // (2) Fill each input buffer in place — no extra allocation, no memcpy inside run()
        for (i in inputBuffers.indices) {
            inputBuffers[i].from(sourceTensors[i])
        }

        // (3) Run inference without passing inputs — bytes are already in place
        val outputs: Array<Tensor> = model.run()

        // (4) Read outputs as typed arrays
        val logits: FloatArray = outputs[0].data()
        // ...consume outputs before the next run(); the buffers are reused.
    }
}

This zero-copy path (getInputBuffers() + run()) is currently Android-only — iOS does not yet expose model-owned input buffers and is planned for a future SDK release. For now, iOS builds a new Tensor per frame and passes it to run(inputs:).


Memory Management Recommendation

Tensor wraps a direct ByteBuffer that lives in off-heap native memory. How you manage that memory depends on who owns the underlying buffer:

  1. Prefer model-owned tensors whenever possible. Tensors returned from ZeticMLangeModel.getInputBuffers() and the output array of ZeticMLangeModel.run() wrap buffers that the model itself allocates and reuses. Filling them in place with from() / copy() or reading outputs directly avoids any extra allocation and has no off-heap cleanup burden — the buffers are released when you call close() on the model.

  2. Only set immediateRelease = true on tensors you allocate yourself. When you build a tensor with Tensor.of(...) (for example, a per-frame camera input) and pass it to run(), the model copies your bytes into its own input buffer. Your tensor is useless after run() returns, so registering it with TensorCleaner via immediateRelease = true lets off-heap memory be reclaimed as soon as the object becomes unreachable — which prevents OutOfMemoryError: Direct buffer memory under tight loops.

Never set immediateRelease = true on tensors obtained from getInputBuffers() or from run()'s return value. Those tensors share memory with the model; if the cleaner fires, the model's next run() will read from already-freed native memory and crash. Leave them with the default immediateRelease = false.


See Also

On this page