MLPacket

· 1 min read

Most examples of model inference start with a tensor. In LumNN, the value that moves between nodes is not only a tensor. It is an MLPacket: a payload plus the contract that describes how the payload should be interpreted.

This matters because a pipeline may connect CPU preprocessing, an ONNX Runtime model, and CPU postprocessing. If every node exposed its backend-specific tensor type, the pipeline would quickly become tied to one runtime. MLPacket gives the pipeline one common object to pass around, while still allowing the payload to live in different runtimes.

let descriptor = MLPacketDescriptor::new(
    MLPacketDataType::Float32,
    vec![1, 3, 224, 224],
);

let packet = context.packet_from_host_tensor(
    descriptor,
    HostTensor::Float32(pixel_values),
)?;

The descriptor is not documentation. It is checked when the packet is built and when a node receives input. If a node expects [1, 3, 224, 224] but receives [1, 224, 224, 3], LumNN should fail at the boundary where the mistake becomes visible.

For models that support dynamic batch size, the contract can say that only the first dimension is flexible:

let expected = MLPacketDescriptor::new(
    MLPacketDataType::Float32,
    vec![1, 3, 224, 224],
)
.with_dynamic_batch();

let actual = MLPacketDescriptor::new(
    MLPacketDataType::Float32,
    vec![4, 3, 224, 224],
);

expected.validate_compatibility(&actual, "pixel_values")?;