RunanywhereAI/RCLI. macOS Apple Silicon에서 모든 추론(받아쓰기·LLM·음성합성·비전·임베딩)을 기기 안 Metal GPU로 실행하는 음성 AI CLI/TUI다. 마이크로 말하면 받아쓰고(STT) → 로컬 LLM이 답을 만들고 → 음성으로 읽어 주며(TTS), 동시에 내 문서를 근거로 답하는 로컬 RAG, 카메라·화면을 보는 VLM(비전), 그리고 "사파리 열어줘" 같은 40개 macOS 제어 액션까지 단일 C++17 바이너리에 담았다. 클라우드 전송·과금이 전혀 없고, 자체 GPU 엔진 'MetalRT'를 쓰되 구형 칩에선 오픈소스 llama.cpp로 자동 폴백한다. (저장소: RunanywhereAI/RCLI · C++17 약 28,500줄 · v0.3.7 · 라이선스 MIT · 스택 llama.cpp / sherpa-onnx / USearch / FTXUI · macOS 13+ Apple Silicon 전용 — TrendShift RAG 토픽, 스타 약 1,500)
"맥에게 말을 걸면, 인터넷 없이 기기 안에서 듣고·생각하고·말하고·행동한다."
보통의 음성 비서는 당신 목소리를 회사 서버로 보내 처리합니다(프라이버시·과금·인터넷 의존). RCLI는 그 모든 계산을 당신의 맥 GPU 안에서 끝냅니다. 받아쓰기 모델, 답을 만드는 LLM, 목소리를 합성하는 TTS, 문서를 찾아 주는 검색까지 전부 기기 안에 들어 있어 비행기 모드에서도 작동합니다. "사진 속에 뭐가 있어?"라고 물으면 카메라를 보고(VLM), "사파리 열어줘"라고 하면 실제로 앱을 엽니다(40개 macOS 액션).
"완전 로컬 프라이버시 + 자체 GPU 엔진(빠르다 주장) + 크래시 복구 같은 '실배포 디테일'."
모든 추론(STT·LLM·TTS·VLM·임베딩)이 Apple Silicon의 Metal GPU에서 돌아 클라우드 전송·API 키·과금이 전혀 없습니다. 받아쓰기 앱(MacWhisper 등)은 받아쓰기만, 클라우드 비서(Siri·Alexa·ChatGPT 음성)는 서버 의존이지만, RCLI는 "듣고→생각하고→도구 실행하고→말하는" 전체 루프를 오프라인으로 완성합니다.
RunAnywhere가 Apple Silicon 전용으로 만든 GPU 추론 엔진 MetalRT를 씁니다. 자사 블로그 기준 LLM 디코드 최대 550 tok/s, STT 실시간 대비 714배, 종단 음성 지연 sub-200ms를 주장합니다. M3 이상에서만 동작하고, M1/M2에서는 오픈소스 llama.cpp로 자동 폴백합니다. (단, 이 수치는 자사 출처이고 MetalRT 자체는 소스가 비공개인 dylib입니다 — 7번·아래 함정 참고.)
MetalRT dylib이 일부 M3/M4에서 segfault 나는 걸 개발진이 알고 있어, 초기화 전에 breadcrumb 파일(~/.rcli/.metalrt_init_in_progress)을 남기고 SIGSEGV/SIGBUS 핸들러를 설치합니다. 그래서 다음 실행 때 "아, 지난번 MetalRT가 죽었구나" 하고 자동으로 llama.cpp로 폴백해 사용자가 크래시 루프에 갇히지 않습니다. 이런 디테일이 "장난감"이 아니라 실제 배포 제품임을 보여 줍니다.
핵심 성능을 책임지는 MetalRT 엔진은 이 저장소에 소스가 없는 독점 dylib입니다(오픈된 건 llama.cpp 폴백 경로뿐). 벤치마크 수치도 자사 블로그 출처라 독립 검증 자료는 없습니다. macOS Apple Silicon 전용이라 윈도우·리눅스·인텔 맥에선 못 돕니다. 또 초기 급성장 뒤 마지막 커밋이 약 3개월 전이라 유지보수 활발도는 둔화된 상태입니다. 다만 학습 교본으로서의 코드 가치는 매우 큽니다.
"순수 C++17 + CMake. UI는 SwiftUI가 아니라 터미널(FTXUI). 추론은 llama.cpp/sherpa-onnx, 벡터는 USearch."
| 레이어 | 실제 사용 기술 |
|---|---|
| 언어 / 빌드 | C++17(67×.h, 39×.cpp) + macOS 브리지 Objective-C++(.mm 3개)/Cocoa(.m 1개). CMake 3.15+, -O3 + LTO, arm64 -mcpu=native |
| UI | FTXUI v5 — 순수 터미널 TUI(푸시투토크·HW 모니터·모델 관리). SwiftUI 아님 |
| LLM 추론 | ① llama.cpp(vendored, GGUF, GGML_METAL=ON, Flash Attention, KV 캐시) ② MetalRT(독점 dylib, MLX safetensors 4bit) |
| STT(받아쓰기) | sherpa-onnx — 스트리밍 Zipformer(transducer) + 오프라인 Whisper base.en / Parakeet TDT 0.6B |
| TTS(음성합성) | sherpa-onnx offline — Kokoro 82M(28 voices) / Piper(VITS) / Kitten |
| VAD(음성구간 검출) | Silero VAD(silero_vad.onnx) |
| VLM(비전) | llama.cpp libmtmd(멀티모달) — Qwen3 VL 2B / LFM2 VL 1.6B / SmolVLM 500M |
| 임베딩 / 벡터 | llama.cpp 임베딩(기본 384차원, Snowflake arctic) + USearch v2.16.5(헤더온리 HNSW) |
| 키워드 검색 | 자체 구현 BM25Index(bm25_index.cpp) |
| 문서 파싱 | PDF → poppler pdftotext, DOCX → word/document.xml unzip 파싱 |
| macOS 제어 | AppleScript + 셸. CoreAudio/AVFoundation/AppKit/Metal 등 14개 프레임워크 링크 |
| 배포 | Homebrew tap + install.sh 원커맨드 + GitHub Releases prebuilt |
"심장은 Orchestrator. STT/LLM/TTS 3개 스레드가 락프리 링버퍼로 이어지고, 말 끊기(barge-in)는 atomic 플래그로."
핵심은 orchestrator.cpp의 Orchestrator 클래스입니다. 3개 동시 스레드(STT·LLM·TTS)가 락프리 RingBuffer + 미리 잡아 둔 MemoryPool(64MB)로 연결되고, 사용자가 응답 도중 끼어드는 barge-in을 atomic 플래그로 즉시 처리합니다.
① 백엔드 추상화 + 핫스왑: LlmBackend::{LLAMACPP, METALRT}를 런타임에 교체(switch_backend()). 파이프라인이 IDLE일 때 모델을 갈아끼웁니다. MetalRT가 죽으면 시그널 핸들러 + breadcrumb로 llama.cpp 폴백.
② 시스템 프롬프트 KV 캐싱: 시스템 프롬프트+도구 정의를 한 번 프리필해 두고, 유저 입력 부분만 디코드(generate_with_cached_prompt())해 지연을 줄입니다.
③ from-scratch 하이브리드 RAG: 벡터(USearch HNSW)와 키워드(BM25)를 RRF(score += 1/(60+rank))로 융합. 청크 텍스트는 mmap zero-copy로 읽고, 쿼리당 힙 할당이 0이 되도록 버퍼를 사전할당하는 최적화까지 들어 있습니다.
④ ModelProfile 추상화: 모델 패밀리(lfm2·qwen3 등)별로 채팅 템플릿과 tool-call 포맷이 달라서, 이를 자동 감지·분기하는 프로파일 레이어를 둡니다. 2-패스 tool calling(호출→실행→결과 주입→재생성)의 핵심.
"기능별로 깔끔히 나뉜 src/ — pipeline(심장)·engines(추론)·rag·actions·audio·cli."
| 경로 | 역할 (LOC) |
|---|---|
| src/cli/ | (~7,100) 명령 디스패치 + TUI 앱/대시보드. 명령: listen·ask·rag·vlm·camera·screen·models·metalrt |
| src/engines/ | (~4,400) stt·llm·tts·vad·vlm·embedding + metalrt_{loader,engine,stt,tts} + model_profile |
| src/api/ | (~3,500) rcli_api.cpp — C ABI 인터페이스(rcli_create/init/...). 정적 라이브러리 librcli의 외부 창구 |
| src/pipeline/ | (~3,000) orchestrator(심장) + sentence_detector + text_sanitizer + wake_word_detector |
| src/actions/ | (~2,100) 40개 macOS 액션: notes·reminders·messages·app_control·window·files·clipboard·media·web·browser + applescript_executor + action_registry |
| src/rag/ | (~1,800) hybrid_retriever + vector_index(USearch) + bm25_index + document_processor + index_builder + embedding_cache |
| src/models/ | (~1,450) LLM/STT/TTS/VLM 모델 카탈로그(URL·크기·우선순위) |
| src/audio/ | (~1,390) audio_io(CoreAudio) + camera_capture.mm + screen_capture.mm + mic_permission.mm + rcli_overlay.m |
| src/core/ | (~1,010) memory_pool · ring_buffer · hardware_profile · types · personality · log |
| src/tools/ | (~300) tool_engine + tool_defs |
| 루트 | CMakeLists.txt·install.sh·Formula/rcli.rb(Homebrew)·scripts/{setup,download_models,setup_metalrt}.sh·CLAUDE.md |
이 폴더 구조는 한 사람의 "감각 기관"을 코드로 나눈 것 같습니다 — audio(귀·눈), engines(뇌의 언어 영역), rag(기억 도서관), actions(손), 그리고 이 모두를 지휘하는 pipeline/orchestrator(중추신경)입니다. "음성 비서를 만든다"는 막연한 목표가 어떤 부품으로 분해되는지 한눈에 보여 주는 좋은 지도입니다.
"온디바이스 음성 풀스택·C++ LLM 추론·from-scratch RAG·tool calling·Apple 플랫폼 통합이 한 레포에."
배울 것: VAD→STT→LLM→TTS를 직접 엮는 법. 락프리 RingBuffer + 멀티스레드 핸드오프(mutex/condvar/atomic), 더블버퍼 TTS로 지연 숨기기, barge-in 인터럽트 설계. "실시간 음성 루프"가 왜 어려운지를 코드로 체득.
실습: 마이크 입력을 링버퍼에 쌓고, 별도 스레드에서 일정 구간씩 꺼내 처리하는 생산자-소비자 구조를 작은 프로그램으로 구현.
배울 것: llama.cpp C API 직접 호출, GGUF 로딩, n_gpu_layers/Flash Attention/KV-cache 튜닝, 토크나이즈/디토크나이즈, 시스템 프롬프트 KV 캐싱. "Ollama가 내부에서 뭘 하는가"를 한 겹 아래에서 보기.
실습: llama.cpp를 빌드해 작은 GGUF 모델을 로드하고, 같은 프롬프트를 n_gpu_layers 값만 바꿔 가며 토큰/초를 측정.
배울 것: HNSW 벡터검색(USearch) + BM25 + RRF 융합을 라이브러리 조립이 아니라 직접. mmap zero-copy 청크 스토어, 쿼리당 무할당 최적화, 시맨틱 청킹(64~512 토큰, 오버랩 50). "RAG는 마법이 아니라 자료구조"임을 이해.
실습: 문서를 청크로 쪼개 임베딩→HNSW 색인하고, 같은 질의를 BM25 결과와 RRF로 합쳐 답 근거를 뽑아 보기.
배울 것: 모델별 네이티브 tool-call 포맷 파싱, 2-패스(호출→실행→결과 주입→재생성), KV 캐시를 깨지 않으려 도구 힌트를 유저 턴에 주입하는 트릭. "에이전트가 실제로 손을 쓰는" 메커니즘.
실습: 작은 로컬 모델에 "도구 목록(JSON)"을 주고, 모델이 호출을 출력하면 파싱해 실제 함수를 실행한 뒤 결과를 다시 모델에 넣는 루프를 구현.
배울 것: Objective-C++ 브리지로 마이크 권한·카메라·화면 캡처, AppleScript로 시스템 제어, 그리고 시그널 핸들러 기반 크래시 복구·breadcrumb 폴백·CMake LTO/dead_strip·Homebrew 패키징. "데모"와 "배포 제품"의 차이를 만드는 디테일.
실습: macOS에서 AppleScript 한 줄(tell application "Safari" to activate)을 C++ system()이나 NSAppleScript로 실행해 보기.
"macOS 13+ · Apple Silicon 필수. MetalRT는 M3+, 그 외엔 llama.cpp 폴백. 기본 모델 묶음 약 1GB."
| 항목 | 요구사항 / 메모 |
|---|---|
| OS | macOS 13+. 윈도우·리눅스·인텔 맥 불가 |
| 칩 | Apple Silicon 필수(arm64 아니면 설치 즉시 실패). MetalRT GPU 엔진은 M3 이상, M1/M2는 자동 llama.cpp 폴백 |
| 디스크/모델 | 기본 rcli setup ~1GB(LFM2 1.2B 731MB + Whisper + Piper + Silero VAD + 임베딩). MetalRT ~0.9GB, 둘 다 ~1.9GB. VLM은 온디맨드 |
| RAM | 명시 수치 없음. 1.2B Q4 모델 + KV 캐시(기본 ctx 4096) 기준 통합메모리 8GB면 동작 가능 추정(레포에 수치 미기재) |
| 인터넷 | 최초 모델 다운로드 시에만. 이후 추론은 완전 오프라인 |
MetalRT의 화려한 속도 수치는 M3 이상 이야기입니다. M1/M2 사용자는 자동으로 llama.cpp 경로로 떨어지므로(여전히 잘 동작) "벤치마크만큼 빠르지 않다"고 실망하지 마세요. 먼저 rcli setup에서 엔진을 선택하고, rcli metalrt로 내 칩에서 실제 동작하는지 점검하는 게 안전합니다.
"설치·대화 → 문서 RAG → 비전 → 액션 추가 → 미니 음성 루프 만들기."
brew tap RunanywhereAI/rcli ... → brew install rcli → rcli setup으로 모델을 받고, rcli TUI에서 스페이스(푸시투토크)로 질문. 답이 음성으로 돌아오고, rcli ask "open Safari"로 앱이 열리는지 확인.
목표: "오프라인 음성 비서"를 체감. 비행기 모드에서도 동작하는지 시험.
rcli rag ingest ~/Documents/notes로 폴더를 색인하고, rcli ask --rag ~/Library/RCLI/index "내 노트에서 X 알려줘"로 근거 기반 답을 받기. 같은 질문을 RAG 없이도 물어 차이를 비교.
목표: 로컬 RAG가 "근거 있는 답"을 어떻게 만드는지 체감. 색인 폴더 안을 열어 청크가 어떻게 저장됐는지 보기.
rcli vlm photo.jpg "이 사진에 뭐가 있어?" 또는 TUI에서 V(카메라)·S(화면)로 실시간 분석. VLM 모델이 온디맨드로 받아져 로컬에서 이미지+텍스트를 함께 추론하는 과정을 관찰.
목표: "멀티모달 추론이 로컬에서 된다"를 체감. 응답 지연이 텍스트 대비 어떤지 비교.
src/actions/의 기존 액션(예: notes·reminders)을 본떠 새 액션(예: "스포티파이 다음 곡")을 만들고 action_registry에 등록. AppleScript 한 줄과 도구 정의(JSON 스키마)를 연결해 음성으로 호출되게.
목표: tool calling + 액션 레지스트리 구조를 손으로 이해. 실제 오픈소스 기여 후보.
sherpa-onnx(STT/TTS/VAD) + llama.cpp(LLM)를 직접 묶어, 마이크→받아쓰기→LLM→음성합성의 작은 루프를 C++로 조립. 링버퍼 핸드오프와 barge-in(말 끊기) 취소 플래그까지 넣으면 RCLI 뼈대가 거의 완성됩니다.
목표: "듣고→생각하고→말하는" 실시간 파이프라인을 내 것으로. 지연을 줄이는 더블버퍼링을 실험.
"C++ 오디오에서 출발해 STT/TTS·로컬 LLM·RAG·tool calling까지, RCLI를 길잡이 삼아."
| 주차 | 주제 | 핵심 학습 + RCLI 연결점 |
|---|---|---|
| 1주 | 실시간 오디오 + 동시성 | CoreAudio 콜백, 락프리 링버퍼, 생산자-소비자, atomic → core/ring_buffer·audio/audio_io |
| 2주 | STT/TTS/VAD (sherpa-onnx) | Zipformer transducer, Silero VAD, Kokoro/Piper TTS, 더블버퍼 → engines/{stt,tts,vad} |
| 3주 | 로컬 LLM (llama.cpp) | GGUF·Metal 오프로드·KV 캐시·토크나이저·시스템프롬프트 캐싱 → engines/llm·model_profile |
| 4주 | 온디바이스 RAG | HNSW(USearch)·BM25·RRF·시맨틱 청킹·mmap → rag/{hybrid_retriever,vector_index,bm25_index} |
| 5주 | tool calling + Apple 통합 | 2-패스 호출, AppleScript 액션, 시그널 핸들러 복구, Homebrew → tools/·actions/·pipeline/orchestrator |
이 로드맵은 "음성 비서를 부품 단위로 분해해 다시 조립하는" 코스입니다. RCLI는 "온디바이스 AI"라는 추상적 유행어를 실제 C++ 코드(오디오·추론·검색·도구)로 풀어 보여 주는 드문 교재라, 시스템 프로그래밍과 AI 추론을 동시에 익히려는 사람에게 알맞습니다.
"이 문서와 저장소에서 반복되는 음성·추론·검색 용어를 한곳에."
| 용어 | 의미 |
|---|---|
| 온디바이스(on-device) | 클라우드가 아닌 사용자 기기에서 추론을 수행. 프라이버시·오프라인·무과금 |
| MetalRT | RunAnywhere의 Apple Silicon 전용 독점 GPU 추론 엔진(소스 비공개 dylib). M3+에서 동작 |
| llama.cpp / GGML / GGUF | 오픈소스 LLM 추론 백엔드 / 텐서 연산 라이브러리 / 양자화 모델 포맷 |
| sherpa-onnx | k2-fsa의 온디바이스 음성 툴킷. ONNX로 STT/TTS/VAD 제공 |
| Zipformer (transducer) | 스트리밍 STT 모델. encoder+decoder+joiner 3-ONNX의 RNN-T 구조 |
| Silero VAD | Voice Activity Detection. 무음/잡음 구간을 걸러 STT를 효율화 |
| Kokoro / Piper(VITS) | 신경망 TTS 모델. Kokoro 82M은 28개 음색, 더블버퍼 합성 |
| VLM / libmtmd | Vision-Language Model / llama.cpp 멀티모달 모듈. 이미지+텍스트 추론 |
| RAG(하이브리드) | 벡터(의미)+BM25(키워드) 검색을 합쳐 문서 근거 답변 생성 |
| RRF (상호순위융합) | 두 랭킹을 1/(k+rank) 합으로 융합(k=60). 스케일이 다른 검색 결과 결합 표준 |
| USearch (HNSW) | 헤더온리 근사 최근접이웃 벡터 인덱스. mmap 로드로 빠른 쿼리 |
| BM25 | 고전 키워드 랭킹 함수(TF-IDF 계열). 정확한 용어 매칭에 강함 |
| barge-in | TTS 응답 재생 중 사용자가 말을 끊으면 atomic 플래그로 즉시 중단하고 재청취 |
| KV cache continuation | 시스템 프롬프트·도구 정의를 한 번 프리필해 재사용, 유저 입력만 디코드 → 지연 단축 |
| tool calling(2-패스) | 모델이 도구 호출을 출력 → 실행 → 결과 주입 → 재생성. 에이전트가 "손을 쓰는" 메커니즘 |