This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/main by this push:
     new dd242c3295 [TIRx] Preserve parameter strides and physical view extents 
(#20788)
dd242c3295 is described below

commit dd242c3295279433f232a559ade6f0f428d6966e
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Oct 18 13:20:00 2026 -0400

    [TIRx] Preserve parameter strides and physical view extents (#20788)
---
 docs/tirx/arch/lowering_pipeline.rst               |   5 +-
 docs/tirx/native_basics/cuda/tensors.rst           |  39 ++++---
 .../backend/trn/transform/lower_trainium_layout.cc |  14 ++-
 tirx/src/tirx/transform/flatten_tensor.cc          |   5 +-
 tirx/src/tirx/transform/flatten_tensor_utils.h     | 119 +++++++++++++++++++++
 tirx/src/tirx/transform/flattened_tensor.h         |  77 -------------
 tirx/src/tirx/transform/lower_tensor_layouts.cc    |   2 +-
 tirx/tests/python/tirx/tile/trn/test_copy.py       |  34 +++++-
 .../python/tirx/transform/test_flatten_tensor.py   |  21 +++-
 .../tirx/transform/test_lower_tensor_layouts.py    |  20 ++--
 10 files changed, 218 insertions(+), 118 deletions(-)

diff --git a/docs/tirx/arch/lowering_pipeline.rst 
b/docs/tirx/arch/lowering_pipeline.rst
index b449425fa6..44b0369f81 100644
--- a/docs/tirx/arch/lowering_pipeline.rst
+++ b/docs/tirx/arch/lowering_pipeline.rst
@@ -140,8 +140,9 @@ The generic pipeline explicitly composes two independently 
usable passes:
   finite CUDA index calls inside ordinary ``Bind`` statements.
 - **``LowerTensorLayouts``** then runs the ``LayoutApplier``: it resolves every
   ``TileLayout``-typed tensor access into concrete physical address arithmetic
-  (``addr = data + elem_offset + layout.apply(coord)``), flattens the tensors,
-  and removes tensor offsets that have been folded into the resulting views.
+  (``addr = data + layout.apply(coord, shape=shape)["m"]``, in elements),
+  flattens the tensors, and accounts for the layout's memory origin exactly 
once
+  in the resulting view or its access indices.
 
 A backend can pair ``TileDispatch`` with its own layout lowering, such as
 Trainium's ``LowerTrainiumLayout``. The generic pipeline inserts ``PrintIR``
diff --git a/docs/tirx/native_basics/cuda/tensors.rst 
b/docs/tirx/native_basics/cuda/tensors.rst
index 8c66d78484..aaba07af26 100644
--- a/docs/tirx/native_basics/cuda/tensors.rst
+++ b/docs/tirx/native_basics/cuda/tensors.rst
@@ -57,11 +57,8 @@ The tensor descriptors share these parameters:
    * - ``shape``
      - logical shape (a tuple of extents)
    * - ``layout``
-     - physical mapping (:doc:`TileLayout <../../layout>`); ``"default"`` = 
dense
-       row-major
-   * - ``elem_offset``
-     - ``elem_offset`` (or ``byte_offset``) places a *view* at an offset into
-       ``data``
+     - physical mapping, including strides and element origin
+       (:doc:`TileLayout <../../layout>`); ``"default"`` = dense row-major
    * - ``align``
      - alignment of the data pointer, in bytes
 
@@ -103,21 +100,23 @@ The ``scope`` argument selects the memory space:
 **A ptr-based tensor is just metadata over a pointer.** For any non-tmem 
tensor,
 the declaration is a pointer plus a layout, and indexing resolves to an 
address::
 
-    addr(tensor[coord]) = tensor.data + elem_offset + layout.apply(coord, 
shape=shape)["m"]
+    addr(tensor[coord]) = tensor.data + layout.apply(coord, shape=shape)["m"]
 
 (``layout.apply`` returns the per-axis mapping; its ``"m"`` component is the
-element offset.) So the *same* logical access compiles to different address
-arithmetic depending purely on the tensor's metadata. Writing
+element offset, including the layout's origin.) Pointer arithmetic here is in
+elements. Strides and origins are represented only by the layout, not by 
separate
+tensor arguments or properties. So the *same* logical access compiles to 
different
+address arithmetic depending purely on the tensor's metadata. Writing
 ``B[i, j] = A[i, j] + 1`` over a 4×8 region, with ``B`` annotated four ways in
 the function signature:
 
 .. code-block:: python
 
-    from tvm.tirx.layout import TileLayout, S
+    from tvm_tirx.tirx.layout import TileLayout, S
 
     B: Tx.Tensor((4, 8), "float32")  # row-major
     B: Tx.Tensor((4, 8), "float32", layout=TileLayout(S[(4, 8) : (1, 4)]))  # 
column-major
-    B: Tx.Tensor((4, 8), "float32", elem_offset=64)  # shifted view
+    B: Tx.Tensor((4, 8), "float32", layout=TileLayout(S[(4, 8)] + 64))  # 
shifted view
     B: Tx.Tensor((4, 8), "float32", layout=TileLayout(S[(4, 8) : (16, 1)]))  # 
row stride 16
 
 each makes ``B[i, j]`` lower to a different index in the generated CUDA (the
@@ -127,9 +126,14 @@ each makes ``B[i, j]`` lower to a different index in the 
generated CUDA (the
 
     B_ptr[((i * 8) + j)]        = ...;   // row-major:        i*8 + j
     B_ptr[((j * 4) + i)]        = ...;   // column-major:     j*4 + i
-    B_ptr[(((i * 8) + j) + 64)] = ...;   // elem_offset=64:   i*8 + j + 64
+    B_ptr[(((i * 8) + j) + 64)] = ...;   // layout origin 64:   i*8 + j + 64
     B_ptr[((i * 16) + j)]       = ...;   // row stride 16:    i*16 + j
 
+Layouts must describe the actual tensor domain; lowering does not resize or
+extend mismatched tile extents. For nonzero shapes and non-negative strides,
+allocation footprints cover the maximum mapped memory address. A declaration
+over a borrowed pointer remains responsible for the storage that pointer 
addresses.
+
 Shared memory
 -------------
 
@@ -176,7 +180,7 @@ Dynamic
 ``sharedMemBytes`` launch parameter), not at compile time. A kernel may have 
**only
 one** dynamic-shared allocation — the *arena*. So you allocate it once and 
``decl``
 each tensor as a view into it: ``Tx.decl_tensor`` with the arena pointer as its
-first argument and an ``elem_offset``:
+first argument and a memory-axis origin in its layout:
 
 .. code-block:: python
 
@@ -187,7 +191,10 @@ first argument and an ``elem_offset``:
         arena.data, ty_args=[Tx.Tensor((64,), "float32", scope="shared.dyn")]
     )  # offset 0
     Bs = Tx.decl_tensor(
-        arena.data, ty_args=[Tx.Tensor((64,), "float32", elem_offset=64, 
scope="shared.dyn")]
+        arena.data,
+        ty_args=[Tx.Tensor(
+            (64,), "float32", scope="shared.dyn", 
layout=Tx.TileLayout(Tx.S[(64,)] + 64)
+        )],
     )  # offset 64
     As[tx] = A[tx]
     Bs[tx] = B[tx]
@@ -404,9 +411,9 @@ or hand you a pointer — they emit no runtime op of their 
own.
 through an expression: ``B.view(...)`` binds the operand exactly as
 ``B.ty.view(B, ...)``. ``TensorType.__expr_properties__`` maps property names
 to getters taking the type and original expression. It preserves conveniences
-such as ``B.shape``, ``B.strides``, ``B.dtype``, ``B.byte_offset``, ``B.sub`` 
and
-``B.data`` without modifying the shared ``Var`` class. ``B.dtype`` returns a
-runtime ``DataType``; ``B.ty.dtype`` stores a ``PrimType``.
+such as ``B.shape``, ``B.dtype``, ``B.layout``, ``B.sub`` and ``B.data`` 
without
+modifying the shared ``Var`` class. ``B.dtype`` returns a runtime ``DataType``;
+``B.ty.dtype`` stores a ``PrimType``.
 
 Existing expression attributes and reflected fields take precedence, followed
 by declared properties and then declared methods. Declared properties are
diff --git a/tirx/src/backend/trn/transform/lower_trainium_layout.cc 
b/tirx/src/backend/trn/transform/lower_trainium_layout.cc
index 896b86ad5d..ef2739e67d 100644
--- a/tirx/src/backend/trn/transform/lower_trainium_layout.cc
+++ b/tirx/src/backend/trn/transform/lower_trainium_layout.cc
@@ -39,7 +39,7 @@
 #include <utility>
 #include <vector>
 
-#include "../../../tirx/transform/flattened_tensor.h"
+#include "../../../tirx/transform/flatten_tensor_utils.h"
 
 namespace tvm {
 namespace tirx {
@@ -73,9 +73,13 @@ class TrainiumLayoutApplier : public 
tirx::StmtExprMutatorWithAnalyzer {
         TensorVar flattened = 
storage_lower->GetFlattenedTensor(tensor.value());
         auto type = 
ffi::make_object<TensorTypeNode>(*tensor.value().ty().get());
         if (const auto* tile = type->layout.as<TileLayoutNode>()) {
-          // Static physical geometry is already embedded in the lowered 
indices.
-          // Retain symbolic geometry that may still define parameter 
variables.
-          bool static_geometry = tile->replica.empty();
+          // Trainium's physical axes are embedded in the lowered indices. 
Ordinary
+          // memory strides still define the parameter ABI, even when they are 
static.
+          // Retain symbolic physical geometry that may define parameter 
variables.
+          bool static_geometry =
+              IsTrainiumLayout(tile) && tile->replica.empty() &&
+              std::any_of(tile->shard.begin(), tile->shard.end(),
+                          [](const Iter& iter) { return 
iter->axis->IsMemoryAxis(); });
           for (const auto& iter : tile->shard) {
             static_geometry &= iter->extent.as<IntImmNode>() && 
iter->stride.as<IntImmNode>();
           }
@@ -211,7 +215,7 @@ class TrainiumLayoutApplier : public 
tirx::StmtExprMutatorWithAnalyzer {
         type = ffi::make_object<TensorTypeNode>(*flattened.ty().get());
       }
     } else {
-      flattened = FlattenedTensor(buf, ffi::GetRef<sym::Analyzer>(analyzer_));
+      flattened = FlattenedTensor(buf, ffi::GetRef<sym::Analyzer>(analyzer_), 
/*fold_origin=*/true);
       type = ffi::make_object<TensorTypeNode>(*flattened.ty().get());
     }
     if (flattened.dtype()->dtype == DLDataType{kDLBool, 8, 1}) {
diff --git a/tirx/src/tirx/transform/flatten_tensor.cc 
b/tirx/src/tirx/transform/flatten_tensor.cc
index bf944975c5..65db1f2ded 100644
--- a/tirx/src/tirx/transform/flatten_tensor.cc
+++ b/tirx/src/tirx/transform/flatten_tensor.cc
@@ -34,7 +34,7 @@
 
 #include <unordered_set>
 
-#include "flattened_tensor.h"
+#include "flatten_tensor_utils.h"
 #include "ir_utils.h"
 
 namespace tvm {
@@ -134,7 +134,8 @@ class TensorFlattener : public StmtExprMutatorWithAnalyzer {
 
     // buf': the storage husk. The linearized indices carry layout and
     // elem_offset, so the husk keeps neither.
-    auto flat = FlattenedTensor(fold_view, 
ffi::GetRef<sym::Analyzer>(analyzer_));
+    auto flat = FlattenedTensor(fold_view, 
ffi::GetRef<sym::Analyzer>(analyzer_),
+                                /*fold_origin=*/true);
     auto type = ffi::make_object<TensorTypeNode>(*flat.ty().get());
     for (size_t i = 0; i < type->shape.size(); ++i) {
       type->shape.Set(i, analyzer_->canonical_simplify(type->shape[i]));
diff --git a/tirx/src/tirx/transform/flatten_tensor_utils.h 
b/tirx/src/tirx/transform/flatten_tensor_utils.h
new file mode 100644
index 0000000000..5e001b100d
--- /dev/null
+++ b/tirx/src/tirx/transform/flatten_tensor_utils.h
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*!
+ * \file flatten_tensor_utils.h
+ * \brief Physical tensor view construction for layout lowering.
+ */
+#ifndef TVM_TIRX_TRANSFORM_FLATTEN_TENSOR_UTILS_H_
+#define TVM_TIRX_TRANSFORM_FLATTEN_TENSOR_UTILS_H_
+
+#include <tvm/ir/prim/op.h>
+#include <tvm/tirx/expr.h>
+#include <tvm/tirx/layout.h>
+
+namespace tvm {
+namespace tirx {
+
+/*!
+ * \brief Construct a one-dimensional physical tensor variable without 
allocating storage.
+ *
+ * The result preserves name, dtype, scope, alignment, and source location, 
and has
+ * no layout. Its shape covers the physical footprint, including stride gaps 
and
+ * conservative padding where needed. The extent is a required backing-storage
+ * bound in elements, not bytes or necessarily the minimum storage size. 
General
+ * layouts include memory-axis offsets and replicas, and exclude 
hardware-thread
+ * axes to give per-thread storage. Swizzles round up to blocks covering the 
bits
+ * they may change; a bare swizzle uses the tensor shape as its domain. 
Without a
+ * layout, the shape is the logical element count.
+ *
+ * Callers bind a changed variable to a decl_tensor over the source pointer 
for a
+ * borrowed view, and preserve any relative origin in that binding's geometry.
+ * Allocation callers apply their own per-thread/backend storage contract; this
+ * helper neither allocates nor rejects borrowed strided or offset views.
+ *
+ * \param tensor Tensor to flatten, assuming valid nonnegative strides and 
nonzero
+ * logical/tile extents, with any layout describing the actual tensor domain.
+ * Mismatched tile domains are not resized or extended.
+ * \param analyzer Optional analyzer; a fresh analyzer is used when absent.
+ * \param fold_origin For shape-aligned regular strides, include the memory 
origin
+ * in the extent for absolute flat indexing. Otherwise the caller retains that
+ * origin separately. General layouts already use an absolute bound regardless
+ * of this flag.
+ * \return Fresh physical tensor variable, or tensor itself if it is already
+ * one-dimensional with no layout.
+ */
+TVM_FFI_INLINE TensorVar FlattenedTensor(const TensorVar& tensor,
+                                         ffi::Optional<sym::Analyzer> analyzer 
= std::nullopt,
+                                         bool fold_origin = false) {
+  if (!analyzer.has_value()) analyzer = sym::Analyzer();
+  const auto& ana = analyzer.value();
+  ffi::Array<PrimExpr> output_shape{1};
+  const auto* tile = tensor.ty()->layout.as<TileLayoutNode>();
+  bool explicit_strides = tile && 
MatchShapeStridePattern(tensor.ty()).has_value();
+  if (explicit_strides) {
+    // Preserve the outer stride footprint and cover every addressed element.
+    PrimExpr span = 1;
+    for (const auto& iter : tile->shard) {
+      span = span + (iter->extent - 1) * iter->stride;
+    }
+    if (tile->IsContiguous(ana)) {
+      for (const auto& extent : tensor.shape()) output_shape.Set(0, 
output_shape[0] * extent);
+    } else {
+      output_shape.Set(0, tvm::max(tile->shard[0]->stride * tensor.shape()[0], 
span));
+    }
+    if (fold_origin) {
+      // Absolute flat indices include the origin.
+      static const Axis memory_axis = Axis::Get("m");
+      if (auto origin = tile->offset.Get(memory_axis)) {
+        output_shape.Set(0, output_shape[0] + origin.value());
+      }
+    }
+  } else if (tile) {
+    output_shape.Set(0, ana->Simplify(tile->GetSpan(ffi::String("m"))));
+  } else if (const auto* compose = 
tensor.ty()->layout.as<ComposeLayoutNode>()) {
+    PrimExpr span = compose->tile_layout->GetSpan(ffi::String("m"));
+    if (compose->tile_layout->IsTrivial()) {
+      span = IntImm(tensor.ty()->DefaultIndexType(), 1);
+      for (const auto& extent : tensor.shape()) span = span * extent;
+    }
+    if (compose->swizzle_len != 0) {
+      int changed_bits = compose->per_element + compose->swizzle_len;
+      if (!compose->swizzle_inner) changed_bits += compose->atom_len;
+      PrimExpr block = IntImm(span.ty(), int64_t{1} << changed_bits);
+      span = floordiv(span + block - 1, block) * block;
+    }
+    output_shape.Set(0, ana->Simplify(span));
+  } else {
+    for (const auto& extent : tensor.shape()) output_shape.Set(0, 
output_shape[0] * extent);
+  }
+
+  if (output_shape.size() == tensor.shape().size() && 
!tensor.ty()->layout.has_value()) {
+    return tensor;
+  }
+  return TensorVar(tensor->name,
+                   TensorType(output_shape, tensor.dtype(), tensor.scope(),
+                              tensor.ty()->data_alignment, std::nullopt),
+                   tensor->loc);
+}
+
+}  // namespace tirx
+}  // namespace tvm
+
+#endif  // TVM_TIRX_TRANSFORM_FLATTEN_TENSOR_UTILS_H_
diff --git a/tirx/src/tirx/transform/flattened_tensor.h 
b/tirx/src/tirx/transform/flattened_tensor.h
deleted file mode 100644
index e71669a279..0000000000
--- a/tirx/src/tirx/transform/flattened_tensor.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-/*!
- * \file flattened_tensor.h
- * \brief Physical tensor view construction for layout lowering.
- */
-#ifndef TVM_TIRX_TRANSFORM_FLATTENED_TENSOR_H_
-#define TVM_TIRX_TRANSFORM_FLATTENED_TENSOR_H_
-
-#include <tvm/ir/prim/op.h>
-#include <tvm/tirx/expr.h>
-#include <tvm/tirx/layout.h>
-
-namespace tvm {
-namespace tirx {
-
-// A changed type requires a fresh variable. Callers bind that variable to a
-// decl_tensor over the source's physical pointer when using it as a view.
-TVM_FFI_INLINE TensorVar FlattenedTensor(const TensorVar& tensor,
-                                         ffi::Optional<sym::Analyzer> analyzer 
= std::nullopt) {
-  ffi::Array<PrimExpr> output_shape{1};
-  const auto* tile = tensor.ty()->layout.as<TileLayoutNode>();
-  bool explicit_strides =
-      tile && !tile->shard.empty() && 
MatchShapeStridePattern(tensor.ty()).has_value();
-  if (explicit_strides) {
-    // Keep the outer allocation footprint and cover every addressed element.
-    PrimExpr span = 1;
-    for (const auto& iter : tile->shard) {
-      span = span + (iter->extent - 1) * iter->stride;
-    }
-    // The flattened footprint excludes the origin, which the caller applies 
separately.
-    if (tile->IsContiguous(analyzer)) {
-      output_shape.Set(0, PrimExpr(1));
-      for (const auto& extent : tensor.shape()) output_shape.Set(0, 
output_shape[0] * extent);
-    } else {
-      output_shape.Set(0, tvm::max(tile->shard[0]->stride * tensor.shape()[0], 
span));
-    }
-  } else {
-    // Otherwise, the flattened extent is the product of the input extents.
-    // This also flattens rank-0 tensors to a rank-1 tensor of shape [1].
-    for (size_t i = 0; i < tensor.shape().size(); i++) {
-      output_shape.Set(0, output_shape[0] * tensor.shape()[i]);
-    }
-  }
-
-  if (output_shape.size() == tensor.shape().size() && 
!tensor.ty()->layout.has_value()) {
-    return tensor;
-  } else {
-    // The original layout describes the old rank; reset it for the flattened 
shape.
-    return TensorVar(tensor->name,
-                     TensorType(output_shape, tensor.dtype(), tensor.scope(),
-                                tensor.ty()->data_alignment, std::nullopt),
-                     tensor->loc);
-  }
-}
-
-}  // namespace tirx
-}  // namespace tvm
-
-#endif  // TVM_TIRX_TRANSFORM_FLATTENED_TENSOR_H_
diff --git a/tirx/src/tirx/transform/lower_tensor_layouts.cc 
b/tirx/src/tirx/transform/lower_tensor_layouts.cc
index a3a861ec2a..8f55b07f57 100644
--- a/tirx/src/tirx/transform/lower_tensor_layouts.cc
+++ b/tirx/src/tirx/transform/lower_tensor_layouts.cc
@@ -41,7 +41,7 @@
 #include <utility>
 #include <vector>
 
-#include "flattened_tensor.h"
+#include "flatten_tensor_utils.h"
 #include "ir_utils.h"
 
 namespace tvm {
diff --git a/tirx/tests/python/tirx/tile/trn/test_copy.py 
b/tirx/tests/python/tirx/tile/trn/test_copy.py
index 4e82e44e0f..996f45f04f 100644
--- a/tirx/tests/python/tirx/tile/trn/test_copy.py
+++ b/tirx/tests/python/tirx/tile/trn/test_copy.py
@@ -15,6 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
+import numpy as np
 import pytest
 import tvm_ffi
 
@@ -25,6 +26,7 @@ from tvm_tirx.backend.trn.tile.instruction_generator import 
InstructionGenerator
 from tvm_tirx.ir import assert_structural_equal as _assert_structural_equal
 from tvm_tirx.script import tirx as T
 from tvm_tirx.sym import Analyzer
+from tvm_tirx.testing import env
 from tvm_tirx.tirx import TensorType, Var
 from tvm_tirx.tirx.layout import S, TileLayout
 
@@ -61,6 +63,32 @@ def test_instruction_generator_compact_global_layout():
     assert tensor.layout is None
 
 
[email protected](not env.has_llvm(), reason="need llvm")
+def test_parameter_stride_contract():
+    """Physical index lowering must preserve the host's DLTensor stride 
checks."""
+    layout = TileLayout(S[(2, 2) : (3, 1)])
+
+    @T.function
+    def copy(
+        A: T.Tensor((2, 2), "float32", layout=layout, data_alignment=1),
+        B: T.Tensor((2, 2), "float32"),
+    ):
+        for i, j in T.grid(2, 2):
+            B[i, j] = A[i, j]
+
+    mod = tvm_tirx.IRModule({"main": copy})
+    lowered = tvm_tirx.backend.trn.transform.LowerTrainiumLayout()(mod)
+    # Execute the lowered scalar accesses and packed wrapper on the host, 
without
+    # requiring a Trainium device or compiling Trainium-specific instructions.
+    lib = tvm_tirx.tirx.compile(lowered, target="llvm")
+    data = np.arange(6, dtype="float32").reshape(2, 3)[:, :2]
+    result = tvm_tirx.runtime.empty((2, 2), "float32")
+    lib(data, result)
+    np.testing.assert_array_equal(result.numpy(), data)
+    with pytest.raises(ValueError, match="strides"):
+        lib(np.ascontiguousarray(data), result)
+
+
 def test_simple_copy():
     src_shape = [128, 512]
     src_layout = T.TileLayout(T.S[(128, 512) : (512, 1)])
@@ -348,7 +376,7 @@ def test_copy_different_shape_dim():
             T.trn.tile.load(A_sbuf, A[i, :, :])
 
     @T.function
-    def expected(A: T.Tensor((32, 128, 512), "float32", layout=None)):
+    def expected(A: T.Tensor((32, 128, 512), "float32", layout=src_layout)):
         T.func_attr({"global_symbol": "copy"})
 
         A_1 = T.decl_tensor(
@@ -500,7 +528,7 @@ def test_copy_with_complex_index():
         T.trn.tile.load(A_sbuf[1, 0:2048, 0:1024], A[2048: 4096, 3072:4096])
 
     @T.function
-    def expected(A: T.Tensor((4096, 4096), "float32", layout=None)):
+    def expected(A: T.Tensor((4096, 4096), "float32", layout=A_layout)):
         T.func_attr({"global_symbol": "copy"})
 
         A_1 = T.decl_tensor(
@@ -536,7 +564,7 @@ def test_copy_with_complex_index_2():
         T.trn.tile.load(A_sbuf[2048: 4096, 3072:4096], A[1, 0:2048, 0:1024])
 
     @T.function
-    def expected(A: T.Tensor((2, 2048, 1024), "float32", layout=None)):
+    def expected(A: T.Tensor((2, 2048, 1024), "float32", layout=A_layout)):
         T.func_attr({"global_symbol": "copy"})
 
         A_1 = T.decl_tensor(
diff --git a/tirx/tests/python/tirx/transform/test_flatten_tensor.py 
b/tirx/tests/python/tirx/transform/test_flatten_tensor.py
index 266f59c759..71aad5dbe2 100644
--- a/tirx/tests/python/tirx/transform/test_flatten_tensor.py
+++ b/tirx/tests/python/tirx/transform/test_flatten_tensor.py
@@ -516,7 +516,7 @@ def test_flatten_remaps_loads_in_folded_elem_offset():
             base = T.alloc_shared([128], "uint64", layout=None)
             mbar = T.decl_tensor(
                 base.data,
-                ty_args=[T.Tensor((1,), "uint64", scope="shared", 
layout=None)],
+                ty_args=[T.Tensor((n[0] + 1,), "uint64", scope="shared", 
layout=None)],
             )
             mbar[n[0]] = T.uint64(1)
 
@@ -563,7 +563,7 @@ def test_tensor_flatten_offset():
             n: T.int32,
             A: T.Tensor((m, n), "float32", layout=T.TileLayout(T.S[(m, n)] + 
(T.int32(100)))),
         ):
-            flat = T.decl_tensor(A.data, ty_args=[T.Tensor((m * n,), 
"float32", layout=None)])
+            flat = T.decl_tensor(A.data, ty_args=[T.Tensor((m * n + 100,), 
"float32", layout=None)])
             T.evaluate(flat[n * 2 + 103])
 
     After = FlattenTensor()(Before)
@@ -702,5 +702,22 @@ def test_tensor_index_merge_rank_four(factored):
     tvm_tirx.ir.assert_structural_equal(After, Expected)
 
 
+def test_split_layout_physical_storage_extent():
+    layout = T.TileLayout(T.S[(3, 4) : (16, 2)])
+
+    @T.function(private=True)
+    def main(data: T.handle):
+        B = T.decl_tensor(data, ty_args=[T.Tensor((12,), "float32", 
layout=layout)])
+        B[11] = T.float32(7)
+
+    # A borrowed view's flattened extent must cover the physical gaps.
+    after = FlattenTensor()(tvm_tirx.IRModule({"main": main}))
+    body = after["main"].body
+    flat = body[0].var
+    assert int(flat.ty.shape[0]) == 39
+    assert int(body[-1].indices[0]) == 38
+    assert flat.ty.layout is None
+
+
 if __name__ == "__main__":
     tvm_tirx.testing.main()
diff --git a/tirx/tests/python/tirx/transform/test_lower_tensor_layouts.py 
b/tirx/tests/python/tirx/transform/test_lower_tensor_layouts.py
index 538f90c894..adc2526ea9 100644
--- a/tirx/tests/python/tirx/transform/test_lower_tensor_layouts.py
+++ b/tirx/tests/python/tirx/transform/test_lower_tensor_layouts.py
@@ -88,7 +88,7 @@ def test_lower_view_get():
                 lane_id: T.let[T.int32] = T.cuda.thread_idx("x") % 32
                 A = T.alloc_local((2,), "float16", layout=None)
                 B = T.decl_tensor(
-                    A.data, ty_args=[T.Tensor((64,), "float16", scope="local", 
layout=None)]
+                    A.data, ty_args=[T.Tensor((2,), "float16", scope="local", 
layout=None)]
                 )
                 A_local = T.decl_tensor(
                     A.data, ty_args=[T.Tensor((2,), "float16", scope="local", 
layout=None)]
@@ -96,7 +96,7 @@ def test_lower_view_get():
                 for i in T.vectorized(2):
                     A_local[i] = T.Cast("float16", 
in_buf_1[T.cuda.thread_idx("x") * 2 + i])
                 B_1 = T.decl_tensor(
-                    A.data, ty_args=[T.Tensor((64,), "float16", scope="local", 
layout=None)]
+                    A.data, ty_args=[T.Tensor((2,), "float16", scope="local", 
layout=None)]
                 )
                 A_local_1 = T.decl_tensor(
                     A.data, ty_args=[T.Tensor((2,), "float16", scope="local", 
layout=None)]
@@ -260,7 +260,7 @@ def test_lower_view_get():
                 lane_id: T.let[T.int32] = T.cuda.thread_idx("x") % 32
                 A = T.alloc_local((8,), layout=None)
                 B = T.decl_tensor(
-                    A.data, ty_args=[T.Tensor((256,), "float32", 
scope="local", layout=None)]
+                    A.data, ty_args=[T.Tensor((8,), "float32", scope="local", 
layout=None)]
                 )
                 A_local = T.decl_tensor(
                     A.data, ty_args=[T.Tensor((8,), "float32", scope="local", 
layout=None)]
@@ -275,7 +275,7 @@ def test_lower_view_get():
                             + T.cuda.thread_idx("x") % 4
                         ]
                 B_1 = T.decl_tensor(
-                    A.data, ty_args=[T.Tensor((256,), "float32", 
scope="local", layout=None)]
+                    A.data, ty_args=[T.Tensor((8,), "float32", scope="local", 
layout=None)]
                 )
                 A_local_1 = T.decl_tensor(
                     A.data, ty_args=[T.Tensor((8,), "float32", scope="local", 
layout=None)]
@@ -477,7 +477,7 @@ def test_lower_view_get():
                 lane_id: T.let[T.int32] = T.cuda.thread_idx("x") % 32
                 acc = T.alloc_local((64,), layout=None)
                 B = T.decl_tensor(
-                    acc.data, ty_args=[T.Tensor((16384,), "float32", 
scope="local", layout=None)]
+                    acc.data, ty_args=[T.Tensor((64,), "float32", 
scope="local", layout=None)]
                 )
                 acc_local = T.decl_tensor(
                     acc.data, ty_args=[T.Tensor((64,), "float32", 
scope="local", layout=None)]
@@ -494,7 +494,7 @@ def test_lower_view_get():
                                 + vec
                             ]
                 B_1 = T.decl_tensor(
-                    acc.data, ty_args=[T.Tensor((16384,), "float32", 
scope="local", layout=None)]
+                    acc.data, ty_args=[T.Tensor((64,), "float32", 
scope="local", layout=None)]
                 )
                 acc_local_1 = T.decl_tensor(
                     acc.data, ty_args=[T.Tensor((64,), "float32", 
scope="local", layout=None)]
@@ -700,10 +700,10 @@ def test_lower_view_get():
                 lane_id: T.let[T.int32] = T.cuda.thread_idx("x") % 32
                 A = T.alloc_local((2,), "float16", layout=None)
                 B = T.decl_tensor(
-                    A.data, ty_args=[T.Tensor((64,), "float16", scope="local", 
layout=None)]
+                    A.data, ty_args=[T.Tensor((2,), "float16", scope="local", 
layout=None)]
                 )
                 B_1 = T.decl_tensor(
-                    A.data, ty_args=[T.Tensor((64,), "float16", scope="local", 
layout=None)]
+                    A.data, ty_args=[T.Tensor((2,), "float16", scope="local", 
layout=None)]
                 )
                 A_local = T.decl_tensor(
                     A.data, ty_args=[T.Tensor((2,), "float16", scope="local", 
layout=None)]
@@ -714,10 +714,10 @@ def test_lower_view_get():
                 )
                 A_local_1[1] = T.Cast("float16", 
in_buf_1[T.cuda.thread_idx("x") * 2 + 1])
                 B_2 = T.decl_tensor(
-                    A.data, ty_args=[T.Tensor((64,), "float16", scope="local", 
layout=None)]
+                    A.data, ty_args=[T.Tensor((2,), "float16", scope="local", 
layout=None)]
                 )
                 B_3 = T.decl_tensor(
-                    A.data, ty_args=[T.Tensor((64,), "float16", scope="local", 
layout=None)]
+                    A.data, ty_args=[T.Tensor((2,), "float16", scope="local", 
layout=None)]
                 )
                 A_local_2 = T.decl_tensor(
                     A.data, ty_args=[T.Tensor((2,), "float16", scope="local", 
layout=None)]

Reply via email to