영상에 YOLO 객체 검출을 실행하고 결과를 중앙에 보고하는 데모 앱 mlops-edge-demo. 에이전트(geo-mlops-edge)를 따로 실행하지 않고 애플리케이션 안에 SDK 런타임(EdgeRuntime)을 포함하는 구성의 예. 소스는 공개 저장소 https://github.com/osom8979/mlops-edge.

데모 창: 왼쪽 위에 장비 ID, 모델·추론 시간, FPS·검출 수, 중앙 연결 상태 표시. 영상은 KITTI 데이터셋(CC BY-NC-SA 3.0)

구성

데모 앱의 데이터 흐름. 영상 재생과 추론은 메인 스레드, SDK 런타임은 별도 스레드에서 실행.
데모 코드가 하는 일SDK 런타임이 하는 일
영상 읽기, YOLO 추론, 창에 결과 표시등록, 하트비트, 명령 롱폴, 로컬 API(8600)
추론 결과를 1초마다 큐에 추가로컬 큐 저장, 연결 시 업로드, 연결 중단 시 누적
수집 대상 프레임을 JPEG 로 인코딩해 frames 수집기에 전달LabelMe 사전 라벨 생성, 중복 방지, 청크 업로드
모델 활성화 이벤트를 받아 재생 중인 모델 교체pull_model 처리(내려받기, 캐시, 활성화)

중앙과 주고받는 데이터는 다음과 같음.

방향내용중앙에서 확인하는 곳
장비 → 중앙등록, 하트비트(10초)Edge Fleet 목록과 디바이스 상세
장비 → 중앙추론 결과 1초에 1건디바이스 상세 추론 결과 탭
장비 → 중앙30프레임마다 JPEG 1장 + LabelMe JSON 1개지정한 데이터셋의 파일 탭. 지정하지 않으면 수집 데이터
중앙 → 장비명령 ping, restart, resync, set_policy, pull_model디바이스 상세 명령 탭

준비

  • uv. Python 3.12 는 저장소의 .python-version 에 맞춰 uv 가 설치.
  • 창을 표시할 화면. 화면이 없는 장비는 --no-show 로 실행.
  • (선택) NVIDIA GPU. 없으면 CPU 로 추론.
  • 장비에서 중앙 API 주소로 향하는 HTTPS 연결, 디바이스 등록용 테넌트 ADMIN 계정( 설정ADMIN).
git clone https://github.com/osom8979/mlops-edge.git
cd mlops-edge
uv sync

Linux 에서는 CUDA 빌드 PyTorch 와 GPU 라이브러리(합계 약 3 GB)를 함께 설치. 회선이 느려 Failed to download distribution due to network timeout 이 발생하면 UV_HTTP_TIMEOUT=600 uv sync 로 재실행.

1. 디바이스와 데이터셋 생성

  1. Edge Fleet 의 디바이스 등록 에서 디바이스 ID 입력(예: edge-yolo-01). 스코프는 기본값(모두 활성화) 유지. 추론 결과에 inference:write, 프레임 업로드에 data:write, 모델 교체에 models:read 필요. 화면 절차는 5분 시작 참고.
  2. 등록 완료 창의 토큰 복사. 창을 닫으면 다시 확인 불가.
  3. (선택) 프레임을 받을 데이터셋 생성. 데이터셋 의 새 데이터셋 에서 직접 업로드 를 선택하고 모달리티를 이미지로 지정. 상세 화면 주소의 ID(ds-…) 복사. 생략하면 프레임은 수집 데이터 에 저장.

2. 설정

저장소의 edge.yaml 에서 세 곳 변경.

central:
  base_url: https://mlops.example.com   # 중앙 API 주소

device:
  id: edge-yolo-01                      # 1단계의 디바이스 ID

collectors:
  - type: frames
    name: demo-frames
    priority: 30                        # 추론 결과(50)보다 낮게. 회선이 좁으면 결과를 먼저 전송
    options:
      every_n_frames: 30                # 30 fps 영상이면 초당 1장
      dataset_id: "ds-0123456789ab"     # 1단계의 데이터셋 ID. 비우면 수집 데이터
      labels: true                      # LabelMe 사전 라벨 동반
      label_conf: 0.5                   # 이 신뢰도 이상인 검출만 사전 라벨로 사용

토큰은 설정 파일이 아니라 .env 에 저장.

cp .env.example .env
# .env 에 입력: GEO_EDGE_CENTRAL__TOKEN=<device-token>
  • 설정 우선순위는 환경 변수 > .env > edge.yaml > 기본값(SDK 규칙과 동일). 전체 항목은 설정 레퍼런스 참고.
  • 데모의 edge.yaml 은 화면에서 빨리 확인하도록 하트비트를 10초로 지정(기본 30초).
  • 토큰이 없어도 실행 가능. 결과는 로컬 큐(data/edge/)에만 누적되고, 토큰 지정 후 재실행하면 전송.

3. 실행

uv run mlops-edge-demo

인자를 생략하면 기본 영상(도심 주행 17초)을 cache/videos/ 에 한 번 내려받아 반복 재생. 가중치 yolo26n.pt(COCO 사전학습)도 cache/weights/ 에 내려받음. 창에서 q 또는 ESC 로 종료.

창 왼쪽 위 상태 표시줄의 의미는 다음과 같음.

줄내용
1MLOps Edge Demo 와 디바이스 ID
2모델 이름:버전 과 프레임당 추론 시간. 시작 시 버전은 pretrained
3FPS, 검출 수와 클래스별 개수
4중앙 연결 상태 / 등록 여부 / 대기 건수 / 동기화 상태 / 이번 실행에서 수집한 프레임 수. 색은 초록 online, 노랑 offline·probing, 빨강 auth_failed

다른 영상과 실행 방식:

uv run mlops-edge-demo ./line-camera.mp4            # 로컬 파일 (내려받지 않고 바로 열기)
uv run mlops-edge-demo rtsp://192.0.2.10/stream     # RTSP 스트림
uv run mlops-edge-demo --no-show                    # 화면 없는 장비. 5초마다 상태를 로그로 출력
옵션기본값설명
SOURCE기본 영상 URLHTTP(S) URL 은 내려받아 캐시. 그 외(파일, 스트림 주소)는 OpenCV 로 직접 열기
--weightsyolo26n.ptYOLO 가중치. 경로 없이 이름만 지정하면 COCO 사전학습 모델을 캐시에 내려받음
--conf0.25검출 신뢰도 임계값(화면 표시와 추론 결과 기준)
--configedge.yaml런타임 설정 파일
--env-file.env토큰 등 비밀값 파일. 없으면 무시
--cache-dircache영상·가중치 캐시
--report-interval1.0추론 결과 전송 간격(초). 0 이면 매 프레임
--once반복 재생 없이 영상 끝에서 종료
--no-show창 없이 실행

데모 안의 런타임이 로컬 API(127.0.0.1:8600)를 열기 때문에 SDK CLI 로 상태 조회 가능(CLI).

uv run geo-mlops-edge status --config edge.yaml          # 연결, 대기 건수, 모델, 조치 필요 사항
uv run geo-mlops-edge queue --config edge.yaml --limit 5

같은 PC 에서 geo-mlops-edge 에이전트가 이미 8600 포트를 사용 중이면 데모는 로컬 API 만 비활성화(local API disabled 경고)하고 중앙 연동은 유지.

4. 중앙에서 확인

  1. Edge Fleet 에서 디바이스 열기. ① SDK 버전과 위치 ② 쌓인 데이터 의 대기 건수가 0 이면 수집분 전송 완료 ③ 수집기 demo-frames 가 running.
    데모 장비의 상세: ① SDK 버전과 위치 ② 대기 0 이면 수집분 전송 완료 ③ frames 수집기 demo-frames 의 상태
  2. 추론 결과 탭에 1초마다 1건 기록. ① 모델은 가중치 파일 이름과 버전 pretrained. 중앙에서 받은 모델로 교체하면 그 모델의 이름과 버전 ② 자세히 로 결과 JSON 펼치기. 검출마다 클래스 번호·이름, 신뢰도, 0~1 로 정규화한 박스 [x1, y1, x2, y2].
    추론 결과 탭: 데모가 1초마다 보낸 YOLO 검출 결과. ① 모델 이름과 버전 ② 결과 JSON(클래스·신뢰도·정규화 박스) 펼치기
  3. 데이터셋 파일 탭에 30프레임마다 이미지 1장과 같은 이름의 LabelMe JSON 등록. 기본 영상은 한 바퀴에 17장. ① 라벨과 짝을 이룬 이미지 수가 학습 가능 여부에 반영 ② 종류 필터를 이미지로 지정하면 프레임만 표시 ③ 카드의 "라벨 있음" 이 짝이 맞은 표시.
    수집 데이터셋의 파일 탭: ① 이미지 17장이 라벨과 짝을 이뤄 학습 가능 ② 종류 필터를 이미지로 ③ 프레임 이미지(같은 이름의 LabelMe 사전 라벨이 짝)
  4. 이미지를 열면 모델이 그린 사전 라벨(사각형) 표시. ① 주석 숨김 으로 원본 확인 ② 메타데이터의 라벨 짝에 같은 이름의 .json.
    수집 프레임 미리보기: 모델이 그린 사전 라벨(사각형) 표시. ① 주석 표시 전환 ② 메타데이터(라벨 짝, 크기)
  • 파일 이름은 <영상 이름>_<프레임 번호 6자리>.jpg. URL 로 받은 영상은 캐시 파일 이름(URL 해시 12자 + 원래 이름)이 영상 이름. 예: c5144a2b5077-kitti-inference-vid_000420.jpg.
  • 사전 라벨은 현재 모델의 예측. 그대로 학습하면 모델이 자기 오류를 다시 학습하므로 학습 전 사람의 검수 필요.
  • 반복 재생하거나 재실행해도 이미 큐에 넣은 프레임은 다시 전송하지 않음. 기록은 data/edge/collectors/demo-frames.seen.

5. 학습한 모델로 교체

수집한 프레임으로 학습한 모델을 이 장비로 다시 전송하는 절차.

  1. 수집 데이터셋 상세의 이 데이터셋으로 학습 으로 학습 생성. 마지막 단계에서 모델 레지스트리 등록 을 활성화하고 등록 모델 이름 지정(예: edge-demo-yolo). 등록하지 않은 모델은 장비가 내려받을 수 있는 목록에 없음. 이름에 / 와 ' 사용 금지(장비의 내려받기 경로와 레지스트리 조회 오류).
  2. 학습 완료 후 디바이스 상세 명령 탭에서 모델 내려받기 를 선택하고 모델과 버전 지정 후 명령 보내기.
  3. 장비가 다음 명령 확인 때 모델을 내려받아 data/edge/models/ 에 캐시하고 활성화. 데모는 활성화 이벤트를 받아 재생 중인 모델을 교체하고 switched model: yolo26n:pretrained -> edge-demo-yolo:1 로그 출력. 이후 추론 결과와 사전 라벨은 새 모델 이름과 버전으로 기록.
  • 화면에 반영하는 것은 YOLO 프레임워크 모델만. 다른 프레임워크 모델은 경고 로그만 남기고 기존 모델로 계속 재생.
  • 캐시한 모델은 다음 실행 시 다시 활성화. 활성화 규칙은 모델과 추론 참고.
  • 승인(Production 승격)이나 배포 화면은 장비로 모델을 전송하지 않음. 장비로 보내는 방법은 pull_model 명령, 장비 CLI, 로컬 API 의 세 가지.

재시작과 종료

  • q·ESC·Ctrl-C·SIGTERM 으로 종료하면 런타임이 큐 정리와 DB 닫기를 마친 뒤 종료.
  • 디바이스 상세의 재시작 이나 restart 명령을 받으면 종료 코드 3 으로 종료. systemd 같은 감시 프로그램 아래에서 실행하면 재기동(배포, 명령·정책·보존).

코드 구성

mlops-edge/
edge.yaml런타임 설정 (중앙 주소, 디바이스 ID, 주기, 보존, 수집기)
.env.example토큰 템플릿
src/mlops_edge_demo/
cli.py명령행 인자, 설정 로드, 조립
video.pyURL 영상 내려받기와 캐시
detector.pyYOLO 추론 결과를 SDK 추론 계약(InferenceOutput)으로 변환
edge.pyEdgeRuntime 을 별도 스레드에서 실행. 결과 보고, 프레임 수집, 모델 교체 연결
player.pyOpenCV 재생 루프와 상태 표시줄

애플리케이션에 런타임을 포함할 때의 핵심은 스레드 분리. OpenCV 창은 메인 스레드에서 동작하고 EdgeRuntime 은 asyncio 이벤트 루프가 필요하므로, 런타임은 별도 스레드의 루프에서 실행하고 영상 루프는 asyncio.run_coroutine_threadsafe 로 작업만 넘긴 뒤 기다리지 않음.

# edge.py 의 요약. 런타임 스레드에서 실행
runtime = EdgeRuntime(settings)
runtime.events.add_listener(on_event)   # pull_model 로 모델이 활성화되면 알림
await runtime.start()                   # 등록·하트비트·명령·업로더·수집기 시작
await serve(runtime, settings, stop)    # 로컬 API (geo_mlops_sdk.edge.daemon.serve)

# 영상 루프(메인 스레드)에서: 수집 대상 프레임만 인코딩해 frames 수집기로 전달
if frames.wants(source, index, seekable=True):
    ok, jpeg = cv2.imencode(".jpg", frame, [cv2.IMWRITE_JPEG_QUALITY, 90])
    asyncio.run_coroutine_threadsafe(
        frames.collect(source, index, jpeg.tobytes(), output, model=model_ref), loop
    )

frames 수집기 API 는 데이터 수집기, 추론 결과 형식(InferenceOutput)은 모델과 추론 참고.

2026-10-02 기준 플랫폼으로 작성

© MLOps Central Platform