Skip to content
Go DevBJ
Go back

RISC-V 엣지 AI, Rust & LWIP: 간헐적 추론 지연과 UDP 패킷 손실 심층 분석

Edit page

안녕하세요, DevBJ입니다. 2026년, 엣지 AI와 임베디드 Rust의 조합은 더 이상 낯선 기술 스택이 아닙니다. 하지만 실제 필드에서는 예상치 못한 복합적인 문제들이 발생하곤 합니다. 오늘은 RISC-V 기반 엣지 AI 디바이스에서 Rust로 구현된 실시간 추론 로직과 LWIP 네트워크 스택이 결합될 때 발생할 수 있는 ‘간헐적 추론 지연 및 UDP 패킷 손실’ 문제를 깊이 파헤쳐보려 합니다.

문제의 발단: 엣지 AI 디바이스의 ‘침묵’

우리가 다루는 시나리오는 다음과 같습니다. 특정 RISC-V MCU 기반의 엣지 디바이스는 카메라로부터 영상을 입력받아 Rust로 구현된 TensorFlow Lite Micro 모델을 통해 실시간 객체 추론을 수행합니다. 추론된 객체의 바운딩 박스, 클래스 ID, 신뢰도 등의 결과는 UDP 패킷을 통해 중앙 AI Agent 서버로 전송됩니다. 이 시스템은 초기 테스트에서는 정상적으로 작동하는 듯 보였습니다. 그러나 실제 환경에서 객체 밀도가 높아지거나(즉, 추론 결과 데이터가 많아짐), 혹은 특정 배경 네트워크 트래픽이 증가하는 시점에 다음과 같은 현상이 보고되기 시작했습니다.

이러한 현상은 실시간성과 신뢰성이 중요한 엣지 AI 시스템에서 치명적인 문제였습니다.

초기 가설: 자원 경합과 버퍼 오버플로우

문제 보고 직후, 우리는 몇 가지 초기 가설을 세웠습니다.

  1. CPU 자원 경합: 추론 작업과 네트워크 전송 작업이 동시에 CPU를 과도하게 사용하여 서로를 방해.
  2. 메모리 부족: 추론 결과 버퍼나 LWIP의 네트워크 버퍼(pbuf)가 부족하여 데이터가 드롭됨.
  3. 네트워크 스택 병목: LWIP 내부 처리 로직이 특정 부하에서 지연을 유발하거나, 동기적으로 동작하는 부분이 추론 메인 루프를 블록함.

이 가설들을 검증하기 위해 디바이스의 동작을 면밀히 분석하기 시작했습니다.

심층 분석: 프로파일링과 네트워크 스택 추적

1. CPU 및 메모리 프로파일링

먼저, RTOS 기반 환경에서 각 태스크의 CPU 사용률과 메모리 할당 패턴을 모니터링했습니다.

프로파일링 결과, 객체 밀도가 높아질수록 inference_task의 CPU 사용률이 증가하는 것은 당연했지만, 동시에 network_send_task의 실행 빈도가 현저히 불규칙해지고, inference_task의 실행 주기도 길어지는 현상이 관찰되었습니다. 특히 udp_send() 호출 시점에 CPU 사용률 피크와 함께 지연이 발생했습니다.

메모리 사용량 분석에서는 pbuf 풀의 할당 실패 로그가 간헐적으로 발견되었습니다. 이는 LWIP 네트워크 버퍼가 부족하다는 명백한 증거였습니다.

2. LWIP UDP 송신 경로 추적

Rust 애플리케이션에서 lwip_sys 바인딩을 통해 udp_send()를 호출하는 과정을 자세히 들여다봤습니다.

// Simplified Rust code snippet
fn send_inference_result(udp_pcb: &mut UdpPcb, data: &[u8], remote_addr: IpAddr, remote_port: u16) -> Result<(), LwipError> {
    let pbuf = pbuf_alloc(PbufType::PBUF_TRANSPORT, data.len() as u16, PbufType::PBUF_RAM)?;
    pbuf_take(pbuf, data)?;
    udp_send(udp_pcb, pbuf)?;
    pbuf_free(pbuf);
    Ok(())
}

여기서 pbuf_allocudp_send는 내부적으로 동기적으로 동작하며, pbuf를 할당하고 데이터를 복사한 후, 네트워크 인터페이스 드라이버로 전달하는 과정을 거칩니다. 만약 pbuf 풀이 고갈되어 pbuf_alloc이 실패하면, 추론 결과는 네트워크로 전송되지 못하고 버려지게 됩니다.

또한, LWIP의 low_level_output 함수를 통해 이더넷 MAC 드라이버로 데이터를 넘기는 과정에서 발생하는 지연도 문제였습니다. 드라이버 내부의 송신 FIFO가 가득 차면, low_level_output은 블록되거나 실패할 수 있으며, 이는 udp_send 호출자의 지연으로 이어집니다.

3. 인터럽트 처리 지연

RISC-V 플랫폼에서 이더넷 수신(RX) 인터럽트의 우선순위와 처리 시간을 확인했습니다. 만약 고속의 인커밍 패킷 처리 중 LWIP의 내부 로직이 CPU를 오랫동안 점유하거나, RX 인터럽트의 처리 시간이 길어져 다음 패킷 수신까지의 간격이 짧아지면, MAC 드라이버의 하드웨어 FIFO 또는 소프트웨어 버퍼가 오버플로우되어 패킷 손실이 발생할 수 있습니다.

근본 원인 분석

심층 분석 결과, 문제의 원인은 다음과 같이 복합적이었습니다.

  1. 동기적 udp_send 호출: inference_task 내에서 추론 결과가 나올 때마다 udp_send()를 직접 호출하는 구조는, 네트워크 전송이 지연될 경우 inference_task 자체의 실행을 지연시키는 주요 원인이었습니다. 이는 실시간 추론 주기를 깨뜨리는 결과를 초래했습니다.
  2. pbuf 풀 부족: 높은 빈도로 많은 추론 데이터를 전송하려 할 때, LWIP의 기본 pbuf 풀 설정이 충분하지 않아 pbuf_alloc 실패가 빈번하게 발생했습니다. 이는 UDP 패킷 손실로 직결됩니다. 특히 PBUF_POOL_BUFSIZE가 전송하려는 최대 UDP 데이터그램 크기보다 작을 경우, 패킷이 조각나거나 전송이 아예 불가능해질 수 있습니다.
  3. 네트워크 드라이버/LWIP 처리 병목: low_level_output 함수 호출이 드라이버 레벨에서 블록되거나, lwip_thread가 다른 우선순위 높은 태스크에 의해 충분히 스케줄링되지 못하면서 LWIP 내부의 상태 머신 진행이 지연되는 현상이 관찰되었습니다.

해결책: 비동기 전송과 LWIP 튜닝

1. 추론 결과와 네트워크 전송의 분리 (비동기화)

가장 중요한 해결책은 inference_tasknetwork_send_task 간의 결합도를 낮추고 비동기적인 데이터 흐름을 만드는 것입니다. inference_task는 추론 결과를 생성한 후, 이를 즉시 네트워크로 보내는 대신 ‘메시지 큐’에 푸시합니다. network_send_task는 이 큐에서 데이터를 팝하여 UDP로 전송합니다.

// Schematic Rust code for asynchronous send
use heapless::mpmc::Queue; // Example: Multi-producer, multi-consumer queue

static mut INFERENCE_QUEUE: Queue<InferenceResult, 16> = Queue::new(); // Capacity 16 results

// In inference_task
fn inference_loop() {
    loop {
        let result = perform_inference();
        // Try to push to queue, if full, handle (e.g., drop oldest, log, or wait)
        unsafe {
            if INFERENCE_QUEUE.enqueue(result).is_err() {
                // Log or handle queue full condition
                // DevBJ: 여기서는 오래된 데이터를 버리는 전략을 사용할 수 있습니다.
            }
        }
        // ... yield or sleep
    }
}

// In network_send_task
fn network_send_loop() {
    loop {
        unsafe {
            if let Some(result) = INFERENCE_QUEUE.dequeue() {
                // Serialize result to bytes
                let data_to_send = serialize_inference_result(&result);
                // Call send_inference_result with the serialized data
                if let Err(e) = send_inference_result(&mut UDP_PCB, &data_to_send, REMOTE_IP, REMOTE_PORT) {
                    // Log network send error
                }
            }
        }
        // ... yield or sleep, possibly waiting for a notification from the queue
    }
}

이러한 구조는 inference_task가 네트워크 전송 지연에 영향을 받지 않고 오직 추론에만 집중할 수 있게 합니다. 큐가 가득 찰 경우, 최신 추론 결과를 버리거나 (실시간성이 중요할 때) 잠시 대기하는 등의 전략을 사용할 수 있습니다.

2. LWIP 버퍼 풀 최적화

lwipopts.h 파일을 면밀히 검토하여 pbuf 관련 설정을 조정했습니다.

// lwipopts.h - Example tuning parameters
#define MEM_SIZE                (16 * 1024)   // 총 힙 메모리 크기 (필요에 따라 증설)
#define PBUF_POOL_SIZE          50            // pbuf 풀에 할당될 pbuf 개수 (기본 10-16개에서 대폭 증설)
#define PBUF_POOL_BUFSIZE       2048          // 각 pbuf의 버퍼 크기 (최대 UDP 데이터그램 크기 + 헤더 고려)
#define TCP_WND                 (8 * 1024)    // TCP 수신 윈도우 크기 (UDP에 직접적 영향은 적지만, 전반적 튜닝)
#define LWIP_MEM_ALIGNMENT      4             // 메모리 정렬 (RISC-V에 맞춰 4바이트)
// 이더넷 패킷 헤더 길이를 고려한 pbuf offset (필요시)
// #define PBUF_LINK_ENCAPSULATION_HLEN  14 

PBUF_POOL_SIZE를 크게 늘리고, PBUF_POOL_BUFSIZE를 전송하려는 최대 UDP 데이터그램 크기에 맞게 충분히 확보하는 것이 중요합니다. 또한 MEM_SIZE도 전체적인 LWIP 동작에 필요한 힙 메모리를 고려하여 조정해야 합니다.

3. 태스크 우선순위 및 LWIP 스케줄링 조정

RTOS 환경에서 network_send_task의 우선순위를 inference_task보다 약간 높게 설정하여 네트워크 전송이 원활하게 이루어지도록 했습니다. 또한, lwip_thread (혹은 tcpip_thread)가 충분히 자주 스케줄링되도록 보장하여 LWIP 내부 처리 지연을 최소화했습니다.

LWIP 스케줄링 방식에 따라 tcpip_callback을 사용하여 LWIP의 일부 처리를 특정 태스크 컨텍스트에서 비동기적으로 수행하게 할 수도 있습니다. 이는 인터럽트 핸들러 내에서 LWIP API 호출을 최소화하고 메인 루프로 LWIP 처리를 오프로드하는 데 유용합니다.

4. 드라이버 버퍼 및 DMA 최적화

이더넷 MAC 드라이버 레벨에서 송수신 버퍼 크기를 최적화하고 DMA 전송을 적극 활용하여 CPU 개입을 최소화하는 것도 중요합니다. 특히 고속 패킷 처리 시에는 DMA가 필수적이며, 드라이버 내부의 FIFO 오버플로우를 방지하기 위해 버퍼 체이닝이나 Ring Buffer 구조를 활용해야 합니다.

결과 및 결론

위 해결책들을 적용한 결과, RISC-V 엣지 AI 디바이스는 객체 밀도가 높은 상황에서도 안정적으로 추론 결과를 전송하기 시작했습니다. 간헐적 추론 지연과 UDP 패킷 손실 현상은 더 이상 관찰되지 않았습니다.

이 삽질을 통해 배운 핵심 교훈은 다음과 같습니다.

엣지 AI 시스템은 복잡한 다중 스택 기술의 결합체입니다. 각 기술 스택의 깊은 이해와 함께 시스템 전반의 상호작용을 파악하는 엔지니어의 시야가 문제 해결의 핵심입니다.


DevBJ | No Bio, Just Log 기술 삽질로그


Edit page
Share this post on:

Next Post
AI Agent를 활용한 임베디드 코드 디버깅 효율화: 엔지니어 시점 분석