Edge AI & Cloud/On-Device AI & Edge Hardware

임베디드 베어메탈 및 FreeRTOS 환경에서 TFLite Micro C++ 라이브러리 포팅 및 링킹 에러 해결 방법

임베디드 친구 2026. 7. 20. 21:14
반응형

TFLite Micro Interpreter C++ 라이브러리를 베어메탈 및 RTOS 환경에 포팅해야 하는 이유

임베디드 AI 디바이스가 증가하면서 딥러닝 모델을 온디바이스로 구동하는 요구사항이 많아졌습니다. Linux가 부팅되지 않는 Bare-metal 환경이나 FreeRTOS 같은 RTOS 환경에서는 표준 C++ 라이브러리의 의존성 관리가 어렵습니다. 하드웨어 리소스가 극도로 제한된 환경에서는 메모리 할당 정책과 컴파일러 옵션 설정이 시스템의 생존을 결정합니다. 많은 임베디드 엔지니어들이 툴체인 설정과 메모리 맵 매핑 단계에서 빌드 실패를 경험합니다. 이 글은 의존성 없는 환경에서 TensorFlow Lite Micro 라이브러리를 빌드하고 링킹하는 명확한 아키텍처 가이드를 제공합니다. 이 가이드를 통해 Target 하드웨어 환경에서 발생할 수 있는 링크 에러를 해결하고 안정적인 추론 인터프리터를 구현할 수 있습니다.

TFLite Micro 빌드 및 C++ 링킹 오류 해결을 위한 핵심 요약

  • Dependency Elimination: 표준 호스트 OS C++ 런타임을 배제하기 위해 컴파일러 플래그에 -nostdlib, -fno-exceptions, -fno-rtti 설정을 반드시 추가해야 합니다.
  • Static Memory Allocation: 런타임 힙(Heap) 에러를 방지하기 위해 tflite::MicroInterpreter 내부에서 관리되는 텐서 아레나 영역을 글로벌 uint8_t 정적 배열로 선언해야 합니다.
  • Platform Abstraction Layer: 타겟 임베디드 시스템의 로깅 출력을 지원하기 위해 tflite::DebugLog() 함수를 보드 툴체인의 UART TX API로 오버라이딩 빌드해야 합니다.

TensorFlow Lite Micro 컴파일러 플래그 설정 및 소스 코드 아키텍처 상세 분석

기존 인터넷 자료들은 리눅스 환경 위에서 작동하는 소스 코드를 그대로 임베디드 target 환경에 적용하여 대량의 undefined reference 링킹 에러를 발생시키는 오류가 있습니다. 텐서플로우 라이트 마이크로는 호스트 운영체제의 파일 시스템과 가상 메모리를 지원하지 않습니다. 따라서 완전한 독립 구조를 가지는 베어메탈 호스트 빌드 파이프라인을 구축해야 합니다.

임베디드 호스트 C++ 툴체인에 설정해야 하는 핵심 컴파일러 플래그의 명세와 목적은 다음과 같습니다.

컴파일러 플래그 (Compiler Flag), 기능 개요 및 목적 (Description and Purpose)

  • -fno-exceptions: C++ 예외 처리 기능을 비활성화하여 바이너리 크기를 줄이고 관련 런타임 링킹을 차단함
  • -fno-rtti: 런타임 타입 정보 기능을 제거하여 메모리 오버헤드를 줄이고 호스트 의존성을 제거함
  • -nostdlib: 시스템 표준 라이브러리 전체를 링크에서 제외하여 베어메탈 링킹 최적화를 활성화함
  • -ffunction-sections: 각 함수를 독립된 섹션으로 분리하여 링커가 미사용 함수를 제거할 수 있도록 준비함
  • -fdata-sections: 각 전역 변수를 독립된 섹션으로 분리하여 사용하지 않는 메모리 영역을 정리함

TFLite Micro 인터프리터 구동을 위한 C++ 초기화 소스 코드 예제는 다음과 같습니다.

#include "tensorflow/lite/micro/micro_interpreter.h"
#include "tensorflow/lite/micro/micro_log.h"
#include "tensorflow/lite/micro/micro_mutable_op_resolver.h"
#include "tensorflow/lite/schema/schema_generated.h"

namespace {
    // Allocate memory for the model execution display area
    constexpr int kTensorArenaSize = 1024 * 60;
    alignas(16) uint8_t tensor_arena[kTensorArenaSize];

    const tflite::Model* model = nullptr;
    tflite::MicroInterpreter* interpreter = nullptr;
}

void InitializeTFLiteMicro() {
    // Load the model from the static byte array
    model = tflite::GetModel(g_model_data);
    if (model->version() != TFLITE_SCHEMA_VERSION) {
        MicroPrintf("Model schema version mismatch!");
        return;
    }

    // Register only the required operations to save memory
    static tflite::MicroMutableOpResolver<2> op_resolver;
    op_resolver.AddFullyConnected();
    op_resolver.AddSoftmax();

    // Instantiate the interpreter framework
    static tflite::MicroInterpreter static_interpreter(
        model, op_resolver, tensor_arena, kTensorArenaSize);
    interpreter = &static_interpreter;

    // Allocate internal tensors from the arena
    TfLiteStatus allocate_status = interpreter->AllocateTensors();
    if (allocate_status != kTfliteOk) {
        MicroPrintf("AllocateTensors() failed!");
        return;
    }
}

Arm GNU Toolchain 환경에서 DebugLog 디버깅 출력을 구현하는 방법

TFLite Micro 내부에서 출력하는 로그 메시지는 tflite::DebugLog() 표준 인터페이스를 거칩니다. 베어메탈 환경에서는 이 함수가 정의되어 있지 않으므로 컴파일러가 undefined reference to DebugLog 오류를 반환합니다. 이 문제를 해결하려면 타겟 MCU 보드의 전용 하드웨어 제어 레지스터 또는 툴체인 하드웨어 추상화 드라이버 API를 사용해 해당 함수를 직접 오버라이딩해야 합니다. 다음은 Arm GNU Toolchain 링커가 인식할 수 있도록 UART TX 함수를 랩핑하여 로그 시스템을 구현한 소스 코드 예제입니다.

#include "tensorflow/lite/micro/debug_log.h"
#include "main.h" // Target MCU HAL Driver header

extern "C" {
    // Override the weak DebugLog implementation for target board
    void DebugLog(const char* format, va_list args) {
        char buffer[128];
        // Format the log string using standard C library arguments
        int length = vsnprintf(buffer, sizeof(buffer), format, args);

        if (length > 0) {
            // Forward the formatted log buffer to the hardware UART peripheral
            HAL_UART_Transmit(&huart1, (uint8_t*)buffer, length, HAL_MAX_DELAY);
        }
    }
}

텐서 아레나 영역 설정 시 alignas(16) 미지정으로 인한 버스 에러 해결 방법

메모리가 극도로 제한된 하드웨어를 다룰 때 엔지니어들이 가장 자주 범하는 실수는 tensor_arena 배열의 데이터 정렬(Alignment) 속성을 무시하는 것입니다. Arm Cortex-M4 또는 Cortex-M7 아키텍처 하드웨어는 특정 비트 단위로 주소가 정렬되지 않은 포인터에 32비트나 64비트 데이터를 읽고 쓰려고 시도할 때 HardFault 예외 또는 버스 정렬 에러를 유발합니다.

TFLite Micro 내부 프레임워크는 속도 향상을 위해 16바이트 정렬을 기본 전제로 포인터 연산을 수행합니다. 속성을 부여하지 않고 일반 uint8_t static_arena[kSize] 형태로 선언하면 링커가 홀수 번지 주소에 배열을 할당할 수 있습니다. 이 상태에서 AllocateTensors() 함수가 실행되면 메모리 주소를 내부에서 재계산하다가 잘못된 포인터 참조 현상이 발생합니다. 이를 예방하기 위해 전역 변수 배열 선언부에 alignas(16) 키워드를 명시적으로 명세하여 컴파일러가 반드시 16바이트 경계 주소 위에 배열 메모리를 정렬하여 배치하도록 강제해야 합니다.

의존성 없는 베어메탈 및 RTOS 환경을 위한 TFLite Micro 포팅 가이드 결론

C++ 의존성이 없는 베어메탈 및 RTOS 환경에 TFLite Micro를 안정적으로 포팅하기 위해서는 아키텍처 단의 하드웨어 특성을 이해해야 합니다. -fno-exceptions 및 -fno-rtti 플래그를 통해 불필요한 표준 라이브러리 링크 관계를 끊어내야 합니다. 하드웨어 레벨의 버스 크래시를 방지하기 위해 alignas(16) 속성을 사용한 메모리 버퍼 오프셋 정렬을 필수로 지정해야 합니다. 마지막으로 하드웨어 타겟 맞춤형 DebugLog 함수를 구현하여 로그 파이프라인을 연결하면 독립적인 온디바이스 AI 런타임 시스템이 최종 완성됩니다.

반응형