MLIR의 'affine' 방언에서 사용하는 다면체 구조, affine/반‑affine 맵, 정수 집합과 다양한 affine 연산을 설명한다.
이 방언(dialect)은 affine 연산과 분석을 위한 강력한 추상화를 제공한다.
affine.apply (affine::AffineApplyOp)affine.delinearize_index (affine::AffineDelinearizeIndexOp)affine.for (affine::AffineForOp)affine.if (affine::AffineIfOp)affine.linearize_index (affine::AffineLinearizeIndexOp)affine.load (affine::AffineLoadOp)affine.max (affine::AffineMaxOp)affine.min (affine::AffineMinOp)affine.parallel (affine::AffineParallelOp)affine.prefetch (affine::AffinePrefetchOp)affine.store (affine::AffineStoreOp)affine.vector_load (affine::AffineVectorLoadOp)affine.vector_store (affine::AffineVectorStoreOp)affine.yield (affine::AffineYieldOp)affine.dma_start (mlir::AffineDmaStartOp)affine.dma_wait (mlir::AffineDmaWaitOp)MLIR은 의존성 분석과 루프 변환을 효율적이고 신뢰성 있게 수행하기 위해 다면체(polyhedral) 컴파일 기법을 사용한다. 이 절에서는 문서 전체에서 사용되는 핵심 개념 몇 가지를 소개한다.
차원(dimension)과 심볼(symbol)은 다면체 구조에 등장할 수 있는 두 종류의 식별자이며, 항상 index 타입이다. 차원은 괄호 () 안에, 심볼은 대괄호 [] 안에 선언된다.
예:
mlir// 2차원에서 3차원으로의 affine 매핑. // d0/d1은 차원, s0는 심볼이다. #affine_map2to3 = affine_map<(d0, d1)[s0] -> (d0, d1 + s0, d1 - s0)>
차원 식별자는 표현하려는 기본 구조(맵, 집합, 혹은 좀 더 구체적으로 루프 중첩이나 텐서)의 차원에 대응한다. 예를 들어, 3중 루프 중첩은 세 개의 차원 식별자를 가진다. 심볼 식별자는 관심 영역에 대해 상수로 취급할 수 있는 미지의 양을 나타낸다.
차원과 심볼은 MLIR의 여러 연산에 의해 SSA 값에 바인딩되며, 두 종류를 구분하기 위해 동일하게 괄호/대괄호 리스트 표기법을 사용한다.
문법:
mlir// 차원 식별자에 전달되는 SSA 값 사용. dim-use-list ::= `(` ssa-use-list? `)` // 심볼을 바인딩하는 데 사용되는 SSA 값 사용. symbol-use-list ::= `[` ssa-use-list? `]` // 대부분의 SSA 값 바인딩은 차원과 심볼을 함께 바인딩한다. dim-and-symbol-use-list ::= dim-use-list symbol-use-list?
차원과 심볼에 바인딩되는 SSA 값은 항상 index 타입이어야 한다.
예:
mlir#affine_map2to3 = affine_map<(d0, d1)[s0] -> (d0, d1 + s0, d1 - s0)> // %N을 affine_map2to3의 s0 심볼에 바인딩한다. %x = memref.alloc()[%N] : memref<40x50xf32, #affine_map2to3>
affine 방언은 강력한 분석과 변환을 가능하게 하기 위해 차원 및 심볼 식별자에 대해 특정 제약을 부과한다. SSA 값의 사용(use)은, 그 SSA 값이 다음 중 하나인 경우 심볼 식별자에 바인딩될 수 있다.
AffineScope 특성을 가진 연산(예: FuncOp)의 영역(region) 인자AffineScope 연산의 최상위 레벨에서 정의된 값(즉, 그 연산이 바로 둘러싸고 있는 값)AffineScope 연산을 지배(dominates)하는 값Pure 연산의 결과AffineScope 연산의 인자인 memref 이거나, 해당 차원이 정적이거나 다시 유효한 심볼에 바인딩된 동적 차원인 memref에 대한 dim 연산의 결과주의: SSA 값의 사용이 어느 AffineScope 특성을 가진 연산에도 포함되어 있지 않다면, 위 규칙 중 4–6만 적용할 수 있다.
위 규칙 (3)의 결과로, 심볼의 유효성은 SSA 사용 위치에 민감하다는 점에 유의하라. 차원은 심볼이 바인딩될 수 있는 모든 것들뿐 아니라, 둘러싸고 있는 affine.for 및 affine.parallel 연산의 귀납 변수(induction variable), 그리고 (재귀적으로 다른 차원과 심볼을 사용할 수 있는) affine.apply 연산의 결과에도 바인딩될 수 있다.
문법:
mliraffine-expr ::= `(` affine-expr `)` | affine-expr `+` affine-expr | affine-expr `-` affine-expr | `-`? integer-literal `*` affine-expr | affine-expr `ceildiv` integer-literal | affine-expr `floordiv` integer-literal | affine-expr `mod` integer-literal | `-`affine-expr | bare-id | `-`? integer-literal multi-dim-affine-expr ::= `(` `)` | `(` affine-expr (`,` affine-expr)* `)`
ceildiv는 첫 번째 인자를 두 번째 인자로 나눈 결과를 그 값보다 크거나 같은 가장 작은 정수로 매핑하는 천장 나눗셈 함수이다. floordiv는 첫 번째 인자를 두 번째 인자로 나눈 결과를 그 값보다 작거나 같은 가장 큰 정수로 매핑하는 바닥 나눗셈 함수이다. mod는 나머지 연산이다. 여기서 두 번째 인자는 항상 양수이므로, 우리 사용에서는 결과가 항상 양수이다. ceildiv, floordiv, mod의 integer-literal 피연산자는 항상 양수여야 한다. bare-id는 index 타입이어야 하는 식별자이다. affine 식에서 연산의 우선순위는 높은 것부터 낮은 것까지 다음과 같다: (1) 괄호, (2) 부정(negation), (3) 나머지, 곱셈, floordiv, ceildiv, (4) 덧셈과 뺄셈. 모든 연산자는 좌결합이다.
_다차원 affine 식(multi-dimensional affine expression)_은 괄호로 둘러싸인, 1차원 affine 식들의 쉼표로 구분된 리스트이다.
맥락(Context): 직관적으로 affine 함수는 선형 함수에 상수를 더한 것이다. 좀 더 형식적으로, Zⁿ에서 정의된 벡터 v→에 대해, 함수 f가 v→의 다차원 affine 함수라는 것은 f(v→)를 M v→ + c→ 꼴로 표현할 수 있을 때이다. 여기서 M은 Zᵐˣⁿ의 상수 행렬이고, c→는 Zᵐ의 상수 벡터이다. m은 이러한 affine 함수의 차원 수이다. MLIR은 양의 정수 상수에 대한 floordiv, ceildiv, mod를 허용하도록 affine 함수의 정의를 확장한다. 이러한 확장은 다면체 컴파일러 커뮤니티에서 준‑affine(quasi‑affine) 함수라 불려 왔다. MLIR은 이러한 다차원 준‑affine 함수를 가리키기 위해 ‘affine map’이라는 용어를 사용한다. 예를 들어, (i + j + 1, j), (i mod 2, j + i), (j, i / 4, i mod 4), (2 i + 1, j)는 (i, j)에 대한 2차원 affine 함수이지만, (i ⋅ j, i²), (i mod j, i / j)는 (i, j)에 대한 affine 함수가 아니다.
문법:
mliraffine-map-inline ::= dim-and-symbol-value-lists `->` multi-dim-affine-expr
차원 리스트와 심볼 리스트의 식별자는 서로 달라야 한다. 이들만이 multi-dim-affine-expr 안에 등장할 수 있는 식별자이다. 명세 안에 하나 이상의 심볼을 가지는 affine 맵은 “symbolic affine map”, 심볼이 없는 것은 “non-symbolic affine map”이라 부른다.
맥락(Context): affine 맵은 차원 인덱스와 심볼 리스트를 결과 리스트로 변환하는 수학적 함수이며, 결과는 인덱스와 심볼을 affine 식으로 조합해 얻는다. affine 맵은 인덱스와 심볼을 구분하는데, 인덱스는 맵이 호출될 때(affine.apply 같은 연산을 통해) affine 맵의 입력이 되는 반면, 심볼은 맵이 설정될 때(예: memref를 만들면서 메모리 layout 맵을 설정할 때) 바인딩된다.
affine 맵은 MLIR의 다양한 핵심 구조에 사용된다. 우리가 그 형태에 부과하는 제약은 표현을 여러 관심 연산에 대해 닫힌(closed) 상태로 유지하면서 강력한 분석과 변환을 가능하게 한다.
문법:
mliraffine-map-id ::= `#` suffix-id // affine 맵 정의는 파일 상단에 위치한다. affine-map-def ::= affine-map-id `=` affine-map-inline module-header-def ::= affine-map-def // affine 맵 사용 시, 인라인 형태 또는 이름을 사용할 수 있다. affine-map ::= affine-map-id | affine-map-inline
affine 매핑은 사용 지점에 인라인으로 정의할 수도 있고, 파일 상단으로 끌어올려 이름을 붙인 다음 해당 이름으로 사용할 수도 있다.
예:
mlir// affine 맵의 파일 밖(out-of-line) 정의와 사용 예. #affine_map42 = affine_map<(d0, d1)[s0] -> (d0, d0 + d1 + s0 floordiv 2)> // alloc 연산에서 affine 맵 정의를 사용하고, // SSA 값 %N을 심볼 s0에 바인딩. %a = memref.alloc()[%N] : memref<4x4xf32, #affine_map42> // 인라인 affine 맵 정의로 동일한 효과. %b = memref.alloc()[%N] : memref<4x4xf32, affine_map<(d0, d1)[s0] -> (d0, d0 + d1 + s0 floordiv 2)>>
Semi‑affine 맵은 심볼 식별자에 대한 곱셈, floordiv, ceildiv, mod를 허용하도록 affine 맵을 확장한 것이다. Semi‑affine 맵은 따라서 affine 맵의 진상위 집합(strict superset)이다.
semi‑affine 식 문법:
mlirsemi-affine-expr ::= `(` semi-affine-expr `)` | semi-affine-expr `+` semi-affine-expr | semi-affine-expr `-` semi-affine-expr | symbol-or-const `*` semi-affine-expr | semi-affine-expr `ceildiv` symbol-or-const | semi-affine-expr `floordiv` symbol-or-const | semi-affine-expr `mod` symbol-or-const | bare-id | `-`? integer-literal symbol-or-const ::= `-`? integer-literal | symbol-id multi-dim-semi-affine-expr ::= `(` semi-affine-expr (`,` semi-affine-expr)* `)`
위 문법에서 연산의 우선순위와 결합 규칙은 affine 식과 동일하다.
semi‑affine 맵 문법:
mlirsemi-affine-map-inline ::= dim-and-symbol-value-lists `->` multi-dim-semi-affine-expr
Semi‑affine 맵은 사용 지점에 인라인으로 정의할 수도, 파일 상단으로 끌어올려 semi‑affine 맵 정의에 이름을 붙여서 이름으로 사용할 수도 있다.
mlirsemi-affine-map-id ::= `#` suffix-id // semi-affine 맵 정의는 파일 상단에 위치한다. semi-affine-map-def ::= semi-affine-map-id `=` semi-affine-map-inline module-header-def ::= semi-affine-map-def // semi-affine 맵 사용 시, 인라인 형태 또는 이름을 사용할 수 있다. semi-affine-map ::= semi-affine-map-id | semi-affine-map-inline
정수 집합(integer set)은 식별자 리스트에 대한 affine 제약의 논리곱(conjunction)이다. 정수 집합과 관련된 식별자는 두 부류로 나뉜다. 집합의 차원 식별자와 집합의 심볼 식별자이다. 집합은 심볼 식별자에 대해 매개변수적(parametric)으로 본다. 문법에서 집합의 차원 식별자 리스트는 괄호로, 심볼은 대괄호로 둘러싸인다.
affine 제약 문법:
mliraffine-constraint ::= affine-expr `>=` `affine-expr` | affine-expr `<=` `affine-expr` | affine-expr `==` `affine-expr` affine-constraint-conjunction ::= affine-constraint (`,` affine-constraint)*
정수 집합은 사용 지점에 인라인으로 정의할 수도 있고, 파일 상단으로 끌어올려 정수 집합 정의에 이름을 붙인 다음 이름으로 사용할 수도 있다.
mlirinteger-set-id ::= `#` suffix-id integer-set-inline ::= dim-and-symbol-value-lists `:` '(' affine-constraint-conjunction? ')' // 정수 집합 선언은 파일 상단에 위치한다. integer-set-decl ::= integer-set-id `=` integer-set-inline // 정수 집합 사용 시, 인라인 형태 또는 이름을 사용할 수 있다. integer-set ::= integer-set-id | integer-set-inline
정수 집합의 차원 수는 집합의 차원 리스트에 등장하는 식별자 수이다. 위 문법에 등장하는 affine-constraint 비단말은 dims와 symbols에서 나온 식별자만을 포함할 수 있다. 제약이 없는 집합은 집합의 모든 차원에 대해 경계가 없는(unbounded) 집합이다.
예:
mlir// 두 개의 심볼을 가진 2차원 정수 집합 예. #set42 = affine_set<(d0, d1)[s0, s1] : (d0 >= 0, -d0 + s0 - 1 >= 0, d1 >= 0, -d1 + s1 - 1 >= 0)> // 어떤 Region 안에서 affine.if #set42(%i, %j)[%M, %N] { ... }
d0와 d1은 집합의 차원 식별자에, s0와 s1은 심볼 식별자에 해당한다.
affine.apply (affine::AffineApplyOp)¶Affine apply 연산
affine.apply 연산은 affine 매핑을 SSA 값 리스트에 적용하여 단일 SSA 값을 생성한다. affine.apply의 차원 및 심볼 피연산자 수는 affine 매핑의 차원 및 심볼 입력 수와 각각 같아야 한다. affine 매핑은 1차원이어야 하며, 따라서 affine.apply 연산은 항상 하나의 값을 반환한다. 입력 피연산자와 결과는 모두 index 타입이어야 한다.
유효한 affine 차원 및 심볼에 대한 규칙에 따라 차원으로 유효한 피연산자는 심볼 피연산자로 사용할 수 없다.
예:
mlir#map = affine_map<(d0, d1) -> (d0 floordiv 8 + d1 floordiv 128)> ... %1 = affine.apply #map (%s, %t) // 인라인 예. %2 = affine.apply affine_map<(i)[s0] -> (i + s0)> (%42)[%n]
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable, InferTypeOpInterface, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
| Attribute | MLIR Type | 설명 |
|---|---|---|
map | ::mlir::AffineMapAttr | AffineMap 속성 |
| Operand | 설명 |
|---|---|
mapOperands | 가변 길이 index |
| Result | 설명 |
|---|---|
| «unnamed» | index |
affine.delinearize_index (affine::AffineDelinearizeIndexOp)¶인덱스 비선형화(delinearize) 연산
문법:
mliroperation ::= `affine.delinearize_index` $linear_index `into` custom<DynamicIndexList>($dynamic_basis, $static_basis, "{}", "::mlir::AsmParser::Delimiter::Paren") attr-dict `:` type($multi_index)
affine.delinearize_index 연산은 단일 index 값을 받아 주어진 기저(basis)에 따라 다중 인덱스를 계산한다.
예:
mlir%indices:3 = affine.delinearize_index %linear_index into (%c16, %c224, %c224) : index, index, index
위 예에서 %indices:3는 개념적으로 다음을 담고 있다.
mlir#map0 = affine_map<()[s0] -> (s0 floordiv 50176)> #map1 = affine_map<()[s0] -> ((s0 mod 50176) floordiv 224)> #map2 = affine_map<()[s0] -> (s0 mod 224)> %indices_0 = affine.apply #map0()[%linear_index] %indices_1 = affine.apply #map1()[%linear_index] %indices_2 = affine.apply #map2()[%linear_index]
즉, %0:3 = affine.delinearize_index %x into (B, C)는 %0 = {%x / (B * C), (%x mod (B * C)) / C, %x mod C}를 생성한다.
기저는 결과 개수 N에 대해 N개 또는 N-1개의 원소를 가질 수 있다. 기저 원소가 N개라면, 첫 번째 원소는 계산에 사용되지는 않지만, affine.delinearize_index에서 항(term)을 소거하거나 %linear_index의 전체 크기에 대한 결론을 도출하는 등의 분석/정규화(canonicalization)에서 사용될 수 있다.
기저가 완전히 제공되면, 해당 delinearize_index 연산은 “outer bound를 가진다”고 말한다. 빌더는 기본적으로 affine.delinearize_index가 outer bound를 가진다고 가정하는데, 이는 이 연산이 처음 정의된 방식이다.
즉, 위 예는 다음과 같이 쓸 수도 있다.
mlir%0:3 = affine.delinearize_index %linear_index into (244, 244) : index, index
getPaddedBasis()와의 대칭성을 위해, OpFoldResult 기반 빌더 호출 시 hasOuterBound가 true인데 기저의 첫 요소가 nullptr인 경우, 그 첫 요소는 무시되고 builder는 마치 outer bound가 없는 것처럼 동작한다.
affine 맵의 제약으로 인해, 모든 기저 원소는 엄격하게 양수여야 한다. 동적 기저 원소가 0이거나 음수인 경우 정의되지 않은 동작이 발생한다.
다른 affine 연산과 마찬가지로, delinearize_index의 하향 변환(lowering)은, 내부 계산이 signed 의미에서 index 타입을 overflow하지 않는다고 가정할 수 있다. 즉, 모든 기저 원소의 곱은 index로서 양수라는 뜻이다.
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
| Attribute | MLIR Type | 설명 |
|---|---|---|
static_basis | ::mlir::DenseI64ArrayAttr | i64 dense array 속성 |
| Operand | 설명 |
|---|---|
linear_index | index |
dynamic_basis | 가변 길이 index |
| Result | 설명 |
|---|---|
multi_index | 가변 길이 index |
affine.for (affine::AffineForOp)¶For 연산
문법:
mliroperation ::= `affine.for` ssa-id `=` lower-bound `to` upper-bound (`step` integer-literal)? `{` op* `}` lower-bound ::= `max`? affine-map-attribute dim-and-symbol-use-list | shorthand-bound upper-bound ::= `min`? affine-map-attribute dim-and-symbol-use-list | shorthand-bound shorthand-bound ::= ssa-id | `-`? integer-literal
affine.for 연산은 affine 루프 중첩을 나타낸다. 이는 하나의 region을 가지며, 그 안에 루프 본문이 들어간다. 이 region은 affine.yield로 끝나는 하나의 블록만을 가져야 한다. 주의: affine.for가 커스텀 형식으로 출력될 때는 terminator가 생략된다. 블록은 루프의 귀납 변수를 나타내는 하나의 index 타입 인자를 가진다.
affine.for 연산은 하한에서 상한까지, 보폭(step)만큼 증가시키며 본문을 여러 번 실행한다. 보폭 step은 양의 정수 상수이며, 지정되지 않으면 기본값은 1이다. 하한과 상한은 반열린 구간(half‑open range)을 지정한다. 즉, 하한은 포함되지만 상한은 포함되지 않는다.
affine.for 연산의 하한과 상한은 affine 매핑을 SSA 값 리스트에 적용한 결과로 표현된다. 이 SSA 값들에는 다른 모든 차원·심볼 바인딩과 마찬가지로 동일한 제약이 적용된다.
경계(bound)를 위한 affine 매핑이 여러 결과를 반환할 수도 있다. 이 경우, 하한에서는 max, 상한에서는 min 키워드가 필요하며, 경계 값은 반환 값들의 최댓값/최솟값이다. 의미상의 모호성은 없지만, MLIR 문법은 사람에게 더 명확하게 보이도록 이 키워드 사용을 요구한다.
많은 상한/하한은 단순하기 때문에 MLIR은 두 가지 축약(custom) 문법을 허용한다. 하나의 ssa-id(예: %N)를 받는 형식은, 그 SSA 값을 단일 심볼을 그대로 반환하는 함수에 적용한 것의 축약형이다. 예를 들어 %N은 ()[s] -> (s) ()[%N]의 축약이다. 정수 리터럴(예: -42) 형식은 상수 값을 반환하는 0항(nullary) 매핑 함수의 축약으로, 예를 들어 ()->(-42)()와 같다.
내부 루프를 역방향으로 순회하는 예:
mlir#map57 = affine_map<(d0)[s0] -> (s0 - d0 - 1)> func.func @simple_example(%A: memref<?x?xf32>, %B: memref<?x?xf32>) { %N = dim %A, 0 : memref<?x?xf32> affine.for %i = 0 to %N step 1 { affine.for %j = 0 to %N { // 보폭 1은 암시적 %0 = affine.apply #map57(%j)[%N] %tmp = call @F1(%A, %i, %0) : (memref<?x?xf32>, index, index)->(f32) call @F2(%tmp, %B, %i, %0) : (f32, memref<?x?xf32>, index, index)->() } } return }
affine.for는 루프 전달 변수(loop-carried variable, iter_args)에 대해서도 동작할 수 있으며, 루프 종료 후 최종 값을 결과로 반환할 수 있다. 변수의 초기값은 루프 하한·상한 피연산자 뒤에 이어지는 SSA 피연산자로 affine.for에 전달된다. 연산의 region은 각 변수에 상응하는 인자를 가지며, 이는 현재 반복에서 해당 변수의 값을 나타낸다.
Region은 affine.yield로 종료되어야 하며, 현재 반복 변수들을 다음 반복으로(또는 마지막 반복인 경우 affine.for의 결과로) 전달한다. 반복 횟수가 0인 affine.for의 경우, 루프 전달 변수의 초기값(SSA 피연산자에 해당)이 연산의 결과가 된다.
예를 들어, memref를 합으로 축약(sum‑reduce)하려면:
mlirfunc.func @reduce(%buffer: memref<1024xf32>) -> (f32) { // 초기 합을 0으로 설정. %sum_0 = arith.constant 0.0 : f32 // iter_args는 초기값을 루프 region 인자에 바인딩한다. %sum = affine.for %i = 0 to 10 step 2 iter_args(%sum_iter = %sum_0) -> (f32) { %t = affine.load %buffer[%i] : memref<1024xf32> %sum_next = arith.addf %sum_iter, %t : f32 // 현재 반복의 합을 다음 반복의 %sum_iter 또는 마지막 반복이라면 %sum으로 전달. affine.yield %sum_next : f32 } return %sum : f32 }
mlir%res:2 = affine.for %i = 0 to 128 iter_args(%arg0 = %init0, %arg1 = %init1) -> (index, index) { %y0 = arith.addi %arg0, %c1 : index %y1 = arith.addi %arg1, %c2 : index affine.yield %y0, %y1 : index, index }
affine.for가 어떤 값을 정의한다면, yield terminator가 명시적으로 존재해야 한다. affine.for 결과의 개수와 타입은 iter_args에 주어진 초기값 및 affine.yield 피연산자와 일치해야 한다.
Traits: AttrSizedOperandSegments, AutomaticAllocationScope, RecursiveMemoryEffects, SingleBlockImplicitTerminator<AffineYieldOp>, SingleBlock
Interfaces: ConditionallySpeculatable, LoopLikeOpInterface, RegionBranchOpInterface
| Attribute | MLIR Type | 설명 |
|---|---|---|
lowerBoundMap | ::mlir::AffineMapAttr | AffineMap 속성 |
upperBoundMap | ::mlir::AffineMapAttr | AffineMap 속성 |
step | ::mlir::IntegerAttr | index 속성 |
| Operand | 설명 |
|---|---|
lowerBoundOperands | 가변 길이 index |
upperBoundOperands | 가변 길이 index |
inits | 가변 길이 임의 타입 |
| Result | 설명 |
|---|---|
results | 가변 길이 임의 타입 |
affine.if (affine::AffineIfOp)¶If‑then‑else 연산
문법:
mliroperation ::= `affine.if` if-op-cond `{` op* `}` (`else` `{` op* `}`)? if-op-cond ::= integer-set-attr dim-and-symbol-use-list
affine.if 연산은 정수 집합(affine 제약의 논리곱)으로 정의된 루프 반복 공간의 부분집합으로 실행을 제한한다. 하나의 affine.if는 선택적으로 else 절을 가질 수 있다.
affine.if의 조건은 정수 집합(affine 제약의 논리곱)과, 그 정수 집합의 차원·심볼에 바인딩되는 SSA 값으로 표현된다. 이 SSA 값들에도 다른 모든 차원·심볼 바인딩과 동일한 제약이 적용된다.
affine.if 연산은 “then”과 “else” 절을 위한 두 개의 region을 가진다. affine.if는 region 안에서 정의된 값을 결과로 반환할 수 있다. 실제로 정의되는 값은 어느 실행 경로가 선택되었는지에 따라 달라진다. affine.if의 각 region은 인자 없는 단일 블록만을 가져야 하며, affine.yield로 종료되어야 한다. affine.if가 값을 정의하지 않는다면 affine.yield는 생략 가능하며, 암시적으로 삽입된다. 그렇지 않다면 반드시 명시되어야 한다. 값이 정의되지 않는 경우, else 블록은 비어 있을 수 있다(즉, 어떤 블록도 포함하지 않을 수 있다).
예:
mlir#set = affine_set<(d0, d1)[s0]: (d0 - 10 >= 0, s0 - d0 - 9 >= 0, d1 - 10 >= 0, s0 - d1 - 9 >= 0)> func.func @reduced_domain_example(%A, %X, %N) : (memref<10xi32>, i32, i32) { affine.for %i = 0 to %N { affine.for %j = 0 to %N { %0 = affine.apply #map42(%j) %tmp = call @S1(%X, %i, %0) affine.if #set(%i, %j)[%N] { %1 = affine.apply #map43(%i, %j) call @S2(%tmp, %A, %i, %1) } } } return }
명시적 yield를 사용하는 예(에지 패딩을 통한 초기화):
mlir#interior = affine_set<(i, j) : (i - 1 >= 0, j - 1 >= 0, 10 - i >= 0, 10 - j >= 0)> (%i, %j) func.func @pad_edges(%I : memref<10x10xf32>) -> (memref<12x12xf32) { %O = alloc memref<12x12xf32> affine.parallel (%i, %j) = (0, 0) to (12, 12) { %1 = affine.if #interior (%i, %j) { %2 = load %I[%i - 1, %j - 1] : memref<10x10xf32> affine.yield %2 } else { %2 = arith.constant 0.0 : f32 affine.yield %2 : f32 } affine.store %1, %O[%i, %j] : memref<12x12xf32> } return %O }
Traits: NoRegionArguments, RecursiveMemoryEffects, RecursivelySpeculatableImplTrait, SingleBlockImplicitTerminator<AffineYieldOp>, SingleBlock
Interfaces: ConditionallySpeculatable, RegionBranchOpInterface
| Attribute | MLIR Type | 설명 |
|---|---|---|
condition | ::mlir::IntegerSetAttr | IntegerSet 속성 |
| Operand | 설명 |
|---|---|
| «unnamed» | 가변 길이 임의 타입 |
| Result | 설명 |
|---|---|
results | 가변 길이 임의 타입 |
affine.linearize_index (affine::AffineLinearizeIndexOp)¶인덱스 선형화(linearize) 연산
문법:
mliroperation ::= `affine.linearize_index` (`disjoint` $disjoint^)? ` ` `[` $multi_index `]` `by` custom<DynamicIndexList>($dynamic_basis, $static_basis, "{}", "::mlir::AsmParser::Delimiter::Paren") attr-dict `:` type($linear_index)
affine.linearize_index 연산은 동등한 길이의 index 값 시퀀스와 기저를 받아 그 기저를 이용해 인덱스를 선형화한다.
즉, 인덱스 %idx_0부터 %idx_{N-1}까지와 기저 원소 b_0(또는 b_1)부터 b_{N-1}까지에 대해 다음을 계산한다.
textsum(i = 0 to N-1) %idx_i * product(j = i + 1 to N-1) B_j
다시 말해, %0 = affine.linearize_index [%z, %y, %x] by (Z, Y, X)는 %0 = %x + %y * X + %z * X * Y를 주며, 즉 %0 = %x + X * (%y + Y * (%z))와 같다.
기저는 linearize_index의 입력 개수 N에 대해 N개 또는 N-1개의 원소를 가질 수 있다. N개의 입력이 제공될 경우 첫 번째 원소는 계산에는 사용되지 않지만, %idx_0에 대한 경계(bound)로서 분석이나 정규화에서 사용할 수 있다.
모든 N개의 기저 원소가 제공되면, 해당 linearize_index 연산은 “outer bound를 가진다”고 말한다.
편의상, 그리고 getPaddedBasis()와의 대칭성을 위해, 이 연산의 빌더에 전달되는 OpFoldResult 집합의 첫 원소가 nullptr이면, 그 원소는 무시된다.
disjoint 속성이 존재하면, 이는 최적화 힌트로서 모든 i에 대해 0 <= %idx_i < B_i라는 의미이다. 즉, %idx_0이 전체 인덱스를 음수로 만들기 위해 음수일 수 있다는 점을 제외하면, 어떤 인덱스도 다른 인덱스에 영향을 주지 않는다. 추가로, disjoint는 모든 기저 원소가 음이 아닌 값이라는 것을 단언(assert)한다.
affine.delinearize_index의 출력은 정의상 disjoint임에 유의하라.
다른 affine 연산과 마찬가지로, 선형화 계산이 signed 의미에서 overflow를 일으키면 정의되지 않은 동작이 발생한다.
예:
mlir%linear_index = affine.linearize_index [%index_0, %index_1, %index_2] by (2, 3, 5) : index // 동일한 효과 %linear_index = affine.linearize_index [%index_0, %index_1, %index_2] by (3, 5) : index
위 예에서 %linear_index는 개념적으로 다음을 담고 있다.
mlir#map = affine_map<()[s0, s1, s2] -> (s0 * 15 + s1 * 5 + s2)> %linear_index = affine.apply #map()[%index_0, %index_1, %index_2]
Traits: AlwaysSpeculatableImplTrait, AttrSizedOperandSegments
Interfaces: ConditionallySpeculatable, InferTypeOpInterface, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
| Attribute | MLIR Type | 설명 |
|---|---|---|
static_basis | ::mlir::DenseI64ArrayAttr | i64 dense array 속성 |
| Operand | 설명 |
|---|---|
multi_index | 가변 길이 index |
dynamic_basis | 가변 길이 index |
| Result | 설명 |
|---|---|
linear_index | index |
affine.load (affine::AffineLoadOp)¶Affine load 연산
문법:
mliroperation ::= ssa-id `=` `affine.load` ssa-use `[` multi-dim-affine-map-of-ssa-ids `]` `:` memref-type
affine.load 연산은 memref의 한 원소를 읽어들이는데, 각 memref 차원의 인덱스는 루프 귀납 변수와 심볼의 affine 식으로 표현된다. affine.load의 출력은 memref 원소 타입과 동일한 타입의 새 값이다. memref의 각 차원마다 루프 귀납 변수와 심볼에 대한 affine 식을 지정해야 한다. symbol 키워드는 SSA 식별자들 중 심볼릭인 것을 표시하는 데 사용할 수 있다.
예 1:
mlir%1 = affine.load %0[%i0 + 3, %i1 + 7] : memref<100x100xf32>
예 2: %n과 %m을 심볼로 나타내기 위해 symbol 키워드를 사용.
mlir%1 = affine.load %0[%i0 + symbol(%n), %i1 + symbol(%m)] : memref<100x100xf32>
Traits: MemRefsNormalizable
Interfaces: AffineMapAccessInterface, AffineReadOpInterface
| Attribute | MLIR Type | 설명 |
|---|---|---|
map | ::mlir::AffineMapAttr | AffineMap 속성 |
| Operand | 설명 |
|---|---|
memref | 임의 값 타입의 memref |
indices | 가변 길이 index |
| Result | 설명 |
|---|---|
result | 임의 타입 |
affine.max (affine::AffineMaxOp)¶Max 연산
affine.max 연산은 다중 결과 affine 맵에서 최댓값을 계산한다.
예:
mlir%0 = affine.max (d0) -> (1000, d0 + 512) (%i0) : index
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable, InferTypeOpInterface, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
| Attribute | MLIR Type | 설명 |
|---|---|---|
map | ::mlir::AffineMapAttr | AffineMap 속성 |
| Operand | 설명 |
|---|---|
operands | 가변 길이 index |
| Result | 설명 |
|---|---|
| «unnamed» | index |
affine.min (affine::AffineMinOp)¶Min 연산
문법:
mliroperation ::= ssa-id `=` `affine.min` affine-map-attribute dim-and-symbol-use-list
affine.min 연산은 affine 매핑을 SSA 값 리스트에 적용하고, 모든 결과 식의 최솟값을 반환한다. affine.min의 차원 및 심볼 인자 수는 affine 매핑의 차원 및 심볼 입력 수와 각각 같아야 하며, affine.min 연산은 항상 하나의 값을 반환한다. 입력 피연산자와 결과는 모두 index 타입이어야 한다.
예:
mlir%0 = affine.min affine_map<(d0)[s0] -> (1000, d0 + 512, s0)> (%arg0)[%arg1]
Traits: AlwaysSpeculatableImplTrait
Interfaces: ConditionallySpeculatable, InferTypeOpInterface, NoMemoryEffect (MemoryEffectOpInterface)
Effects: MemoryEffects::Effect{}
| Attribute | MLIR Type | 설명 |
|---|---|---|
map | ::mlir::AffineMapAttr | AffineMap 속성 |
| Operand | 설명 |
|---|---|
operands | 가변 길이 index |
| Result | 설명 |
|---|---|
| «unnamed» | index |
affine.parallel (affine::AffineParallelOp)¶다중 인덱스 병렬 band 연산
affine.parallel 연산은 초직사각형(hyper‑rectangular) affine 병렬 band를 나타내며, 그 귀납 변수들에 대해 0개 이상의 SSA 값을 정의한다. 이는 병렬 band 본문을 담는 하나의 region을 가진다. 귀납 변수들은 이 region의 인자로 표현된다. 이 SSA 값들은 항상 머신 워드 크기인 index 타입이다. 보폭(step)은 양의 정수 상수이며, 지정되지 않으면 기본값은 1이다. 하한과 상한은 반열린 구간을 지정하며, 하한은 포함되고 상한은 포함되지 않는다. 본문 region은 정확히 하나의 블록만을 포함해야 하고, affine.yield로 종료되어야 한다.
병렬 연산의 하한과 상한은 affine 매핑을 SSA 값 리스트에 적용한 결과로 표현된다. 이 SSA 값들에도 다른 모든 차원·심볼 바인딩과 동일한 제약이 적용된다. 각 맵의 식 리스트는 각 bound 그룹 속성에 따라 해석된다. 하나의 식만 그룹에 속한다면, 해당 식 결과가 대응하는 루프 귀납 변수의 하한(또는 상한)이 된다. 여러 식이 그룹에 속한다면, 하한(또는 상한)은 이 식들로부터 얻은 값들의 최댓값(또는 최솟값)이다. 루프 band는 group bounds 속성의 원소 수만큼의 루프를 가진다.
각 affine.yield가 내는 값은 AtomicRMWKind 열거형에 정의된 축약(reduction) 방식 중 하나로 축약/누적된다. 축약 순서는 정의되어 있지 않으며, lowering은 어떤 유효한 순서라도 생성할 수 있다. 반복 횟수가 0인 루프는 각 축약에 연관된 항등값(identity)을 결과로 생성한다(예: addf는 0.0, mulf는 1.0). 반복 횟수가 1이 아닌 assign 축약은 정의되지 않은 결과를 낸다.
참고: AffineParallelOp::build를 호출하면 필요한 region과 블록이 생성되고, trivial(값을 내지 않는) 경우 terminator도 삽입된다. 파서 역시 텍스트 표현에서 누락되었더라도 필요한 region, 블록, terminator를 생성한다.
예(3x3 valid convolution):
mlirfunc.func @conv_2d(%D : memref<100x100xf32>, %K : memref<3x3xf32>) -> (memref<98x98xf32>) { %O = memref.alloc() : memref<98x98xf32> affine.parallel (%x, %y) = (0, 0) to (98, 98) { %0 = affine.parallel (%kx, %ky) = (0, 0) to (2, 2) reduce ("addf") -> f32 { %1 = affine.load %D[%x + %kx, %y + %ky] : memref<100x100xf32> %2 = affine.load %K[%kx, %ky] : memref<3x3xf32> %3 = arith.mulf %1, %2 : f32 affine.yield %3 : f32 } affine.store %0, %O[%x, %y] : memref<98x98xf32> } return %O : memref<98x98xf32> }
예(크기를 완전히 나누지 못할 수도 있는 타일링):
mliraffine.parallel (%ii, %jj) = (0, 0) to (%N, %M) step (32, 32) { affine.parallel (%i, %j) = (%ii, %jj) to (min(%ii + 32, %N), min(%jj + 32, %M)) { call @f(%i, %j) : (index, index) -> () } }
Traits: AutomaticAllocationScope, MemRefsNormalizable, RecursiveMemoryEffects, RecursivelySpeculatableImplTrait, SingleBlockImplicitTerminator<AffineYieldOp>, SingleBlock
Interfaces: ConditionallySpeculatable, LoopLikeOpInterface
| Attribute | MLIR Type | 설명 |
|---|---|---|
reductions | ::mlir::ArrayAttr | 축약 연산들 |
lowerBoundsMap | ::mlir::AffineMapAttr | AffineMap 속성 |
lowerBoundsGroups | ::mlir::DenseIntElementsAttr | 32비트 부호 없는 정수 요소 속성 |
upperBoundsMap | ::mlir::AffineMapAttr | AffineMap 속성 |
upperBoundsGroups | ::mlir::DenseIntElementsAttr | 32비트 부호 없는 정수 요소 속성 |
steps | ::mlir::ArrayAttr | 64비트 정수 배열 속성 |
| Operand | 설명 |
|---|---|
mapOperands | 가변 길이 index |
| Result | 설명 |
|---|---|
results | 가변 길이 임의 타입 |
affine.prefetch (affine::AffinePrefetchOp)¶Affine prefetch 연산
affine.prefetch 연산은, affine.load와 유사한 affine 첨자를 통해 지정된 memref 위치에서 데이터를 미리 가져온다(prefetch). 이 연산에는 아래와 같이 세 가지 속성이 있다: 읽기/쓰기 지정자, locality 힌트, 캐시 타입 지정자.
mliraffine.prefetch %0[%i, %j + 5], read, locality<3>, data : memref<400x400xi32>
읽기/쓰기 지정자는 read 또는 write 중 하나이다. locality 힌트 지정자는 locality<0>(locality 없음)에서 locality<3>(극도로 local, 캐시에 유지)까지이다. 캐시 타입 지정자는 data 또는 instr이며, data 캐시 또는 instruction 캐시에 대한 prefetch인지를 나타낸다.
Traits: MemRefsNormalizable
Interfaces: AffineMapAccessInterface
| Attribute | MLIR Type | 설명 |
|---|---|---|
isWrite | ::mlir::BoolAttr | bool 속성 |
localityHint | ::mlir::IntegerAttr | 최소값 0, 최대값 3인 32비트 부호 없는 정수 속성 |
isDataCache | ::mlir::BoolAttr | bool 속성 |
map | ::mlir::AffineMapAttr | AffineMap 속성 |
| Operand | 설명 |
|---|---|
memref | 임의 값 타입의 memref |
indices | 가변 길이 index |
affine.store (affine::AffineStoreOp)¶Affine store 연산
문법:
mliroperation ::= `affine.store` ssa-use, ssa-use `[` multi-dim-affine-map-of-ssa-ids `]` `:` memref-type
affine.store 연산은 memref의 한 원소에 값을 기록하며, 각 memref 차원의 인덱스는 루프 귀납 변수와 심볼의 affine 식으로 표현된다. affine.store 연산은 memref 원소 타입과 동일한 타입의 새 값을 저장한다. memref의 각 차원마다 루프 귀납 변수와 심볼에 대한 affine 식을 지정해야 한다. symbol 키워드는 SSA 식별자들 중 심볼릭인 것을 표시하는 데 사용할 수 있다.
예 1:
mliraffine.store %v0, %0[%i0 + 3, %i1 + 7] : memref<100x100xf32>
예 2: %n과 %m을 심볼로 나타내기 위해 symbol 키워드를 사용.
mliraffine.store %v0, %0[%i0 + symbol(%n), %i1 + symbol(%m)] : memref<100x100xf32>
Traits: MemRefsNormalizable
Interfaces: AffineMapAccessInterface, AffineWriteOpInterface
| Attribute | MLIR Type | 설명 |
|---|---|---|
map | ::mlir::AffineMapAttr | AffineMap 속성 |
| Operand | 설명 |
|---|---|
value | 임의 타입 |
memref | 임의 값 타입의 memref |
indices | 가변 길이 index |
affine.vector_load (affine::AffineVectorLoadOp)¶Affine vector load 연산
affine.vector_load는 affine.load의 벡터 대응 연산이다. 이는 첫 번째 피연산자로 주어진 MemRef의 슬라이스를, 같은 기본 원소 타입을 가지는 vector로 읽어들인다. 각 memref 차원의 인덱스는 루프 귀납 변수와 심볼의 affine 식이다. 이 인덱스들은 memref 내에서 읽기 시작 위치를 결정한다. 반환되는 벡터 타입의 shape이 memref에서 읽히는 슬라이스의 shape을 결정하며, 이 슬라이스는 해당 shape 차원에 대해 연속적이다. 스트라이드(strided) 벡터 load는 추후 지원될 예정이다. memref의 각 차원마다 루프 귀납 변수와 심볼에 대한 affine 식을 지정해야 한다. symbol 키워드는 SSA 식별자들 중 심볼릭인 것을 표시하는 데 사용할 수 있다.
예 1: 8‑wide f32 벡터 load.
mlir%1 = affine.vector_load %0[%i0 + 3, %i1 + 7] : memref<100x100xf32>, vector<8xf32>
예 2: 4‑wide f32 벡터 load. %n과 %m을 심볼로 나타내기 위해 symbol 키워드를 사용.
mlir%1 = affine.vector_load %0[%i0 + symbol(%n), %i1 + symbol(%m)] : memref<100x100xf32>, vector<4xf32>
예 3: 2차원 f32 벡터 load.
mlir%1 = affine.vector_load %0[%i0, %i1] : memref<100x100xf32>, vector<2x8xf32>
TODO:
Traits: MemRefsNormalizable
Interfaces: AffineMapAccessInterface, AffineReadOpInterface
| Attribute | MLIR Type | 설명 |
|---|---|---|
map | ::mlir::AffineMapAttr | AffineMap 속성 |
| Operand | 설명 |
|---|---|
memref | 임의 값 타입의 memref |
indices | 가변 길이 index |
| Result | 설명 |
|---|---|
result | 임의 값 타입의 vector |
affine.vector_store (affine::AffineVectorStoreOp)¶Affine vector store 연산
affine.vector_store는 affine.store의 벡터 대응 연산이다. 이는 첫 번째 피연산자로 주어진 vector를, 두 번째 피연산자로 주어진 동일 기본 원소 타입의 MemRef 내의 슬라이스에 기록한다. 각 memref 차원의 인덱스는 루프 귀납 변수와 심볼의 affine 식이다. 이 인덱스들은 memref 내에서 쓰기 시작 위치를 결정한다. 입력 벡터의 shape이 memref에 기록될 슬라이스의 shape을 결정하며, 이 슬라이스는 해당 shape 차원에 대해 연속적이다. 스트라이드 벡터 store는 추후 지원될 예정이다. memref의 각 차원마다 루프 귀납 변수와 심볼에 대한 affine 식을 지정해야 한다. symbol 키워드는 SSA 식별자들 중 심볼릭인 것을 표시하는 데 사용할 수 있다.
예 1: 8‑wide f32 벡터 store.
mliraffine.vector_store %v0, %0[%i0 + 3, %i1 + 7] : memref<100x100xf32>, vector<8xf32>
예 2: 4‑wide f32 벡터 store. %n과 %m을 심볼로 나타내기 위해 symbol 키워드를 사용.
mliraffine.vector_store %v0, %0[%i0 + symbol(%n), %i1 + symbol(%m)] : memref<100x100xf32>, vector<4xf32>
예 3: 2차원 f32 벡터 store.
mliraffine.vector_store %v0, %0[%i0, %i1] : memref<100x100xf32>, vector<2x8xf32>
TODO:
Traits: MemRefsNormalizable
Interfaces: AffineMapAccessInterface, AffineWriteOpInterface
| Attribute | MLIR Type | 설명 |
|---|---|---|
map | ::mlir::AffineMapAttr | AffineMap 속성 |
| Operand | 설명 |
|---|---|
value | 임의 값 타입의 vector |
memref | 임의 값 타입의 memref |
indices | 가변 길이 index |
affine.yield (affine::AffineYieldOp)¶부모 연산에 값 반환(yield)
문법:
mliroperation ::= `affine.yield` attr-dict ($operands^ `:` type($operands))?
affine.yield는 affine op region에서 0개 이상의 SSA 값을 반환(yield)하며, 해당 region을 종료하는 terminator이다. 반환된 값이 어떻게 사용되는지는 부모 연산이 정의한다. affine.yield가 피연산자를 가진다면, 이 피연산자들은 부모 연산의 결과와 타입 및 개수가 모두 일치해야 한다. 부모 연산이 값을 정의하지 않는 경우, 커스텀 문법에서는 affine.yield를 생략할 수 있으며, 빌더가 암시적으로 삽입한다. 그렇지 않다면 어떤 값이 반환되는지 나타내기 위해 문법에 반드시 명시해야 한다.
Traits: AlwaysSpeculatableImplTrait, MemRefsNormalizable, ReturnLike, Terminator
Interfaces: ConditionallySpeculatable, NoMemoryEffect (MemoryEffectOpInterface), RegionBranchTerminatorOpInterface
Effects: MemoryEffects::Effect{}
| Operand | 설명 |
|---|---|
operands | 가변 길이 임의 타입 |
affine.dma_start (mlir::AffineDmaStartOp)¶문법:
mliroperation ::= `affine.dma_start` ssa-use `[` multi-dim-affine-map-of-ssa-ids `]`, `[` multi-dim-affine-map-of-ssa-ids `]`, `[` multi-dim-affine-map-of-ssa-ids `]`, ssa-use `:` memref-type
affine.dma_start 연산은 소스 memref에서 목적지 memref로 데이터를 전송하는 non‑blocking DMA 연산을 시작한다. 소스와 목적지 memref는 차원 수가 달라도 되지만, 원소 타입은 같아야 한다. 피연산자에는 소스 memref 및 그 인덱스들, 목적지 memref 및 그 인덱스들, 전송할 데이터의 크기(해당 memref 원소 타입의 원소 개수), 태그 memref 및 그 인덱스들, 그리고 선택적으로 마지막에 stride 및 number_of_elements_per_stride 인자가 포함된다. 태그 위치는 AffineDmaWaitOp가 완료 여부를 확인할 때 사용된다. 소스 memref, 목적지 memref, 태그 memref의 인덱스는 affine.load/store와 동일한 제약을 가진다. 특히 memref의 각 차원 인덱스는 루프 귀납 변수와 심볼의 affine 식이어야 한다. 선택적 stride 인자는 index 타입이어야 하며, 더 느린 메모리 공간(보다 작은 memory space id를 가진 메모리 공간)에 대한 stride를 지정한다. 이는 한 stride마다 number_of_elements_per_stride씩 전송하는데, %num_elements를 모두 전송할 때까지 반복된다. stride 인자는 둘 다 지정하거나 둘 다 생략해야 한다. %num_elements의 값은 number_of_elements_per_stride의 배수여야 한다.
예 1:
예를 들어, memory space 0의 memref %src에서 인덱스 [%i + 3, %j]에 위치한 256개 원소를, memory space 1의 memref %dst 인덱스 [%k + 7, %l]로 전송하는 DmaStartOp는 다음과 같이 지정된다.
mlir%num_elements = arith.constant 256 %idx = arith.constant 0 : index %tag = memref.alloc() : memref<1xi32, 4> affine.dma_start %src[%i + 3, %j], %dst[%k + 7, %l], %tag[%idx], %num_elements : memref<40x128xf32, 0>, memref<2x1024xf32, 1>, memref<1xi32, 2>
예 2:
%stride와 %num_elt_per_stride가 지정되면, DMA는 memory space 0에서 %stride 원소마다 %num_elt_per_stride 원소를 전송하며, %num_elements를 모두 전송할 때까지 이를 반복한다.
mliraffine.dma_start %src[%i, %j], %dst[%k, %l], %tag[%idx], %num_elements, %stride, %num_elt_per_stride : ...
affine.dma_wait (mlir::AffineDmaWaitOp)¶문법:
mliroperation ::= `affine.dma_wait` ssa-use `[` multi-dim-affine-map-of-ssa-ids `]`, ssa-use `:` memref-type
affine.dma_wait 연산은 태그 원소 %tag[%index]와 연관된 DMA 연산이 완료될 때까지 블록(block)한다. %tag는 memref이며, %index는 다른 load/store 인덱스와 동일한 제약을 가진 index여야 한다. 특히 memref의 각 차원 인덱스는 루프 귀납 변수와 심볼의 affine 식이어야 한다. %num_elements는 해당 DMA 연산과 연관된 원소 개수이다.
예:
mliraffine.dma_start %src[%i, %j], %dst[%k, %l], %tag[%index], %num_elements : memref<2048xf32, 0>, memref<256xf32, 1>, memref<1xi32, 2> ... ... affine.dma_wait %tag[%index], %num_elements : memref<1xi32, 2>