Phân tích lập chỉ mục

Tài liệu này mô tả tính năng phân tích lập chỉ mục HLO, cho phép bạn tính toán biểu tượng các bản đồ lập chỉ mục cho các hoạt động HLO. Bản đồ lập chỉ mục là một hàm ánh xạ các chỉ mục của một tensor với các chỉ mục của một tensor khác, ví dụ: các chỉ mục của đầu ra lệnh HLO với các chỉ mục của đầu vào lệnh HLO hoặc ngược lại.

Ví dụ:

Đối với thông báo truyền phát từ tensor<20xf32> đến tensor<10x20x30xf32>

p0 = f32[20] parameter(0)
bc0 = f32[10, 20, 30] broadcast(p0), dimensions={1}

bản đồ lập chỉ mục từ đầu ra đến đầu vào là \((i, j, k) \mapsto (j)\) với $i \in [0, 10]\(, \)j \in [0, 20]\( and \)k \in [0, 30]$.

Động lực

GPU XLA sử dụng một số giải pháp riêng để lý giải về việc kết hợp, sử dụng toán hạng và giao thức xếp kề (xem thêm thông tin chi tiết bên dưới). Mục tiêu của việc phân tích lập chỉ mục là cung cấp một thành phần có thể sử dụng lại cho các trường hợp sử dụng như vậy. Phân tích lập chỉ mục được xây dựng trên cơ sở hạ tầng Bản đồ Affine của MLIR và thêm ngữ nghĩa HLO.

Kết hợp

Việc lý giải về việc hợp nhất bộ nhớ trở nên khả thi trong những trường hợp không nhỏ khi chúng ta biết phần tử/phần nào của dữ liệu đầu vào được đọc để tính toán một phần tử của đầu ra.

Sử dụng toán hạng

Mức sử dụng toán hạng trong XLA cho biết mức sử dụng mỗi giá trị đầu vào của lệnh, giả sử đầu ra của lệnh đó được sử dụng đầy đủ. Hiện tại, mức sử dụng cũng không được tính cho trường hợp chung. Dữ liệu phân tích lập chỉ mục cho phép tính toán việc sử dụng một cách chính xác.

Cách phân ô

Thẻ thông tin/Lát cắt là tập con siêu chữ nhật của một tensor được tham số hoá bằng độ lệch, kích thước và sải chân. Truyền thẻ thông tin là một cách để tính toán các tham số thẻ thông tin của nhà sản xuất/người dùng tiêu thụ bằng các tham số xếp kề của chính hoạt động đó. Hiện đã có một thư viện thực hiện việc này chosoftmax và dấu chấm. Việc truyền thẻ thông tin có thể trở nên tổng quát và mạnh mẽ hơn nếu được thể hiện qua bản đồ lập chỉ mục.

Hàm và miền

Sơ đồ lập chỉ mục là một hàm \(\boldsymbol{f}(\boldsymbol{d}, \boldsymbol{s})\)giúp liên kết một đa chỉ mục \(\boldsymbol{d}\) của tensor \(A\) với các phần tử/dải ô của tensor \(B\). Tham số \(\boldsymbol{s}\) là phạm vi các chỉ mục của các chiều có trong tensor \(B\), nhưng không có trong tensor \(A\).

Ví dụ: nếu chúng ta giảm từ tensor<2x4x8x16xf32> xuống tensor<4x8xf32>, thì bản đồ lập chỉ mục từ đầu ra 2D đến đầu vào 4D sẽ là\((d_0, d_1) \mapsto (s_0, d_0, d_1, s_1)\), trong đó \(d_i\) là các tham số kích thước tương ứng với các chỉ mục của tensor đầu ra. Tham số \(s_j\)mã hoá nhiều giá trị, tức là để tính toán một phần tử \((d_0, d_1)\) của dữ liệu đầu ra, chúng ta cần \((s_0, d_0, d_1, s_1)\) phần tử của dữ liệu đầu vào đó, trong đó \(s_0 \in [0, 2)\) và\(s_1 \in [0, 16)\).

Quá trình ánh xạ này có thể được tạo từ các thuộc tính của lệnh HLO hoặc mối liên kết của các lệnh không được kết hợp để lập chỉ mục cho quá trình hợp nhất. Mối liên kết cũng có một miền chỉ định các phần tử của tensor mà ánh xạ tồn tại.

\[ \begin{eqnarray} \boldsymbol{f}(\boldsymbol{d}, \boldsymbol{s})\; &s.t.& \\ \boldsymbol{lb}_d &\leq& \boldsymbol{d} \leq \boldsymbol{ub}_d \\ \boldsymbol{lb}_s &\leq& \boldsymbol{s} \leq \boldsymbol{ub}_s \\ \boldsymbol{lb}_g &\leq& \boldsymbol{g}(\boldsymbol{d}, \boldsymbol{s}) \leq \boldsymbol{ub}_g \end{eqnarray} \]

Vì muốn giảm thiểu việc tính toán lại, chúng ta cần một thư viện để tính toán tượng trưng. XLA đã phụ thuộc vào MLIR, vì vậy, chúng tôi sử dụng mlir::AffineMap thay vì viết thư viện số học tượng trưng.

Một AffineMap thông thường sẽ hiển thị như sau

(d0)[s0, s1] -> (s0 + 5, d0 * 2, s1 * 3 + 50)

AffineMap thuận tiện có 2 loại tham số: thứ nguyênký hiệu mà chúng ta có thể sử dụng \(\boldsymbol d\) và \(\boldsymbol s\) tương ứng. AffineMap không chứa bất kỳ siêu dữ liệu nào về phạm vi phương diện, vì vậy, chúng ta phải tự cung cấp dữ liệu này.

struct Range {
 int64_t lower_bound;
 int64_t upper_bound;
};

struct IndexingMap {
 mlir::AffineMap affine_map;
 std::vector<Range> dimension_ranges;
 std::vector<Range> symbol_ranges;
 llvm::DenseMap<mlir::AffineExpr, Range> expr_ranges;
};

dim_ranges mã hoá các quy tắc ràng buộc hộp bao gồm cho các tham số kích thước \(\boldsymbol{d}\) của bản đồ lập chỉ mục, thường trùng với hình dạng của tensor đầu ra cho các hoạt động như hoán vị, giảm, phần tử, dấu chấm, nhưng có một số ngoại lệ như HloConcatenateInstruction.

symbol_ranges mã hoá các giá trị có thể có mà tham số \(\boldsymbol {s}\) có thể lấy.

Hãy cùng nghiên cứu từng ví dụ để tìm hiểu ý nghĩa thực sự của tất cả những điều trên.

Bản đồ lập chỉ mục cho các hoạt động chưa hợp nhất

Theo nguyên tử

Đối với một hoạt động theo phần tử, bản đồ lập chỉ mục là một danh tính.

  p0 = f32[10, 20] parameter(0)
  p1 = f32[10, 20] parameter(1)
  add = f32[10, 20] add(p0, p1)

Đầu ra để nhập vào bản đồ:

  • đầu ra -> input_0: \((d_0, d_1) \mapsto (d_0, d_1)\) for $\boldsymbol{d} \in [0,9] \times [0, 19]\(, i.e. \)\boldsymbol{d} \in {\rm Dom}(đầu ra)$
  • đầu ra -> input_1: \((d_0, d_1) \mapsto (d_0, d_1)\) cho $\boldsymbol{d} \in {\rm Dom} (đầu ra)$

Bản đồ đầu vào để ra

  • input_i -> output: \((d_0, d_1) \mapsto (d_0, d_1)\) cho $\boldsymbol{d} \in {\rm Dom}(output)$

Truyền

Việc truyền tin có nghĩa là một số kích thước sẽ bị xoá khi chúng ta ánh xạ đầu ra với đầu vào và được thêm vào khi ánh xạ đầu vào với đầu ra.

p0 = f32[20] parameter(0)
bc0 = f32[10, 20, 30] broadcast(p0), dimensions={1}

Đầu ra để ánh xạ đầu vào:

  • đầu ra -> đầu vào: \((d_0, d_1, d_2) \mapsto (d_1)\) cho $\boldsymbol{d} \in {\rm Dom}(output)$

Ánh xạ đầu vào để đầu ra

  • đầu vào -> đầu ra: \((d_0) \mapsto (s_0, d_1, s_1)\) cho $\boldsymbol{d} \in {\rm Dom}(output)\( and \)\boldsymbol{s} \in [0, 9] \times [0, 29]$.

Xin lưu ý rằng hiện tại chúng ta đã có \(\boldsymbol s\) ở bên phải để liên kết dữ liệu từ đầu vào đến đầu ra. Đó là những ký hiệu đại diện cho phạm vi giá trị. Ví dụ: trong trường hợp cụ thể này, mọi phần tử của đầu vào có chỉ mục \(d_0\) đều được liên kết với một lát cắt 10x1x30 của đầu ra.

Hằng số và Iota

Điểm thuận tiện là các hàm này không có bất kỳ tham số đầu vào nào nên không có gì để tính lập chỉ mục.

Hoán vị

Bản đồ lập chỉ mục cho hoán vị là sự hoán vị của các kích thước đầu vào/đầu ra.

p0 = f32[3, 12288, 6, 128] parameter(0)
transpose = f32[3, 6, 128, 12288] transpose(p0), dimensions={0, 2, 3, 1}

Đầu ra để ánh xạ đầu vào:

  • đầu ra -> đầu vào: \((d_0, d_1, d_2, d_3) \mapsto (d_0, d_3, d_1, d_2)\) cho\(\boldsymbol{d} \in {\rm Dom}(output)\)

Ánh xạ đầu vào để đầu ra:

  • đầu vào -> đầu ra: \((d_0, d_1, d_2, d_3) \mapsto (d_0, d_2, d_3, d_1)\) cho\(\boldsymbol{d} \in {\rm Dom}(input)\)

Úp rổ ngược

Bản đồ lập chỉ mục cho những thay đổi đảo ngược các phương diện được khôi phục thành $upper_bound(d_i) - d_i$:

p0 = f32[1, 17, 9, 9] parameter(0)
reverse = f32[1, 17, 9, 9] reverse(p0), dimensions={1, 2}

Đầu ra để ánh xạ đầu vào:

  • đầu ra -> đầu vào: $(d_0, d_1, d_2, d_3) \mapsto (d_0, -d_1 + 16, -d_2 + 8, d_3)\( for \)\boldsymbol{d} \in {\rm Dom}(đầu ra)$

Ánh xạ đầu vào để đầu ra:

  • đầu vào -> đầu ra: $(d_0, d_1, d_2, d_3) \mapsto (d_0, -d_1 + 16, -d_2 + 8, d_3)\( for \)\boldsymbol{d} \in {\rm Dom}(đầu vào)$

(Variadic)Giảm thiểu

Việc rút gọn biến thiên có một vài đầu vào và một vài đơn vị đầu vào, quá trình liên kết từ đầu ra đến đầu vào sẽ thêm các kích thước được thu gọn. Vì vậy, theo một cách nào đó, nó hoạt động giống như nghịch đảo với thông báo truyền tin.

p0 = f32[256,10] parameter(0)
p0_init = f32[] constant(-inf)
p1 = s32[256,10] parameter(1)
p1_init = s32[] constant(0)
reduce = (f32[10], s32[10]) reduce(p0, p1, p0_init, p1_init),
  dimensions={0}, to_apply=min

Đầu ra để nhập vào bản đồ:

  • đầu ra -> input_j: \((d_0) \mapsto (s_0, d_0)\) cho $\boldsymbol{d} \in {\rm Dom}(đầu ra)\( and \)\boldsymbol{s} \in [0, 9]$
  • đầu ra -> init_j: \((d_0) \mapsto ()\) cho $\boldsymbol{d} \in {\rm Dom}(output)$

Bản đồ đầu vào để ra:

  • input_i -> output_j: \((d_0, d_1) \mapsto (d_1)\) cho $\boldsymbol{d} \in {\rm Dom}(input)$
  • init_i -> output_j: \(() \mapsto (s_0)\) cho \(\boldsymbol{s} \in [0, 9]\)

cho \(i, j = 0, \ldots, INPUT\\_COUNT\).

Slice (lát cắt)

Việc lập chỉ mục từ đầu ra đến đầu vào cho lát cắt sẽ tạo ra một bản đồ lập chỉ mục nổi bật phù hợp với mọi phần tử của đầu ra. Việc ánh xạ từ đầu vào đến đầu ra bị hạn chế trong một dải ô dọc của các phần tử trong đầu vào.

p0 = f32[10, 20, 50] parameter(0)
slice = f32[5, 3, 25] slice(f32[10, 20, 50] p0),
  slice={[5:10:1], [3:20:7], [0:50:2]}

Đầu ra để ánh xạ đầu vào:

  • đầu ra -> đầu vào: \((d_0, d_1, d_2) \mapsto (d_0 + 5, 7d_1 + 3, 2d_2)\) cho\(\boldsymbol{d} \in {\rm Dom}(output)\)

Ánh xạ đầu vào để đầu ra:

  • đầu vào -> đầu ra: \((d_0, d_1, d_2) \mapsto (d_0, d_1 / 7, d_2 / 2)\) cho\(\boldsymbol{d} \in [5, 9] \times [3, 19] \times [0, 49]\) với các sải chân $[1, 7, 2]$.

TBD: lập chỉ mục đầu vào đến đầu ra

Đổi hình dạng

Hình dạng lại có nhiều hương vị khác nhau.

Thu gọn hình dạng

Đây là hình dạng "tuyến tính hoá" từ N-D thành 1D.

p0 = f32[4,8] parameter(0)
reshape = f32[32] reshape(p0)

Đầu ra để ánh xạ đầu vào:

  • đầu ra -> đầu vào: \((d_0) \mapsto (d_0 / 8, d_0 \mod 8)\) cho $\boldsymbol{d} \in {\rm Dom}(output)$

Ánh xạ đầu vào để đầu ra:

  • đầu vào -> đầu ra: \((d_0, d_1) \mapsto (8 d_0 + d_1)\) cho $\boldsymbol{d} \in {\rm Dom}(đầu vào)$.

Mở rộng hình dạng

Đây là hoạt động ngược "thu gọn hình dạng", nó định dạng lại đầu vào 1D thành đầu ra N-D.

p0 = f32[32] parameter(0)
reshape = f32[4, 8] reshape(p0)

Đầu ra để ánh xạ đầu vào:

  • đầu ra -> đầu vào: \((d_0, d_1) \mapsto (8 d_0 + d_1)\) cho $\boldsymbol{d} \in {\rm Dom}(output)$

Ánh xạ đầu vào để đầu ra:

  • đầu vào -> đầu ra: \((d_0) \mapsto (d_0 / 8, d_0 \mod 8)\) cho $\boldsymbol{d} \in {\rm Dom}(input)$.

Đổi hình dạng chung

Đây là các hoạt động định hình lại không thể được biểu thị dưới dạng một hình dạng mở rộng hoặc thu gọn duy nhất. Chúng chỉ có thể được biểu thị dưới dạng một cấu trúc gồm 2 hoặc nhiều hình dạng mở rộng hoặc thu gọn.

Ví dụ 1: Tuyến tính hoá – phi tuyến tính hoá.
p0 = f32[4,8] parameter(0)
reshape = f32[2, 4, 4] reshape(p0)

Hình dạng lại này có thể được biểu thị dưới dạng một cấu trúc thu gọn hình dạng của tensor<4x8xf32> thành tensor<32xf32>, sau đó là mở rộng hình dạng thành tensor<2x4x4xf32>.

Đầu ra để ánh xạ đầu vào:

  • đầu ra -> đầu vào: $(d_0, d_1, d_2) \mapsto (2d_0 + (4d_1 + d_2) / 8, 4d_1 + d_2) \mod 8)$

trong \(\boldsymbol{d} \in {\rm Dom}(output)\)

Ánh xạ đầu vào để đầu ra:

  • đầu vào -> đầu ra: $(d_0, d_1) \mapsto ((8d_0 + d_1) / 16, ((8d_0 + d_1) \mod 16) / 4, d_1 \mod 4)$

cho \(\boldsymbol{d} \in {\rm Dom}(input)\).

Ví dụ 2: Hình dạng con mở rộng và thu gọn
p0 = f32[4, 8, 12] parameter(0)
reshape = f32[32, 3, 4] reshape(p0)

Hình dạng lại này có thể được biểu thị dưới dạng một thành phần gồm hai hình dạng lại. Khung đầu tiên thu gọn kích thước ngoài cùng tensor<4x8x12xf32> thành tensor<32x12xf32> và kích thước thứ hai mở rộng kích thước trong cùng tensor<32x12xf32> thành tensor<32x3x4xf32>.

Đầu ra để ánh xạ đầu vào:

  • đầu ra -> đầu vào: \((d_0, d_1, d_2) \mapsto (d_0 / 8, d_0 \mod 8, 4d_1 + d_2)\)cho \(\boldsymbol{d} \in {\rm Dom}(output)\)

Ánh xạ đầu vào để đầu ra:

  • đầu vào -> đầu ra: \((d_0, d_1, d_2) \mapsto (8d_0 + d_1, d_2 / 4, d_2 \mod 4)\)cho \(\boldsymbol{d} \in {\rm Dom}(input)\).

Bitcast

Một hoạt động bitcast có thể được biểu thị dưới dạng một trình tự của hoán vị-đổi hình-dạng-hoá. Do đó, các bản đồ lập chỉ mục của API này chỉ là một cấu trúc của các bản đồ lập chỉ mục cho trình tự này.

Nối

Ánh xạ đầu ra với đầu vào cho concat được xác định cho tất cả các đầu vào, nhưng với các miền không chồng chéo, tức là chỉ có một trong các đầu vào sẽ được sử dụng tại một thời điểm.

p0 = f32[3,50] parameter(0)
p1 = f32[3,30] parameter(1)
concat = f32[3,80] concatenate(f32[3,50] p0, f32[3,30] p1),
  dimensions={1}

Đầu ra để ánh xạ đầu vào:

  • đầu ra -> đầu vào 1:

\((d_0, d_1) \mapsto (d_0, d_1)\) cho \(\boldsymbol{d} \in [0, 2] \times [0, 49]\)

  • đầu ra -> đầu vào 2:

\((d_0, d_1) \mapsto (d_0, d_1 - 50)\) cho $\boldsymbol{d} \in [0, 2] \times [50, 79]$

Các dữ liệu đầu vào cho bản đồ đầu ra:

  • đầu vào 1 -> đầu ra: \((d_0, d_1) \mapsto (d_0, d_1)\) cho $\boldsymbol{d} \in {\rm Dom}(input_1)$.
  • đầu vào 2 -> đầu ra: \((d_0, d_1) \mapsto (d_0, d_1 + 50)\) cho $\boldsymbol{d} \in {\rm Dom}(input_2)$.

Dấu chấm (triển khai đầu ra đến đầu vào

Bản đồ lập chỉ mục cho dấu chấm rất giống với bản đồ rút gọn.

p0 = f32[4, 128, 256] parameter(0)
p1 = f32[4, 256, 64] parameter(1)
dot = f32[4, 128, 64] dot(p0, p1),
  lhs_batch_dims={0}, rhs_batch_dims={0},
  lhs_contracting_dims={2}, rhs_contracting_dims={1}

Liên kết đầu ra với đầu vào:

  • đầu ra -> input_1: \((d_0, d_1, d_2) \mapsto (d_0, d_1, s_0)\) cho\(\boldsymbol{d} \in {\rm Dom}(output)\) và \(\boldsymbol{s} \in [0, 255]\)
  • đầu ra -> input_2: \((d_0, d_1, d_2) \mapsto (d_0, s_0, d_2)\) cho\(\boldsymbol{d} \in {\rm Dom}(output)\) và \(\boldsymbol{s} \in [0, 255]\)

Các dữ liệu đầu vào cho bản đồ đầu ra:

  • input_1 -> output: \((d_0, d_1, d_2) \mapsto (d_0, d_1, s_0)\) cho \(\boldsymbol{d} \in {\rm Dom}(input_1)\) và \(\boldsymbol{s} \in [0, 63]\)
  • input_2 -> output: \((d_0, d_1, d_2) \mapsto (d_0, s_0, d_1)\) cho \(\boldsymbol{d} \in {\rm Dom}(input_2)\) và \(\boldsymbol{s} \in [0, 127]\)

Thu nhỏ cửa sổ (chưa xác định)

Đệm (chưa xác định)

Lập chỉ mục Maps cho Fusion

Bản đồ lập chỉ mục cho fusion op là một thành phần của bản đồ lập chỉ mục cho mọi op trong cụm. Có thể xảy ra trường hợp một số dữ liệu đầu vào được đọc nhiều lần với các mẫu truy cập khác nhau.

Một dữ liệu đầu vào, một số bản đồ lập chỉ mục

Sau đây là ví dụ về \(p_0 + p_0^T\)

f {
  p0 = f32[1000, 1000] parameter(0)
  transpose_p0 = f32[1000, 1000]{0, 1} transpose(p0), dimensions={1, 0}
  ROOT a0 = f32[1000, 1000] add(p0, transpose_p0)
}

Bản đồ lập chỉ mục từ đầu ra đến đầu vào cho p0 sẽ là $(d_0, d_1) \mapsto (d_0, d_1)\( and \)(d_0, d_1) \mapsto (d_1, d_0)$. Tức là để tính toán một phần tử của đầu ra, chúng ta có thể phải đọc tham số đầu vào hai lần.

Một mục nhập, bản đồ lập chỉ mục đã loại bỏ trùng lặp

img

Có những trường hợp bản đồ lập chỉ mục thực sự giống nhau, mặc dù điều này không rõ ràng ngay lập tức.

f {
  p0 = f32[20, 10, 50] parameter(0)
  lhs_transpose_1 = f32[10, 20, 50] transpose(p0), dimensions={1, 0, 2}
  lhs_e = f32[10, 20, 50] exponential(lhs_transpose_1)
  lhs_transpose_2 = f32[10, 50, 20] transpose(lhs_e), dimensions={0, 2, 1}
  rhs_transpose_1 = f32[50, 10, 20] transpose(p0), dimensions={2, 1, 0}
  rhs_log = f32[50, 10, 20] exponential(rhs_transpose_1)
  rhs_transpose_2 = f32[10, 50, 20] transpose(rhs_log), dimensions={1, 0, 2}
  ROOT add = f32[10, 50, 20] add(lhs_transpose_2, rhs_transpose_2)
}

Bản đồ lập chỉ mục từ đầu ra đến đầu vào cho p0 trong trường hợp này chỉ là $(d_0, d_1, d_2) \mapsto (d_2, d_0, d_1)$.

Tối đa hoá hiệu suất

img

Bản đồ lập chỉ mục từ đầu ra đến đầu vào cho parameter 0 cho Softmax:

  • \((d_0, d_1, d_2) \mapsto (d_0, d_1, d_2)\)
  • \((d_0, d_1, d_2)[s_0] \mapsto (d_0, d_1, s_0)\)

for \(\boldsymbol{d} \in {\rm Dom}(output)\) và \(\boldsymbol{s} \in [0, 124]\)là kích thước trong cùng của đầu vào.

Trình đơn giản hoá bản đồ lập chỉ mục

Trình đơn giản hoá mặc định cho mlir::AffineMap ngược dòng không thể đưa ra bất kỳ giả định nào về phạm vi kích thước/ký hiệu. Do đó, phương thức này không thể đơn giản hoá biểu thức bằng moddiv một cách hiệu quả.

Chúng ta có thể tận dụng kiến thức về giới hạn dưới và giới hạn trên của biểu thức phụ trong bản đồ affine để đơn giản hoá các biểu thức đó hơn nữa.

Trình đơn giản hoá có thể viết lại các biểu thức sau.

  1. \((d_0, d_1) \mapsto (d_0 + d1 / 16, d1 \mod 16)\) cho $\boldsymbol{d} \in [0, 6] \times [0, 14]\( becomes \)(d_0, d_1) \mapsto (d_0, d_1)$
  2. $(d_0, d_1, d_2) \mapsto ((100d_0 + 10d_1 + d_2) /100, ((100d_0 + 10d_1 + d_2) \mod 100) / 10, d_2 \mod 10_0 d_0,\( becomes \)d_1,\( becomes \)d_1, d_1\( for \)
  3. $(d_0, d_1, d_2) \mapsto ((16d_0 + 4d_1 + d_2) /8, (16d_0 + 4d_1 + d_2) \mod 8)\( for \)d_i \in [0, 9]\( becomes \)(d_0, d_1, d_1\d_2) (d_1, d_2) (d_1, d_1, d_2)
  4. \((d_0, d_1) \mapsto (-(-11d_0 - d_1 + 109) / 11 + 9)\) cho $\boldsymbol{d} \in [0, 9] \times [0, 10]\( becomes \)(d_0, d_1) \mapsto (d_0)$.

Trình đơn giản hoá bản đồ lập chỉ mục cho phép chúng ta hiểu rằng một số hình dạng lại theo chuỗi trong HLO huỷ lẫn nhau.

p0 = f32[10, 10, 10] parameter(0)
reshape1 = f32[50, 20] reshape(p0)
reshape2 = f32[10, 10, 10] reshape(reshape1)

Sau khi có được thành phần của bản đồ lập chỉ mục và bản đồ đơn giản hoá, chúng tôi sẽ có được

\((d_0, d_1, d_2) \mapsto (d_0, d_1, d_2)\).