이 가이드는 IREE 프로젝트의 코드 구조와 개발자를 위한 핵심 도구를 개괄적으로 소개합니다.
이 가이드는 개발자를 위해 IREE의 프로젝트 구조와 주요 도구에 대한 개요를 제공합니다.
/compiler/: MLIR 다이얼렉트, LLVM 컴파일러 패스, 모듈 변환 코드 등
/runtime/: VM과 HAL 드라이버를 포함한 독립 실행형 런타임 코드
/integrations/: TensorFlow와 같은 다른 프레임워크와 IREE 간의 통합
/tests/: 전체 컴파일러→런타임 워크플로를 위한 테스트
/tools/: 개발자 도구(iree-compile, iree-run-module 등)
/samples/: 별도의 https://github.com/iree-org/iree-experimental 리포지토리도 참고
Flow, HAL, Stream, VM 등)IREE의 코어 컴파일러는 지원되는 입력 MLIR 다이얼렉트(예: stablehlo, tosa, linalg)의 프로그램을 입력으로 받습니다. 임포트 도구와 API를 사용해 TensorFlow SavedModel 같은 프레임워크별 포맷을 MLIR 모듈로 변환할 수 있습니다. 프로그램은 궁극적으로 IREE의 대상 배포 플랫폼 조합에서 실행 가능한 모듈로 컴파일되지만, IREE의 개발자 도구를 사용하면 개별 컴파일러 패스, 변환, 기타 트랜스포메이션을 단계별로 실행할 수 있습니다.
iree-opt는 IREE의 컴파일러 패스를 테스트하는 도구입니다. mlir-opt와 유사하며 .mlir 입력 파일에 대해 IREE의 컴파일러 패스 집합을 실행합니다. 자세한 내용은 MLIR 용어집의 "conversion"을 참고하세요. iree-opt가 수행하는 변환은 개별 패스의 국소적인 조작부터 여러 단계를 포함하는 광범위한 파이프라인까지 다양합니다.
리포지토리에 체크인된 테스트 .mlir 파일에는 일반적으로 파일 상단에 RUN 블록이 포함되어 실행할 패스와 생성된 출력을 테스트하기 위한 FileCheck 사용 여부가 지정됩니다.
다음은 작은 컴파일러 패스를 테스트 파일에 대해 실행하는 예시입니다:
$ ../iree-build/tools/iree-opt \
--split-input-file \
--mlir-print-ir-before-all \
--iree-util-drop-compiler-hints \
$PWD/compiler/src/iree/compiler/Dialect/Util/Transforms/test/drop_compiler_hints.mlir
좀 더 복잡한 예시로, fullyconnected.mlir 모델 파일에 대해 VMVX 백엔드를 대상으로 IREE의 전체 변환 파이프라인을 실행하는 방법입니다:
$ ../iree-build/tools/iree-opt \
--iree-transformation-pipeline \
--iree-hal-target-device=local \
--iree-hal-local-target-device-backends=vmvx \
$PWD/tests/e2e/stablehlo_models/fullyconnected.mlir
iree-compile은 지원되는 입력 MLIR 어셈블리로부터 바이너리를 생성하는 IREE의 메인 컴파일러 드라이버입니다.
예를 들어, simple.mlir를 IREE 모듈로 변환하려면 다음과 같이 실행합니다:
$ ../iree-build/tools/iree-compile \
--iree-hal-target-device=local \
--iree-hal-local-target-device-backends=vmvx \
$PWD/samples/models/simple_abs.mlir \
-o /tmp/simple_abs_vmvx.vmfb
팁
iree-benchmark-module과 iree-run-module은 많은 플래그를 공유합니다.
iree-run-module 프로그램은 이미 변환된 IREE 모듈을 입력으로 받아 제공된 입력과 함께 내보낸 함수를 실행합니다.
이 프로그램은 iree-compile과 순차적으로 사용하여 .mlir 파일을 IREE 모듈로 변환한 후 실행할 수 있습니다. 다음은 위의 simple_abs.mlir에서 컴파일된 간단한 simple_abs_vmvx.vmfb를 IREE의 local-task CPU 디바이스에서 실행하는 예시 명령입니다:
$ ../iree-build/tools/iree-run-module \
--module=/tmp/simple_abs_vmvx.vmfb \
--device=local-task \
--function=abs \
--input=f32=-2
스칼라 입력은 value로, 버퍼 입력은 [shape]xtype=[value]로 전달됩니다.
| MLIR 타입 | 설명 | 입력 예시 |
|---|---|---|
i32 | 스칼라 | --input=1234 |
tensor<i32> | 0차원 텐서 | --input=i32=1234 |
tensor<1xi32> | 1차원 텐서(형상 [1]) | --input=1xi32=1234 |
tensor<2xi32> | 1차원 텐서(형상 [2]) | --input="2xi32=12 34" |
tensor<2x3xi32> | 2차원 텐서(형상 [2, 3]) | --input="2x3xi32=[1 2 3][4 5 6]" |
기타 사용 예시 고급 사용 예시는 다음 테스트 파일을 참고하세요:
소스 파일: tools/test/iree-run-module.mlir
// RUN: (iree-compile --iree-hal-target-device=local --iree-hal-local-target-device-backends=vmvx %s | \
// RUN: iree-run-module --device=local-task --module=- --function=abs --input="2xf32=-2 3") | FileCheck %s
// RUN: (iree-compile --iree-hal-target-device=local --iree-hal-local-target-device-backends=llvm-cpu %s | \
// RUN: iree-run-module --device=local-task --module=- --function=abs --input="2xf32=-2 3") | FileCheck %s
// CHECK-LABEL: EXEC @abs
func.func @abs(%input : tensor<2xf32>) -> (tensor<2xf32>) {
%result = math.absf %input : tensor<2xf32>
return %result : tensor<2xf32>
}
// INPUT-BUFFERS: result[1]: hal.buffer_view
// INPUT-BUFFERS-NEXT: 2xf32=-2.0 3.0
소스 파일: tools/test/iree-run-module-inputs.mlir
// Passing no inputs is okay.
// RUN: (iree-compile %s | \
// RUN: iree-run-module --module=- --function=no_input) | \
// RUN: FileCheck --check-prefix=NO-INPUT %s
// NO-INPUT-LABEL: EXEC @no_input
func.func @no_input() { return }
// -----
// Scalars use the form `--input=value`. Type (float/int) should be omitted.
// * The VM does not use i1/i8 types, so i32 VM types are returned instead.
// RUN: (iree-compile %s | \
// RUN: iree-run-module --module=- \
// RUN: --function=scalars \
// RUN: --input=1 \
// RUN: --input=5 \
// RUN: --input=1234 \
// RUN: --input=-3.14) | \
// RUN: FileCheck --check-prefix=INPUT-SCALARS %s
// INPUT-SCALARS-LABEL: EXEC @scalars
func.func @scalars(%arg0: i1, %arg1: i64, %arg2: i32, %arg3: f32) -> (i1, i64, i32, f32) {
// INPUT-SCALARS: result[0]: i32=1
// INPUT-SCALARS: result[1]: i64=5
// INPUT-SCALARS: result[2]: i32=1234
// INPUT-SCALARS: result[3]: f32=-3.14
return %arg0, %arg1, %arg2, %arg3 : i1, i64, i32, f32
}
// -----
// Buffers ("tensors") use the form `--input=[shape]xtype=[value]`.
// * If any values are omitted, zeroes will be used.
// * Quotes should be used around values with spaces.
// * Brackets may also be used to separate element values.
// RUN: (iree-compile --iree-hal-target-device=local \
// RUN: --iree-hal-local-target-device-backends=vmvx %s | \
// RUN: iree-run-module --device=local-sync \
// RUN: --module=- \
// RUN: --function=buffers \
// RUN: --input=i32=5 \
// RUN: --input=2xi32 \
// RUN: --input="2x3xi32=1 2 3 4 5 6") | \
// RUN: FileCheck --check-prefix=INPUT-BUFFERS %s
// INPUT-BUFFERS-LABEL: EXEC @buffers
func.func @buffers(%arg0: tensor<i32>, %arg1: tensor<2xi32>, %arg2: tensor<2x3xi32>) -> (tensor<i32>, tensor<2xi32>, tensor<2x3xi32>) {
// INPUT-BUFFERS: result[0]: hal.buffer_view
// INPUT-BUFFERS-NEXT: i32=5
// INPUT-BUFFERS: result[1]: hal.buffer_view
// INPUT-BUFFERS-NEXT: 2xi32=0 0
// INPUT-BUFFERS: result[2]: hal.buffer_view
// INPUT-BUFFERS-NEXT: 2x3xi32=[1 2 3][4 5 6]
return %arg0, %arg1, %arg2 : tensor<i32>, tensor<2xi32>, tensor<2x3xi32>
}
// -----
// Buffer values can be read from binary files with `@some/file.bin`.
// * numpy npy files from numpy.save or previous tooling output can be read to
// provide 1+ values.
// * Some data types may be converted (i32 -> si32 here) - bug?
// RUN: (iree-compile --iree-hal-target-device=local \
// RUN: --iree-hal-local-target-device-backends=vmvx \
// RUN: -o=%t.vmfb %s && \
// RUN: iree-run-module --device=local-sync \
// RUN: --module=%t.vmfb \
// RUN: --function=npy_round_trip \
// RUN: --input=2xi32=11,12 \
// RUN: --input=3xi32=1,2,3 \
// RUN: --output=@%t.npy \
// RUN: --output=+%t.npy && \
// RUN: iree-run-module --device=local-sync \
// RUN: --module=%t.vmfb \
// RUN: --function=npy_round_trip \
// RUN: --input=*%t.npy) | \
// RUN: FileCheck --check-prefix=INPUT-NUMPY %s
// INPUT-NUMPY-LABEL: EXEC @npy_round_trip
func.func @npy_round_trip(%arg0: tensor<2xi32>, %arg1: tensor<3xi32>) -> (tensor<2xi32>, tensor<3xi32>) {
// INPUT-NUMPY: result[0]: hal.buffer_view
// INPUT-NUMPY-NEXT: 2xsi32=11 12
// INPUT-NUMPY: result[1]: hal.buffer_view
// INPUT-NUMPY-NEXT: 3xsi32=1 2 3
return %arg0, %arg1 : tensor<2xi32>, tensor<3xi32>
}
// -----
// Verify parsing of signless small integer types passes either signed or
// unsigned range checks.
// RUN: (iree-compile --iree-hal-target-device=local \
// RUN: --iree-hal-local-target-device-backends=vmvx %s | \
// RUN: iree-run-module --device=local-sync \
// RUN: --module=- \
// RUN: --function=small_buffers \
// RUN: --input="2xi16=65535 -32767" \
// RUN: --input="3xi8=-6 250 0xFF") | \
// RUN: FileCheck --check-prefix=INPUT-SMALL-INTEGERS %s
// INPUT-SMALL-INTEGERS-LABEL: EXEC @small_buffers
func.func @small_buffers(%arg0: tensor<2xi16>, %arg1: tensor<3xi8>) -> (tensor<2xi16>, tensor<3xi8>) {
// Signedness of printing signless values is unspecified.
// INPUT-SMALL-INTEGERS: result[0]: hal.buffer_view
// INPUT-SMALL-INTEGERS-NEXT: 2xi16=-1 -32767
// INPUT-SMALL-INTEGERS: result[1]: hal.buffer_view
// INPUT-SMALL-INTEGERS-NEXT: 3xi8=-6 -6 -1
return %arg0, %arg1 : tensor<2xi16>, tensor<3xi8>
}
소스 파일: tools/test/iree-run-module-outputs.mlir
// Tests that execution providing no outputs is ok.
// RUN: (iree-compile --iree-hal-target-device=local \
// RUN: --iree-hal-local-target-device-backends=vmvx %s | \
// RUN: iree-run-module --device=local-sync --module=- --function=no_output) | \
// RUN: FileCheck --check-prefix=NO-OUTPUT %s
// NO-OUTPUT-LABEL: EXEC @no_output
func.func @no_output() { return }
// -----
// Tests the default output printing to stdout.
// RUN: (iree-compile --iree-hal-target-device=local \
// RUN: --iree-hal-local-target-device-backends=vmvx %s | \
// RUN: iree-run-module --device=local-sync --module=- --function=default) | \
// RUN: FileCheck --check-prefix=OUTPUT-DEFAULT %s
// OUTPUT-DEFAULT-LABEL: EXEC @default
func.func @default() -> (i32, tensor<f32>, tensor<?x4xi32>) {
// OUTPUT-DEFAULT: result[0]: i32=123
%0 = arith.constant 123 : i32
// OUTPUT-DEFAULT: result[1]: hal.buffer_view
// OUTPUT-DEFAULT-NEXT: f32=4
%1 = arith.constant dense<4.0> : tensor<f32>
// OUTPUT-DEFAULT: result[2]: hal.buffer_view
// OUTPUT-DEFAULT-NEXT: 2x4xi32=[0 1 2 3][4 5 6 7]
%2 = flow.tensor.dynamic_constant dense<[[0,1,2,3],[4,5,6,7]]> : tensor<2x4xi32> -> tensor<?x4xi32>
return %0, %1, %2 : i32, tensor<f32>, tensor<?x4xi32>
}
// -----
// Tests explicit output to npy files by producing a concatenated .npy and then
// printing the results in python. This also verifies our npy files can be
// parsed by numpy.
// RUN: (iree-compile --iree-hal-target-device=local \
// RUN: --iree-hal-local-target-device-backends=vmvx %s | \
// RUN: iree-run-module --device=local-sync --module=- --function=numpy \
// RUN: --output= \
// RUN: --output=@%t.npy \
// RUN: --output=+%t.npy) && \
// RUN: "%PYTHON" %S/echo_npy.py %t.npy | \
// RUN: FileCheck --check-prefix=OUTPUT-NUMPY %s
func.func @numpy() -> (i32, tensor<f32>, tensor<?x4xi32>) {
// Output skipped:
%0 = arith.constant 123 : i32
// OUTPUT-NUMPY{LITERAL}: 4.0
%1 = arith.constant dense<4.0> : tensor<f32>
// OUTPUT-NUMPY-NEXT{LITERAL}: [[0 1 2 3]
// OUTPUT-NUMPY-NEXT{LITERAL}: [4 5 6 7]]
%2 = flow.tensor.dynamic_constant dense<[[0,1,2,3],[4,5,6,7]]> : tensor<2x4xi32> -> tensor<?x4xi32>
return %0, %1, %2 : i32, tensor<f32>, tensor<?x4xi32>
}
// -----
// Tests output to binary files by round-tripping the output of a function into
// another invocation reading from the binary files. Each output is written to
// its own file (optimal for alignment/easier to inspect).
// RUN: (iree-compile --iree-hal-target-device=local \
// RUN: --iree-hal-local-target-device-backends=vmvx %s -o=%t.vmfb && \
// RUN: iree-run-module --device=local-sync \
// RUN: --module=%t.vmfb \
// RUN: --function=write_binary \
// RUN: --output=@%t.0.bin \
// RUN: --output=@%t.1.bin && \
// RUN: iree-run-module --device=local-sync \
// RUN: --module=%t.vmfb \
// RUN: --function=echo_binary \
// RUN: --input=f32=@%t.0.bin \
// RUN: --input=2x4xi32=@%t.1.bin) | \
// RUN: FileCheck --check-prefix=OUTPUT-BINARY %s
// Tests output to binary files by round-tripping the output of a function into
// another invocation reading from the binary files. The values are appended to
// a single file and read from the single file.
// RUN: (iree-compile --iree-hal-target-device=local \
// RUN: --iree-hal-local-target-device-backends=vmvx \
// RUN: -o=%t.vmfb %s && \
// RUN: iree-run-module --device=local-sync \
// RUN: --module=%t.vmfb \
// RUN: --function=write_binary \
// RUN: --output=@%t.bin \
// RUN: --output=+%t.bin && \
// RUN: iree-run-module --device=local-sync \
// RUN: --module=%t.vmfb \
// RUN: --function=echo_binary \
// RUN: --input=f32=@%t.bin \
// RUN: --input=2x4xi32=+%t.bin) | \
// RUN: FileCheck --check-prefix=OUTPUT-BINARY %s
func.func @write_binary() -> (tensor<f32>, tensor<?x4xi32>) {
%0 = arith.constant dense<4.0> : tensor<f32>
%1 = flow.tensor.dynamic_constant dense<[[0,1,2,3],[4,5,6,7]]> : tensor<2x4xi32> -> tensor<?x4xi32>
return %0, %1 : tensor<f32>, tensor<?x4xi32>
}
func.func @echo_binary(%arg0: tensor<f32>, %arg1: tensor<?x4xi32>) -> (tensor<f32>, tensor<?x4xi32>) {
// OUTPUT-BINARY{LITERAL}: f32=4
// OUTPUT-BINARY{LITERAL}: 2x4xi32=[0 1 2 3][4 5 6 7]
return %arg0, %arg1 : tensor<f32>, tensor<?x4xi32>
}
소스 파일: tools/test/iree-run-module-expected.mlir
// RUN: (iree-compile --iree-hal-target-device=local --iree-hal-local-target-device-backends=vmvx %s | \
// RUN: iree-run-module --device=local-task --module=- --function=abs --input=f32=-2 --expected_output=f32=-2 --expected_output=f32=2.0) | \
// RUN: FileCheck %s --check-prefix=SUCCESS-MATCHES
// RUN: (iree-compile --iree-hal-target-device=local --iree-hal-local-target-device-backends=vmvx %s | \
// RUN: iree-run-module --device=local-task --module=- --function=abs --input=f32=-2 --expected_output=f32=-2 --expected_output="(ignored)") | \
// RUN: FileCheck %s --check-prefix=SUCCESS-IGNORED
// RUN: (iree-compile --iree-hal-target-device=local --iree-hal-local-target-device-backends=vmvx %s | \
// RUN: iree-run-module --device=local-task --module=- --function=abs --input=f32=-2 --expected_output=f32=-2 --expected_output=f32=2.1 --expected_f32_threshold=0.1) | \
// RUN: FileCheck %s --check-prefix=SUCCESS-THRESHOLD
// RUN: (iree-compile --iree-hal-target-device=local --iree-hal-local-target-device-backends=vmvx %s | \
// RUN: not iree-run-module --device=local-task --module=- --function=abs --input=f32=-2 --expected_output=f32=123 --expected_output=f32=2.0) | \
// RUN: FileCheck %s --check-prefix=FAILED-FIRST
// RUN: (iree-compile --iree-hal-target-device=local --iree-hal-local-target-device-backends=vmvx %s | \
// RUN: not iree-run-module --device=local-task --module=- --function=abs --input=f32=-2 --expected_output=f32=-2 --expected_output=f32=4.5) | \
// RUN: FileCheck %s --check-prefix=FAILED-SECOND
// RUN: (iree-compile --iree-hal-target-device=local --iree-hal-local-target-device-backends=vmvx %s | \
// RUN: not iree-run-module --device=local-task --module=- --function=abs --input=f32=-2 --expected_output=f32=-2 --expected_output=4xf32=2.0) | \
// RUN: FileCheck %s --check-prefix=FAILED-SHAPE
// SUCCESS-MATCHES: [SUCCESS]
// SUCCESS-THRESHOLD: [SUCCESS]
// SUCCESS-IGNORED: [SUCCESS]
// FAILED-FIRST: [FAILED] result[0]: element at index 0 (-2) does not match the expected (123)
// FAILED-SECOND: [FAILED] result[1]: element at index 0 (2) does not match the expected (4.5)
// FAILED-SHAPE: [FAILED] result[1]: metadata is f32; expected that the view matches 4xf32
func.func @abs(%input: tensor<f32>) -> (tensor<f32>, tensor<f32>) {
%result = math.absf %input : tensor<f32>
return %input, %result : tensor<f32>, tensor<f32>
}
iree-check-module 프로그램은 이미 변환된 IREE 모듈을 입력으로 받아 googletest 테스트의 연속으로 실행합니다. 이는 IREE 체크 프레임워크의 테스트 러너입니다.
$ ../iree-build/tools/iree-compile \
--iree-input-type=stablehlo \
--iree-hal-target-device=local \
--iree-hal-local-target-device-backends=vmvx \
$PWD/tests/e2e/stablehlo_ops/abs.mlir \
-o /tmp/abs.vmfb
$ ../iree-build/tools/iree-check-module \
--device=local-task \
--module=/tmp/abs.vmfb
iree-run-mlir 프로그램은 .mlir 파일을 입력으로 받아 IREE 바이트코드 모듈로 변환하고 모듈을 실행합니다.
이는 프로덕션 용도가 아닌 테스트와 디버깅을 위해 설계되었으므로, 보통은 명시적으로 지정해야 하는 일부 작업(예: 기본적으로 모든 함수를 exported로 표시하고 모두 실행하기)을 추가로 수행합니다.
예를 들어, samples/models/simple_abs.mlir의 내용을 실행하려면:
# iree-run-mlir <compiler flags> [input.mlir] <runtime flags>
$ ../iree-build/tools/iree-run-mlir \
--iree-hal-target-device=local \
--iree-hal-local-target-device-backends=vmvx \
$PWD/samples/models/simple_abs.mlir \
--input=f32=-2
iree-dump-module 프로그램은 IREE 모듈 FlatBuffer 파일의 내용을 출력합니다.
예를 들어, 위에서 변환한 모듈을 살펴보려면:
../iree-build/tools/iree-dump-module /tmp/simple_abs_vmvx.vmfb
모든 IREE 도구는 파일에서 입력 값을 읽는 기능을 지원합니다. 이는 디버깅에 매우 유용합니다. 각 도구에서 --help를 사용해 설정할 플래그를 확인하세요. 입력은 줄바꿈으로 구분되어야 하며, 각 입력은 스칼라 또는 버퍼여야 합니다. 스칼라는 type=value, 버퍼는 [shape]xtype=[value] 형식이어야 합니다. 예시:
1x5xf32=1,-2,-3,4,-5
1x5x3x1xf32=15,14,13,12,11,10,9,8,7,6,5,4,3,2,1
numpy 파일에서 읽기:
--input=@input.npy
raw 바이너리 파일은 메타데이터가 없으므로, 형상과 dtype을 함께 지정하여 바이너리 파일에서 읽기:
--input=[shape]xdtype=@input.bin
--iree-flow-trace-dispatch-tensors링크이 플래그는 각 디스패치 함수의 입력과 출력을 트레이싱하도록 활성화합니다. IREE는 ML 워크로드를 여러 디스패치 함수로 분할하므로, 테스트 케이스를 더 쉽게 좁힐 수 있습니다. 플래그를 켜면, IREE는 각 디스패치 함수의 전후에 트레이스 포인트를 삽입합니다. 첫 번째 트레이스 op는 입력용이고, 두 번째 트레이스 op는 출력용입니다. 하나의 디스패치 함수에 두 개의 이벤트가 생성됩니다.