Skip to content

[WIP] Permute shape in torch_tensor_from_blob#415

Open
jatkinson1000 wants to merge 1 commit intomainfrom
C-layout
Open

[WIP] Permute shape in torch_tensor_from_blob#415
jatkinson1000 wants to merge 1 commit intomainfrom
C-layout

Conversation

@jatkinson1000
Copy link
Member

@jatkinson1000 jatkinson1000 commented Jul 27, 2025

Exploring the functionality of layout following query raised in #412

The aim is to generate row-major tensors with a 'transposed' shape when layout [n, n-1, ..., 2, 1] is passed.

To make this backwards-compatible (non-breaking) I have added a permute_shape argument.

However, there could be some debate about making this the default, since permute_shape=.false. with a non-monotonically increasing layout arguably produces a 'useless' tensor (see impending description below).
It is arguably the expectation of the user (principle of least surprise) that the use of layout should 'transpose' the shape as well as the indexing.

This is complicated by the fact we use an ambiguous example in the docs (section 3) with a 2x2 array where the result of both operations is identical.

IIRC this was always the original intent (though users always had to be explicit when passing the desired shape in) but perhaps got lost in changes to move away from separate Fortran and C routines and abstracting details from users that occurred before we had explicit tests to catch this sort of thing.

TODO:

  • Make proposed change in code
  • Check existing tests pass unaffected
  • Write tests to check for correct row-major tensors in Torch generated from Fortran transpose with layout = [2, 1] and permute_shape=.true.
  • Add explanation here of working to explore this issue
  • Check that this correctly performs what is (arguably) described in the docs (section 3) and is perhaps expected.
  • Discuss what the default value of permute_shape should be (see above)
  • Improve documentation with clear examples
  • Add changelog
  • Make minor version release

There is perhaps some discussion to be had around "should we require users to transpose the data themselves before calling this routine, as is the current implementation, or should we instead make the Fortran transpose part of the internals?"
I.e. providing torch_tensor_from_array() instead with a row-major=.true. argument that will perform the Fortran transpose ([m, n] -> [n, m]) and then calculate appropriate shape ([m, n]) and strides ([n, 1]) to appear in the same in Torch as it does in Fortran.

…llow shape to be adjusted according to `layout` in order to create a row-major 'transpose' in Torch.
@jatkinson1000
Copy link
Member Author

jatkinson1000 commented Jul 27, 2025

Take the data in memory as:

[ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 ]

This comes from a (column-major) Fortran array with shape [2, 3]:

[ 1.0, 3.0, 5.0, 
  2.0, 4.0, 6.0 ]

Calling torch_tensor_to_array() with layout = [1, 2] leaves this alone in memory to create an identical looking Torch tensor of shape [2, 3] but accessed from the 'row-major' Torch with strides [1, 2]:

[ 1.0, 3.0, 5.0, 
  2.0, 4.0, 6.0 ]

This means the 'same' Tensor appears in Torch as it does in Fortran, but requiring strides in memory to move along rows.


Calling torch_tensor_to_array() with layout = [2, 1] again leaves the data alone in memory to create a Torch tensor preserving the shape [2, 3] but accessed in 'row-major-like' strides [3, 1]:

[ 1.0, 2.0, 3.0, 
  4.0, 5.0, 6.0 ]

This 'row-major' access to underlying data preserves the Fortran shape.
As such the array corresponds to neither to the original array in Fortran, nor to its transpose.


A more realistic scenario is that users want to use the same array in Torch as in Fortran, but with 'row-major' access to the data instead of Fortran's column-major layout/access.
They perhaps wish to obtain 'row-major' access to the data for efficiency in Torch (when large tensors are involved), but doing so with a 'transpose' of the original Fortran data for the purposes of maintaining the 'scientific meaning' of the array. See #412 for example.

This can be achieved with the new permute_shape = .true. option introduced in this PR.
Calling torch_tensor_to_array() with layout = [2, 1] and permute_shape = .true. leaves the data alone in memory to create a Torch tensor, but permutes the shape to be [3, 2] then giving strided access in [2, 1]:

[ 1.0, 2.0, 
  3.0, 4.0,
  5.0, 6.0 ]

i.e. the transpose of the Fortran array accessed in a 'row-major' way.


To conclude:

Suppose a user wants to access the Fortran array using the same shape and indexing from Torch, but with row-major layout in memory for internal (to Torch) efficiency.

They start with a (column-major) Fortran array with shape [2, 3]:

[ 1.0, 3.0, 5.0, 
  2.0, 4.0, 6.0 ]

appearing in memory as

[ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 ]

They first transpose the array in Fortran:

[ 1.0, 2.0, 
  3.0, 4.0,
  5.0, 6.0 ]

to appear in Memory as:

[ 1.0, 3.0, 5.0, 2.0, 4.0, 6.0 ]

and then call torch_tensor_to_array() with layout = [2, 1] and permute_shape = .true. to leave the data alone in memory, but permute shape to [2, 3] with strided access of [3, 1]:

[ 1.0, 3.0, 5.0, 
  2.0, 4.0, 6.0 ]

Exactly as would be expected had we declared this tensor from first principles in Torch.

@Mikolaj-A-Kowalski
Copy link
Member

Just though I will comment since having a look at FTorch for a first time only recently I may be not a bad stand-in for a 'user' ;-)

And I confirm that I was expecting layout to permute the array! So I guess this might be in favour of permute_shape=.true.

However that being said I would also argue that in general the argument layout is rather confusing. It is basically equivalent of order argument of reshape, but it becomes a bit weird since the shape is implicit in the case of from_array_constructor. In the end to understand what is going on I had ended up with a mental model that performs flatten + reshape when constructing tensor form the array, which is a bit clunky. And since we are talking about a future interface a lite bit (for v2.0), I would like to raise a question whether not get rid of the layout argument in general... Instead perform all extra manipulations on the torch side just by modifying the tensors.

Basically to obtain a contiguous view of a shape (2,3,4) array one could do something along the lines of (using changes from #423 )

type(torch_tensor) :: tensor
real, target :: data(2,3,4) 
real, target :: data_t(4,3,2) 

! Perform transpose on fortran code 
data_t = reshape(data, [4,3,2], order=[3,2,1])

! Always use default layout when building a tensor from array
! Creates non-contiguous tensor of shape [4,3,2] 
call torch_tensor_from_array(tensor, data_t, torch_kCPU)

! We reshape the tensor to contiguous view of shape [2,3,4] stored on data_t 
call torch_tensor_shallow_copy(tensor, torch_tensor_permute(tensor, [3,2,1])

The above is of course a bit of a sketch and some details need to be confirmed (i.e. would everything be going ok with finalisation of tensors produced as a result of the function etc.).

To summarise: I have been just thinking if we could keep the array construction as simple as possible and instead enable extra features (such as row-major tensors), by exposing more torch functionality?

@jatkinson1000 jatkinson1000 added the RAB Issues and PRs associated with RAB label Jan 5, 2026
@jatkinson1000 jatkinson1000 added this to the v1.1 milestone Jan 5, 2026
@joewallwork
Copy link
Collaborator

Pushing this back to v1.2 as not urgent.

@joewallwork joewallwork modified the milestones: v1.1, v1.2 Feb 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

RAB Issues and PRs associated with RAB

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants