Runbook
Landmark Assistant 데모 앱 운영 가이드
팀원 PC에서 Sprint 1 Streamlit demo app을 실행하고, 같은 Wi-Fi의 스마트폰 브라우저로 시연하기 위한 legacy 절차다. 최신 Sprint 2 Flutter/온디바이스 앱 artifact 구조는 Model Serving Contract를 따른다.
현재 문서 상태
이 문서는 Sprint 1 demo app 재현용이다. best.pt 직접 실행은 개발/비교용으로 남긴다. Sprint 1 시연 기본값은
mobile_artifacts_int8/를 사용하는 python run.py --int8다.
최신 Sprint 2 앱 전달용 모델은 mobile_artifacts/fp16의 image/text encoder 분리 bundle이다.
Sprint 2 앱 artifact 기준
새 앱 구현에서는 landmark_encoder.onnx 하나를 찾지 않는다.
manifest.json을 읽어 image encoder와 text encoder 파일명을 각각 확인한다.
mobile_artifacts/fp16/
mobileclip2_s3_server_full_ce_hardneg_image_encoder_fp16_mixed.onnx
mobileclip2_s3_server_full_ce_hardneg_image_encoder_fp16_mixed.onnx.data
mobileclip2_s3_server_full_ce_hardneg_text_encoder_fp16_mixed.onnx
mobileclip2_s3_server_full_ce_hardneg_text_encoder_fp16_mixed.onnx.data
manifest.json
preprocessing.json
tokenizer.json
prototype_index.json
classes.json
labels_master.json
config.yaml
이미지 검색은 image -> image encoder -> embedding -> prototype_index 흐름이고,
텍스트 검색은 query -> tokenizer -> text encoder -> text embedding -> text catalog/index 흐름이다.
상세 화면 정보는 모델 artifact가 아니라 앱의 landmark catalog 또는 DB에서 조회한다.
데모 앱 구조 한눈에 보기
Sprint 1 데모 앱은 Streamlit UI, 모델/검색 코드, 정적 자산, 모바일 artifact를 분리해서 운용한다. GitHub repo에는 실행 코드와 작은 JSON 자산이 들어가고, 큰 모델 파일은 별도 전달한다.
landmark-demo-app/
run.py # Streamlit launcher
config.toml # PyTorch best.pt 실행 설정
config.onnx.toml # FP32 ONNX 실행 설정
config.int8.toml # Sprint 1 기본: dynamic INT8 ONNX 실행 설정
pyproject.toml # pip install -e . / .[onnx] 의존성
best.pt # PyTorch checkpoint, git에 올리지 않음
mobile_artifacts_int8/ # Sprint 1 데모용 INT8 ONNX artifact, git에 올리지 않음
assets/
landmark_info.json # 상세 페이지 metadata
landmark_text_catalog_v2.json # 사람이 관리하는 한/영 검색 표현 사전
landmark_text_index.json # text catalog를 embedding으로 변환한 검색 인덱스
prototype_index.json # class별 image prototype
hero_images/ # 상세 페이지 대표 이미지
src/landmark_demo/
app.py # Streamlit 화면과 사용자 interaction
config.py # TOML config loader
data.py # assets 로딩과 정합성 검증
inference.py # PyTorch/ONNX image, text encoder wrapper
model.py # MobileCLIP2 checkpoint wrapper
search.py # image/text search, confidence policy
logging_util.py # JSONL debug log
scripts/
build_assets.py # best.pt + Dataset -> prototype/text/hero assets
export_mobile_onnx.py # best.pt -> ONNX artifact
benchmark_mobile_artifact.py # PyTorch vs ONNX 비교
tests/
fixtures/ # demo regression 입력과 기대 결과
실행 모드
| 명령 | 사용 config | 모델 경로 | 용도 |
|---|---|---|---|
python run.py |
config.toml |
best.pt |
개발/비교용 PyTorch 원본 실행. |
python run.py --onnx |
config.onnx.toml |
mobile_artifacts/ |
FP32 ONNX export 결과 확인. |
python run.py --int8 |
config.int8.toml |
mobile_artifacts_int8/ |
Sprint 1 demo app 재현 경로. |
run.py는 선택된 config를 LANDMARK_DEMO_CONFIG 환경변수에 넣고
streamlit run src/landmark_demo/app.py를 0.0.0.0:8501로 실행한다.
그래서 같은 Wi-Fi의 스마트폰에서도 http://<노트북_IP>:8501로 접근할 수 있다.
런타임 흐름
사용자 입력
├─ 이미지 업로드
│ └─ app.py
│ └─ inference.py: ONNX image encoder로 image embedding 생성
│ └─ search.py: prototype_index와 cosine 비교
│ └─ confidence policy: matched / ambiguous / out_of_scope / low_quality
│ └─ app.py: Top-3, 안내 문구, 상세 페이지 표시
└─ 자연어 검색
└─ app.py
└─ inference.py: text encoder 사용 가능 시 text embedding 생성
└─ search.py: landmark_text_index embedding + keyword 문자열 보정
└─ confidence policy 적용
이미지 검색은 prototype_index.json를 직접 사용하고,
자연어 검색은 landmark_text_index.json와 keyword matching을 함께 사용한다.
landmark_text_catalog_v2.json는 사람이 편집하는 원본 사전이고,
앱 실행 시 직접 비교되는 것은 이 사전에서 생성된 landmark_text_index.json다.
주요 모듈 책임
| 파일 | 책임 | 문제 발생 시 먼저 볼 것 |
|---|---|---|
app.py |
Streamlit 화면, 탭 전환, 업로드, 결과 카드, 상세 페이지. | 화면이 안 뜨거나 UI flow가 이상할 때. |
config.py |
config.toml 계열 파일을 읽어 경로, threshold, backend를 결정. |
다른 artifact/config로 실행해야 할 때. |
data.py |
landmark_info, prototype_index, landmark_text_index 로딩과 정합성 확인. |
자산 누락, landmark_id 불일치, 상세정보 오류. |
inference.py |
이미지 전처리, PyTorch/ONNX encoder 호출, text encoder wrapper. | 모델 로딩, ONNX Runtime, latency, embedding 생성 오류. |
search.py |
cosine similarity, keyword score, fusion score, threshold/margin decision. | 검색 결과 순위, ambiguous/OOS 판정, 점수 정책 조정. |
logging_util.py |
검색 입력, Top-3, decision, reason code를 JSONL로 저장. | 시연 후 실패 사례 분석. |
Sprint 1 데모앱 파일 배치
landmark-demo-app/
run.py
config.int8.toml
assets/
src/
mobile_artifacts_int8/
landmark_encoder.onnx
landmark_encoder.onnx.data
preprocessing.json
prototype_index.json
labels_master.json
manifest.json
quantization_regression_report.json
이 구조는 Sprint 1 demo app 전용이다. 압축을 풀었을 때 mobile_artifacts_int8/mobile_artifacts_int8/...처럼 폴더가 두 겹이면 안 된다.
landmark_encoder.onnx가 바로 mobile_artifacts_int8/ 안에 있어야 한다.
Sprint 2 앱에서는 이 파일명이 아니라 manifest.json의 image/text encoder 항목을 따른다.
빠른 시작
git clone https://github.com/HyunKN/landmark-demo-app.git
cd landmark-demo-app
pip install -e ".[onnx]"
# mobile_artifacts_int8 폴더를 repo 루트에 둔 뒤
python run.py --int8
브라우저에서 http://localhost:8501을 연다.
스마트폰 브라우저 시연
- 노트북과 스마트폰을 같은 Wi-Fi에 연결한다.
- 노트북에서
python run.py --int8를 실행한다. - Windows 방화벽이 뜨면 Python 또는 8501 포트를 허용한다.
- 노트북 IP를 확인한다:
ipconfig의 IPv4 주소. - 스마트폰 브라우저에서
http://<노트북_IP>:8501로 접속한다.
이 방식은 “스마트폰 화면에서 앱처럼 사용”을 보여주지만, 추론은 노트북에서 수행한다. 실제 스마트폰 NPU latency는 Qualcomm AI Hub 측정값을 별도 근거로 제시한다.
기능
| 탭 | 입력 | 동작 |
|---|---|---|
| 이미지 | JPEG/PNG/WEBP, 10MB 이하 | ONNX image encoder → prototype cosine → Top-3 또는 confidence policy 안내. |
| 자연어 | 한국어/영어 검색어 | Sprint 1 demo app은 text index와 keyword 기반 검색. Sprint 2 앱은 별도 text encoder ONNX를 사용한다. |
2026-05-17 검증 이후 이름 검색 탭은 제거했다. Sprint 1 시연 흐름을 이미지 검색과 자연어 검색으로 단순화하고, 장소 이름 검색은 자연어 탭의 keyword/catalog 검색에 흡수한다.
신뢰도 상태
| 상태 | 화면 의미 | 로그에서 볼 것 |
|---|---|---|
matched |
가장 가능성이 높은 랜드마크와 Top-3 후보를 표시. | top1_score, margin, reason_codes |
ambiguous |
한 곳으로 확정하지 않고 가까운 후보를 함께 표시. | 낮은 margin, 중간 top1 score. |
out_of_scope |
지원 범위 밖으로 안내. 후보는 참고용으로만 노출 가능. | top1 below reject, keyword miss, screenshot-like 등. |
low_quality |
재촬영 또는 더 선명한 사진 요청. | blur/dark/min-side 품질 사유. |
개발/비교용 명령
# PyTorch 원본 실행: best.pt 필요
python run.py
# FP32 ONNX 실행: mobile_artifacts/ 필요
python run.py --onnx
# 팀원 시연 기본값: mobile_artifacts_int8/ 필요
python run.py --int8
주의
Sprint 1의 mobile_artifacts_int8.zip과 Sprint 2의
mobileclip2_s3_fp16_mixed_artifacts.zip은 용량 때문에 GitHub repo에 올리지 않는다.
팀원에게 압축 파일로 따로 전달한다. GitHub에는 실행 코드와 설정만 올라간다.