# Chandra OCR 2 — 사내 Document Intelligence 서비스 기술 문서
> **작성일**: 2026-04-15
> **작성자**: AI Engineering Team
> **서비스 서버**: DGX H200 (10.150.6.159)
> **상태**: 운영 중
-----
## 1. 개요
Chandra OCR 2는 Datalab에서 개발한 최신 OCR(Optical Character Recognition) 모델로, 문서 이미지를 구조화된 HTML/Markdown/JSON으로 변환하면서 레이아웃 정보를 보존합니다. olmOCR 벤치마크에서 85.9% 점수로 오픈소스 OCR 모델 중 최고 성능(SOTA)을 기록하고 있으며, 90개 이상의 언어를 지원합니다.
사내에서는 기존 DotsOCR(Qwen3-VL-235B 기반) 파이프라인을 Chandra OCR 2로 교체하여, 동일한 FastAPI 인터페이스(`POST /process-file/`)를 유지하면서 더 높은 정확도와 효율적인 리소스 사용을 실현하고 있습니다.
### 1.1 기존 시스템 대비 개선점
|항목 |기존 (DotsOCR) |현재 (Chandra OCR 2) |
|-----------|---------------------------------|---------------------------|
|추론 모델 |Qwen3-VL-235B (MoE, ~235B params)|Chandra OCR 2 (5B params) |
|모델 크기 |~130GB+ |~10.6GB |
|GPU 요구사항 |멀티 GPU 필수 |단일 GPU (24GB+) |
|출력 형식 |레이아웃 JSON (커스텀 프롬프트) |HTML → Markdown/JSON (네이티브)|
|olmOCR 벤치마크|64.6% (Qwen3-VL-8B 기준) |85.9% |
|다국어 지원 |40+ 언어 |90+ 언어 |
|한국어 정확도 |82.3% |88.7% |
-----
## 2. 모델 소개
### 2.1 Chandra OCR 2란?
Chandra OCR 2는 복잡한 문서 — 손글씨, 테이블, 수식, 양식 등 — 을 높은 정확도로 인식하여 구조화된 디지털 포맷으로 변환하는 OCR 모델입니다. 단순한 텍스트 추출이 아니라, 문서의 공간적 레이아웃(바운딩 박스), 요소 유형(텍스트, 테이블, 수식 등), 읽기 순서를 함께 보존합니다.
### 2.2 모델 아키텍처
Chandra OCR 2는 Alibaba의 Qwen 3.5 비전-언어 모델을 기반(base model)으로 하여, 문서 특화 태스크에 파인튜닝한 모델입니다.
```
┌─────────────────────────────────────────────┐
│ Chandra OCR 2 (5B) │
│ │
│ ┌─────────────┐ ┌────────────────────┐ │
│ │ Vision │ │ Language Model │ │
│ │ Encoder │───▶│ (Qwen 3.5 기반) │ │
│ │ │ │ │ │
│ │ 이미지 입력 │ │ HTML/Markdown 출력 │ │
│ └─────────────┘ └────────────────────┘ │
│ │
│ Fine-tuned on document-specific tasks │
└─────────────────────────────────────────────┘
```
**주요 구성 요소:**
- **Vision Encoder**: 문서 이미지를 시각 토큰으로 인코딩하는 비전 인코더
- **Language Model**: Qwen 3.5 아키텍처 기반 텍스트 생성 모델 (5B 파라미터)
- **Projection Layer**: 비전 토큰을 언어 모델 입력으로 변환하는 투사 레이어
### 2.3 학습 방식
Chandra OCR 2의 학습은 “강력한 범용 기반 모델에서 시작하여, 태스크 특화 데이터로 집중 파인튜닝”하는 방식을 따릅니다.
**기반 모델 (Base Model)**
Qwen 3.5는 Alibaba의 Qwen 팀이 개발한 비전-언어 모델로, 이미지와 텍스트를 함께 이해하고 추론할 수 있는 모델입니다. 이 모델이 Chandra OCR 2의 “인식 백본(perception backbone)“을 제공합니다.
**파인튜닝 (Fine-tuning)**
Datalab은 이 기반 모델을 다양한 카테고리의 대규모 문서 데이터셋으로 파인튜닝했습니다:
- 학술 논문, 기술 보고서
- 손글씨 양식 및 메모
- 복잡한 테이블 (병합 셀, 다중 헤더)
- 수학 수식 (인라인/블록)
- 다국어 문서 (90+ 언어)
- 스캔된 문서 및 팩스
- 다단 레이아웃 (신문, 교과서)
- 양식 (체크박스, 라디오 버튼)
- 차트, 다이어그램
파인튜닝의 핵심은 단순 문자 인식이 아니라 **레이아웃 인식(layout-aware) OCR**에 초점을 맞춘 것입니다. 모델이 “문서가 어떻게 생겼는지”를 내재화하여, 콘텐츠의 존재뿐 아니라 위치와 공간적 관계까지 이해합니다.
### 2.4 모델 스펙
|항목 |값 |
|--------|--------------------------------|
|모델명 |datalab-to/chandra-ocr-2 |
|파라미터 수 |5B (BF16) |
|모델 크기 |~10.6GB |
|기반 모델 |Qwen 3.5 (Vision-Language Model)|
|아키텍처 |qwen3_5 (Transformers) |
|최대 출력 토큰|12,384 |
|정규화 좌표 |0-1000 (bbox) |
|라이선스 |OpenRAIL-M (연구/개인/스타트업 무료) |
### 2.5 벤치마크 성능
**olmOCR Benchmark (영문 문서)**
|모델 |ArXiv |Tables |Multi-column|Overall |
|-------------|--------|--------|------------|--------|
|**Chandra 2**|**90.2**|**89.9**|83.5 |**85.9**|
|dots.ocr 1.5 |85.9 |90.7 |85.3 |83.9 |
|Chandra 1 |82.2 |88.0 |81.2 |83.1 |
|olmOCR 2 |83.0 |84.9 |83.7 |82.4 |
|GPT-4o |53.5 |70.0 |69.3 |69.9 |
|Qwen 3 VL 8B |70.2 |45.6 |62.1 |64.6 |
**다국어 벤치마크 (주요 언어)**
|언어 |Chandra 2|Gemini 2.5 Flash|GPT-5 Mini|
|-----------|---------|----------------|----------|
|한국어 (ko) |81.5% |84.8% |78.4% |
|일본어 (ja) |86.9% |80.0% |76.1% |
|중국어 (zh) |88.7% |70.0% |70.4% |
|영어 (en) |94.8% |88.3% |93.8% |
|평균 (43개 언어)|**77.8%**|67.6% |60.5% |
### 2.6 지원 문서 유형
|유형 |설명 |
|--------|--------------------|
|손글씨 |필기체, 메모, 의사 처방전 등 |
|테이블 |병합 셀, 다중 헤더, 재무제표 |
|수식 |인라인/블록 수식 → LaTeX 변환|
|양식 |체크박스, 라디오 버튼, 입력 필드 |
|다단 레이아웃 |신문, 교과서, 학술 논문 |
|차트/다이어그램|데이터 추출, Mermaid 변환 |
|화학식 |SMILES 표기 변환 |
-----
## 3. 시스템 아키텍처
### 3.1 전체 구성도
```
외부 클라이언트
│
POST /process-file/
│
▼
┌─────────────────────────────────────────────────────────────┐
│ DGX H200 서버 (10.150.6.159) │
│ │
│ ┌──────────────────────┐ │
│ │ FastAPI 서비스 │ ← 호스트 실행 (uvicorn :10001) │
│ │ - 파일 업로드/검증 │ │
│ │ - 복호화 요청 │──→ 복호화 서버 (10.150.6.47:9001) │
│ │ - Office→PDF 변환 │──→ LibreOffice (libre1~4) │
│ │ - 이미지 전처리 │ │
│ │ - Chandra OCR 요청 │ │
│ │ - 결과 파싱/변환 │ │
│ └──────────┬───────────┘ │
│ │ http://localhost:10002 │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ vLLM 서버 (Docker) │ ← GPU #3 (H200 141GB) │
│ │ - chandra-ocr-2 모델 │ │
│ │ - OpenAI-compatible │ │
│ │ - 컨테이너 내부 :8000 │ │
│ │ - 호스트 :10002 │ │
│ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 3.2 처리 파이프라인
```
1. 파일 업로드
└→ MIME 타입 검증, 크기 제한 (10MB)
2. 복호화
└→ 10.150.6.47:9001 복호화 서버 호출
└→ 이미지 파일은 복호화 생략
3. 파일 변환
└→ Office 파일 (docx, pptx 등) → LibreOffice 컨테이너로 PDF 변환
└→ PDF → PyMuPDF로 페이지별 이미지 추출
4. 이미지 전처리
└→ 해상도 리사이징 (min: 100,352px / max: 1,003,520px)
5. Chandra OCR 추론
└→ vLLM 서버에 OCR Layout 프롬프트 + 이미지 전송
└→ HTML 형식 응답 수신 (data-bbox, data-label 포함)
6. 출력 파싱
└→ HTML → Markdown 변환 (수식, 테이블 보존)
└→ HTML → JSON 레이아웃 블록 추출 (bbox, category, text)
└→ HTML → 레이아웃 시각화 이미지 생성
7. 응답 반환
└→ full_markdown, filtered_markdown, page_markdowns,
page_processed_images, json, total_pages
```
### 3.3 구성 요소별 상세
|구성 요소 |기술 스택 |포트 |역할 |
|-----------|-----------------------------|----------------------|---------------------|
|FastAPI |Python 3.12, uvicorn, aiohttp|10001 |API 서버, 파일 처리 오케스트레이션|
|vLLM |vllm-openai:v0.17.0, Docker |10002 (외부) / 8000 (내부)|Chandra OCR 2 모델 서빙 |
|LibreOffice|libre1~4 Docker 컨테이너 |- |Office → PDF 변환 |
|복호화 서버 |별도 서버 (10.150.6.47) |9001 |암호화 문서 복호화 |
-----
## 4. Chandra OCR 2의 추론 방식
### 4.1 프롬프트 구조
Chandra OCR 2는 자체 네이티브 프롬프트를 사용하여 HTML 형식의 레이아웃 출력을 생성합니다. 기존 DotsOCR의 레이아웃 JSON 프롬프트와는 근본적으로 다른 방식입니다.
**OCR Layout 프롬프트 (핵심)**
모델에게 이미지를 HTML 레이아웃 블록으로 OCR하도록 지시합니다. 각 블록은 `
` 태그에 `data-bbox`(바운딩 박스, 0-1000 정규화)와 `data-label`(레이블)을 포함합니다.
**지원 레이블 목록:**
|레이블 |설명 |
|-------------------------|-----------------|
|Text |일반 텍스트 단락 |
|Section-Header |섹션/챕터 제목 |
|Table |테이블 (HTML 구조 보존) |
|Image / Figure |이미지, 도표, 차트 |
|Caption |이미지/테이블 캡션 |
|Footnote |각주 |
|Equation-Block |수식 블록 (LaTeX) |
|List-Group |목록 (순서/비순서) |
|Form |양식 (체크박스, 라디오 버튼)|
|Code-Block |코드 블록 |
|Page-Header / Page-Footer|머리글/바닥글 |
|Table-Of-Contents |목차 |
|Complex-Block |복합 레이아웃 |
|Chemical-Block |화학식 (SMILES) |
|Diagram |다이어그램 (Mermaid) |
|Bibliography |참고문헌 |
### 4.2 모델 출력 형식
Chandra OCR 2는 HTML을 출력하며, 각 레이아웃 블록이 `
` 태그로 구분됩니다:
```html
HBM 수급 전망
```
### 4.3 출력 파싱 파이프라인
모델의 HTML 출력은 세 가지 형태로 변환됩니다:
```
Chandra HTML 출력
│
├─→ parse_markdown() → Markdown 텍스트
│ - 수식: $...$ / $$...$$
│ - 테이블: HTML 유지
│ - 이미지: 설명 텍스트
│
├─→ parse_chunks() → JSON 배열
│ - bbox: 실제 이미지 좌표 (0-1000 → 픽셀)
│ - category: 레이블
│ - text: HTML 내용
│
└─→ draw_layout() → 시각화 이미지
- 레이블별 색상 구분
- 바운딩 박스 오버레이
```
-----
## 5. 배포 구성
### 5.1 서버 환경
|항목 |값 |
|--------------|------------------------------|
|서버 |DGX H200 |
|IP |10.150.6.159 |
|GPU |NVIDIA H200 × 8 (141GB VRAM 각)|
|Chandra 할당 GPU|#3 |
|OS |Ubuntu |
|Docker |24.0+ |
|Docker Compose|v2 |
### 5.2 모델 가중치
```
경로: ~/projects/models/chandra-ocr-2/
파일:
├── config.json
├── generation_config.json
├── model.safetensors # ~10.6GB
├── preprocessor_config.json
├── processor_config.json
├── tokenizer.json
├── tokenizer_config.json
├── chat_template.jinja
└── video_preprocessor_config.json
```
### 5.3 Docker Compose 구성
**vLLM 서버 (GPU)**
```yaml
vllm-server:
image: vllm/vllm-openai:v0.17.0
container_name: chandra-vllm
environment:
- NVIDIA_VISIBLE_DEVICES=3
- CUDA_VISIBLE_DEVICES=3
- HF_HUB_OFFLINE=1
- TRANSFORMERS_OFFLINE=1
command: >
--model /models/chandra-ocr-2
--served-model-name chandra
--dtype bfloat16
--max-model-len 32768
--max-num-seqs 64
--max-num-batched-tokens 8192
--enable-prefix-caching
--gpu-memory-utilization 0.90
--trust-remote-code
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids: ["3"]
capabilities: [gpu]
```
**주요 파라미터 설명:**
|파라미터 |값 |설명 |
|------------------------|-----|--------------------------|
|`max-model-len` |32768|입력+출력 합산 최대 토큰 수 |
|`max-num-seqs` |64 |동시 처리 가능한 시퀀스 수 |
|`max-num-batched-tokens`|8192 |배치당 최대 토큰 수 |
|`gpu-memory-utilization`|0.90 |GPU 메모리 사용률 (90%) |
|`enable-prefix-caching` |true |OCR 프롬프트가 동일하므로 캐싱으로 성능 향상|
### 5.4 FastAPI 서비스
현재 호스트의 가상환경에서 직접 실행 중:
```bash
nohup bash -c 'uvicorn main:app --host 0.0.0.0 --port 10001 \
--workers 20 --log-level info | tee -a uvicorn.log' > nohup.out 2>&1 &
```
컨테이너화 버전도 준비되어 있으며, `chandra-fastapi:latest` 이미지로 전환 가능합니다.
-----
## 6. API 사용 가이드
### 6.1 엔드포인트
```
POST http://10.150.6.159:10001/process-file/
```
### 6.2 요청 파라미터
|파라미터 |타입 |기본값 |설명 |
|----------|----|-----|----------|
|`file` |File|(필수) |업로드할 문서 파일|
|`highqual`|bool|false|고해상도 처리 모드|
### 6.3 지원 파일 형식
|구분 |확장자 |
|----------|-----------------------|
|PDF |.pdf |
|Word |.doc, .docx |
|PowerPoint|.ppt, .pptx |
|Excel |.xls, .xlsx |
|이미지 |.jpg, .jpeg, .png, .bmp|
### 6.4 호출 예시
**Python:**
```python
import requests
import json
API_URL = "http://10.150.6.159:10001/process-file/"
with open("document.pdf", "rb") as f:
response = requests.post(API_URL, files={"file": f})
result = response.json()
# 전체 마크다운
print(result["full_markdown"])
# 페이지별 마크다운
for i, page_md in enumerate(result["page_markdowns"]):
print(f"=== Page {i+1} ===")
print(page_md)
# 레이아웃 JSON
print(json.dumps(result["json"], indent=2, ensure_ascii=False))
```
**curl:**
```bash
curl -X POST http://10.150.6.159:10001/process-file/ \
-F "file=@document.pdf"
```
**고해상도 모드:**
```bash
curl -X POST "http://10.150.6.159:10001/process-file/?highqual=true" \
-F "file=@document.pdf"
```
### 6.5 응답 필드
|필드 |타입 |설명 |
|-----------------------|-----------------|--------------------------------------|
|`job_id` |string |요청별 고유 식별자 (UUID) |
|`filename` |string |업로드된 원본 파일명 |
|`status` |string |처리 상태 (`"processed"`) |
|`total_pages` |int |총 페이지 수 |
|`full_markdown` |string |Base64 이미지 포함 전체 마크다운. 문서 원본 재현용 |
|`filtered_markdown` |string |이미지 제외 텍스트 전용 마크다운. RAG/LLM 입력에 적합 |
|`page_markdowns` |list[string] |페이지별 분리된 마크다운 배열 |
|`page_processed_images`|list[string] |레이아웃 시각화 이미지 (Base64 PNG). OCR 품질 검증용 |
|`json` |list[dict] / dict|구조화된 레이아웃 데이터 (bbox + category + text)|
**JSON 필드 상세:**
```json
{
"bbox": [64, 66, 850, 105],
"category": "Section-Header",
"text": "
HBM 수급 전망
"
}
```
- `bbox`: 바운딩 박스 `[x1, y1, x2, y2]` (픽셀 단위)
- `category`: 레이아웃 블록 유형
- `text`: 해당 블록의 HTML 내용
### 6.6 에러 응답
|HTTP 코드|상황 |
|-------|------------------|
|400 |지원하지 않는 파일 형식 |
|413 |파일 크기 초과 (10MB 제한)|
|500 |내부 서버 오류 |
-----
## 7. 운영 가이드
### 7.1 서비스 상태 확인
```bash
# API 서버 상태
curl http://10.150.6.159:10001/
# vLLM 서버 상태
curl http://localhost:10002/health
# 모델 정보
curl http://localhost:10002/v1/models
# Docker 상태
docker ps | grep chandra
# GPU 사용량
nvidia-smi
```
### 7.2 로그 확인
```bash
# vLLM 로그
docker compose logs -f vllm-server
docker compose logs --tail 100 vllm-server
# FastAPI 로그 (호스트 실행 시)
tail -f uvicorn.log
# FastAPI 로그 (컨테이너 실행 시)
docker compose logs -f fastapi
```
### 7.3 서비스 재시작
```bash
# vLLM 서버 재시작
docker compose down vllm-server
docker compose up -d vllm-server
# FastAPI 재시작 (호스트 실행 시)
kill $(lsof -t -i :10001)
nohup bash -c 'uvicorn main:app --host 0.0.0.0 --port 10001 \
--workers 20 --log-level info | tee -a uvicorn.log' > nohup.out 2>&1 &
```
### 7.4 트러블슈팅
|증상 |원인 |해결 |
|----------------------------|-------------------|-------------------------------------------|
|`context length is only N` |`max-model-len` 부족 |docker-compose.yml에서 값 증가 후 재기동 |
|`'category' KeyError` |DotsOCR 프롬프트 사용 중 |Chandra 네이티브 프롬프트로 교체 확인 |
|`Connection refused` (10002)|vLLM 모델 로딩 중 |로그 확인, 로딩 완료 대기 (1~3분) |
|GPU OOM |동시 요청 과다 |`max-num-seqs`, `gpu-memory-utilization` 조정|
|한글 깨짐 |이미지 해상도 부족 |`highqual=true` 또는 `max_pixels` 증가 |
|Office 변환 실패 |LibreOffice 컨테이너 문제|`docker ps`로 libre1~4 상태 확인 |
-----
## 8. 폐쇄망 배포 절차
사내 폐쇄망 환경에서의 배포는 외부 PC에서 빌드 후 전송하는 방식으로 수행합니다.
### 8.1 전송 파일 목록
|파일 |용량 |설명 |
|----------------------------|-------|---------------------------|
|`vllm-openai-v0.17.0.tar.gz`|~4-5GB |vLLM 서버 Docker 이미지 |
|`chandra-fastapi.tar.gz` |~1GB |FastAPI 서비스 Docker 이미지 (선택)|
|`chandra-ocr-2-model/` |~10.6GB|모델 가중치 |
|`docker-compose.yml` |- |서비스 구성 파일 |
|소스 코드 (*.py) |- |FastAPI 애플리케이션 코드 |
### 8.2 배포 순서
```
[외부 PC (인터넷 가능)]
1. 모델 가중치 다운로드 (huggingface_hub)
2. Docker 이미지 Pull/Build
3. docker save | gzip → tar.gz
4. USB/외장HDD로 전송
[폐쇄망 서버]
5. docker load < *.tar.gz
6. 모델 파일 배치
7. docker-compose.yml 배치
8. docker compose up -d
9. 동작 확인
```
### 8.3 폐쇄망 필수 환경변수
```bash
HF_HUB_OFFLINE=1 # HuggingFace Hub 외부 접속 차단
TRANSFORMERS_OFFLINE=1 # Transformers 외부 접속 차단
```
-----
## 9. 소스 코드 구조
```
~/projects/chandra/scripts/
├── main.py # FastAPI 앱 (엔드포인트 정의)
├── config_chandra.py # 설정 (vLLM 주소, 프롬프트, 허용 타입 등)
├── tool_chandra.py # 문서 처리 핵심 로직 (프롬프트, 파싱, 시각화)
├── utils_chandra.py # 유틸리티 (이미지 처리, PDF 변환, LibreOffice 연동)
├── inference_chandra.py # vLLM 비동기 추론 모듈
├── docker-compose.yml # Docker 서비스 구성
├── Dockerfile # FastAPI 컨테이너 빌드용
└── requirements.txt # Python 의존성
```
|파일 |역할 |
|----------------------|-------------------------------------------------------------------|
|`main.py` |FastAPI 앱. `POST /process-file/` 엔드포인트 정의. 기존 DotsOCR과 동일한 인터페이스 유지|
|`config_chandra.py` |vLLM 서버 주소, 모델명, 토큰 제한, 이미지 리사이징 설정, 복호화 서버 주소 등 |
|`tool_chandra.py` |Chandra 네이티브 프롬프트 정의, HTML→Markdown/JSON 파싱, 레이아웃 시각화, 복호화, 재시도 로직 |
|`utils_chandra.py` |이미지 리사이징, PDF→이미지 변환, LibreOffice 연동, 마크다운 문법 정제 |
|`inference_chandra.py`|vLLM OpenAI-compatible API 비동기 호출 |
-----
## 10. 참고 자료
|항목 |링크 |
|---------------------------|-----------------------------------------------|
|Chandra GitHub |https://github.com/datalab-to/chandra |
|Chandra OCR 2 (HuggingFace)|https://huggingface.co/datalab-to/chandra-ocr-2|
|vLLM 공식 문서 |https://docs.vllm.ai |
|Qwen 3.5 모델 |https://github.com/QwenLM/Qwen3 |
|olmOCR Benchmark |https://github.com/allenai/olmocr |
|Datalab Playground |https://www.datalab.to/playground |