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.tensorImport
import com.zeticai.mlange.core.tensor.Tensor
import com.zeticai.mlange.core.tensor.DataTypeCompanion 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,
): TensorOverloads are provided for the following source types:
| Source | Default DataType |
|---|---|
ByteBuffer | DataType.Int8 |
ByteArray | DataType.Int8 |
FloatArray, FloatBuffer | DataType.Float32 |
DoubleArray, DoubleBuffer | DataType.Float64 |
IntArray, IntBuffer | DataType.Int32 |
LongArray, LongBuffer | DataType.Int64 |
ShortArray, ShortBuffer | DataType.Int16 |
CharArray, CharBuffer | DataType.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,
): TensorSupported 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,
): Tensorval 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,
)| Parameter | Type | Description |
|---|---|---|
data | ByteBuffer | A direct ByteBuffer holding the raw tensor bytes. Non-direct buffers will throw. |
dataType | DataType | Element type of the tensor. See DataType. Defaults to DataType.Int8. |
shape | IntArray | Tensor shape. The product of its dimensions times dataType.size must equal data.capacity(). |
immediateRelease | Boolean | When 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(): Intsize()
Returns the size of the tensor in bytes (equivalent to data.capacity()).
fun size(): Intdata<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(): TSupported T:
| Type | Returns |
|---|---|
ByteBuffer | The underlying buffer itself. |
IntBuffer, LongBuffer, FloatBuffer, DoubleBuffer, CharBuffer, ShortBuffer | A typed view on the buffer. |
ByteArray, IntArray, LongArray, FloatArray, DoubleArray, CharArray, ShortArray | A 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): TSupported 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| Variant | Element size (bytes) |
|---|---|
DataType.Float32 | 4 |
DataType.Float64 | 8 |
DataType.Float16, DataType.BFloat16 | 2 |
DataType.UInt8, DataType.Int8, DataType.QInt8, DataType.QInt4 | 1 |
DataType.UInt16, DataType.Int16, DataType.QInt16 | 2 |
DataType.UInt32, DataType.Int32, DataType.QInt32 | 4 |
DataType.UInt64, DataType.Int64 | 8 |
DataType.Boolean | 1 |
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: ByteBufferPrefer 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:
-
Prefer model-owned tensors whenever possible. Tensors returned from
ZeticMLangeModel.getInputBuffers()and the output array ofZeticMLangeModel.run()wrap buffers that the model itself allocates and reuses. Filling them in place withfrom()/copy()or reading outputs directly avoids any extra allocation and has no off-heap cleanup burden — the buffers are released when you callclose()on the model. -
Only set
immediateRelease = trueon tensors you allocate yourself. When you build a tensor withTensor.of(...)(for example, a per-frame camera input) and pass it torun(), the model copies your bytes into its own input buffer. Your tensor is useless afterrun()returns, so registering it withTensorCleanerviaimmediateRelease = truelets off-heap memory be reclaimed as soon as the object becomes unreachable — which preventsOutOfMemoryError: Direct buffer memoryunder 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
- ZeticMLangeModel (Android): Uses
Tensoras the input and output type ofrun() - Tensor (iOS): iOS equivalent
- Enums and Constants: Other Android SDK enums