GitHub 트렌딩 딥다이브 · 2026-06-22

PixelRAG 딥다이브
— 웹을 "읽지" 말고 "스크린샷으로 검색"하기

문서(웹페이지·PDF·이미지)를 텍스트로 뜯어내지 않고, 화면 캡처 그대로 비전 모델에 넣어 검색하는 새로운 RAG. 표·차트·수식·레이아웃이 깨지지 않아 "사람이 눈으로 읽듯" 찾아낸다. 버클리(Berkeley SkyLab) 논문 "Web Screenshots Beat Text for Retrieval-Augmented Generation"의 공식 코드베이스다. (저장소: StarTrail-org/PixelRAG · Python(+소수 TS) · ★28→급상승 · Apache-2.0)

목차
  1. 프로젝트 한줄 요약
  2. 왜 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석
  5. 디렉토리 구조 해부
  6. 학습 포인트 (기술별)
  7. 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한줄 요약

"문서를 글자로 바꾸지 말고, 찍은 사진 그대로 검색한다."

한 줄로

PixelRAG는 웹페이지·PDF를 스크린샷으로 찍어 그 이미지를 그대로 검색하는 RAG 시스템이다.

보통 검색 시스템은 웹페이지의 HTML을 긁어 "글자"만 뽑아낸다. 그 과정에서 표·그래프·수식·페이지 배치가 망가진다. PixelRAG는 발상을 뒤집는다 — 페이지를 사진(픽셀)으로 찍어 비전-언어 모델에게 통째로 보여주고, "이 그림과 가장 비슷한 질문"을 찾는다. 사람이 책장을 넘기며 눈으로 답을 찾는 방식에 가깝다.

여기서 RAG를 먼저 풀어두자. 챗봇(LLM)은 자기가 학습한 것만 알기 때문에, 모르는 최신 정보나 사내 문서를 물으면 지어낸다. 그래서 "질문에 관련된 문서를 먼저 찾아와서(Retrieval), 그걸 같이 넣어주고 답을 생성(Generation)"하게 만든다. 이 "검색 + 생성" 묶음이 RAG다. PixelRAG는 그 중 '찾아오는' 부분을 텍스트가 아니라 이미지로 한다.

용어
RAG (Retrieval-Augmented Generation) — 검색 증강 생성
LLM에게 답하기 전에 관련 문서를 먼저 검색해 함께 넣어주는 방식. "오픈북 시험"이라고 보면 된다 — 외운 것만 쓰지 말고, 책에서 찾아 보고 답하라는 것. 보통은 문서를 글자로 쪼개 검색하지만, PixelRAG는 문서를 이미지로 검색한다.
용어
VLM (Vision-Language Model) — 비전-언어 모델
그림과 글자를 동시에 이해하는 AI. 사진을 보여주면 "여기 표에 매출 1억이라 써 있네"처럼 읽어낸다. PixelRAG는 Qwen3-VL-Embedding-2B라는 VLM을 써서, 스크린샷을 숫자 벡터(embedding)로 바꾼다.

핵심 슬로건은 README 첫 줄 그대로다 — "The end of web parsing(웹 파싱의 종말)." 즉, 웹페이지를 분해(parsing)해 텍스트로 만드는 고전적 방법을 더 이상 쓰지 말자는 선언이다. 벤치마크의 기준은 위키백과 전체(약 828만 문서)이지만, 시스템 자체는 어떤 문서 더미에도 쓸 수 있는 범용 도구다.

2왜 주목받는가

텍스트 파싱이 잃어버리는 것 — 표·차트·레이아웃을, 픽셀은 안 잃는다.

RAG의 오랜 약점은 "문서를 글자로 바꾸는 첫 단계"에 있었다. 잘 만든 보고서 PDF를 텍스트로 추출해 보면, 깔끔하던 표가 숫자 죽처럼 엉키고, 그래프는 통째로 사라지며, 2단 편집은 줄이 뒤섞인다. 정보의 30~50%가 '글자가 아닌 형태'로 담겨 있는데, 텍스트 파싱은 그걸 버린다. PixelRAG는 이 버려지던 정보를 픽셀 그대로 보존한다.

기존 방식의 함정
"HTML → 텍스트" 파이프라인이 조용히 버리는 것들

표의 행·열 관계, 막대그래프의 높이, 수식, 페이지 레이아웃, 이미지 안 글씨(인포그래픽)… 이 모두가 텍스트 추출 단계에서 사라진다. 게다가 사이트마다 HTML 구조가 달라 파서를 끝없이 손봐야 한다. "표가 중요한 질문"에서 텍스트 RAG가 약한 이유가 이것이다.

PixelRAG의 해결
"페이지를 사진으로" 찍으면 파서가 필요 없다

어떤 사이트든 똑같이 스크린샷 한 장으로 처리된다 — 사이트별 파서가 0개. 표·그래프·레이아웃이 그림 안에 그대로 살아 있고, 비전 모델이 사람처럼 "보고" 이해한다. 실제 벤치마크에서 표가 많은 질문(NQ-Tables)·사실 검색(SimpleQA)에서 텍스트 RAG를 앞선다.

숫자로 보는 효과 (논문 Table 1, 리더 모델 Qwen3.5-4B, 상위 3개 문서)

아래는 같은 질문 세트에 대해 "검색을 어떻게 했느냐"만 바꿔 정답률을 잰 것이다. naive=검색 거의 없음, Trafilatura=고전 텍스트 추출 RAG, base=PixelRAG(스크린샷), LoRA=PixelRAG를 위키 스크린샷에 맞게 미세조정한 버전.

벤치마크naive텍스트(Trafilatura)PixelRAG basePixelRAG LoRA
NQ (일반 질문)30.455.957.958.7
NQ-Tables (표 질문)24.542.547.048.8
SimpleQA (사실 검색)7.071.673.878.8
EVQA (시각 QA)27.229.640.745.1
LiveVQA (실시간)63.659.070.370.0

표·시각 질문(NQ-Tables·EVQA·LiveVQA)에서 격차가 특히 크다 — 바로 텍스트 파싱이 약한 영역이다. 여기에 더해 트렌딩을 끌어올린 두 번째 이유가 있다: "AI 에이전트에게 눈을 달아주는" Claude Code 플러그인(pixelbrowse)을 함께 낸 것. 코딩 에이전트가 웹페이지를 글자가 아니라 스크린샷으로 직접 보게 해주는 도구라, 에이전트 개발자 사이에서 화제가 됐다(8번 섹션에서 직접 써본다).

비유

텍스트 RAG = 책을 OCR로 긁어 메모로 검색 — 표·그림은 메모에서 빠진다.
PixelRAG = 책 페이지를 사진으로 찍어 앨범에서 검색 — 펼친 모습 그대로라 표도 그림도 다 남아 있다. "정확히 어느 페이지였더라?"를 사진첩 넘기듯 찾는다.

3기술 스택 전체 지도

렌더링은 가볍게(토치 없음), 임베딩·검색은 무겁게(GPU·FAISS). 단계별로 의존성을 쪼갰다.

PixelRAG는 uv(빠른 파이썬 패키지 매니저)로 묶인 파이썬 프로젝트다. 영리한 점은 단계마다 필요한 라이브러리만 따로 설치하게 쪼갠 것 — 스크린샷만 찍고 싶은 사람은 무거운 PyTorch를 안 깔아도 된다. 아래가 "어느 단계에 무엇이 쓰이는지" 지도다.

레이어 / 단계핵심 기술역할
언어·런타임Python ≥3.12 · uv · hatchling전체 코드. uv로 의존성 관리, hatchling으로 빌드.
① 렌더(가벼움)Chrome DevTools Protocol · websockets · pillow · pymupdf · pyturbojpeg헤드리스 크롬을 직접 조종해 페이지를 JPEG 타일로 캡처. PyTorch 없음.
② 임베딩(무거움)PyTorch ≥2.9 · transformers ≥4.57 · Qwen3-VL-Embedding-2B · vLLM/SGLang타일 이미지를 2048차원 벡터로 변환. GPU 다중 사용.
③ 인덱스FAISS (faiss-cpu / faiss-gpu-cu12) · numpy수천만 개 벡터를 빠르게 검색하는 색인 구축.
④ 서빙FastAPI · uvicorn · pydantic · qwen-vl-utils검색 API 서버. 질문을 벡터로 바꿔 FAISS에 질의.
⑤ 학습(별도 프로젝트)PyTorch 2.9.1+cu129 · PEFT(LoRA) · accelerate · wandb임베딩 모델을 스크린샷에 맞게 미세조정. 독립된 uv 환경.
대규모 분산boto3(S3) · libzim(Kiwix)여러 머신이 S3로 작업을 나눠 위키 전체를 캡처.
프론트(웹)Next.js (web/ 폴더, 별도)pixelrag.ai 데모 사이트. 본체와 분리.
용어
FAISS — Facebook AI Similarity Search
메타(페이스북)가 만든 벡터 검색 라이브러리. "이 벡터와 가장 비슷한 벡터 10개 찾아줘"를 수천만 개 중에서도 밀리초 단위로 해낸다. PixelRAG의 검색 심장. 위키 인덱스는 약 2,800만 벡터 · 215GB 규모다.
용어
임베딩(embedding) · 2048차원 벡터
이미지나 글의 "의미"를 숫자 목록(벡터)으로 압축한 것. 비슷한 의미면 벡터도 가까이 모인다. PixelRAG는 스크린샷 한 조각을 길이 2048짜리 숫자 줄로 바꾼다 — 이게 검색의 좌표가 된다.
용어
CDP (Chrome DevTools Protocol)
크롬 브라우저를 프로그램이 원격 조종하는 약속(프로토콜). "이 페이지 열어 · 스크롤해 · 스크린샷 찍어"를 코드로 명령한다. PixelRAG는 무거운 Playwright 대신 이걸 웹소켓으로 직접 말해 더 빠르게 캡처한다.

왜 이렇게 단계별로 쪼갰을까? 위키 전체를 다루는 시스템이라 각 단계의 규모와 하드웨어가 완전히 다르기 때문이다. 렌더는 CPU만 많으면 되고(GPU 불필요), 임베딩은 GPU가 핵심이며, 검색은 메모리(RAM)가 관건이다. 한 덩어리로 묶으면 "스크린샷만 찍고 싶은데 PyTorch 2GB를 깔아야 하는" 낭비가 생긴다. 그래서 pip install 'pixelrag[embed]'처럼 필요한 단계만 골라 설치한다.

4아키텍처 심화 분석

먼저 전체 그림 한 장 → 그다음 "질문 하나가 답이 되기까지"를 한 줄기로 따라간다.

PixelRAG는 5단계 파이프라인이다. 문서가 들어가 → 그림이 되고 → 숫자가 되고 → 색인이 되고 → 검색된다. 먼저 숲을 보자.

┌─────────────────────────────────────────────────────────────┐ │ 문서 (웹 URL · PDF · 이미지) │ └───────────────┬─────────────────────────────────────────────┘ │ ① RENDER (pixelshot — 헤드리스 크롬 + CDP) ▼ 타일 이미지 tile_0000.jpg … (875 × 8192 px) + tiles.json │ ② CHUNK (긴 타일을 1024px 조각으로 절단) ▼ 조각 chunk_*.png (시각 토큰 약 8배 절감) │ ③ EMBED (Qwen3-VL-Embedding-2B, GPU) ▼ 벡터 .npz embeddings float16 [N, 2048] + 메타데이터 │ ④ BUILD-INDEX (FAISS IndexIVFFlat) ▼ index.faiss + metadata.npz + summary.json │ ⑤ SERVE (FastAPI /search) ▼ ┌─────────────────────────────────────────────────────────────┐ │ 질문 → (같은 모델로) 질문 벡터 → FAISS 검색 → 타일 반환 │ └─────────────────────────────────────────────────────────────┘

이 다섯 단계는 모두 하나의 명령어 pixelrag로 부른다(예: pixelrag embed, pixelrag serve). 단, 1단계 캡처만은 별도 명령 pixelshot이다. 이제 대표 흐름 한 줄기를 입력부터 출력까지 손으로 따라가 보자.

흐름 ① — "웹페이지 한 장이 검색 가능한 벡터가 되기까지"

# ① RENDER — 헤드리스 크롬을 CDP로 조종해 캡처
1. 페이지 로드   크롬을 띄우고(viewport 폭 875px) URL 접속
2. 준비 대기     load → 네트워크 잠잠 → document.fonts.ready → rAF 2번(최대 1초)
3. 타일 캡처   페이지를 위→아래로 8192px씩 끊어 JPEG로 저장
                 (각 타일 직전 그 위치로 스크롤 — 안 보이면 크롬이 안 그림)
                 → tile_0000.jpg, tile_0001.jpg … + tiles.json(높이·목록)

# ② CHUNK — 긴 타일을 1024px 조각으로 자른다
4. 절단          8192px 타일 1장 → 1024px 조각 8개 (chunk_XXXX_YY.png)
                 왜? 조각이 작을수록 비전 "토큰" 수가 8배 줄어 처리량↑

# ③ EMBED — 비전 모델로 숫자 벡터화 (GPU)
5. 임베딩     각 조각을 Qwen3-VL-Embedding-2B에 통과
                 마지막 토큰 추출(last-token pool) + 길이1로 정규화(L2)
                 → embeddings float16 [N, 2048]  (.npz로 저장)

# ④ BUILD-INDEX — FAISS 색인 구축 (CPU, 일부 GPU)
6. 색인       50만 개 표본으로 군집(k-means) 학습 → 벡터 전부 추가
                 → index.faiss (IndexIVFFlat, 코사인=내적, nlist 4096)

여기서 가장 영리한 두 결정을 짚자. (1) 조각내기(chunk) — 비전 모델은 큰 그림일수록 시각 토큰(이미지를 잘게 나눈 처리 단위)을 많이 쓴다. 8192px 타일을 통째로 넣으면 토큰이 폭발한다. 1024px 조각으로 자르면 토큰이 약 8배 줄어 같은 GPU로 8배 빠르게 처리된다. (2) 정규화 + 내적 — 벡터를 길이 1로 맞추면(L2 정규화), 두 벡터의 내적(inner product)이 곧 코사인 유사도가 된다. 그래서 FAISS를 "내적" 모드로 돌리면 "방향(의미)이 가장 비슷한" 조각을 찾는 검색이 된다.

용어
타일(tile)과 조각(chunk)
타일=긴 웹페이지를 화면 높이로 끊은 큰 캡처(875×8192px). 조각=그 타일을 다시 1024px씩 자른 작은 단위. 검색·임베딩은 "조각" 단위로 한다. 위키 전체는 약 3,000만 개의 1024×1024 조각(≈5.6TB)이 된다.

흐름 ② — "질문을 던지면 답 타일이 나오기까지"(서빙)

# ⑤ SERVE — FastAPI 검색 서버
1. 질문 인코딩  "프랑스 수도는?"을 같은 Qwen3-VL 모델로 벡터화
                 (인덱스와 똑같이: 마지막 토큰 + L2 정규화 → 코사인 일치)
2. FAISS 검색    index.search(질문벡터, k) → 가장 가까운 조각 id들
3. 결과 해석  각 id를 (article_id, tile_index, chunk_index)로 풀어
                 실제 타일 이미지 경로/URL로 변환
4. 응답          타일 이미지(또는 base64)·문서 정보를 JSON으로 반환
# 실제 호출 예시 (README)
curl -X POST http://localhost:30001/search \
  -H "Content-Type: application/json" \
  -d '{"queries": [{"text": "What is the capital of France?"}], "n_docs": 5}'

질문을 벡터로 바꿀 때 인덱스를 만들 때와 토씨 하나 안 틀리게 같은 방식(마지막 토큰 풀링 + L2 정규화)을 쓴다는 게 핵심이다. 그래야 질문 벡터와 문서 벡터가 같은 좌표계에 놓여 "코사인 유사도 1.0 = 완벽히 같은 방향"이 의미를 갖는다. GPU에서 질문 하나 인코딩은 약 42ms, FAISS 검색은 CPU에서 처리된다.

용어
FAISS IndexIVFFlat · nlist · nprobe
2,800만 벡터를 전부 일일이 비교하면 느리다. IVF는 벡터를 미리 nlist개(여기선 4096개) 군집으로 나눠두고, 검색 때는 질문과 가까운 nprobe개(기본 128개) 군집만 본다 — "도서관 전체 대신 관련 서가 몇 개만 뒤지기". 정확도(많이 보기)와 속도(적게 보기)의 손잡이가 nprobe다.

설계 패턴 — 이 레포에서 "정상"이 뭔지

코드를 처음 열면 낯선 관례가 몇 개 보인다. 미리 알아두면 길을 덜 잃는다. (가) render·embed·index·serve는 폴더가 나뉘어 있지만 사실 하나의 패키지다(루트 pyproject.toml 하나가 다섯 소스 폴더를 한 휠로 묶는다). (나) 오직 train/만 진짜 별도 프로젝트라 자체 pyproject.toml과 고정된 PyTorch 버전을 갖는다 — 학습은 cd train 후 따로 설치한다. (다) pixelrag <단계> 명령은 내부의 STAGES 표를 보고 해당 모듈을 그때그때 불러온다 — 그래서 안 깐 단계를 부르면 "pip install 'pixelrag[...]' 하세요" 안내가 뜬다.

초보자 함정
"폴더가 5개니 패키지도 5개"라고 보면 헷갈린다

render/embed/index/serve 폴더에는 pyproject.toml이 없다. 이들의 의존성은 루트의 "extras"([embed]·[serve]…)로 정의되고, 진입점은 src/pixelrag/cli.pySTAGES 표가 분배한다. 자체 설정 파일을 가진 건 train/ 하나뿐이다. 이 구조를 모르면 "왜 render/pyproject.toml이 없지?"에서 막힌다.

5디렉토리 구조 해부

폴더 이름이 곧 파이프라인 단계다. 다섯 단계 + 학습 + 플러그인.

최상위 폴더를 "단계 순서"로 읽으면 구조가 한눈에 들어온다. 문서가 들어가 검색되어 나오는 길을 따라 폴더가 배치돼 있다.

PixelRAG/ ├── src/pixelrag/ ★ 우산 CLI (pixelrag <단계> 분배 — cli.py의 STAGES 표) ├── render/ ① 캡처: 문서 → 타일 이미지 │ └── src/pixelrag_render/ │ ├── render.py 공개 API(render_url) + pixelshot 명령 │ ├── chrome.py 크롬 탐색 / 터보 가능 여부 판단 │ ├── backends/ cdp.py(표준) · fast_cdp.py(터보) · pdf.py │ └── strategies/ 캡처 전략 실험 ~18종(파이프라인·멀티탭 등) ├── embed/ ② 임베딩 + ③ 색인 │ └── src/pixelrag_embed/ │ ├── chunk.py 타일 → 1024px 조각 절단 │ ├── embed.py Qwen3-VL로 조각 → 벡터(.npz) │ └── index.py FAISS IndexIVFFlat 구축 ★검색 심장 ├── index/ 오케스트레이터(전체 파이프라인을 한 번에) │ └── src/pixelrag_index/ │ ├── pipelines.py source→ingest→chunk→embed→index 묶음 │ ├── sources/ local · web · pdf · kiwix(위키 ZIM) │ └── distributed.py S3 기반 다중 머신 작업 분배 ├── serve/ ④ 서빙 │ └── src/pixelrag_serve/ │ ├── api.py FastAPI 검색 서버(/search 등, 766줄) │ └── render_ondemand.py 요청 시 ZIM에서 타일 즉석 생성 ├── train/ ⑤ 학습 (★독립 uv 프로젝트 — 자체 pyproject) │ ├── README.md LoRA 미세조정 레시피 │ └── recipes/ · sft/ 절제 실험 · 리더 모델 학습 ├── plugin/ pixelbrowse — "에이전트에게 눈을" Claude Code 플러그인 │ ├── commands/screenshot.md /screenshot 슬래시 명령 │ └── skills/pixelbrowse/ 스킬 정의(SKILL.md) ├── eval/ 벤치마크 재현(Table 1) · 리트리버 25종 ├── docs/ 처리량 최적화 · 논문 재현 문서 ├── web/ Next.js 데모 사이트(pixelrag.ai) ├── pyproject.toml ★ 루트 — 5개 소스를 한 휠로 묶고 extras 정의 └── uv.lock 잠금 파일(465KB)
위치한 줄 역할
src/pixelrag/cli.py우산 명령. pixelrag embed/serve…를 받아 해당 모듈로 분배.
render/…/backends/cdp.py크롬을 웹소켓 CDP로 직접 조종해 페이지를 JPEG 타일로 캡처.
embed/…/chunk.py8192px 타일을 1024px 조각으로 절단(토큰 8배 절감의 출발).
embed/…/index.pyFAISS 색인 빌더. IndexIVFFlat·내적·nlist 4096. 검색의 핵심.
serve/…/api.py검색 API. 질문 인코딩 → FAISS 질의 → 타일 반환.
index/…/pipelines.py다섯 단계를 한 번에 돌리는 오케스트레이터.
index/…/distributed.pyS3로 여러 머신이 작업을 나눠 위키 전체를 캡처.
train/유일한 별도 프로젝트. 임베딩 모델 LoRA 미세조정.
plugin/Claude Code가 웹을 스크린샷으로 보게 하는 pixelbrowse 플러그인.

6학습 포인트 (기술별)

이 레포 하나로 "멀티모달 검색 + 헤드리스 자동화 + FAISS + LoRA + 대규모 분산"을 한꺼번에.

PixelRAG는 단일 기술 예제가 아니라 현대 AI 검색 시스템의 부품이 다 모인 교과서다. 분야별로 무엇을 건질지 짚는다.

① 멀티모달(비전) 검색 · 임베딩

"이미지를 검색 좌표로" 만드는 실전

VLM(Qwen3-VL-Embedding-2B)으로 스크린샷을 벡터화하고, last-token 풀링 + L2 정규화로 코사인 검색을 만드는 전 과정을 본다. 텍스트 임베딩만 알던 사람이 멀티모달 임베딩으로 넘어가는 가장 좋은 실물 예제. ColPali 계열(시각 문서 검색)의 사고방식을 코드로 익힌다.

실습: embed/src/pixelrag_embed/embed.py에서 조각이 벡터가 되는 함수를 찾아, 왜 정규화가 코사인을 만드는지 따라가기.

② 헤드리스 브라우저 자동화 (CDP)

Playwright 없이 크롬을 "맨손으로" 조종하기

무거운 Playwright 대신 웹소켓으로 CDP를 직접 말해 페이지를 캡처한다. 페이지가 "다 그려졌는지" 판단하는 트릭(fonts.ready·rAF 2번·스크롤 후 대기)이 특히 알짜다 — 웹 크롤링·스크린샷 자동화를 하는 누구에게나 보석 같은 코드. 화면 밖 영역은 크롬이 안 그려서 타일이 빈다는 함정과 그 해결도 본다.

실습: render/…/backends/cdp.py의 "준비 대기" 로직을 읽고, 왜 rAF를 1초로 제한했는지 주석에서 찾기.

③ FAISS 대규모 벡터 검색

2,800만 벡터를 메모리에 올리고 밀리초로 검색

IndexIVFFlat 구축의 정석을 본다 — 50만 표본으로 k-means 군집 학습, GPU로 학습/CPU로 추가(VRAM이 30M×2048을 못 담으니 10만 개씩 배치로), nlist·nprobe 튜닝. "벡터 DB"를 라이브러리로 직접 다루는 경험은 RAG 엔지니어의 필수 근육이다.

실습: embed/…/index.pybuild_ivf()에서 nlist=4096·nprobe=128이 왜 그 값인지 주석 확인 후, 작은 데이터로 nprobe를 바꿔 정확도·속도 비교.

④ LoRA 미세조정 (PEFT)

21억 모델을 2,500만 파라미터만 손대 길들이기

train/에서 LoRA로 임베딩 모델을 위키 스크린샷에 맞춘다. 전체(2.15B)가 아니라 1.18%(2,540만)만 학습하는 효율, hard negative(헷갈리는 오답)·텍스트 워밍업·GradCache(메모리 절약) 같은 대조학습 기법을 실제 명령어로 본다. 공개된 어댑터(102MB)로 재학습 없이 결과만 써볼 수도 있다.

실습: train/README.md의 학습 명령에서 --lora-vit를 빼면 학습 파라미터가 어떻게 변하는지(2,540만 → 1,280만) 비교.

⑤ 대규모 분산 파이프라인 · 에이전트 도구

위키 전체를 하루에 캡처하는 엔지니어링

S3로 여러 머신이 작업을 claim/heartbeat로 나눠 갖는 분산 코디네이터(distributed.py), asyncio work-stealing 큐로 크롬 48개를 굴리는 처리량 최적화(초당 100타일+)를 본다. 덤으로 plugin/에서 코딩 에이전트에게 "눈"을 다는 Claude Code 플러그인 설계도 학습.

실습: docs/screenshot-throughput-optimization.md를 읽고, 왜 base64·웹소켓 전송을 버리고 rawFilePath로 디스크에 바로 쓰는 게 빠른지 정리.

7시스템 요구사항

"맛보기"는 노트북도 OK. "위키 전체"는 데이터센터급. 단계마다 천차만별.

PixelRAG의 요구사항은 무엇을 하느냐에 따라 극단적으로 갈린다. 미리 만든 위키 인덱스로 검색만 해보는 건 가볍지만, 위키 전체를 직접 캡처·임베딩하는 건 수십 TB 디스크와 다중 GPU가 필요하다.

용도요구사항
스크린샷만(렌더)크롬 + CPU. GPU·PyTorch 불필요. 노트북에서도 동작. 터보 캡처는 패치된 headless_shell 필요.
질문 인코딩·서빙GPU 1장(Qwen3-VL-Embedding-2B 로드). 질문 1건 인코딩 ≈ 42ms. macOS는 MPS, 없으면 CPU로도 가능(느림).
미리 만든 위키 인덱스 검색FAISS 인덱스 215GB를 메모리에 — 대용량 RAM 서버. 인덱스는 HuggingFace(StarTrail-org/pixelrag-faiss-indexes)에서 받음.
임베딩(대량)다중 GPU(--gpu-ids 0,1,2,3) + vLLM/SGLang. bf16 추론.
LoRA 학습40GB+ GPU(H100/A100) + 리더용 GPU 1장 더(vLLM). 디스크 ≈ 95GB(학습 이미지). PyTorch 2.9.1+cu129 고정.
위키 전체 캡처(분산)고코어 CPU 서버(예: 128코어 EPYC) 여러 대. 타일 ≈8.5TB(JPEG) ~ 5.6TB(PNG 조각). 4대면 ~하루.
API 키(학습 시)OPENAI_API_KEY(채점용 gpt-4.1) + WANDB_API_KEY(로깅, offline 가능).

가장 가벼운 출발점은 "미리 만든 인덱스로 검색"이 아니라 사실 "내 문서 몇 개를 직접 렌더·임베딩"이다(위키 215GB는 개인이 감당하기 어렵다). README의 "Build an index from local documents"가 그 길로, my_docs/ 폴더 하나로 작은 인덱스를 만들어 검색까지 끝낼 수 있다 — GPU 1장이면 충분하다.

8직접 해볼 수 있는 실습 과제

"에이전트에게 눈 달기"는 10분, "내 문서 검색 엔진"은 한나절. 난이도 순.

실습 1 난이도 ★☆☆ 입문

웹페이지를 스크린샷 타일로 찍어보기

임베딩·GPU 없이 렌더 부분만 설치(pip install pixelrag)하고, pixelshot https://news.ycombinator.com --output ./tiles로 페이지를 JPEG 타일로 캡처한다. tiles.jsontile_0000.jpg들이 어떻게 생겼는지 직접 본다.

목표: "긴 페이지가 어떻게 타일로 쪼개지나"를 눈으로 확인. PyTorch 없이도 1단계가 도는 걸 체감.
실습 2 난이도 ★☆☆ 입문

Claude Code 에이전트에게 "눈" 달아주기 (pixelbrowse)

플러그인을 설치(claude plugin marketplace add StarTrail-org/PixelRAGclaude plugin install pixelbrowse@pixelrag-plugins)하고, /screenshot https://example.com으로 에이전트가 페이지를 글자가 아니라 그림으로 읽게 한다.

목표: 코딩 에이전트가 "본다"는 게 뭔지 체감. 왜 타일 높이를 1568px로 제한하는지(클로드 비전이 그보다 크면 축소해 글씨가 뭉갬) 이해.
실습 3 난이도 ★★☆ 중급

내 문서 폴더로 작은 검색 엔진 만들기

GPU 1장으로 pixelrag[index]를 설치하고, my_docs/에 PDF·이미지 몇 개를 넣어 pixelrag index buildpixelrag serve까지 돌린다. curl로 질문을 던져 어느 타일이 답으로 오는지 확인한다.

목표: render→chunk→embed→index→serve 5단계를 내 손으로 완주. "스크린샷 RAG"를 작은 규모로 완성.
실습 4 난이도 ★★☆ 중급

nprobe를 바꿔 정확도 ↔ 속도 저울 직접 돌리기

실습 3의 인덱스로 /search 요청에 nprobe를 8 → 128 → 2000으로 바꿔가며 같은 질문의 결과·응답시간을 비교한다. "더 많은 서가를 뒤질수록 정확하지만 느려진다"를 수치로 본다.

목표: IVF 검색의 핵심 손잡이를 손으로 체감. 왜 SimpleQA 평가는 nprobe를 2000까지 올리는지 이해.
실습 5 난이도 ★★★ 고급

공개 LoRA 어댑터를 붙여 검색 품질 비교

서빙 시 --peft-adapter로 공개 어댑터(Chrisyichuan/wiki-screenshot-embedding-lorackpt200)를 얹어 merge_and_unload한 뒤, base 모델과 같은 질문 세트의 정답률을 비교한다(논문 Table 1의 base↔LoRA 격차 재현).

목표: 미세조정이 검색을 얼마나 끌어올리는지 직접 측정. PEFT 어댑터를 추론에 합치는 실전 경험.

9관련 기술 심화 학습 로드맵

PixelRAG를 100% 소화하는 8주 코스(주당 6~8시간 가정).

주차주제학습 자료 / 할 일
1주차RAG·임베딩 기초벡터·코사인 유사도·임베딩 개념. 텍스트 RAG 한 번 만들어보기.
2주차멀티모달·VLMQwen3-VL 계열 개요, CLIP/ColPali 등 "이미지 검색" 흐름. 실습 1.
3주차헤드리스 브라우저 · CDPChrome DevTools Protocol 기초. render/…/cdp.py 정독. 실습 2.
4주차임베딩 파이프라인chunk.py·embed.py 읽기. 시각 토큰·정규화 원리. 실습 3 전반.
5주차FAISS 벡터 검색IVF·Flat·PQ 차이, nlist/nprobe 튜닝. index.py 정독. 실습 4.
6주차검색 서버(FastAPI)serve/…/api.py의 /search·/tile 흐름. 질문 인코딩 일치성.
7주차대조학습 · LoRAhard negative·GradCache·PEFT. train/README.md. 실습 5.
8주차대규모·분산처리량 최적화 문서 + distributed.py(S3 코디네이터). 위키 재현 읽기.

10핵심 키워드 사전

이 문서와 레포에 등장한 용어를 한자리에.

용어의미
PixelRAG문서를 스크린샷(픽셀)으로 찍어 그 이미지로 검색하는 RAG. 버클리 논문의 공식 코드.
RAG검색 증강 생성. LLM이 답하기 전에 관련 문서를 검색해 함께 넣는 방식("오픈북 시험").
VLM비전-언어 모델. 그림과 글자를 동시에 이해하는 AI. 여기선 Qwen3-VL.
임베딩이미지/글의 의미를 숫자 벡터(여기선 길이 2048)로 압축한 것. 검색의 좌표.
타일(tile)긴 페이지를 화면 높이로 끊은 캡처(875×8192px).
조각(chunk)타일을 다시 1024px씩 자른 단위. 임베딩·검색의 실제 단위(토큰 8배 절감).
시각 토큰VLM이 이미지를 잘게 나눠 처리하는 단위. 그림이 클수록 많아짐 → 조각내 줄임.
FAISS메타의 벡터 검색 라이브러리. 수천만 벡터 중 유사한 것을 밀리초로 찾음.
IndexIVFFlatFAISS 색인 종류. 벡터를 nlist개 군집으로 나눠 일부(nprobe)만 검색해 빠름.
nlist / nprobe군집 개수(4096) / 검색 시 살펴볼 군집 수(기본 128). nprobe가 정확도↔속도 손잡이.
코사인 유사도 / 내적두 벡터의 방향이 얼마나 같은지. 길이1로 정규화하면 내적=코사인이 됨.
CDPChrome DevTools Protocol. 프로그램이 크롬을 원격 조종하는 약속.
headless 크롬화면 없이 백그라운드로 도는 크롬. 자동 캡처·크롤링에 사용.
Qwen3-VL-Embedding-2BPixelRAG가 쓰는 20억 파라미터 비전 임베딩 모델. 출력 차원 2048.
LoRA큰 모델의 일부(여기선 1.18%)만 학습해 길들이는 효율적 미세조정 기법.
PEFTParameter-Efficient Fine-Tuning. LoRA 등 "소량만 학습" 기법의 묶음(라이브러리).
hard negative대조학습에서 "정답과 헷갈리게 비슷한 오답". 일부러 넣어 모델을 까다롭게 훈련.
GradCache큰 배치를 작게 쪼개 누적해 메모리를 아끼는 대조학습 기법.
vLLM / SGLangLLM을 빠르게 대량 추론하는 서빙 엔진. 임베딩·리더 추론 가속에 사용.
uvRust로 만든 초고속 파이썬 패키지·환경 매니저. 이 레포의 설치·잠금 도구.
Kiwix / ZIM위키백과 등을 오프라인 파일(ZIM)로 묶은 포맷. 위키 전체 캡처의 원본.
pixelbrowse에이전트(Claude Code)가 웹을 스크린샷으로 보게 하는 동봉 플러그인.

11참고 링크

원문·모델·관련 기술 더 파기.