Skip to content
Published on

Edge AI 완전 가이드 2025: 온디바이스 추론, 모델 최적화, TensorRT/ONNX/CoreML

Authors

들어가며: 왜 Edge AI인가?

AI의 미래는 클라우드만이 아닙니다. 점점 더 많은 AI 추론이 데이터가 생성되는 곳, 즉 엣지 디바이스에서 실행되고 있습니다. Edge AI는 클라우드로 데이터를 보내지 않고 스마트폰, IoT 기기, 자동차, 의료 기기 등에서 직접 AI 모델을 실행하는 패러다임입니다.

Edge AI가 필요한 5가지 이유:

  1. 레이턴시: 클라우드 왕복 없이 밀리초 단위 응답. 자율주행차는 100ms 지연도 치명적입니다.
  2. 프라이버시: 민감한 데이터(얼굴, 음성, 의료)가 디바이스를 떠나지 않습니다.
  3. 대역폭 절감: 카메라가 초당 수 GB의 영상을 생성하지만, 추론 결과만 전송하면 됩니다.
  4. 비용 절감: 클라우드 GPU 비용 없이 로컬에서 추론을 실행합니다.
  5. 오프라인 동작: 네트워크 연결 없이도 AI 기능이 작동합니다.

1. Edge AI vs Cloud AI 비교

비교 항목Edge AICloud AI
레이턴시1-10ms (로컬)50-200ms (네트워크 왕복)
프라이버시데이터가 디바이스에 유지클라우드로 데이터 전송 필요
대역폭최소 (결과만 전송)대량 (원본 데이터 전송)
비용초기 하드웨어 비용지속적 클라우드 비용
오프라인완전 지원불가능
모델 크기제한적 (MB~수GB)무제한 (수백GB 가능)
컴퓨팅 파워제한적 (NPU/GPU)거의 무제한
업데이트OTA 업데이트 필요즉시 배포 가능
확장성디바이스 수에 비례클라우드 리소스로 탄력적
정확도최적화로 인한 소폭 저하 가능최대 정확도

2. 추론 런타임 비교

2.1 TensorRT (NVIDIA)

NVIDIA GPU 전용 고성능 추론 엔진입니다.

import tensorrt as trt
import numpy as np

# TensorRT 엔진 빌드
logger = trt.Logger(trt.Logger.WARNING)
builder = trt.Builder(logger)
network = builder.create_network(
    1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH)
)
parser = trt.OnnxParser(network, logger)

# ONNX 모델 로드
with open("model.onnx", "rb") as f:
    parser.parse(f.read())

# 최적화 프로필 설정
config = builder.create_builder_config()
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30)  # 1GB

# INT8 양자화 활성화
config.set_flag(trt.BuilderFlag.INT8)
config.int8_calibrator = MyCalibrator(calibration_data)

# FP16 활성화
config.set_flag(trt.BuilderFlag.FP16)

# 엔진 빌드 및 직렬화
serialized_engine = builder.build_serialized_network(network, config)
with open("model.engine", "wb") as f:
    f.write(serialized_engine)

TensorRT의 주요 최적화 기법:

1. 레이어 퓨전: Conv + BatchNorm + ReLU를 단일 커널로 합침
2. 커널 오토튜닝: 하드웨어별 최적 CUDA 커널 자동 선택
3. 동적 텐서 메모리: 메모리 재사용으로 총 사용량 절감
4. 정밀도 캘리브레이션: INT8 양자화 시 정확도 손실 최소화
5. 다중 스트림 실행: 여러 추론을 병렬로 실행

2.2 ONNX Runtime

크로스 플랫폼 추론 엔진으로, 다양한 하드웨어에서 동작합니다.

import onnxruntime as ort
import numpy as np

# 세션 옵션 설정
sess_options = ort.SessionOptions()
sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
sess_options.intra_op_num_threads = 4
sess_options.inter_op_num_threads = 2

# Execution Provider 선택
# CPU: CPUExecutionProvider
# GPU: CUDAExecutionProvider, TensorrtExecutionProvider
# 모바일: CoreMLExecutionProvider, NNAPIExecutionProvider
providers = [
    ('TensorrtExecutionProvider', {
        'trt_max_workspace_size': 2147483648,
        'trt_fp16_enable': True,
        'trt_int8_enable': True,
    }),
    ('CUDAExecutionProvider', {
        'device_id': 0,
        'arena_extend_strategy': 'kNextPowerOfTwo',
    }),
    'CPUExecutionProvider'
]

session = ort.InferenceSession("model.onnx", sess_options, providers=providers)

# 추론 실행
input_name = session.get_inputs()[0].name
output = session.run(None, {input_name: input_data})

2.3 TensorFlow Lite (TFLite)

모바일 및 임베디드 디바이스를 위한 경량 추론 엔진입니다.

import tensorflow as tf

# TFLite 모델 변환
converter = tf.lite.TFLiteConverter.from_saved_model("saved_model_dir")

# 양자화 설정
converter.optimizations = [tf.lite.Optimize.DEFAULT]

# 완전 정수 양자화 (INT8)
def representative_dataset():
    for data in calibration_data:
        yield [data.astype(np.float32)]

converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8
converter.inference_output_type = tf.int8

tflite_model = converter.convert()

# 모델 저장
with open("model_int8.tflite", "wb") as f:
    f.write(tflite_model)
# TFLite 추론 실행
interpreter = tf.lite.Interpreter(model_path="model_int8.tflite")
interpreter.allocate_tensors()

input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

interpreter.set_tensor(input_details[0]['index'], input_data)
interpreter.invoke()
output = interpreter.get_tensor(output_details[0]['index'])

2.4 CoreML (Apple)

Apple 디바이스 전용 추론 프레임워크입니다.

import coremltools as ct

# PyTorch -> CoreML 변환
model = torch.load("model.pt")
model.eval()

traced_model = torch.jit.trace(model, torch.randn(1, 3, 224, 224))
coreml_model = ct.convert(
    traced_model,
    inputs=[ct.ImageType(name="image", shape=(1, 3, 224, 224))],
    compute_precision=ct.precision.FLOAT16,
    compute_units=ct.ComputeUnit.ALL,  # CPU + GPU + Neural Engine
)

# 메타데이터 추가
coreml_model.author = "ML Team"
coreml_model.short_description = "Image classifier"

coreml_model.save("MyModel.mlpackage")
// Swift에서 CoreML 사용
import CoreML
import Vision

let model = try! MyModel(configuration: MLModelConfiguration())
let request = VNCoreMLRequest(model: try! VNCoreMLModel(for: model.model))

let handler = VNImageRequestHandler(cgImage: image, options: [:])
try! handler.perform([request])

if let results = request.results as? [VNClassificationObservation] {
    print("Top prediction: \(results.first!.identifier)")
}

2.5 OpenVINO (Intel)

Intel 하드웨어를 위한 추론 엔진입니다.

import openvino as ov

core = ov.Core()

# 모델 로드 및 컴파일
model = core.read_model("model.xml")
compiled_model = core.compile_model(model, "CPU", config={
    "PERFORMANCE_HINT": "LATENCY",
    "INFERENCE_NUM_THREADS": "4",
})

# INT8 양자화 (NNCF)
import nncf

calibration_dataset = nncf.Dataset(calibration_loader)
quantized_model = nncf.quantize(
    model,
    calibration_dataset,
    preset=nncf.QuantizationPreset.MIXED,
    subset_size=300,
)

# 양자화 모델 저장
ov.save_model(quantized_model, "model_int8.xml")

2.6 런타임 비교 요약

추론 런타임 비교:
========================================
런타임        | 플랫폼         | 주요 하드웨어    | 언어
TensorRT     | Linux/Windows  | NVIDIA GPU      | C++, Python
ONNX Runtime | 크로스 플랫폼    | CPU/GPU/NPU     | C++, Python, C#, Java
TFLite       | 모바일/임베디드  | CPU/GPU/NPU     | C++, Java, Swift, Python
CoreML       | Apple 전용      | ANE/GPU/CPU     | Swift, Objective-C
OpenVINO     | Intel 전용      | CPU/iGPU/VPU    | C++, Python
MLC LLM      | 크로스 플랫폼    | CPU/GPU/NPU     | C++, Python, Swift

3. 모델 최적화 기법

3.1 양자화 (Quantization)

양자화는 모델 가중치의 정밀도를 낮추어 크기와 추론 속도를 개선하는 핵심 기법입니다.

Post-Training Quantization (PTQ)

학습 후 양자화로, 추가 학습 없이 모델을 양자화합니다.

# PyTorch PTQ 예시
import torch
from torch.quantization import quantize_dynamic

# 동적 양자화 (가장 간단)
model_fp32 = load_model()
model_int8 = quantize_dynamic(
    model_fp32,
    {torch.nn.Linear, torch.nn.LSTM},
    dtype=torch.qint8
)

# 정적 양자화 (더 높은 성능)
from torch.quantization import prepare, convert, get_default_qconfig

model_fp32.eval()
model_fp32.qconfig = get_default_qconfig('x86')
model_prepared = prepare(model_fp32)

# 캘리브레이션 데이터로 통계 수집
with torch.no_grad():
    for batch in calibration_loader:
        model_prepared(batch)

model_int8 = convert(model_prepared)

Quantization-Aware Training (QAT)

학습 과정에서 양자화 효과를 시뮬레이션하여 정확도 손실을 최소화합니다.

import torch
from torch.quantization import prepare_qat, convert

model.train()
model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')
model_prepared = prepare_qat(model)

# QAT 학습 (일반 학습과 동일)
optimizer = torch.optim.Adam(model_prepared.parameters(), lr=1e-4)
for epoch in range(num_epochs):
    for batch in train_loader:
        output = model_prepared(batch)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()

# 양자화 변환
model_prepared.eval()
model_int8 = convert(model_prepared)

LLM 양자화 기법

# GPTQ (GPU 기반 양자화)
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig

quantize_config = BaseQuantizeConfig(
    bits=4,
    group_size=128,
    desc_act=False,
)

model = AutoGPTQForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B",
    quantize_config
)
model.quantize(calibration_data)
model.save_quantized("llama-3.1-8b-gptq-4bit")
# AWQ (Activation-aware Weight Quantization)
from awq import AutoAWQForCausalLM

model = AutoAWQForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B")

quant_config = {
    "zero_point": True,
    "q_group_size": 128,
    "w_bit": 4,
    "version": "GEMM"
}

model.quantize(tokenizer, quant_config=quant_config)
model.save_quantized("llama-3.1-8b-awq-4bit")
# bitsandbytes (간단한 양자화)
from transformers import AutoModelForCausalLM, BitsAndBytesConfig

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

model = AutoModelForCausalLM.from_pretrained(
    "meta-llama/Llama-3.1-8B",
    quantization_config=bnb_config,
    device_map="auto",
)

양자화 정밀도별 비교:

정밀도    | 모델 크기 (7B 기준) | 메모리   | 속도 향상 | 정확도 손실
FP32     | 28GB               | 28GB    | 1x       | 기준
FP16     | 14GB               | 14GB    | 2x       | 무시 가능
INT8     | 7GB                | 7GB     | 3-4x     | 미미함 (0.5% 이하)
INT4     | 3.5GB              | 3.5GB   | 4-6x     | 소폭 (1-2%)

3.2 프루닝 (Pruning)

프루닝은 모델에서 불필요한 가중치나 뉴런을 제거하는 기법입니다.

비구조적 프루닝 (Unstructured Pruning)

개별 가중치를 0으로 만들어 희소(sparse) 모델을 생성합니다.

import torch.nn.utils.prune as prune

# 크기 기반 프루닝 (Magnitude-based)
model = load_model()

# 전체 모델에 50% 프루닝 적용
parameters_to_prune = [
    (module, 'weight') for module in model.modules()
    if isinstance(module, (torch.nn.Linear, torch.nn.Conv2d))
]

prune.global_unstructured(
    parameters_to_prune,
    pruning_method=prune.L1Unstructured,
    amount=0.5,  # 50% 프루닝
)

# 프루닝 마스크를 영구 적용
for module, name in parameters_to_prune:
    prune.remove(module, name)

# 희소성 확인
total = 0
zero = 0
for p in model.parameters():
    total += p.numel()
    zero += (p == 0).sum().item()
print(f"Sparsity: {zero/total:.2%}")

구조적 프루닝 (Structured Pruning)

전체 채널이나 헤드를 제거하여 실제 속도 향상을 달성합니다.

# Attention Head 프루닝 예시
import torch.nn.utils.prune as prune

# 특정 레이어의 출력 채널 30% 프루닝
prune.ln_structured(
    model.layer1.conv1,
    name='weight',
    amount=0.3,
    n=2,
    dim=0  # 출력 채널 차원
)

이동 프루닝 (Movement Pruning)

파인튜닝 과정에서 중요하지 않은 가중치를 식별하여 제거합니다.

프루닝 기법 비교:
========================================
기법          | 희소성 패턴 | 실제 속도 향상 | 정확도 보존
크기 기반     | 비구조적   | 제한적*       | 높음
구조적        | 구조적     | 높음          | 중간
이동 프루닝   | 비구조적   | 제한적*       | 매우 높음

* 비구조적 프루닝은 전용 하드웨어/라이브러리 필요

3.3 지식 증류 (Knowledge Distillation)

큰 교사(teacher) 모델의 지식을 작은 학생(student) 모델로 전달하는 기법입니다.

import torch
import torch.nn.functional as F

class DistillationLoss(torch.nn.Module):
    def __init__(self, temperature=4.0, alpha=0.5):
        super().__init__()
        self.temperature = temperature
        self.alpha = alpha
        self.ce_loss = torch.nn.CrossEntropyLoss()

    def forward(self, student_logits, teacher_logits, labels):
        # 소프트 타겟 손실 (KL Divergence)
        soft_loss = F.kl_div(
            F.log_softmax(student_logits / self.temperature, dim=1),
            F.softmax(teacher_logits / self.temperature, dim=1),
            reduction='batchmean'
        ) * (self.temperature ** 2)

        # 하드 타겟 손실 (Cross-Entropy)
        hard_loss = self.ce_loss(student_logits, labels)

        return self.alpha * soft_loss + (1 - self.alpha) * hard_loss

# 학습 루프
teacher_model.eval()
student_model.train()

for batch in train_loader:
    inputs, labels = batch
    with torch.no_grad():
        teacher_logits = teacher_model(inputs)
    student_logits = student_model(inputs)

    loss = distillation_loss(student_logits, teacher_logits, labels)
    loss.backward()
    optimizer.step()

지식 증류의 대표적 성공 사례:

교사 -> 학생 모델 예시:
BERT-base (110M) -> DistilBERT (66M): 40% 작고, 97% 성능 유지
GPT-4 -> Phi-3 Mini (3.8B): 대형 모델의 지식을 소형 모델로 전달
Llama 70B -> Llama 8B: 지식 증류 + 합성 데이터로 성능 극대화

3.4 Neural Architecture Search (NAS)

자동으로 최적의 모델 아키텍처를 탐색하는 기법입니다.

NAS로 발견된 효율적 아키텍처:
========================================
모델           | 파라미터 | Top-1 정확도 | 추론 속도
EfficientNet-B0| 5.3M    | 77.3%       | 매우 빠름
MobileNetV3   | 5.4M    | 75.2%       | 매우 빠름
EfficientNetV2| 21M     | 85.7%       | 빠름
MnasNet       | 4.2M    | 74.0%       | 매우 빠름

4. 하드웨어 랜드스케이프

4.1 NVIDIA (Jetson, T4/L4)

NVIDIA 엣지 AI 하드웨어:
========================================
디바이스         | TOPS  | 메모리  | TDP   | 용도
Jetson Orin Nano | 40    | 4-8GB  | 7-15W | 임베디드 AI
Jetson Orin NX   | 100   | 8-16GB | 10-25W| 로봇, 드론
Jetson AGX Orin  | 275   | 32-64GB| 15-60W| 자율주행, 고성능
T4 (데이터센터)   | 130   | 16GB   | 70W   | 엣지 서버
L4 (데이터센터)   | 120   | 24GB   | 72W   | 비디오 AI
# Jetson에서 TensorRT 추론 실행
# JetPack SDK 설치 후

# 모델 변환 (ONNX -> TensorRT)
/usr/src/tensorrt/bin/trtexec \
  --onnx=model.onnx \
  --saveEngine=model.engine \
  --fp16 \
  --workspace=2048 \
  --minShapes=input:1x3x224x224 \
  --optShapes=input:4x3x224x224 \
  --maxShapes=input:8x3x224x224

# 벤치마크
/usr/src/tensorrt/bin/trtexec --loadEngine=model.engine --batch=4

4.2 Apple (Neural Engine, CoreML, MLX)

Apple Silicon AI 성능:
========================================
칩셋          | Neural Engine TOPS | GPU | 통합 메모리
M1            | 11                 | 8코어  | 8-16GB
M2            | 15.8               | 10코어 | 8-24GB
M3            | 18                 | 10코어 | 8-36GB
M4            | 38                 | 10코어 | 16-64GB
A17 Pro       | 35                 | 6코어  | 8GB
A18 Pro       | 35                 | 6코어  | 8GB
# MLX 프레임워크 (Apple Silicon 네이티브)
import mlx.core as mx
import mlx.nn as nn

class SimpleModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear1 = nn.Linear(784, 256)
        self.linear2 = nn.Linear(256, 10)

    def __call__(self, x):
        x = nn.relu(self.linear1(x))
        return self.linear2(x)

model = SimpleModel()
input_data = mx.random.normal((1, 784))
output = model(input_data)
mx.eval(output)  # 지연 평가 실행

4.3 Qualcomm (Snapdragon NPU)

Qualcomm AI Engine:
========================================
칩셋              | NPU TOPS | 용도
Snapdragon 8 Gen 3| 45       | 플래그십 스마트폰
Snapdragon 8 Gen 2| 36       | 프리미엄 스마트폰
Snapdragon 7+ Gen 2| 13      | 중급 스마트폰
QCS6490           | 12       | IoT, 카메라

4.4 Google (Edge TPU, MediaPipe)

# Edge TPU 모델 컴파일
edgetpu_compiler --min_runtime_version 15 model_int8.tflite

# 결과: model_int8_edgetpu.tflite
# MediaPipe 실시간 추론
import mediapipe as mp
import cv2

mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=2,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5,
)

cap = cv2.VideoCapture(0)
while cap.isOpened():
    ret, frame = cap.read()
    results = hands.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
    if results.multi_hand_landmarks:
        for hand_landmarks in results.multi_hand_landmarks:
            # 21개 랜드마크 처리
            pass

4.5 Intel (OpenVINO, Movidius)

Intel AI 가속기:
========================================
하드웨어          | 성능      | 용도
Core Ultra NPU   | 10-34 TOPS| 노트북/데스크탑 AI
Arc GPU          | 가변      | 데스크탑/워크스테이션
Movidius VPU     | 4 TOPS    | IoT, 카메라 (단종)
Gaudi 2/3        | 서버급    | 엣지 서버

5. 온디바이스 LLM

5.1 llama.cpp

C/C++로 작성된 경량 LLM 추론 엔진입니다.

# 빌드
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make -j$(nproc)

# Metal 지원 (macOS)
make LLAMA_METAL=1 -j$(nproc)

# CUDA 지원
make LLAMA_CUDA=1 -j$(nproc)

# 모델 양자화 (GGUF 포맷)
./llama-quantize models/llama-3.1-8b-f16.gguf \
  models/llama-3.1-8b-q4_k_m.gguf Q4_K_M

# 추론 실행
./llama-cli \
  -m models/llama-3.1-8b-q4_k_m.gguf \
  -p "Explain quantum computing in simple terms:" \
  -n 256 \
  -ngl 99  # GPU 레이어 수

5.2 GGUF 포맷 양자화 수준

GGUF 양자화 비교 (Llama 3.1 8B 기준):
========================================
양자화     | 크기    | 메모리  | 품질     | 속도
Q2_K      | 2.96GB | 5.4GB  | 낮음     | 매우 빠름
Q3_K_M    | 3.52GB | 6.0GB  | 보통     | 빠름
Q4_K_M    | 4.58GB | 7.0GB  | 좋음     | 빠름
Q5_K_M    | 5.33GB | 7.8GB  | 매우 좋음 | 보통
Q6_K      | 6.14GB | 8.6GB  | 우수     | 보통
Q8_0      | 7.95GB | 10.4GB | 거의 원본 | 느림
F16       | 15.0GB | 17.5GB | 원본     | 느림

5.3 MLC LLM

다양한 플랫폼에서 LLM을 실행할 수 있는 프레임워크입니다.

# MLC LLM 설치
pip install mlc-llm

# 모델 컴파일 (Vulkan 백엔드)
mlc_llm compile ./dist/Llama-3.1-8B-q4f16_1-MLC/ \
  --device vulkan \
  --output ./dist/Llama-3.1-8B-q4f16_1-vulkan/

# iOS/Android용 컴파일
mlc_llm compile ./dist/Llama-3.1-8B-q4f16_1-MLC/ \
  --device iphone \
  --output ./dist/Llama-3.1-8B-q4f16_1-ios/

5.4 MLX (Apple Silicon)

# MLX로 LLM 추론
from mlx_lm import load, generate

model, tokenizer = load("mlx-community/Llama-3.1-8B-Instruct-4bit")

prompt = "What is machine learning?"
response = generate(
    model,
    tokenizer,
    prompt=prompt,
    max_tokens=256,
    temp=0.7,
)
print(response)

5.5 엣지 디바이스에서 실행 가능한 LLM

온디바이스 LLM 비교:
========================================
모델              | 크기  | RAM 요구 | 속도 (tok/s) | 품질
Phi-3 Mini (3.8B) | 2.3GB | 4GB     | 20-40       | 우수
Gemma 2 (2B)      | 1.4GB | 3GB     | 30-50       | 좋음
Llama 3.2 (1B)    | 0.7GB | 2GB     | 40-60       | 보통
Llama 3.2 (3B)    | 1.8GB | 3.5GB   | 25-40       | 좋음
Qwen2.5 (3B)      | 1.8GB | 3.5GB   | 25-40       | 좋음
SmolLM (1.7B)     | 1.0GB | 2.5GB   | 35-55       | 보통

* Q4_K_M 양자화 기준, 스마트폰에서의 대략적 수치

6. Federated Learning (연합 학습)

6.1 FedAvg 알고리즘

연합 학습의 가장 기본적인 알고리즘입니다.

# FedAvg 의사코드
def federated_averaging(global_model, clients, rounds, local_epochs):
    for round_num in range(rounds):
        # 1. 글로벌 모델을 각 클라이언트에 배포
        client_models = []
        client_sizes = []

        for client in selected_clients:
            # 2. 각 클라이언트에서 로컬 학습
            local_model = copy.deepcopy(global_model)
            local_model = train_local(
                local_model,
                client.data,
                epochs=local_epochs,
                lr=0.01
            )
            client_models.append(local_model.state_dict())
            client_sizes.append(len(client.data))

        # 3. 가중 평균으로 글로벌 모델 업데이트
        total_size = sum(client_sizes)
        new_global = {}
        for key in global_model.state_dict():
            new_global[key] = sum(
                client_models[i][key] * (client_sizes[i] / total_size)
                for i in range(len(client_models))
            )
        global_model.load_state_dict(new_global)

    return global_model

6.2 Flower 프레임워크

# Flower 서버
import flwr as fl

strategy = fl.server.strategy.FedAvg(
    fraction_fit=0.3,          # 30% 클라이언트 참여
    fraction_evaluate=0.2,     # 20% 클라이언트 평가
    min_fit_clients=2,         # 최소 참여 클라이언트
    min_evaluate_clients=2,
    min_available_clients=3,
)

fl.server.start_server(
    server_address="0.0.0.0:8080",
    config=fl.server.ServerConfig(num_rounds=10),
    strategy=strategy,
)
# Flower 클라이언트
import flwr as fl
import torch

class FlowerClient(fl.client.NumPyClient):
    def __init__(self, model, trainloader, testloader):
        self.model = model
        self.trainloader = trainloader
        self.testloader = testloader

    def get_parameters(self, config):
        return [val.cpu().numpy() for val in self.model.parameters()]

    def set_parameters(self, parameters):
        for param, new_val in zip(self.model.parameters(), parameters):
            param.data = torch.tensor(new_val)

    def fit(self, parameters, config):
        self.set_parameters(parameters)
        train(self.model, self.trainloader, epochs=1)
        return self.get_parameters(config), len(self.trainloader.dataset), {}

    def evaluate(self, parameters, config):
        self.set_parameters(parameters)
        loss, accuracy = test(self.model, self.testloader)
        return float(loss), len(self.testloader.dataset), {"accuracy": float(accuracy)}

fl.client.start_numpy_client(
    server_address="localhost:8080",
    client=FlowerClient(model, trainloader, testloader),
)

6.3 차분 프라이버시 (Differential Privacy)

# Opacus를 사용한 차분 프라이버시 학습
from opacus import PrivacyEngine

model = create_model()
optimizer = torch.optim.SGD(model.parameters(), lr=0.05)

privacy_engine = PrivacyEngine()
model, optimizer, train_loader = privacy_engine.make_private_with_epsilon(
    module=model,
    optimizer=optimizer,
    data_loader=train_loader,
    epochs=10,
    target_epsilon=1.0,    # 프라이버시 예산
    target_delta=1e-5,     # 실패 확률
    max_grad_norm=1.0,     # 그래디언트 클리핑
)

# 학습 (일반 학습과 동일)
for batch in train_loader:
    output = model(batch)
    loss = criterion(output, target)
    loss.backward()
    optimizer.step()

# 사용된 프라이버시 예산 확인
epsilon = privacy_engine.get_epsilon(delta=1e-5)
print(f"Epsilon: {epsilon:.2f}")

7. 프라이버시 보존 AI

7.1 온디바이스 처리

민감한 데이터를 디바이스 밖으로 보내지 않고 처리하는 접근법입니다.

온디바이스 프라이버시 보존 사례:
========================================
사례                    | 기술                    | 프라이버시 보장
Apple Face ID           | Neural Engine + Secure Enclave | 얼굴 데이터 디바이스 내 처리
Google Keyboard 예측     | Federated Learning      | 타이핑 데이터 서버 미전송
Apple Siri 음성 인식    | CoreML 온디바이스       | 음성 데이터 로컬 처리
Samsung Knox AI         | NPU + TEE              | 기업 데이터 격리

7.2 Secure Aggregation

연합 학습에서 개별 모델 업데이트를 서버도 볼 수 없게 암호화합니다.

Secure Aggregation 프로토콜:
========================================
1. 각 클라이언트가 마스크 생성 (페어와이즈 시드 교환)
2. 로컬 모델 업데이트에 마스크를 더해 전송
3. 서버는 마스크된 업데이트만 수집
4. 집계 시 마스크가 상쇄되어 합계만 복원
5. 개별 업데이트는 서버도 복원 불가

8. 배포 파이프라인

8.1 모델 변환 워크플로우

학습 프레임워크 → 중간 형식 → 추론 엔진 → 디바이스
========================================
PyTorch  ─┐
           ├──► ONNX ──┬──► TensorRT (.engine)  ──► NVIDIA GPU
TensorFlow┤           ├──► ONNX Runtime           ──► 크로스 플랫폼
           │           ├──► OpenVINO (.xml)       ──► Intel
           ├──► TFLite ──► TFLite Runtime         ──► 모바일/IoT
           ├──► CoreML ──► CoreML Runtime          ──► Apple
           └──► GGUF   ──► llama.cpp              ──► 모든 플랫폼

8.2 OTA (Over-The-Air) 모델 업데이트

# 모델 버전 관리 및 OTA 업데이트 예시
import hashlib
import json
import requests

class ModelManager:
    def __init__(self, model_dir, manifest_url):
        self.model_dir = model_dir
        self.manifest_url = manifest_url

    def check_update(self):
        """서버에서 최신 모델 매니페스트 확인"""
        manifest = requests.get(self.manifest_url).json()
        current_version = self.get_current_version()

        if manifest["version"] > current_version:
            return manifest
        return None

    def download_model(self, manifest):
        """델타 업데이트 또는 전체 다운로드"""
        if manifest.get("delta_url"):
            # 델타 패치 다운로드 (크기 절감)
            patch = requests.get(manifest["delta_url"]).content
            self.apply_delta(patch)
        else:
            # 전체 모델 다운로드
            model_data = requests.get(manifest["model_url"]).content
            self.save_model(model_data, manifest)

    def validate_model(self, model_path, expected_hash):
        """SHA-256 해시로 무결성 검증"""
        with open(model_path, "rb") as f:
            file_hash = hashlib.sha256(f.read()).hexdigest()
        return file_hash == expected_hash

    def rollback(self):
        """문제 발생 시 이전 버전으로 롤백"""
        # 이전 버전 복원 로직
        pass

8.3 모델 패키징

# 모델 매니페스트 (model_manifest.json)
model_name: "image-classifier-v2"
version: "2.1.0"
framework: "tflite"
file: "model_int8.tflite"
input_shape: [1, 224, 224, 3]
input_type: "uint8"
output_shape: [1, 1000]
labels_file: "labels.txt"
hardware_requirements:
  min_ram_mb: 256
  supported_delegates: ["gpu", "nnapi", "xnnpack"]
metrics:
  accuracy: 0.943
  latency_ms: 12.5
  model_size_mb: 4.2

9. 활용 사례

9.1 자율주행

자율주행 Edge AI 스택:
========================================
센서           | AI 작업            | 하드웨어        | 레이턴시 요구
카메라 (8-12) | 객체 탐지/분류     | NVIDIA Orin     | 10ms 이하
LiDAR         | 3D 포인트 클라우드  | NVIDIA Orin     | 20ms 이하
레이더         | 거리/속도 추정     | DSP            | 5ms 이하
퓨전           | 센서 퓨전/판단     | NVIDIA Orin     | 30ms 이하
계획           | 경로 계획          | CPU/GPU        | 50ms 이하

9.2 스마트 카메라

# 엣지 디바이스에서 실시간 객체 탐지
import cv2
import numpy as np

# TFLite 모델 로드 (SSD MobileNet)
interpreter = tf.lite.Interpreter(
    model_path="ssd_mobilenet_v2_int8.tflite",
    num_threads=4
)
interpreter.allocate_tensors()

cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    # 전처리
    input_data = preprocess(frame, target_size=(300, 300))

    # 추론
    interpreter.set_tensor(input_details[0]['index'], input_data)
    interpreter.invoke()

    # 결과 파싱
    boxes = interpreter.get_tensor(output_details[0]['index'])
    classes = interpreter.get_tensor(output_details[1]['index'])
    scores = interpreter.get_tensor(output_details[2]['index'])

    # 높은 신뢰도 결과만 표시
    for i in range(len(scores[0])):
        if scores[0][i] > 0.5:
            draw_box(frame, boxes[0][i], classes[0][i], scores[0][i])

9.3 음성 어시스턴트

온디바이스 음성 처리 파이프라인:
========================================
1. Wake Word 탐지: 작은 모델 (50KB-1MB), 항상 실행, NPU
2. 음성 활동 탐지(VAD): 음성 구간 식별, 매우 경량
3. 음성 인식(ASR): Whisper Tiny/Base (39M-74M 파라미터)
4. 자연어 이해(NLU): 의도 분류 + 개체명 인식
5. 응답 생성: 소형 LLM 또는 템플릿 기반
6. 음성 합성(TTS): 텍스트를 음성으로 변환

9.4 의료 기기

의료 Edge AI 활용:
========================================
기기              | AI 작업              | 규제 고려사항
스마트 워치       | ECG 부정맥 탐지      | FDA 클래스 II
청진기            | 심음/폐음 분석       | FDA 클래스 II
안저 카메라       | 당뇨 망막병증 스크리닝 | FDA 클래스 II
CT/MRI 보조      | 병변 탐지 하이라이트  | FDA 클래스 III
혈당 측정기       | 혈당 추세 예측       | FDA 클래스 II

9.5 산업 IoT

산업 Edge AI 활용:
========================================
영역              | AI 작업              | 이점
품질 검사         | 시각 결함 탐지       | 실시간, 100% 검사
예측 유지보수     | 진동/소음 이상 탐지   | 다운타임 40% 감소
에너지 최적화     | 소비 패턴 예측       | 에너지 15% 절감
안전 모니터링     | PPE 착용 여부 감지    | 사고 예방
로봇 제어         | 경로 계획/장애물 회피  | 자율 운영

10. 과제와 한계

10.1 전력 제약

전력 효율 비교:
========================================
디바이스               | 전력  | AI 성능   | TOPS/W
NVIDIA Jetson Orin Nano| 7-15W | 40 TOPS  | 2.7-5.7
Apple A18 Pro          | ~5W   | 35 TOPS  | 7.0
Google Edge TPU        | 2W    | 4 TOPS   | 2.0
Qualcomm Snapdragon 8g3| ~5W  | 45 TOPS  | 9.0
Intel Core Ultra NPU   | ~5W  | 34 TOPS  | 6.8

10.2 열 관리

모바일 디바이스에서 지속적인 AI 추론은 발열 문제를 일으킵니다. 서멀 스로틀링이 발생하면 성능이 30-50% 저하될 수 있습니다.

10.3 메모리 제한

디바이스별 메모리 제약:
========================================
디바이스 유형      | 일반적 RAM | AI 사용 가능 메모리
IoT 센서          | 256KB-4MB | 극히 제한
마이크로컨트롤러   | 2-16MB    | 매우 제한
스마트 카메라     | 256MB-2GB | 수백 MB
스마트폰          | 6-16GB    | 2-8GB
엣지 서버         | 16-128GB  | 대부분 사용 가능

10.4 모델 업데이트의 어려움

OTA 모델 업데이트 과제:
========================================
- 대역폭 제한: 대용량 모델을 무선으로 전송
- 배터리 소모: 다운로드 + 변환 과정의 에너지
- A/B 테스트: 구버전/신버전 동시 보관 필요
- 롤백: 실패 시 이전 버전 복구 메커니즘
- 무결성: 모델 변조 방지 (서명/해시 검증)

퀴즈

Q1: Edge AI와 Cloud AI의 주요 차이점을 5가지 이상 설명하세요.
  1. 레이턴시: Edge AI는 1-10ms 로컬 추론, Cloud AI는 50-200ms 네트워크 왕복 필요
  2. 프라이버시: Edge AI는 데이터가 디바이스에 유지, Cloud AI는 데이터 전송 필요
  3. 대역폭: Edge AI는 결과만 전송(최소), Cloud AI는 원본 데이터 전송(대량)
  4. 오프라인: Edge AI는 네트워크 없이 동작 가능, Cloud AI는 불가능
  5. 컴퓨팅 파워: Edge AI는 제한적(NPU 수십 TOPS), Cloud AI는 거의 무제한
  6. 모델 크기: Edge AI는 MB에서 수 GB 제한, Cloud AI는 수백 GB 가능
  7. 비용 구조: Edge AI는 초기 하드웨어 비용, Cloud AI는 지속적 서비스 비용
Q2: PTQ와 QAT의 차이를 설명하고, 각각의 장단점을 비교하세요.

PTQ (Post-Training Quantization):

  • 이미 학습된 모델을 추가 학습 없이 양자화
  • 장점: 간편함, 학습 데이터 불필요 (캘리브레이션 데이터만 필요), 빠른 적용
  • 단점: 정확도 손실이 QAT보다 클 수 있음, 특히 INT4에서

QAT (Quantization-Aware Training):

  • 학습 과정에서 양자화 효과를 시뮬레이션
  • 장점: 정확도 손실 최소화, INT4에서도 좋은 품질
  • 단점: 추가 학습 필요 (학습 데이터, GPU, 시간), 구현 복잡

일반적으로 INT8에서는 PTQ만으로도 충분하고, INT4 이하에서는 QAT가 권장됩니다.

Q3: 연합 학습(Federated Learning)의 FedAvg 알고리즘을 설명하세요.

FedAvg (Federated Averaging)의 과정:

  1. 모델 배포: 서버가 글로벌 모델을 선택된 클라이언트들에게 전송
  2. 로컬 학습: 각 클라이언트가 자신의 로컬 데이터로 모델을 학습 (여러 에포크)
  3. 업데이트 수집: 학습된 모델의 파라미터를 서버로 전송 (원본 데이터는 전송하지 않음)
  4. 가중 평균: 서버가 각 클라이언트의 데이터 크기에 비례하여 가중 평균으로 글로벌 모델 업데이트
  5. 반복: 위 과정을 여러 라운드 반복

핵심: 데이터는 디바이스를 떠나지 않고, 모델 업데이트(가중치)만 서버와 주고받아 프라이버시를 보존합니다.

Q4: GGUF 양자화에서 Q4_K_M이 무엇을 의미하는지 설명하세요.

GGUF 양자화 명명 규칙:

  • Q: Quantization (양자화)
  • 4: 비트 수 (4비트). 가중치당 평균 4비트 사용
  • K: K-quant 방식. 블록별로 다른 양자화 수준을 적용하여 중요한 레이어는 더 높은 정밀도 유지
  • M: Medium quality. S(Small/저품질), M(Medium), L(Large/고품질) 중 중간 수준

Q4_K_M은 4비트 K-quant 중간 품질 양자화로, 모델 크기를 약 4-5배 줄이면서 품질 손실을 최소화하는 가장 인기 있는 양자화 수준입니다. 7B 모델 기준 약 4.58GB로 스마트폰에서도 실행 가능합니다.

Q5: 모델 프루닝에서 구조적 프루닝과 비구조적 프루닝의 차이는 무엇인가요?

비구조적 프루닝 (Unstructured Pruning):

  • 개별 가중치를 0으로 설정하여 희소(sparse) 행렬 생성
  • 높은 희소성 달성 가능 (90% 이상)
  • 이론적 연산 절감은 크지만, 일반 하드웨어에서 실제 속도 향상은 제한적
  • 전용 희소 행렬 연산 하드웨어/라이브러리가 필요

구조적 프루닝 (Structured Pruning):

  • 전체 채널, 필터, Attention Head 등 구조적 단위를 제거
  • 결과 모델이 일반적인 밀집(dense) 텐서 연산으로 동작
  • 일반 하드웨어에서도 실제 속도 향상을 달성
  • 희소성 수준은 비구조적보다 낮을 수 있음 (30-50% 정도)

실용적으로는 구조적 프루닝이 실제 추론 속도 향상에 더 효과적이고, 비구조적 프루닝은 NVIDIA의 Sparse Tensor Core 같은 전용 하드웨어에서 효과적입니다.


참고 자료

  1. TensorRT Developer Guide
  2. ONNX Runtime Documentation
  3. TensorFlow Lite Guide
  4. CoreML Documentation
  5. OpenVINO Documentation
  6. llama.cpp Repository
  7. MLC LLM Documentation
  8. MLX Documentation
  9. Flower Federated Learning
  10. Opacus Differential Privacy
  11. NVIDIA Jetson Developer
  12. MediaPipe Solutions
  13. Qualcomm AI Engine
  14. Edge AI and Vision Alliance