13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] 엣지 AI

  • [AI] OpenVINO 도입 가이드: 기존 AI 모델 최적화 전환 기준

    [AI] OpenVINO 도입 가이드: 기존 AI 모델 최적화 전환 기준

    OpenVINO 도입 가이드: 기존 AI 모델 최적화 전환 기준

    OpenVINO 도입을 검토할 때 저는 먼저 병목부터 가릅니다. 엣지 장비나 사내 서버에서 추론만 오래 돌려보면 결국 질문이 하나로 모이거든요. 지금 느린 지점이 모델인지, 런타임인지, 하드웨어 배치인지를 먼저 나눠야 판단이 덜 흔들립니다. ONNX Runtime에서 이미 잘 도는데도 지연 시간이 출렁이거나 첫 요청만 유독 느릴 때가 있는데, 이런 경우 OpenVINO는 프레임워크를 갈아엎는 선택이라기보다 인텔 CPU 중심 추론 경로를 별도로 최적화하는 수단에 가깝습니다.

    이 글은 OpenVINO 입문서가 아니라, 기존 AI 모델을 언제 OpenVINO 경로로 분기할지 판단하는 실무 메모에 가깝습니다. 저는 보통 세 가지를 같이 봅니다. 첫째, 운영 하드웨어가 정말 인텔 CPU 중심인지. 둘째, ONNX 산출물이 이미 안정적으로 나오는지. 셋째, 병목의 본체가 모델 실행 경로인지 아니면 전처리·후처리·API 계층인지입니다. 사내 블로그에 ONNX export 체크리스트 글이 있다면 그 글과 함께 보시면 판단이 훨씬 빨라집니다.

    OpenVINO 도입 판단을 위한 전체 아키텍처 개요 이미지

    기존 ONNX 추론 경로와 OpenVINO 최적화 경로를 나란히 보여주는 개요 이미지입니다.

    OpenVINO 도입, 왜 여기서 판단이 갈리냐면요

    실무에서는 성능 숫자 하나보다 성능의 성격이 더 중요합니다. 같은 모델이라도 초당 많이 처리해야 하는 배치 작업과 한 요청 응답이 빨라야 하는 API는 최적점이 다르더라고요. OpenVINO는 특히 인텔 하드웨어에서 이 차이를 런타임 수준에서 조정하기 편합니다. 반대로 이미 NVIDIA GPU 최적화와 TensorRT 경로가 운영 표준이라면, OpenVINO를 주 경로로 들이는 순간 복잡도만 커질 수 있습니다.

    제가 보기에 OpenVINO는 아래 조건에서 특히 잘 맞았습니다.

    • 인텔 CPU가 메인 실행 장치라서 CPU 스레드 배치와 추론 요청 수 조정이 성능에 직접 반영되는 경우
    • 여러 엣지 노드에 같은 모델을 배포해야 해서 아티팩트를 단순하게 유지하고 싶은 경우
    • 학습 프레임워크는 그대로 두고, 배포 레이어만 별도로 최적화하고 싶은 경우
    • ONNX까지는 이미 정리됐지만 실제 서비스 지연 시간 편차가 커서 런타임 튜닝이 필요한 경우

    반대로 아래 상황이면 저는 먼저 보류합니다.

    • 커스텀 연산자가 많아 변환 호환성 자체가 리스크인 경우
    • GPU 혼합 환경에서 특정 벤더 최적화가 이미 운영 표준인 경우
    • 모델 추론보다 이미지 디코딩, 리사이즈, NMS, 직렬화가 더 무거운 경우
    • 서비스가 단건 응답 위주인데 비동기 다중 요청에서만 처리량이 좋아지는 경우

    OpenVINO 도입 기준을 AI 모델 최적화 관점에서 보면

    저는 “느리다”보다 “어디서 손해 보는가”를 먼저 적어 둡니다. 이 단계가 흐리면 OpenVINO를 붙인 뒤에도 왜 빨라졌는지, 왜 안 빨라졌는지 설명을 못 하게 되더라고요. 이 작업이 좀 귀찮아 보여도 나중엔 진짜 편합니다.

    상황 관찰 신호 OpenVINO 도입 적합도 제가 실제로 내리는 판단
    인텔 CPU 중심 API 서버 반복 추론 지연 편차가 크고 CPU 사용 패턴이 불안정함 높음 ONNX는 유지하고 OpenVINO IR을 추가 산출해 A/B 비교합니다.
    엣지 장비 다수 배포 GPU 없이 운영하고 패키징 단순성이 중요함 높음 IR 산출물 관리와 첫 로딩 시간, 캐시 전략까지 같이 검토합니다.
    NVIDIA GPU 표준 환경 TensorRT나 CUDA 경로가 이미 검증됨 낮음 주 경로는 유지하고 CPU fallback이 필요할 때만 제한적으로 씁니다.
    커스텀 연산자 다수 변환 경고가 반복되거나 지원 여부가 불명확함 주의 호환성 검증이 끝나기 전에는 마이그레이션 일정에 넣지 않습니다.
    서비스 응답보다 배치 처리량이 중요 동시 요청 수를 늘릴수록 전체 효율이 올라감 높음 <code>-hint throughput 기준으로 먼저 보고, API와 배치 경로를 분리합니다.
    첫 요청만 느림 웜업 이후엔 안정적이나 cold start가 큼 보통 이상 모델 캐시와 사전 컴파일 전략을 먼저 넣고 재측정합니다.

    OpenVINO 도입을 추천하는 가장 강한 신호는 이겁니다. 인텔 CPU 추론이 실제 핵심 경로이고, 모델은 이미 ONNX로 정리됐고, 남은 문제가 운영 레이어 성능과 일관성인 경우요. 이때는 투자 대비 회수 속도가 꽤 빠른 편입니다.

    반대로 학습 파이프라인까지 한 번에 옮기려는 접근은 저는 거의 항상 말립니다. 바꿔야 할 지점이 많아지는 순간 병목 분리가 안 되거든요. 가장 덜 아픈 경로는 여전히 학습은 기존 프레임워크 유지, 배포 추론만 OpenVINO로 분기하는 방식입니다.

    OpenVINO와 ONNX 관계, 실무에서는 이렇게 보시면 덜 헷갈립니다

    실무 관점에서 ONNX는 모델 교환용 공용 산출물이고, OpenVINO는 그 산출물을 인텔 실행 환경에 맞게 최적화해 배포하는 계층에 가깝습니다. 둘 중 하나만 고르는 관계가 아니라, ONNX를 중간 계약서처럼 두고 OpenVINO IR을 배포 전용 산출물로 추가하는 구조가 제일 안전했습니다.

    이 구조를 쓰면 좋은 점이 분명합니다.

    1. 학습 코드와 배포 코드를 억지로 묶지 않아도 됩니다.
    2. ONNX Runtime 경로를 롤백 스위치로 남길 수 있습니다.
    3. 변환 실패가 나도 원본 산출물 체계가 무너지지 않습니다.
    4. CPU 전용 최적화 실험을 서비스 전체 변경 없이 진행할 수 있습니다.

    실제로는 아래 흐름을 많이 씁니다. PyTorch에서 ONNX export를 만들고, OpenVINO IR로 한 번 더 변환한 뒤, benchmark_app으로 모델 단독 성능을 먼저 확인하고, 그다음 애플리케이션에 붙입니다. 이 순서를 지키면 앱 버그와 런타임 문제를 섞어 보지 않게 됩니다.

    실전 구현 1: ONNX 모델을 OpenVINO IR로 변환하기

    여기서는 이미 model.onnx가 있다고 가정하겠습니다. 현재 OpenVINO 배포판에서는 Python 패키지 openvino만으로 기본 런타임과 변환 도구를 시작할 수 있습니다. 예전 글처럼 openvino-dev를 기본 전제로 두는 문서는 아직 많지만, 최신 환경에서는 필수 전제처럼 쓰지 않는 편이 덜 헷갈립니다.

    python3 -m venv .venv
    source .venv/bin/activate
    python -m pip install --upgrade pip
    python -m pip install openvino onnx onnxruntime
    
    # 1) 가장 단순한 CLI 변환
    ovc model.onnx --output_model build/model.xml
    
    # 2) 입력 shape를 명시해야 하는 경우 예시
    ovc model.onnx --output_model build/model.xml --input_shape [1,3,224,224]

    여기서 --input_shape를 굳이 명시하는 이유는 변환 성공 여부보다 런타임 입력 계약을 명확히 하기 위해서입니다. 동적 차원을 넓게 둔 ONNX를 그대로 넘기면 앱 쪽 전처리 코드가 shape를 암묵적으로 가정하다가 운영 중에 터지는 일이 꽤 잦았습니다.

    import openvino as ov
    
    onnx_path = "model.onnx"
    ir_path = "build/model.xml"
    
    ov_model = ov.convert_model(onnx_path)
    ov.save_model(ov_model, ir_path)
    
    print("saved:", ir_path)

    변환 단계에서 제가 꼭 보는 건 세 가지입니다.

    • model.xml과 model.bin이 함께 생성되는지
    • 변환 로그에 unsupported operation, shape inference, precision 관련 경고가 남는지
    • 입력 이름과 입력 shape가 애플리케이션 코드가 기대하는 값과 맞는지

    중요한 건 변환 완료 자체보다 입력 계약이 명확해졌는가입니다. 현업에서 진짜 자주 나는 장애는 변환 실패보다 “변환은 됐는데 앱 입력이 미묘하게 안 맞는 상태”였어요.

    ONNX에서 OpenVINO IR로 변환되는 OpenVINO 도입 구성 이미지

    ONNX 파일이 OpenVINO IR로 바뀌고 CPU 플러그인으로 연결되는 과정을 보여주는 구성 이미지입니다.

    실전 구현 2: 인텔 CPU 추론과 기본 검증

    변환 직후에는 애플리케이션에 바로 붙이지 말고 모델 단독 성능부터 봅니다. 여기서 저는 목표를 둘로 나눕니다. API라면 지연 시간 중심, 배치 작업이라면 처리량 중심입니다. 이걸 안 나누고 평균 숫자 하나만 보면 해석이 자주 틀어집니다.

    # 지연 시간 우선: 단건 응답형 API 검증
    benchmark_app -m build/model.xml -d CPU -hint latency -api sync -t 15 \
      -report_type average_counters -report_folder reports/latency \
      -exec_graph_path reports/latency/exec_graph.xml
    
    # 처리량 우선: 다중 요청 또는 배치 작업 검증
    benchmark_app -m build/model.xml -d CPU -hint throughput -api async -t 15 \
      -report_type average_counters -report_folder reports/throughput \
      -pc

    제가 실제로 읽는 포인트는 아래입니다.

    • -hint latency에서 좋아지는 모델은 실시간 API 후보입니다. 반대로 -hint throughput에서만 성능이 오르면 단건 응답이 중요한 서비스에는 그대로 넣지 않는 편이 안전합니다.
    • -api async에서만 효율이 오르는 경우는 요청을 동시에 밀어 넣을 수 있을 때만 이득입니다. 호출 패턴이 순차형이면 체감 차이가 약할 수 있습니다.
    • -pc와 -report_type average_counters는 레이어별 힌트를 주지만, 서비스 전체 지연 시간을 그대로 대변하지는 않습니다.
    • -exec_graph_path를 남기면 실행 그래프를 따로 볼 수 있어서 레이어 융합 여부를 확인하기 좋습니다.

    CPU 쪽은 너무 빨리 저수준 옵션으로 내려가지 않는 게 중요합니다. 저는 보통 -hint latency나 -hint throughput로 시작하고, 그다음에만 -nthreads, -nireq, 필요 시 -pin 같은 옵션을 건드립니다. 시작부터 스레드 수를 직접 고정하면 휴리스틱보다 못한 조합으로 들어가는 일도 있더라고요.

    # CPU 스레드 경쟁이 의심될 때만 추가 비교
    benchmark_app -m build/model.xml -d CPU -hint latency -api async -t 15 \
      -nthreads 8
    
    # 동시 요청 수가 실제 서비스와 맞지 않는지 확인할 때
    benchmark_app -m build/model.xml -d CPU -hint throughput -api async -t 15 \
      -nireq 4

    NUMA나 멀티소켓 서버라면 스레드 고정 정책까지 볼 가치가 있습니다. 다만 이건 버전과 장치 구성에 따라 옵션 차이가 있어서, benchmark_app -h로 현재 설치 버전이 지원하는 플래그를 먼저 확인하는 게 안전합니다. 소형 단일 서버에서는 이런 저수준 옵션보다 입력 파이프라인 정리 쪽이 효과가 더 클 때가 많았습니다.

    파이썬 서비스에 붙일 때는 아래 정도로 시작하면 충분합니다.

    import openvino as ov
    import numpy as np
    
    core = ov.Core()
    core.set_property({"CACHE_DIR": "./ov_cache"})
    
    compiled_model = core.compile_model("build/model.xml", "CPU", {
        "PERFORMANCE_HINT": "LATENCY"
    })
    
    input_port = compiled_model.input(0)
    output_port = compiled_model.output(0)
    
    sample = np.random.rand(*input_port.shape).astype(np.float32)
    result = compiled_model({input_port.any_name: sample})
    output = result[output_port]
    
    print(output.shape)

    여기서 CACHE_DIR를 넣는 건 첫 요청 지연을 줄이기 위한 준비입니다. 다만 이건 무조건 빨라진다가 아니라 재시작이 잦고 컴파일 비용이 의미 있는 모델에서 특히 효과를 볼 수 있는 옵션으로 보는 편이 정확합니다. 장치와 모델에 따라 체감 차이가 달라서, 넣고 다시 재측정해야 합니다.

    주의사항과 트러블슈팅: 막히는 지점은 늘 비슷합니다

    제가 반복해서 본 실패 모드는 대체로 네 가지였습니다. 증상만 보는 것보다 근본 원인을 먼저 적어두면 대응이 훨씬 빨라집니다.

    1. 입력 shape 문제: 원인은 모델이 아니라 입력 계약 누락인 경우가 많습니다

    에러 메시지는 모델이 까다로운 것처럼 보이는데, 실제로는 전처리 코드가 NCHW, NHWC, dtype, 배치 차원을 제각각 가정하는 경우가 대부분이었습니다. 특히 ONNX 단계에서 동적 차원을 허용해 둔 모델은 “받아줄 줄 알았는데 실제 런타임 입력은 다르다”는 일이 자주 납니다. 해결은 단순합니다. 변환 시점에 입력 shape를 명시하고, 서비스 입력 스키마를 코드와 문서 양쪽에 고정하세요.

    2. 변환은 됐는데 추론 결과가 흔들리는 경우: 원인은 연산자 호환성보다 export 품질인 때도 많습니다

    이건 OpenVINO 쪽 문제로만 보면 안 됩니다. ONNX export 단계에서 이미 불안정한 그래프가 만들어졌거나, 커스텀 연산이 우회 변환되면서 의미가 달라지는 경우가 있습니다. 이런 때는 OpenVINO IR만 보지 말고 원본 프레임워크 출력, ONNX Runtime 출력, OpenVINO 출력을 같은 샘플로 나란히 비교해야 합니다. 분류 모델이면 top-k, 탐지 모델이면 박스 좌표와 score 분포를 같이 보는 편이 좋습니다.

    3. benchmark_app는 좋아 보이는데 API는 그대로인 경우: 원인은 모델 밖에 있습니다

    이건 정말 흔합니다. 예를 들어 FastAPI 뒤에 이미지 추론 API가 있고, 요청마다 파일 업로드를 받아 Pillow로 디코딩하고 NumPy 배열로 바꾸고, 후처리로 박스를 정리해 JSON으로 내보낸다고 해보겠습니다. 이 구조에서는 모델 추론이 빨라져도 디코딩, 리사이즈, 직렬화, 네트워크 대기가 더 크면 전체 응답 시간은 거의 안 줄 수 있습니다. OpenVINO 도입이 실패한 게 아니라 최적화 대상 선정이 틀린 것에 가깝습니다.

    4. 첫 요청만 유독 느린 경우: 원인은 cold start와 컴파일 비용입니다

    서비스 재배포 직후 첫 요청이 길게 나오는 건 흔히 모델 읽기, 컴파일, 캐시 미적용 때문입니다. 이때는 앱 시작 시 웜업 추론을 한 번 수행하고, CACHE_DIR를 켠 뒤 재시작 후 첫 요청을 다시 재보셔야 합니다. 첫 요청과 반복 요청을 같은 수치로 섞으면 운영 판단이 틀어집니다.

    제가 운영 검토 때 쓰는 간단한 구분법도 있습니다.

    증상 먼저 의심할 곳 근본 원인 후보 우선 조치
    모델은 로드되는데 입력 단계에서 바로 실패 전처리 shape, layout, dtype 불일치 입력 스키마를 고정하고 샘플 입력을 저장해 재현합니다.
    변환 경고 없이 결과만 이상함 export 품질 ONNX 그래프 의미 손실, 커스텀 연산 우회 프레임워크/ONNX/OpenVINO 3단 비교를 합니다.
    단독 벤치마크는 빠른데 API가 안 빨라짐 서비스 외곽 디코딩, 후처리, 직렬화, I/O 병목 모델과 서비스 벤치마크를 분리합니다.
    첫 요청만 느림 초기화 단계 컴파일, 캐시 미사용, 웜업 부재 캐시와 웜업을 넣고 cold start를 따로 측정합니다.
    인텔 CPU 추론 성능 검증과 OpenVINO 도입 병목 분석 이미지

    지연 시간과 처리량, CPU 사용 패턴을 함께 점검하는 검증 장면을 표현한 이미지입니다.

    OpenVINO 도입 검증에서 무엇을 보고 결정할지

    AI 모델 최적화에서 숫자 하나만 보면 거의 항상 놓치는 게 생깁니다. 저는 최소한 아래 네 축을 같이 봅니다.

    1. 기능 동등성: 같은 입력에서 결과 의미가 유지되는지 확인합니다. 분류면 top-k, 탐지면 박스 수와 score 경향, 세그멘테이션이면 마스크 경계를 봅니다.
    2. cold start와 steady state 분리: 첫 요청과 반복 요청을 같은 그래프에 섞지 않습니다.
    3. 단건 지연과 전체 처리량 분리: API냐 배치냐에 따라 좋은 설정이 다릅니다.
    4. 모델 안과 밖 분리: 모델 단독 벤치마크와 서비스 엔드투엔드 벤치마크를 따로 남깁니다.

    이 기준으로 보면 의사결정이 꽤 선명해집니다.

    • 정확도 변화가 없고 지연 시간 편차가 줄었다: 운영 전환 후보로 충분합니다.
    • 처리량은 좋아졌는데 단건 응답이 나빠졌다: 배치 경로엔 적합하지만 실시간 API엔 별도 설정이 필요합니다.
    • 변환 경고가 남고 일부 샘플 출력이 흔들린다: 런타임 튜닝 전에 export와 호환성부터 다시 잡아야 합니다.
    • 모델 숫자는 좋아졌는데 서비스 체감이 없다: 전처리와 후처리, I/O 비용이 본체일 가능성이 큽니다.

    실무에서는 여기서 결론을 분기하면 됩니다. 단건 응답형 서비스면 LATENCY 힌트부터, 다중 요청이나 배치형 작업이면 THROUGHPUT 힌트부터 시작하세요. 두 결과가 서로 다르게 나오면 “어느 쪽이 더 빠른가”보다 “현재 서비스 목표가 어느 쪽인가”로 결정하는 편이 맞습니다.

    운영 전환 체크리스트: 저는 이 항목이 안 채워지면 배포 안 합니다

    • 산출물 이원화: ONNX와 OpenVINO IR을 같이 보관하고, 어떤 버전에서 변환했는지 기록합니다.
    • 입력 계약 문서화: shape, dtype, layout, 채널 순서를 문서와 테스트 샘플로 고정합니다.
    • 검증 샘플 고정: 대표 입력 세트를 정해 변환 전후 비교를 자동화합니다.
    • 성능 측정 분리: 모델 단독 수치와 API 전체 수치를 별도로 저장합니다.
    • 롤백 스위치 유지: OpenVINO 경로에 장애가 나면 ONNX Runtime으로 즉시 되돌릴 수 있어야 합니다.
    • cold start 대비: 웜업 전략과 캐시 디렉터리 사용 여부를 배포 스크립트에 포함합니다.

    여기서 많이 놓치는 게 롤백입니다. 배포 표준을 바꾸는 게 아니라 실행 경로를 하나 더 두는 것이라고 생각하면 결정이 조금 쉬워집니다. 운영 안정성은 새로운 엔진을 넣는 것보다, 문제 났을 때 되돌아갈 길이 있는지가 더 크게 좌우합니다.

    OpenVINO 도입 전후 선택 기준을 정리한 요약 이미지

    어떤 환경에서 OpenVINO 도입이 유리한지 한눈에 정리한 요약 이미지입니다.

    자주 묻는 질문

    ONNX가 이미 있는데 굳이 OpenVINO까지 가야 하나요?

    인텔 CPU 추론이 핵심 경로라면 검토 가치는 충분합니다. 다만 기준은 단순하지 않습니다. ONNX Runtime이 이미 안정적이고 실제 병목이 전처리나 API 레이어라면 굳이 바꿀 이유는 약합니다. 반대로 CPU 지연 편차나 cold start, 요청 처리 패턴 최적화가 고민이라면 OpenVINO를 별도 경로로 두는 편이 낫습니다.

    엣지 AI 환경에서 특히 의미가 큰가요?

    네, 특히 GPU 없는 소형 노드에서 그렇습니다. 다만 “엣지라서 무조건”은 아닙니다. 장점은 하드웨어 특화 최적화와 배포 단순성이고, 단점은 변환 산출물 관리와 호환성 검증이 추가된다는 점입니다. 그래서 저는 엣지 환경일수록 산출물 버전 관리와 입력 스키마 고정을 더 엄격하게 잡습니다.

    도입 순서는 어떻게 잡는 게 안전한가요?

    가장 안전한 순서는 이렇습니다. 학습 코드는 그대로 유지하고, ONNX를 공통 산출물로 두고, OpenVINO IR을 배포 전용 산출물로 추가하세요. 그다음 모델 단독 벤치마크, 기능 비교, 서비스 A/B 테스트 순으로 가면 됩니다. 운영 경로를 한 번에 갈아엎는 방식은 추천하지 않습니다.

    여기서 이렇게 결정하시면 됩니다

    제 판단 기준은 꽤 단순합니다. 인텔 CPU 추론이 핵심이고, ONNX 산출물이 이미 안정적이며, 지금 문제의 본체가 모델 실행 경로라면 OpenVINO 도입 쪽으로 가는 편이 맞습니다. 이 경우에는 학습 스택을 건드리지 말고, 배포 레이어만 OpenVINO로 분기하세요. 특히 실시간 API라면 LATENCY 힌트 중심으로, 배치 작업이라면 THROUGHPUT 힌트 중심으로 검증하면 방향이 빨리 잡힙니다.

    반대로 GPU 최적화 체계가 이미 굳어 있거나, 모델보다 전처리·후처리·네트워크가 더 느리다면 지금 당장 OpenVINO로 옮길 이유는 약합니다. 이런 상황에서는 엔진을 바꾸기보다 병목 측정부터 다시 하는 게 맞습니다. 그리고 커스텀 연산자가 많은 모델이라면 성능 실험보다 먼저 호환성 검증을 별도 작업으로 떼어 두세요. 실무에서는 이 순서를 지키는 팀이 결국 덜 헤맵니다.

  • [Cloud] Cloudflare Workers AI 활용 사례: 엣지 AI 서비스 구축기

    [Cloud] Cloudflare Workers AI 활용 사례: 엣지 AI 서비스 구축기

    [클라우드] Cloudflare Workers AI 활용 사례: 엣지 AI 서비스 구축기

    Cloudflare Workers AI 활용 이야기를 해보려고 합니다. 요즘 AI 기능 하나 붙이려 해도 어디에 올릴지부터 고민되시죠. 중앙 리전(region, 클라우드 지역)에 모델 서버를 두면 구조는 익숙한데, 지연 시간(latency, 응답 지연)이나 운영 복잡도가 은근히 발목을 잡거든요. 저도 홈랩이랑 사내 테스트 환경에서 이것저것 붙여보다가, “이걸 꼭 무거운 GPU 서버로만 풀어야 하나?” 싶은 순간이 많았습니다. 처음엔 반신반의했는데, Cloudflare Workers AI를 같이 써보니까 엣지 AI(edge AI, 사용자와 가까운 위치에서 추론하는 방식) 서비스의 방향이 꽤 선명해지더라고요.

    특히 Cloudflare Workers AI 활용 포인트는 단순합니다. 기존 Workers(워커스, 서버리스 런타임) 위에서 요청을 받고, AI 추론을 붙이고, 필요하면 KV(Key-Value 저장소)나 R2(Object Storage, 오브젝트 스토리지) 같은 주변 서비스를 연결해서 바로 서비스 형태로 만들 수 있다는 점입니다. 서버를 직접 띄우고 헬스체크하고 오토스케일링 붙이는 부담이 줄어드니까, 작은 팀이나 1인 개발자에게 꽤 현실적인 선택지였어요.

    이번 글에서는 제가 직접 구성해본 흐름을 기준으로, 엣지 AI와 서버리스 AI를 어떻게 조합했는지, 그리고 실제로 어디서 삽질했는지까지 정리해보겠습니다. 너무 화려한 데모보다, “이 정도면 실서비스 PoC(Proof of Concept, 개념 검증)로는 충분하겠다” 싶은 수준에 맞춰 설명드릴게요.

    사용자 요청이 Cloudflare 엣지로 들어와 Workers와 AI 추론, 저장소 연동으로 이어지는 전체 구조 예시입니다.

    Cloudflare Workers AI란 무엇인가

    쉽게 말해, Cloudflare 네트워크 위에서 AI 추론을 호출하는 방식입니다. 우리가 직접 AI 모델 배포 환경을 일일이 운영하기보다, Worker 코드 안에서 AI 작업을 요청하고 결과를 응답으로 돌려주는 식이죠. 여기서 중요한 건 “엣지에서 실행되는 애플리케이션 로직”과 “AI 추론 호출”이 한 흐름으로 이어진다는 점입니다.

    처음 접하면 “그럼 모든 모델이 로컬처럼 바로 도는 건가?” 하고 헷갈릴 수 있어요. 저도 처음엔 그랬거든요. 실제로 써보니까 핵심은 이렇습니다.

    • Workers: HTTP 요청을 받고 라우팅, 인증, 전처리, 후처리를 담당합니다.
    • Workers AI: 텍스트 생성, 분류, 임베딩(embedding, 의미 벡터화) 같은 AI 작업을 호출합니다.
    • 엣지 AI: 사용자와 가까운 네트워크 지점에서 빠르게 응답 체감을 만드는 접근입니다.
    • 서버리스 AI: GPU 서버 운영보다 코드 중심으로 기능을 붙이는 방식입니다.

    이 조합이 왜 좋았냐면, 서비스 초기에 필요한 건 대개 “정답률 0.1% 더 높은 모델”보다도 빨리 붙고, 운영이 단순하고, 비용 구조를 예측하기 쉬운 아키텍처인 경우가 많기 때문입니다. 특히 문의 분류, 간단한 요약, 입력 정제, 임베딩 기반 검색 전처리 같은 작업은 중앙 집중형 대형 백엔드보다 엣지 쪽이 훨씬 손에 잘 붙는 경우가 있더라고요.

    어떤 서비스에 잘 맞는가: 엣지 AI와 서버리스 AI 활용 기준

    모든 AI 워크로드가 여기에 맞는 건 아닙니다. 이게 중요한 포인트예요. 작게 쪼갤 수 있는 추론 작업에 특히 잘 맞습니다. 제가 테스트하면서 잘 맞는다고 느낀 케이스는 아래와 같았습니다.

    1. 사용자 입력 유해성 검사나 형식 검증 같은 프런트 관문 처리
    2. 짧은 텍스트 요약이나 카테고리 분류
    3. 검색 전 임베딩 생성과 간단한 추천 로직
    4. 챗봇 앞단의 프롬프트 가공 및 응답 후처리
    5. 파일 업로드 후 메타데이터 추출 같은 이벤트성 작업

    반대로 긴 세션을 유지해야 하거나, 아주 무거운 후처리 파이프라인이 붙는 작업은 별도 백엔드와 역할을 나누는 편이 낫습니다. 저도 처음엔 모든 걸 Worker 하나에 넣어보려다가 금방 정신 차렸습니다. 서비스 경계가 흐려지면 디버깅이 정말 힘들어지거든요.

    구분 Cloudflare Workers AI 활용 전통적 모델 서버 운영
    배포 방식 코드 중심의 빠른 배포 서버/컨테이너 운영 필요
    운영 부담 상대적으로 낮음 스케일링, 패치, 모니터링 직접 관리
    적합한 작업 짧은 추론, 엣지 응답 최적화 장시간 처리, 복잡한 파이프라인
    개발 속도 PoC에 유리 초기 구축 시간 길어짐

    실전 구현: 가장 단순한 Workers AI API 만들기

    이제 실제로 붙여보겠습니다. 예시는 “사용자 문의를 요약하고 카테고리 라벨을 붙여주는 API”로 잡아볼게요. 이 패턴은 생각보다 응용 범위가 넓더라고요.

    1. 프로젝트 생성

    Cloudflare에서는 보통 Wrangler(랭글러, CLI 도구)를 사용해 Worker 프로젝트를 만듭니다.

    npm create cloudflare@latest workers-ai-demo
    cd workers-ai-demo
    npm install
    

    생성 과정에서 Worker 템플릿을 선택하고, JavaScript 또는 TypeScript 기반으로 시작하면 됩니다. 저는 이런 종류는 TypeScript가 조금 더 편하더라고요. 입력과 출력 스키마를 잡기가 수월해서요.

    2. Worker 코드 작성

    핵심은 요청 본문을 받고, AI 바인딩(binding, 서비스 연결 객체)을 통해 추론을 호출한 뒤, 결과를 JSON으로 반환하는 구조입니다.

    export default {
      async fetch(request, env) {
        if (request.method !== "POST") {
          return new Response("Method Not Allowed", { status: 405 });
        }
    
        const body = await request.json();
        const text = body.text;
    
        if (!text || typeof text !== "string") {
          return Response.json({ error: "text is required" }, { status: 400 });
        }
    
        const prompt = [
          "You are a support triage assistant.",
          "Summarize the user message in Korean in one sentence.",
          "Then assign one category among billing, technical, account, other.",
          "Return JSON with summary and category.",
          "User message:",
          text
        ].join("\n");
    
        const result = await env.AI.run("@cf/meta/llama-3-8b-instruct", {
          prompt
        });
    
        return Response.json({
          input: text,
          result
        });
      }
    };
    

    여기서 모델 식별자는 실제 Cloudflare에서 제공하는 카탈로그 기준으로 맞춰야 합니다. 글을 읽는 시점에 사용 가능한 모델 목록은 바뀔 수 있으니, 이 부분은 공식 문서를 한 번 확인하셔야 합니다. 저는 예전에도 이런 부분 대충 외워서 넣었다가 바로 에러 봤습니다. 이런 건 꼭 현재 콘솔 기준으로 맞추셔야 해요.

    Cloudflare Workers AI 활용 설정과 AI 바인딩 연결 흐름 이미지

    HTTP 요청이 Worker 코드로 들어오고, AI 바인딩을 통해 추론이 호출되는 내부 흐름을 정리한 이미지입니다.

    3. 설정 파일 확인

    설정 파일에서는 Worker 이름, 진입점, 호환성 날짜 같은 기본값과 함께 AI 바인딩을 선언합니다.

    name = "workers-ai-demo"
    main = "src/index.js"
    compatibility_date = "2024-05-01"
    
    [ai]
    binding = "AI"
    

    여기서 binding 이름이 코드의 env.AI와 일치해야 합니다. 별거 아닌데, 이거 안 맞으면 “왜 env에 아무것도 없지?” 하면서 시간 정말 많이 잡아먹습니다. 저도 한 번 오타로 한참 돌았네요.

    4. 로컬 개발과 배포

    npx wrangler dev
    npx wrangler deploy
    

    배포 후에는 발급된 Worker URL로 POST 요청을 보내 테스트하면 됩니다.

    curl -X POST https://your-worker.your-subdomain.workers.dev \
      -H "Content-Type: application/json" \
      -d '{"text":"로그인이 자꾸 풀리고 결제 영수증도 확인이 안 됩니다."}'
    

    이 단계에서 드디어 첫 응답이 오면 정말 기분이 좋습니다. 드디어 됐다! 싶은 순간이 있어요. 사실 구조 자체는 단순한데, AI 기능이 바로 붙는다는 체감이 꽤 큽니다.

    실서비스 형태로 확장하기: 캐시, 저장소, 검증 로직

    단순 데모에서 한 단계만 올라가도 필요한 게 생깁니다. 예를 들어 같은 입력이 반복되면 결과를 캐시(cache, 재사용 저장)하고 싶고, 요청 로그는 저장하고 싶고, 프롬프트 인젝션(prompt injection, 입력을 통한 지시 왜곡)도 어느 정도 방어하고 싶어지죠.

    제가 실제로 써보니까 아래 3가지는 거의 필수에 가깝더라고요.

    • 입력 검증: 너무 긴 본문, 빈 문자열, 예상 외 필드 차단
    • 응답 캐시: 동일 입력 반복 호출 방지
    • 로그 저장: 어떤 입력에서 어떤 결과가 나왔는지 추적

    예를 들어 요청 해시(hash, 고정 길이 요약값)를 키로 써서 KV에 저장해두면 재호출을 줄일 수 있습니다.

    async function sha256(text) {
      const data = new TextEncoder().encode(text);
      const digest = await crypto.subtle.digest("SHA-256", data);
      return Array.from(new Uint8Array(digest))
        .map((b) => b.toString(16).padStart(2, "0"))
        .join("");
    }
    
    export default {
      async fetch(request, env) {
        const body = await request.json();
        const text = body.text?.trim();
    
        if (!text) {
          return Response.json({ error: "empty text" }, { status: 400 });
        }
    
        const cacheKey = await sha256(text);
        const cached = await env.RESULTS_KV.get(cacheKey, "json");
        if (cached) {
          return Response.json({ source: "cache", data: cached });
        }
    
        const result = await env.AI.run("@cf/meta/llama-3-8b-instruct", {
          prompt: "Summarize in Korean: " + text
        });
    
        await env.RESULTS_KV.put(cacheKey, JSON.stringify(result), {
          expirationTtl: 3600
        });
    
        return Response.json({ source: "ai", data: result });
      }
    };
    

    이렇게만 해도 체감이 확 달라집니다. 특히 짧은 문의 요약이나 반복 질의가 많은 내부 도구에서는요. Cloudflare Workers AI 활용 포인트가 여기서 또 살아납니다. AI 자체보다도, 그 앞뒤의 경량 로직을 엣지에서 같이 처리하니까 전체 구성이 매끈해져요.

    ⚠️ 트러블슈팅: 제가 실제로 막혔던 지점들

    이 섹션은 좀 현실적으로 가보겠습니다. 멋진 성공담보다 이런 게 더 도움 되더라고요.

    1. 바인딩 이름 불일치

    아까 잠깐 언급했지만, 설정의 AI 바인딩과 코드의 환경 변수 이름이 다르면 바로 실패합니다. 에러 메시지가 친절한 편일 때도 있지만, 처음 보면 감이 안 올 수 있어요.

    • 증상: env.AI가 비어 있거나 호출 실패
    • 원인: 설정 파일의 binding 이름과 코드 불일치
    • 해결: 설정 파일과 코드 이름을 동일하게 맞추기

    2. 응답 포맷이 들쭉날쭉함

    생성형 모델은 항상 예쁘게 JSON을 주지 않습니다. “JSON으로 줘”라고 했는데 설명까지 붙여주는 경우도 있거든요. 저도 처음엔 파싱 로직을 믿고 갔다가 깨졌습니다.

    • 증상: JSON 파싱 실패
    • 원인: 모델 응답이 순수 JSON이 아님
    • 해결: 출력 형식을 더 엄격하게 지시하거나, 후처리 파서를 둠

    여기서는 가능하면 모델에게 자유 서술을 덜 주고, 출력 스키마를 명확히 제한하는 쪽이 낫습니다.

    3. 무거운 워크로드를 한 번에 몰아넣음

    처음엔 이게 만능처럼 보여서, 요약도 하고 분류도 하고 벡터도 만들고 로그도 남기고 알림도 보내고 한 번에 다 넣고 싶어집니다. 근데 여기서 구조가 금방 꼬입니다.

    • 증상: 디버깅 난이도 상승, 응답 지연 증가
    • 원인: 하나의 Worker에 역할 과다 집중
    • 해결: 전처리 API, 추론 API, 비동기 저장 작업을 분리

    이건 인프라 쪽에서 흔히 하는 실수랑 비슷합니다. 처음엔 단순해 보여도 책임 분리를 안 해두면 나중에 훨씬 더 힘들어요.

    4. 모델 선택보다 프롬프트 설계가 더 중요했던 경우

    모델만 바꾸면 해결될 줄 알았는데, 실제로는 프롬프트(prompt, 모델에게 주는 지시문) 설계가 더 큰 영향을 준 경우가 많았습니다. 특히 분류 작업은 라벨 정의를 명확히 안 해두면 결과가 흔들리더라고요. 혹시 이런 경험 있으신가요? 모델 탓만 하다가 결국 입력 문장 설계부터 다시 보는 경우요.

    Cloudflare Workers AI 활용 중 발생한 트러블슈팅 대시보드 이미지

    바인딩 오류, 응답 포맷 문제, 역할 과다 집중 같은 흔한 장애 포인트를 시각적으로 정리한 이미지입니다.

    검증과 결과: 무엇이 좋아졌나

    완성 후에는 꼭 확인해야 합니다. 그냥 “응답 온다”에서 끝내면 안 되거든요. 저는 아래 기준으로 검증했습니다.

    1. 기능 검증: 입력별로 요약과 분류가 의도대로 나오는지
    2. 지연 시간 확인: 중앙 백엔드 경유 대비 체감 응답이 나아졌는지
    3. 재현성 확인: 동일 입력에서 품질이 과도하게 흔들리지 않는지
    4. 운영성 확인: 로그 추적과 에러 원인 파악이 쉬운지

    여기서 중요한 건 과한 기대를 버리는 겁니다. 이 구조의 장점은 최고 성능 경쟁보다 빠른 서비스화와 운영 단순화에 있습니다. 실제로 써보니까, 고객 문의 분류기나 내부 자동화 도구처럼 “완벽한 창의성”보다 “일관된 처리”가 중요한 곳에 특히 잘 맞더라고요.

    curl -X POST https://your-worker.your-subdomain.workers.dev \
      -H "Content-Type: application/json" \
      -d '{"text":"회원 탈퇴 후 재가입이 안 되고 인증 메일도 오지 않습니다."}'
    
    {
      "source": "ai",
      "data": {
        "summary": "회원 탈퇴 이후 재가입과 인증 메일 수신에 문제가 있는 상태입니다.",
        "category": "account"
      }
    }
    

    이 정도 흐름만 안정적으로 나오면 PoC 단계에서는 꽤 괜찮습니다. 특히 AI 모델 배포를 직접 무겁게 하지 않고도 사용자-facing API를 만들 수 있다는 점이 정말 좋았습니다. 물론 대규모 서비스로 갈수록 관측성(observability, 관찰 가능성), 비용 추적, 프롬프트 버전 관리가 더 중요해지겠지만요.

    Cloudflare Workers AI 활용 결과와 엣지 AI 검증 화면

    API 호출 결과, 캐시 적중 여부, 응답 흐름 확인 같은 검증 포인트를 한눈에 볼 수 있는 예시 이미지입니다.

    Cloudflare Workers AI 활용 시 운영 팁

    마지막으로, 제가 정리해둔 운영 팁 몇 가지를 공유드릴게요. 이런 건 문서 한 줄보다 실제 운영에서 더 크게 다가오더라고요.

    • 프롬프트 버전 관리: 코드와 같이 관리해야 롤백이 쉽습니다.
    • 입력 길이 제한: 무제한 입력을 열어두면 품질과 비용이 같이 흔들립니다.
    • 캐시 전략 분리: 요약, 분류, 임베딩은 캐시 정책이 달라야 합니다.
    • 장애 대비: AI 호출 실패 시 기본 응답 또는 재시도 정책을 준비합니다.
    • 비동기 분리: 저장, 알림, 통계 적재는 가능하면 본 응답 경로에서 떼어냅니다.

    그리고 하나 더. Cloudflare Workers AI 활용은 만능 해법이 아니라, 서비스의 앞단을 영리하게 가볍게 만드는 선택지라고 보는 게 맞습니다. 이 관점으로 접근하면 훨씬 덜 실망하고, 훨씬 더 잘 쓰게 됩니다.

    정리: 엣지 AI 서비스는 작게 시작하는 게 맞습니다

    오늘 정리한 내용을 한 문장으로 줄이면 이겁니다. 엣지 AI와 서버리스 AI는 작은 기능을 빠르게 서비스화할 때 강력하다. 저도 처음엔 이게 뭔가 싶었는데, 직접 붙여보니까 “아, 이건 대형 모델 인프라를 대체한다기보다 앞단 자동화를 엄청 빠르게 만든다”는 쪽에 가깝더라고요.

    만약 지금 AI 기능을 제품에 붙이려는데 인프라 부담이 걱정되신다면, 문의 분류기나 요약 API처럼 작고 분명한 문제부터 시작해보시는 걸 권합니다. 거기서 검증이 되면 검색, 추천, 간단한 어시스턴트 기능으로 확장하면 되고요. 다음 글에서는 Workers와 KV, R2를 같이 써서 RAG(Retrieval-Augmented Generation, 검색 증강 생성) 비슷한 흐름을 어떻게 가볍게 실험할 수 있는지 다뤄볼 예정입니다. 이전 글에서 다뤘던 엣지 캐시 전략과도 연결해서 보시면 더 이해가 잘 되실 거예요.

    전통적 모델 서버 방식과 엣지 AI 방식의 차이, 적용하기 좋은 워크로드를 요약한 인포그래픽입니다.

    자주 묻는 질문

    Q1. Cloudflare Workers AI 활용은 어떤 팀에 가장 잘 맞나요?

    A. 작은 팀, 빠른 PoC가 필요한 팀, 그리고 AI 모델 운영보다 서비스 통합이 더 급한 팀에 잘 맞습니다.

    Q2. 서버리스 AI만으로 모든 서비스를 구성할 수 있나요?

    A. 아닙니다. 장시간 작업이나 복잡한 파이프라인은 별도 백엔드와 역할을 나누는 편이 더 안정적입니다.

    Q3. AI 모델 배포를 완전히 대체하나요?

    A. 완전 대체라기보다, 특정 구간에서는 훨씬 더 가볍고 빠른 대안이 됩니다. 특히 요청 전처리, 요약, 분류 같은 작업에서 장점이 큽니다.

  • [HomeLabs] 홈랩 AI 성능 비교: Jetson Nano와 구형 GPU 벤치마크

    [HomeLabs] 홈랩 AI 성능 비교: Jetson Nano와 구형 GPU 벤치마크

    홈랩 AI 성능 비교: Jetson Nano와 구형 GPU 벤치마크

    홈랩 AI 성능이 궁금해서 장비를 하나씩 꺼내 비교해보신 적 있으신가요? 저도 홈랩에 굴러다니던 Jetson Nano와 예전에 쓰던 구형 GPU 장비를 다시 올려서 AI 추론(Inference, 학습이 아니라 이미 학습된 모델을 실행하는 과정) 성능을 비교해봤습니다. 처음엔 “둘 다 오래된 장비인데 큰 차이 있겠어?” 싶었는데, 막상 돌려보면 체감 포인트가 꽤 다르더라고요. 특히 로컬 LLM(Local LLM, 인터넷 없이 내 장비에서 직접 돌리는 언어 모델) 쪽은 숫자 하나보다 메모리, 전력, 드라이버, 발열이 훨씬 중요했습니다.

    이 글은 막연한 감상문이 아니라, 홈랩에서 재현 가능한 기준으로 Jetson Nano와 구형 GPU를 어떻게 비교하면 되는지 정리한 글입니다. 직접 삽질하면서 느낀 점도 담았고, 어떤 항목을 봐야 실제 운영에 도움이 되는지도 같이 풀어보겠습니다. 집에서 소형 AI 노드 하나 만들어보려는 분이라면 꽤 실용적으로 보실 수 있을 거예요.

    홈랩 AI 성능 비교를 위한 Jetson Nano와 구형 GPU 아키텍처 이미지

    Jetson Nano, 구형 GPU 데스크톱, NAS 또는 측정용 노트북이 함께 연결된 홈랩 AI 벤치마크 구성 예시입니다.

    왜 홈랩 AI 성능 비교에서 Jetson Nano와 구형 GPU를 같이 봐야 할까

    쉽게 말해 둘은 출발점이 다릅니다. Jetson Nano는 소형 엣지 장비(edge device)라 전력과 크기에서 강점이 있고, 구형 GPU는 데스크톱이나 워크스테이션에 꽂아 순간 성능을 끌어올리는 쪽에 가깝습니다. 그래서 같은 AI 추론이라도 쓰임새가 달라집니다. 장비 하나만 보고 결론 내리면 생각보다 자주 틀립니다.

    • Jetson Nano: 저전력, 작은 크기, GPIO 같은 확장성, 카메라 연동이 편합니다.
    • 구형 GPU: 절대 성능이 더 잘 나오는 경우가 많고, 로컬 개발 환경을 맞추기 쉽습니다.
    • 로컬 LLM: 둘 다 시도는 가능하지만, Jetson Nano는 모델 크기와 메모리 제약을 아주 강하게 탑니다.
    • 비용 관점: 이미 가지고 있는 장비를 재활용하면 가성비가 좋아집니다.

    여기서 중요한 포인트가 하나 있습니다. 벤치마크는 단순히 “누가 더 빠르냐”가 아닙니다. 홈랩에서는 보통 아래 네 가지를 같이 봐야 해요.

    항목 Jetson Nano 구형 GPU 홈랩 관점 해석
    전력 효율 강점 상대적으로 불리 24시간 운영이면 중요합니다.
    순간 추론 속도 제한적 유리한 경우 많음 대화형 응답 체감에 영향이 큽니다.
    설치 난이도 JetPack 계열 의존성 큼 드라이버와 CUDA 조합 확인 필요 막히는 지점이 서로 다릅니다.
    용도 적합성 비전, 센서 연동, 경량 워크로드 실험, 로컬 LLM, 배치 추론 목적을 먼저 정하는 게 맞습니다.

    홈랩 AI 성능 비교에서 꼭 맞춰야 하는 기준

    처음에는 장비마다 되는 모델을 그냥 올려서 돌렸거든요. 그런데 그렇게 하면 결과가 사실상 의미가 없습니다. 벤치마크(Benchmark, 성능 비교 테스트)는 조건을 맞춰야 비교가 됩니다. 이 부분을 대충 넘기면 로그는 많이 남는데 결론은 흐려지더라고요.

    1. 가능하면 같은 모델을 씁니다.
    2. 같은 양자화(Quantization, 모델을 더 작은 정밀도로 압축하는 방식) 옵션을 씁니다.
    3. 입력 길이와 스레드 수를 고정합니다.
    4. 첫 실행과 반복 실행을 구분합니다.
    5. 속도뿐 아니라 메모리와 발열도 기록합니다.

    특히 로컬 LLM은 첫 토큰 시간(Time To First Token)과 초당 토큰 수(tokens per second)를 같이 봐야 합니다. 반면 이미지 분류나 객체 탐지처럼 전형적인 엣지 AI 워크로드는 지연 시간(latency)과 초당 처리량(throughput)이 더 중요하죠. 그래서 저는 홈랩에서 보통 두 갈래로 나눠 봅니다.

    • LLM 계열: 응답 생성 속도, 메모리 사용량, 프롬프트 길이 영향
    • 비전 계열: 프레임 처리 속도, 추론 지연, 장시간 안정성

    이번 글에서는 제목에 맞춰 AI 추론과 로컬 LLM 기준으로 설명하되, Jetson Nano의 특성을 고려해서 “무리한 대형 모델” 대신 “경량 모델 또는 CPU 기준 테스트” 중심으로 접근하겠습니다. Jetson Nano는 실사용 자체는 가능하지만, 최신 데스크톱 GPU처럼 여유 있게 돌리는 그림과는 거리가 있습니다.

    실전 비교 환경 설계: 이렇게 맞추면 덜 틀립니다

    실제로 써보니까 결과를 망치는 건 코드보다 환경 차이였습니다. 그래서 저는 아래처럼 아주 보수적으로 기준을 잡습니다. 조금 답답할 정도로 조건을 고정해두면 나중에 표를 볼 때 훨씬 덜 헷갈려요.

    비교 대상 예시

    • Jetson Nano: JetPack 기반 Ubuntu 환경
    • 구형 GPU 시스템: 리눅스 데스크톱 또는 서버
    • 동일 저장소: 같은 커밋의 벤치마크 도구 사용

    측정 항목

    • 응답 속도: 초당 토큰 수 또는 평균 지연 시간
    • 메모리 사용량: OOM(Out Of Memory, 메모리 부족) 발생 여부 포함
    • 전력과 발열: 장시간 운영 가능성 확인
    • 재현성: 3회 이상 반복 후 편차 체크

    여기서 팁 하나 드리면, Jetson Nano는 “된다/안 된다”의 경계가 생각보다 명확합니다. 모델이 조금만 커져도 스와핑(swapping, 메모리를 디스크로 넘기는 현상) 때문에 체감 성능이 급격히 무너집니다. 반대로 구형 GPU는 드라이버만 잘 맞으면 의외로 꽤 버텨줍니다. 그래서 홈랩 AI 성능을 볼 때는 최고점보다 지속 가능한 설정을 찾는 게 더 중요합니다.

    홈랩 AI 성능 실험 조건을 정리한 Jetson Nano와 구형 GPU 비교 이미지

    동일 모델, 동일 프롬프트 길이, 반복 횟수, 메모리 기록 항목을 정리한 벤치마크 설계 화면 예시입니다.

    실전 구현 1: 벤치마크 도구 준비

    재현성과 단순함 때문에 저는 llama.cpp 계열 도구를 기준점으로 잡는 편입니다. 공식 저장소에서 <code>llama-cli와 llama-bench를 함께 쓸 수 있어서 비교 작업이 단순하거든요. 물론 Jetson Nano에서 최신 대형 모델을 기대하면 실망하기 쉽습니다. 여기서는 작은 GGUF 양자화 모델을 기준으로 “돌아가는지, 얼마나 버티는지”를 보는 쪽이 현실적입니다.

    1. 공통 패키지 설치

    sudo apt update
    sudo apt install -y git build-essential cmake python3 python3-pip htop

    기본 빌드 도구와 모니터링 도구만 먼저 맞춰둡니다. 여기서 패키지 버전을 과하게 올리기보다, 두 장비에서 공통으로 무리 없이 깔리는 구성이 더 낫더라고요.

    2. 저장소 받기

    git clone https://github.com/ggml-org/llama.cpp.git
    cd llama.cpp

    예전에는 다른 저장소 경로를 기억하시는 분도 있는데, 현재는 ggml-org/llama.cpp 기준으로 보는 편이 안전합니다.

    3. Jetson Nano에서 CPU 기준 빌드

    cmake -B build
    cmake --build build -j

    Jetson Nano는 GPU 오프로딩보다 우선 CPU 기준선부터 확보하는 게 좋습니다. 빌드가 단순해야 비교도 단순해지거든요.

    4. 구형 NVIDIA GPU 시스템에서 CUDA 사용 가능 시 빌드

    cmake -B build -DGGML_CUDA=ON
    cmake --build build -j

    여기서 주의하실 점이 있습니다. 구형 GPU는 CUDA 지원 범위가 카드 세대와 드라이버 조합에 따라 갈립니다. 그래서 “CUDA 빌드가 되느냐” 자체가 1차 체크포인트예요. 안 되면 억지로 붙잡지 말고 CPU 기준선부터 확보하세요. 저도 처음엔 드라이버만 붙잡고 시간 꽤 날렸습니다.

    5. 시스템 상태 확인

    uname -a
    free -h
    htop

    Jetson Nano에서는 가능하면 별도 터미널에서 리소스를 같이 보는 게 좋습니다. tegrastats는 Jetson Linux에서 기본 제공되는 모니터링 도구라 실사용할 때 꽤 편합니다.

    tegrastats

    구형 NVIDIA GPU 시스템에서는 아래 명령으로 GPU 상태를 같이 확인합니다.

    nvidia-smi

    실전 구현 2: 벤치마크 실행과 기록 자동화

    실제 비교는 손으로 돌리면 금방 꼬입니다. 입력 길이 하나 바뀌고, 스레드 하나 바뀌면 결과가 달라지거든요. 그래서 아주 단순한 기록 스크립트라도 두는 편이 낫습니다. 복잡한 자동화보다 조건 고정이 먼저예요.

    mkdir -p ~/bench-results
    MODEL_PATH=/path/to/model.gguf
    THREADS=4
    PROMPT="홈랩에서 로컬 LLM을 운영할 때 가장 중요한 자원은 무엇인가요?"
    
    ./build/bin/llama-bench -m "$MODEL_PATH" | tee ~/bench-results/bench_cpu.txt
    ./build/bin/llama-cli -m "$MODEL_PATH" -t $THREADS -p "$PROMPT" -n 64 | tee ~/bench-results/run_cpu.txt

    위 예시는 CPU 기준선 확인용입니다. 먼저 이 값을 잡아두면 나중에 GPU 오프로딩이나 다른 장비를 붙였을 때 비교가 훨씬 쉬워집니다.

    MODEL_PATH=/path/to/model.gguf
    THREADS=4
    PROMPT="Jetson Nano와 구형 GPU의 추론 특성을 비교해 주세요."
    
    ./build/bin/llama-bench -m "$MODEL_PATH" | tee ~/bench-results/bench_gpu.txt
    ./build/bin/llama-cli -m "$MODEL_PATH" -t $THREADS -ngl 20 -p "$PROMPT" -n 64 | tee ~/bench-results/run_gpu.txt

    -ngl처럼 GPU 레이어 오프로딩 옵션은 지원 카드와 빌드 방식에 따라 체감 차이가 큽니다. 그래서 GPU 시스템에서는 값을 조금씩 올려가며 확인하는 방식이 가장 현실적이었습니다. 한 번에 정답 값을 찾으려 하면 오히려 더 오래 걸리더라고요.

    조금 더 정리된 로그를 남기고 싶다면 Python으로 타임스탬프만 붙여도 충분합니다.

    import subprocess
    import time
    from pathlib import Path
    
    out_dir = Path.home() / "bench-results"
    out_dir.mkdir(exist_ok=True)
    log_file = out_dir / "session.log"
    cmd = [
        "./build/bin/llama-cli",
        "-m", "/path/to/model.gguf",
        "-t", "4",
        "-p", "홈랩 AI 성능 비교 테스트를 시작합니다.",
        "-n", "64",
    ]
    
    start = time.time()
    result = subprocess.run(cmd, capture_output=True, text=True)
    elapsed = time.time() - start
    
    with log_file.open("a", encoding="utf-8") as f:
        f.write(f"time={elapsed:.2f}s\n")
        f.write(result.stdout)
        f.write("\n---\n")
    
    print(result.stdout)

    이 정도만 해도 홈랩에서는 충분합니다. 핵심은 연구 논문처럼 정교한 계측보다, 같은 조건으로 반복 가능한지를 확보하는 데 있습니다. 이게 쌓이면 나중에 장비 교체할 때도 판단이 훨씬 빨라져요.

    홈랩 AI 성능 측정을 위해 Jetson Nano와 구형 GPU 벤치마크를 실행하는 이미지

    벤치마크 실행 중 리소스 사용량과 추론 로그를 동시에 관찰하는 실제 운영 화면을 표현한 이미지입니다.

    실제 겪었던 문제와 해결 방법

    이 파트는 정말 중요합니다. 스펙표보다 도움이 더 많이 됩니다. 직접 해보니 아래 네 가지에서 가장 많이 막혔습니다.

    1. Jetson Nano에서 메모리 부족

    증상: 실행은 되는데 엄청 느리거나, 아예 프로세스가 죽습니다.

    원인: 모델 크기, 컨텍스트 길이(context length), 스레드 수가 장비 한계를 넘긴 경우가 많습니다.

    해결:

    • 더 작은 모델 또는 더 강한 양자화를 사용합니다.
    • 생성 길이(-n)와 컨텍스트 길이를 줄입니다.
    • 백그라운드 서비스부터 정리합니다.

    2. 구형 GPU에서 CUDA 빌드는 되는데 속도가 애매함

    증상: GPU를 쓰는데도 기대만큼 빠르지 않습니다.

    원인: PCIe 대역폭, VRAM 한계, 오프로딩 비율이 맞지 않는 경우가 있습니다.

    해결:

    • -ngl 값을 여러 단계로 바꿔봅니다.
    • GPU 메모리에 안 맞는 모델은 과감히 낮춥니다.
    • CPU와 GPU 혼합 구성이 오히려 느릴 수 있다는 점을 염두에 둡니다.

    3. 같은 모델인데 결과 편차가 큼

    증상: 첫 실행과 두 번째 실행이 꽤 다릅니다.

    원인: 캐시, 발열 스로틀링(thermal throttling, 온도 때문에 성능이 낮아지는 현상), 백그라운드 작업 영향입니다.

    해결:

    • 최소 3회 반복합니다.
    • 첫 실행은 워밍업(warm-up)으로 분리합니다.
    • 장비 온도가 안정된 뒤 다시 측정합니다.

    4. 로컬 LLM이 되긴 되는데 체감이 나쁨

    이건 성능 숫자와 사용 경험이 다를 때 생깁니다. 초당 토큰 수가 아주 나쁘지 않아도 첫 토큰이 늦으면 답답하거든요. 그래서 저는 벤치마크 결과를 볼 때 “총 처리량”보다 “대화가 가능한가”를 먼저 봅니다. 홈랩 장비는 특히 그렇습니다. 이 차이가 실제 만족도를 꽤 크게 갈라요.

    홈랩 AI 성능 검증과 결과 해석: 숫자보다 중요한 것

    이제 결과를 어떻게 읽을지 이야기해보겠습니다. 정확한 수치를 여기서 단정적으로 적지는 않겠습니다. 장비 상태, 모델 크기, 양자화, 드라이버 조합에 따라 차이가 꽤 크거든요. 대신 여러 번 비교하면서 거의 공통적으로 느낀 경향은 있습니다.

    • Jetson Nano는 경량 추론이나 엣지 연동에서 매력이 큽니다.
    • 구형 GPU는 전력과 소음은 불리해도, 로컬 실험용 추론 체감은 더 좋은 경우가 많습니다.
    • 로컬 LLM은 Jetson Nano에서 “가능 여부”를 먼저 확인해야 하고, 구형 GPU는 “쓸 만한 응답 속도”가 나오는지 확인하면 됩니다.
    • 홈랩 AI 성능은 절대 속도보다도 안정적으로 몇 시간 돌릴 수 있는지가 더 중요합니다.

    제가 추천하는 결과 기록 방식은 아래처럼 아주 단순한 표입니다.

    테스트 항목 Jetson Nano 구형 GPU 메모
    부팅 후 준비 시간 기록 기록 드라이버 초기화 포함
    모델 로드 체감 느림/보통/빠름 느림/보통/빠름 주관 평가도 같이 적기
    첫 토큰 반응 기록 기록 대화형 사용성 핵심
    반복 실행 안정성 좋음/보통/불안정 좋음/보통/불안정 발열 영향 체크
    장시간 운영 적합성 높음 중간 전력과 소음도 반영

    결론만 빨리 말씀드리면 이렇습니다. Jetson Nano는 “작고 오래 도는 노드”, 구형 GPU는 “실험이 빠른 노드”로 보는 게 맞았습니다. 둘 중 하나가 절대 우위라기보다 역할이 다르더라고요.

    홈랩 AI 성능 결과를 요약한 Jetson Nano와 구형 GPU 벤치마크 대시보드 이미지

    응답 속도, 메모리 사용량, 안정성 항목을 한눈에 비교하는 홈랩 AI 성능 결과 대시보드 이미지입니다.

    어떤 장비를 고르면 좋을까

    이 부분은 가장 많이들 궁금해하시는 대목이죠. 제 경험으로는 목적을 먼저 정하면 선택이 훨씬 쉬워집니다. 성능표보다 운영 방식이 더 중요할 때가 정말 많습니다.

    1. 센서, 카메라, 경량 추론이 목적이면 Jetson Nano가 잘 맞습니다.
    2. 로컬 LLM 체험과 빠른 반복 실험이 목적이면 구형 GPU가 유리합니다.
    3. 24시간 홈랩 운영이 중요하면 전력과 발열부터 계산하세요.
    4. 개발 생산성이 중요하면 드라이버와 빌드 편의성도 성능만큼 중요합니다.

    홈랩은 “최신 장비가 답”인 분야가 아니었습니다. 남는 장비를 얼마나 목적에 맞게 배치하느냐가 훨씬 중요하더라고요. 저도 처음엔 큰 모델만 쫓아갔는데, 실제로는 작은 모델을 빠르고 안정적으로 돌리는 구성이 더 자주 쓰였습니다.

    홈랩 AI 성능 기준으로 Jetson Nano와 구형 GPU 선택 포인트를 보여주는 이미지

    전력 효율, 추론 속도, 소음, 설치 난이도 기준으로 두 장비를 선택하는 요약 인포그래픽입니다.

    정리와 다음 단계

    이번 홈랩 AI 성능 비교에서 핵심은 단순했습니다. Jetson Nano와 구형 GPU는 같은 AI 추론 장비처럼 보여도 실제 운영 포인트가 다릅니다. Jetson Nano는 저전력 엣지 추론에, 구형 GPU는 로컬 LLM 실험과 반응성 확보에 더 어울립니다. 직접 해보니 숫자 하나보다 메모리 한계와 발열, 그리고 드라이버 삽질 시간이 결과를 더 크게 좌우했습니다.

    다음 글에서는 실제로 홈랩에서 작은 모델을 올려 API 서버 형태로 붙이는 방법, 예를 들면 FastAPI 기반 추론 엔드포인트나 reverse proxy 구성까지 이어서 다뤄볼 예정입니다. 이전 글에서 다룬 홈서버 리소스 모니터링 구성과 함께 보시면 흐름이 더 잘 잡힐 거예요.

    자주 묻는 질문

    Jetson Nano로 로컬 LLM 운영이 가능할까요?

    가능은 하지만 모델 크기와 양자화 수준에 따라 체감이 크게 달라집니다. 기대치를 낮추고 경량 구성부터 시작하는 편이 훨씬 낫습니다.

    구형 GPU가 항상 더 좋은가요?

    항상 그렇진 않습니다. 전력, 소음, 공간, 안정성까지 같이 보면 홈랩에서는 Jetson Nano가 더 좋은 선택일 때도 있습니다.

    벤치마크에서 가장 먼저 기록할 값은 뭔가요?

    첫 토큰 반응 시간, 반복 실행 안정성, 메모리 부족 여부부터 보시면 됩니다. 이 세 가지가 실제 체감과 가장 잘 연결됩니다.