The board receives all the data. Why does the answer change?
A camera passes an image to a vision program: the pixel count is right, the buffer contains every byte, and no value is lost. Yet the first model calculation differs from expectations. Before blaming an accelerator or retraining the network, ask a simpler question: do the three numbers interpreted as a pixel’s channels actually belong to that pixel? We develop a minimal case where dimension checks pass but data meaning changes.
We separate three operations: changing dimensions, permuting axes, and making memory contiguous. This is an educational analysis executed on a host computer with Python 3.14.0 and NumPy 2.5.3, not a microcontroller or EL-AI product test. We report no latency, energy, or trained-network accuracy. A linear filter exposes every step instead of hiding it among thousands of weights.
An image is a table with several coordinates
A tensor here is simply numbers indexed by several coordinates. N counts images processed together, H is height, W width, and C channels. NHWC lists image, row, column, channel; NCHW puts channel before spatial coordinates. They can describe the same image under different conventions. The required equality is y[n, c, h, w]=x[n, h, w, c] at every valid coordinate, not merely equal element counts.
Our single image has two rows, two columns, and three synthetic channels. In reading order its pixels are (1,10,100), (2,20,200), (3,30,300), (4,40,400). Distinct values reveal swaps. This is not an eight-bit RGB photograph: 400 exceeds 255, so we use int16 integers, two bytes each. Twelve elements occupy 24 bytes of numeric content, excluding Python metadata, allocator overhead, and other buffers.
Changing shape is not changing order
With default C order, reshape(1,3,2,2) reads the sequence into the new shape with the final index changing fastest. Here the sequence stays 1,10,100,2,20,200,3,30,300,4,40,400. The first channel becomes [1,10,100,2], rather than [1,2,3,4]. Dimensions fit, but values from different channels and pixels have been mixed. NumPy documents indexing order, not a semantic conversion between image formats.
By contrast, transpose(0,3,1,2) maps new axis 0 to old 0, new 1 to old 3, and so on, preserving the desired coordinate equality. In this experiment it returns a view sharing the original storage, without copying content at that step. This is not a claim about every library or device. Logical access and physical arrangement differ; a NumPy view carries information that an interface receiving only a pointer might not receive.
Where do the bytes actually go?
To understand the interface, first calculate offsets for contiguous arrays. Let b=2 bytes per element and use zero-based indices. The following offset is a byte distance from the buffer start, not an absolute address. Layout determines the fastest-changing index. Noncontiguous views require actual strides: byte movements for a one-step change in each index, rather than these contiguous formulas alone.
Input strides are (24,12,6,2) bytes; the NCHW view has (24,2,12,6). Moving one channel in the view advances two bytes, while moving one column advances six. A contiguous NCHW copy has (24,8,4,2), with four elements per channel plane. ascontiguousarray on the view creates that correct copy. On the wrong reshape it cannot reconstruct pixels: contiguity preserves a semantic mistake. Here wrong reshape and correct copy even have identical strides but different values at the same coordinates.
A filter we can check by hand
Now measure the calculation change. Our filter weights channels by 1,2,3 and sums per pixel, without activation, normalisation, or bias. The correct first pixel gives 1+2·10+3·100=321. In the reshape, that position contains 1,20,300, giving 1+40+900=941. This is not rounding error: different data are being multiplied. int64 accumulation separates the layout issue from integer overflow.
| Pixel | Correct | Reshape |
|---|---|---|
| (0,0) | 321 | 941 |
| (0,1) | 642 | 422 |
| (1,0) | 963 | 226 |
| (1,1) | 1284 | 1262 |

Total output changes from 3210 to 2851, but preserving a total would not suffice: spatial permutations can leave a sum unchanged while altering the map. An all-ones input hides this error entirely: every grouping yields 1+2+3=6, a counterexample checked by the code. Uniform-image tests can therefore reassure falsely; distinct values, coordinate-wise comparisons, and a known pre-conversion reference are needed.
What the test guarantees, and what the board still needs
The experiment checks all twelve coordinate correspondences, the inverse permutation, contiguous-copy equality, and memory sharing. A logical permutation changes metadata; materialising M elements reads and writes them, requiring work proportional to M and an additional buffer here. Each array’s 24 bytes describe content, not process peak RAM: shared views and copies have different implications. These counts establish neither time nor power.
On-device validation still needs the runtime’s exact contract: axis order, type, contiguity, supported strides, buffer lifetime, and preprocessing. NHWC/NCHW conversion does not automatically exchange red and blue, resize, or apply normalisation scales and means. “Always copy” is not a universal rule: unnecessary copies consume memory, while omitting a required one can change what is read. The correct alternative depends on the contract and requires real-platform measurements.
The answer: counting bytes is not enough
The same numbers can yield a wrong result because a model reads coordinate-specific meaning, not an unordered collection. Our executed case separates three guarantees: reshape fits a shape, transpose preserves axis correspondence, and a contiguous copy satisfies a physical arrangement. None automatically replaces the others. Making that correspondence explicit and testable before optimising embedded inference avoids blaming training for an interface mistake.
Sources and executed material
NumPy 2.5 — reshape: indexing order, views and copies.
NumPy 2.5 — transpose: axis correspondence.
NumPy 2.5 — ascontiguousarray: contiguous shape and content.
import numpy as np
x=np.array([[[[1,10,100],[2,20,200]],[[3,30,300],[4,40,400]]]],dtype=np.int16)
y=x.transpose(0,3,1,2)
z=x.reshape(1,3,2,2)
w=np.array([1,2,3],dtype=np.int64)[None,:,None,None]
print((y.astype(np.int64)*w).sum(axis=1))
print((z.astype(np.int64)*w).sum(axis=1))
assert np.array_equal(y.transpose(0,2,3,1),x)
Code, data, and instructions · JSON. Educational calculations executed with Python 3.14.0; figures with Matplotlib 3.11.2. AI-assisted analysis, without claiming peer review or human review. Original illustrative ImageGen cover: it does not document EL-AI people, premises, or installations. Sources accessed 24 October 2026.

