2025년 11월 6일 목요일

LiteLLM 기술 소개

이 글은 LiteLLM 기술을 소개한다.

LIteLLM 필요성
정교한 AI 도구 통합 시스템을 구축한 후에도, 클라이언트가 비용 절감을 위해 OpenAI 모델에서 Claude 모델로의 전환을 요구하는 상황이 발생할 수 있다. 또는 민감한 데이터 처리를 위해 로컬 모델을 사용하고, 일반 질의에는 클라우드 모델을 활용해야 하는 복합적인 요구사항이 존재한다. 적절한 추상화 계층이 부재할 경우, 이러한 변경 사항은 매번 통합 코드를 재작성해야 하는 복잡성을 초래한다.

LiteLLM과 MCP(Model Context Protocol)의 결합은 이러한 문제를 단순한 설정 변경 수준으로 단순화하는 아키텍처를 제공한다. LiteLLM은 보편적인 게이트웨이(Universal Gateway) 역할을 수행하며, MCP는 모델과 도구 간의 상호작용을 표준화한다. 이 통합을 통해 OpenAI, Anthropic, AWS Bedrock 또는 Ollama를 통한 로컬 모델 등 다양한 환경에서도 MCP 기반 도구들이 원활하게 작동한다.

언어 모델과 게이트웨이 기능
MCP 서버는 AI 시스템이 활용할 수 있는 표준화된 도구 집합을 노출한다. 예를 들어, 고객 서비스 MCP 서버는 `get_recent_customers` (최근 고객 목록 조회), `create_support_ticket` (지원 티켓 생성), `calculate_account_value` (계정 가치 분석)와 같은 도구(Tool)를 제공할 수 있다. MCP의 핵심은 이 도구들이 OpenAI, Claude, LangChain 등 MCP 호환 클라이언트를 사용하는 모든 시스템에서 동일하게 작동한다는 점이다. 이는 도구 통합에 대한 표준화의 이점을 명확히 보여준다.

LiteLLM은 언어 모델을 위한 보편적 번역기(Universal Translator)로 기능하는 라이브러리이다. 이는 한 번 작성된 코드가 지원되는 모든 모델에서 실행될 수 있도록 보장한다. 주요 기능으로는 백 개 이상의 다수 모델 지원, 통합된 인터페이스 제공, 요청 분산을 위한 로드 밸런싱, 공급자 전반의 비용 추적, 그리고 장애 발생 시 자동 전환되는 폴백(Fallback) 지원이 포함된다.
LiteLLM은 근본적으로 다양한 LLM 공급자의 API 엔드포인트를 OpenAI의 API 명세와 유사한 표준화된 인터페이스로 추상화한다. 이는 일종의 프록시(Proxy) 서버 역할을 수행하며, 각기 다른 입력 및 출력 형식을 동적으로 변환한다. 이로 인해 개발자는 기본 코드를 변경하지 않고도 백엔드에서 사용하는 모델을 자유롭게 교체할 수 있다.

LiteLLM과 MCP의 결합은 유연성을 지원한다. MCP 도구는 모든 LLM 공급자와 호환되며, 공급자 간 전환 시에도 도구 통합 코드를 변경할 필요가 없다. 이는 공급자 종속성(Vendor Lock-in)을 제거한다. 또한 가장 비용 효율적인 공급자에게 요청을 라우팅하여 비용을 최적화할 수 있다. 민감 데이터는 로컬 모델로, 일반 쿼리는 클라우드 모델로 처리하는 컴플라이언스 준수 아키텍처 구현이 용이해진다.

LiteLLM 기능
LiteLLM은 여러 공급자에 걸쳐 도구 실행 과정을 표준화한다. 모델이 도구 호출(Tool Call)을 반환하면, LiteLLM은 이를 표준 형식으로 수신한다. 이후 MCP를 통해 해당 도구를 실행하고, 그 결과(Tool Result)를 다시 각 공급자가 요구하는 적절한 형식(예: `tool` 역할의 메시지)으로 변환하여 대화 흐름을 완성한다.

이 아키텍처는 다양한 실제 시나리오에 적용 가능하다. 단순 쿼리는 저비용 모델로, 복잡한 쿼리는 고성능 모델로 라우팅하는 비용 최적화 라우팅이 가능하다. 또한 개인식별정보(PII)가 포함된 메시지는 로컬 모델로, 그 외에는 클라우드 모델로 전송하는 규정 준수 기반 라우팅을 구현할 수 있다. LiteLLM은 여러 모델에 대한 폴백 체인을 구성하여, 특정 공급자에서 장애가 발생하더라도 다음 모델로 자동 전환하여 서비스 안정성을 확보한다.


고급 활용 패턴으로, 각 공급자별로 최적화된 매개변수(예: `temperature`, `max_tokens`)를 동적으로 적용할 수 있다. LiteLLM은 `completion_cost` 유틸리티를 통해 요청별 비용을 자동으로 추적하는 기능도 제공한다. 결론적으로, 도구 계층을 MCP로 추상화하고 모델 계층을 LiteLLM으로 추상화함으로써, AI 시스템은 코드 변경 없이 비즈니스 요구사항 변화에 능동적으로 적응할 수 있다. 

레퍼런스

DeepSeek OCR 기능 및 기술

이 글은 DeepSeek OCR 기능 및 주요 기술을 정리한다. 
Deepseek OCR 실행 예시

머리말
DeepSeek-OCR은 이미지나 PDF 문서를 구조화된 마크다운(Markdown) 텍스트로 변환하기 위해 설계된 30억(3B) 매개변수 규모의 비전-언어 모델(VLM)이다. 이는 중국의 AI 기업 DeepSeek AI에 의해 개발되었으며, 이 기업은 DeepSeek Coder 및 LLM 시리즈와 같은 고성용 오픈웨이트 모델을 저비용으로 개발하여 주목받은 바 있다.

전통적인 OCR(광학 문자 인식) 시스템은 문자를 감지하고 단어를 조립한 후 구조를 추론하는 순차적 방식을 사용한다. 이로 인해 복잡한 테이블, 양식, 또는 다단 레이아웃(layout)을 정확하게 인식하는 데 한계가 있었다. DeepSeek-OCR은 이러한 접근 방식과 근본적으로 차별화된다. 이 모델은 전체 문서 페이지를 단일한 시각적 컨텍스트로 취급하며, 레이아웃을 인식하는 비전-언어 과제로 문제를 재정의한다.

핵심 기술
딥시크OCR은 '컨텍스트 광학 압축(contexts optical compression)'을 지원한다. 이 모델은 DeepEncoder라는 시각 인코더와 DeepSeek3B-MoE라는 언어 디코더로 구성된다. 
딥시크OCR 구조

DeepEncoder는 SAM(Segment Anything Model)과 CLIP의 개념을 활용하여 1024x1024 픽셀 이미지에서 생성될 수 있는 수천 개의 시각적 패치(patch)를 단 수백 개의 핵심적인 '비전 토큰(vision token)'으로 압축한다. 이 압축된 시각 정보는 텍스트 정보를 픽셀로 인코딩하는 것이 정보 밀도 측면에서 더 효율적이라는 통찰에 기반한다.

이 압축 기술을 통해 10배의 압축률에서도 97% 수준의 텍스트 복원 정확도를 달성하며, 기존 방식 대비 7배에서 20배 적은 토큰으로 전체 문서를 표현할 수 있다. 결과적으로 LLM 디코더의 제한된 컨텍스트 윈도우(context window) 내에서도 긴 문서 전체의 구조를 이해하고 처리하는 것이 가능해진다.

DeepSeek-OCR은 속도와 정확도를 조절할 수 있는 다양한 프리셋(preset)을 제공한다. Tiny, Small, Base, Large 모드는 입력 이미지 해상도를 조절하여 일반적인 문서 처리에 사용된다. 

성능
성능 측면에서 DeepSeek-OCR은 OmniDocBench와 같은 표준 벤치마크에서 Tesseract, PaddleOCR과 같은 전통적인 오픈소스 OCR 엔진의 성능을 크게 능가한다. 특히 테이블 및 복잡한 구조 인식에서 강점을 보이며, Google Document AI와 같은 상용 API와 경쟁 가능한 수준의 정확도를 제공한다. 고성능 GPU(A6000 등) 환경에서는 페이지당 1초 미만의 추론 속도를 달성할 수 있다.

운영 환경에서 이 모델은 약 6-7GB의 가중치(bf16 기준)를 가지며, CPU 실행도 가능하지만 실질적인 성능을 위해 8GB에서 12GB VRAM을 갖춘 GPU가 권장된다.

프로덕션 환경에서의 효율적인 서빙을 위해 vLLM이 공식적으로 지원된다. vLLM은 PagedAttention이라는 핵심 기술을 사용하는 고성능 LLM 추론 엔진이다. PagedAttention은 운영체제의 가상 메모리 페이징과 유사하게, KV 캐시(Key-Value Cache)를 비연속적인 소규모 '블록(block)' 단위로 분할하여 관리한다. 이는 기존 서빙 방식의 비효율적인 메모리 과다 할당(over-reservation) 및 단편화 문제를 해결하여, 메모리 사용량을 최적화하고 연속적인 배치 처리를 통해 전체 처리량(throughput)을 극대화한다.

의존성
구현 시에는 `pdf2image` 라이브러리(poppler 의존성)를 사용하여 PDF를 페이지별 PNG 이미지로 변환하는 전처리가 필요하다. 이때 입력 DPI(150-180은 속도, 220 이상은 품질) 설정이 결과물의 품질과 속도 간의 중요한 절충점이 된다. 모델 로드 시에는 `trust_remote_code=True` 플래그가 필요하며, `torch.bfloat16` 타입을 사용하여 메모리 사용량을 줄일 수 있다. 추론은 OCR 작업의 결정론적(deterministic) 특성을 고려하여 `do_sample=False`로 설정하는 것이 일반적이다.

레퍼런스

AI 에이전트 프레임웍의 불편한 진실

이 글은 AI 에이전트 프레임웍의 불편한 진실을 경험을 반영해 이야기해보도록 한다. 

에이전트의 기반 LLM의 근본적 한계. 환각
AI 에이전트 프레임워크는 근본적으로 그 기반이 되는 대규모 언어 모델(LLM)의 성능에 종속된다. 에이전트의 자율적 행동, 계획 수립, 도구 사용 결정은 모두 LLM의 추론 능력에서 비롯된다. 그러나 LLM은 '환각(Hallucination)'이라는 고질적인 한계를 지닌다. 환각은 모델이 사실에 근거하지 않거나 맥락과 무관한 정보를 확신을 가지고 생성하는 현상이다. 에이전트 시스템에서 이러한 환각은 단순한 오답을 넘어, 존재하지 않는 API를 호출하려 하거나, 잘못된 사실을 기반으로 후속 계획을 수립하는 등 치명적인 오류로 이어진다.

RAG는 만능 도구가 아니다.
검색 증강 생성(RAG)은 에이전트가 외부 지식에 접근하도록 돕는 핵심 기술로 사용되지만, 이는 만능 해결책이 아니다. RAG 시스템의 효율성은 검색(Retrieval)과 생성(Generation) 두 단계의 품질에 모두 의존한다. 검색 단계에서 벡터 데이터베이스가 사용자의 복잡한 의도와 무관하거나 오래된 정보를 반환할 경우, LLM은 부정확한 컨텍스트를 기반으로 응답을 생성하게 된다. 또한, 원본 문서를 의미론적으로 적절하게 분할(chunking)하는 과정 자체가 복잡한 엔지니어링 문제이다. RAG는 검색된 정보가 LLM의 환각을 억제할 것이라 기대되지만, 모델이 제공된 컨텍스트를 무시하거나 잘못 해석하여 여전히 환각을 일으키는 경우는 빈번하게 발생한다.

블랙박스화된 프레임웍. 간단한 질문 하나로 인해 발생되는 일들?
최신 에이전트 프레임워크는 높은 수준의 추상화를 제공하여 개발자가 복잡한 로직 없이도 에이전트를 구현할 수 있도록 지원한다. 그러나 이러한 추상화는 시스템을 '블랙박스(Black Box)'로 만든다. 사용자의 간단한 질의 하나를 처리하기 위해, 프레임워크 내부에서는 수많은 연쇄 반응이 일어난다. 여기에는 질의 분석을 위한 LLM 호출, 적절한 도구 선택을 위한 추론, 도구 입력값 포맷팅, 실제 도구 실행(API, DB 조회), 결과 파싱, 그리고 최종 응답 생성을 위한 또 다른 LLM 호출 등이 포함된다. 이 과정 중 어느 한 단계에서 오류가 발생할 경우, 추상화된 계층에 가려져 문제의 근본 원인을 파악하고 디버깅하는 것은 극도로 어려워진다.
가려져 있는 수많은 프롬프트

토큰 사용은 숨겨져 있다.
에이전트 프레임워크의 블랙박스 특성은 예측 불가능한 비용 문제로 직결된다. 에이전트가 자율적으로 작동하며 ReAct(Reason-Act) 프롬프트나 CoT(Chain of Thought)와 같은 추론 과정을 거칠 때, 사용자가 인지하지 못하는 수많은 내부적 LLM 호출이 발생한다. 사용자는 단 하나의 질의를 입력했지만, 시스템은 계획 수립, 도구 사용, 중간 평가, 최종 응답 생성을 위해 여러 차례 LLM API와 통신하며 막대한 양의 토큰을 소모한다. 이러한 '숨겨진 토큰 사용량'은 시스템의 운영 비용을 예측 불가능하게 만들며, 프로토타입 단계에서는 드러나지 않았던 비용 문제가 실제 서비스 운영 시 심각한 장애물로 작용한다.

멀티 에이전트과 가난한 인프라의 충돌
최근의 에이전트 연구는 단일 에이전트를 넘어, 여러 전문화된 에이전트가 협업하는 '멀티 에이전트(Multi-Agent)' 시스템으로 확장되고 있다. 그러나 이러한 복잡한 시스템은 막대한 계산 자원과 정교한 인프라를 요구한다. 각 에이전트는 독립적인 추론을 위해 LLM을 호출해야 하며, 에이전트 간의 통신과 조율(orchestration) 과정 역시 추가적인 LLM 호출을 유발한다. 이는 시스템 전체의 지연 시간(latency)을 기하급수적으로 증가시킨다. 소규모 기업이나 개발자가 보유한 제한된 '빈약한 인프라'로는 이러한 복합적인 상호작용을 감당하기 어렵다. 결과적으로, 멀티 에이전트 시스템은 개념적으로는 강력하지만, 극심한 속도 저하와 자원 병목 현상으로 인해 실질적인 프로덕션 환경에 적용되기 어려운 한계에 부딪힌다.

누가 돈을 벌어가나?
현재 AI 에이전트 생태계의 경제적 구조를 살펴보면, 수익은 특정 주체에 집중되는 경향이 있다. 가장 큰 수익은 LLM API를 제공하는 거대 기술 기업(예: OpenAI, Anthropic, Google)이 창출한다. 에이전트 프레임워크가 복잡한 내부 추론을 위해 더 많은 토큰을 소모할수록, 이들 모델 공급자의 매출은 증가한다. 또한, LangChain(LangSmith)이나 LlamaIndex와 같이 에이전트 개발 프레임워크를 제공하는 기업들은 개발 과정을 단순화하는 도구와 관찰 가능성(observability) 솔루션, 엔터프라이즈 지원을 유료화하며 수익을 창출한다. 반면, 이러한 도구를 활용하여 실제 애플리케이션을 구축하려는 다수의 개발자나 기업은 높은 API 비용과 확장성의 한계라는 현실적 장벽에 직면하게 된다.

마무리
결론적으로, AI 에이전트 프레임워크는 자율적으로 작업을 수행하는 AI라는 매력적인 비전을 제시하지만, 그 이면에는 불편한 진실이 존재한다. 현재의 에이전트는 기반 LLM의 환각 문제, RAG 시스템의 취약성, 프레임워크의 불투명성, 그리고 통제 불가능한 토큰 비용이라는 근본적인 한계에 직면해 있다. 이러한 문제들은 에이전트 시스템을 실험적인 프로토타입 수준에서 안정적인 프로덕션 서비스로 이전하는 데 가장 큰 장애물로 작용한다. 진정한 자율 에이전트의 구현은 프레임워크의 발전뿐만 아니라, LLM 자체의 신뢰성 향상과 이를 뒷받침할 수 있는 성숙한 인프라스트럭처의 확보를 전제로 한다.

레퍼런스
부록: 숨겨져 있는 토큰 사용

2025년 10월 29일 수요일

Deepseek 기반 문서 레이아웃 파티셔닝, OCR 파싱 기술 분석 및 멀티모달 데이터셋 파이프라인 개발

이 글은 Deepseek OCR 파싱(Parsing) 기술을 간략히 분석해 설명한다. 

AI 에이전트 개발 시 제일 문제가 되는 것 중 하나가 LLM의 먹이인 컨텍스트를 비정형 데이터에서 추출하는 챌린지이다. 이를 해결하려는 다양한 방법들이 있는 데, 보통은 파서, OCR, 벡터라이징 기술 등 여러가지를 조합해 솔류션을 구현한다. 

최근 오픈소스로 DeepSeek OCR(https://github.com/deepseek-ai/DeepSeek-OCR) 이 릴리즈되었다. 문서 이미지에서 그림, 텍스트, 벡터 등을 인식해 레이아웃을 유지한 체 디지털 데이터로 변환할 수 있다. 


물론 학습된 데이터 기준이겠지만, 다양한 비정형 멀티모달 데이터를 처리하려 노력한 것 같다. 

참고로, OCR을 좀 더 가성비 있게 처리하는 여러 라이브러리가 있다. 아직 이런 VLM은 너무 많은 VRAM 메모리를 사용한다. 

이외 PyMuPDF, pdfplumber, python-doctr[torch], Paddle OCR, pytesseract 등이 성능이 좋다. 단, 각 라이브러리 마다 장단점(예. 레이아웃 파티션만 됨, OCR만 잘됨. 이미지 묘사만 잘 됨. 의존성 높아 설치 난해. 속도 매우 느리나 성능 좋음. GPU 사용해야 함. 버전 개선 없음 등)을 고려해야 한다.

레퍼런스
VRAM 소모: 약 2GB ~ 4GB (8GB 환경에 아주 쾌적함)
특징: 최근 가장 각광받는 오픈소스 경량 AI 레이아웃 모델.
멀티모달 적합성 (최상): 텍스트, 그림, 표, 캡션을 딥러닝 기반으로 분리. 
  • Unstructured (partition_pdf)
VRAM 소모: 약 3GB ~ 5GB (내부적으로 YOLOX나 Detectron2 모델 사용 시)
특징: LLM/RAG 데이터 전처리의 업계 표준(De facto standard).
멀티모달 적합성 (상): 성능은 훌륭하고 범용성이 뛰어나지만, 무거운 편이고 종속성(Dependency) 설치가 꽤 까다로워 가상환경이 꼬이기 쉬움. 
  • PyMuPDF (fitz)
VRAM 소모: 0GB (순수 CPU 구동)
특징: 파이썬 PDF 라이브러리 중 압도적인 처리 속도.
멀티모달 적합성 (중하): AI 딥러닝 방식이 아니라 규칙 기반(Rule-based)으로 좌표를 계산. 도면과 텍스트가 복잡하게 섞인 레이아웃을 논리적으로 분리하는 데는 한계가 명확.
  • pdfplumber
VRAM 소모: 0GB (순수 CPU 구동)
특징: 표(Table) 추출. 문서의 선(Line)을 인식해서 표를 데이터프레임으로 추출.
멀티모달 적합성 (하): 전체 레이아웃을 파티셔닝하는 용도보다는, 특정 페이지에 있는 표의 구조를 유지하며 긁어올 때 쓰는 보조 도구에 가까움. 처리 속도도 매우 느린 편.

2025년 10월 12일 일요일

Fast diffusion LLM 분석 및 주요 동작 메커니즘 확인

최근 Diffusion 모델은 이미지 생성을 넘어 텍스트 생성 분야에서도 주목받고 있다. 특히 구글의 Gemini Diffusion, Inception Labs의 Mercury 등은 초당 1,000 토큰 이상을 생성하는 압도적인 속도를 보여주며 비-오토 리그레시브(Non-Autoregressive, NAR) 모델의 가능성을 증명했다.

NVIDIA에서 발표한 Fast-dLLM은 기존 Diffusion LLM의 추론 속도를 훈련 없이 가속하는 방법에 대한 논문이다. 이 논문은 Key-Value Cache와 병렬 디코딩(Parallel Decoding) 전략을 통해 성능 향상을 이끌어냈다.
Fast dLLM 성능 비교 모습

AR과 NAR: 근본적인 차이점

기존 트랜스포머에 Fast-dLLM의 개념을 적용하기 전, 두 모델의 근본적인 차이를 이해해야 한다.

오토-리그레시브 (AR) 트랜스포머: 이전 타임스텝의 출력이 다음 타임스텝의 입력이 되는 순차적 방식이다. 문장을 생성할 때, 이전에 생성된 단어들을 바탕으로 다음 단어 하나를 예측한다. 디코더의 Self-Attention에 Causal Mask를 적용하여 미래의 토큰을 참조하지 못하도록 막는 것이 핵심이다.

비-오토 리그레시브 (NAR) / Diffusion LLM: 문장의 여러 토큰, 혹은 전체 토큰을 동시에 생성하는 방식이다4444. Fast-dLLM이 기반하는 Masked Diffusion Model (MDM)은 [MASK] 토큰으로 가득 찬 시퀀스에서 시작하여, 여러 번의 정제(refinement) 단계를 거쳐 전체 문장을 완성한다5. 이 과정에서 디코더는 문장 전체의 맥락을 파악해야 하므로 양방향(Bidirectional) 어텐션을 사용한다.

이처럼 두 모델은 디코더의 동작 방식이 근본적으로 다르므로, 논문의 아키텍처를 그대로 이식하는 대신 학습 및 추론 과정을 시뮬레이션하는 방식으로 접근해야 한다.

Diffusion의 학습법: Masked Language Modeling
Diffusion 모델의 학습 목표는 노이즈가 낀 데이터에서 원본 데이터를 복원하는, 이른바 "Denoising" 과정이다. 텍스트 분야에서는 이 노이즈를 [MASK] 토큰으로 대체하여 구현한다. 이는 BERT의 MASK 방식 학습 아이디어와 유사해 보인다.

기존 AR 트랜스포머가 이 Denoising 능력을 학습하도록 데이터셋을 수정해야 한다. 타겟 문장의 일부를 랜덤하게 [MASK] 토큰으로 교체하고, 모델이 이 마스킹된 문장을 입력받아 원본 문장 전체를 예측하도록 학습 목표를 설정하는 것이다. 
주요 구현을 의사코드로 확인해 보겠다.

class MaskedSeq2SeqDataset(Dataset):
    def __init__(self, pairs: List[Tuple[str, str]], src_tok: Tokenizer, tgt_tok: Tokenizer, max_len: int = 40):
        self.pairs = pairs
        self.src_tok, self.tgt_tok = src_tok, tgt_tok
        self.max_len = max_len

    def __getitem__(self, idx):
        src_txt, tgt_txt = self.pairs[idx]
        src_ids = self.src_tok.encode(src_txt, add_eos=True)[:self.max_len]
        tgt_ids = self.tgt_tok.encode(tgt_txt, add_sos=True, add_eos=True)[:self.max_len]
       
        # 코사인 스케줄에 따라 마스킹할 개수 결정 (개념적인 시간 t에 해당)
        t_rand = random.random()
        num_to_mask = math.ceil(len(tgt_ids) * math.cos(t_rand * math.pi / 2))
       
        masked_tgt_ids = list(tgt_ids)
        maskable_indices = [i for i, t_id in enumerate(tgt_ids) if t_id not in (self.tgt_tok.sos_id, self.tgt_tok.eos_id)]
       
        if len(maskable_indices) > 0 and num_to_mask > 0:
            indices_to_mask = random.sample(maskable_indices, min(num_to_mask, len(maskable_indices)))
            for i in indices_to_mask:
                masked_tgt_ids[i] = self.tgt_tok.mask_id

        return (torch.tensor(src_ids, dtype=torch.long),
                torch.tensor(masked_tgt_ids, dtype=torch.long),
                torch.tensor(tgt_ids, dtype=torch.long))


논문은 여러 토큰을 동시에 예측할 때 발생하는 품질 저하의 원인을 조건부 독립 가정(conditional independence assumption)으로 지적한다. 즉, 모델이 각 토큰의 확률을 독립적으로 계산하여 샘플링하기 때문에 "high card"나 "full house" 대신 "high house"와 같은 부자연스러운 조합이 생성될 수 있다는 것이다.

이에 대한 해결책으로 Confidence-Aware Parallel Decoding 전략을 제안한다. 이 전략은 모델이 예측한 확률 값, 즉 '자신감(confidence)'이 특정 임계값(threshold)을 넘는 토큰들만 선택적으로 예측하고, 나머지는 다음 스텝에서 다시 예측하도록 남겨두는 방식이다.

또한 추론 과정을 여러 블록(block)으로 나누어 점진적으로 생성하는 Block-wise Generation 방식을 채택했다. 
이를 논문에서는 다음과 같은 알고리즘으로 기술했다. 

이런 논문의 아이디어를 종합하여 구현한 추론 함수는 다음과 같다.

@torch.no_grad()
def fast_dllm_decode(
    model: NARTransformer,
    src_tensor: torch.Tensor,
    src_text: str,
    tgt_tok: Tokenizer,
    max_len: int,
    num_blocks: int = 2,
    steps_per_block: int = 12,
    confidence_threshold: float = 0.9,
    visualize: bool = False,
):
    # ... (초기화 및 시각화 헬퍼 함수 생략) ...

    # 블록 단위로 외부 루프를 순회한다.
    for k in range(num_blocks):
        start_idx = 1 + k * block_size
        end_idx = min(start_idx + block_size, max_len)
       
        # 아래 루프가 논문의 개념적인 '시간 t'의 흐름을 나타낸다.
        # 블록 내에서 여러 스텝에 걸쳐 점진적으로 MASK를 채운다.
        for t in range(steps_per_block):
            step_count += 1
           
            # 1. 모델을 통해 모든 위치의 토큰 확률을 예측한다.
            logits = model.decode_step(ys, memory, tgt_pad_mask)
            probs = F.softmax(logits, dim=-1)
            confidences, predictions = probs.max(dim=-1)
           
            # 현재 블록 내의 MASK 위치만 unmask 후보로 고려한다.
            mask_positions = (ys == tgt_tok.mask_id) & current_block_mask
           
            if not mask_positions.any(): break
           
            # 2. Confidence가 임계값을 넘는 위치만 선택한다.
            unmask_candidates = (confidences > confidence_threshold) & mask_positions
           
            # 3. 만약 임계값을 넘는 토큰이 없다면, 가장 자신있는 토큰 하나만 선택하여 진행을 보장.
            if not unmask_candidates.any():
                masked_confidences = confidences.where(mask_positions, torch.tensor(-1.0, device=device))
                if masked_confidences.max() > -1:
                    highest_idx = masked_confidences.argmax(dim=1, keepdim=True)
                    unmask_candidates.scatter_(1, highest_idx, 1)

            if visualize:
                _visual_print(...)

            # 4. 선택된 위치의 MASK 토큰을 예측된 토큰으로 교체한다.
            ys.masked_scatter_(unmask_candidates, predictions[unmask_candidates])

    # ... (최종 결과 반환) ...


실제로 간단하게 개발해 보면 다음과 같이 동작하는 것을 확인할 수 있다. 

참고로 학습은 간단한 영-한 문장쌍 데이터셋을 간략히 구축해 진행하였고, 메커니즘만 확인할 목적으로 최소한의 GPU 리소스만 사용할 수 있도록 배치크기, 레이어 깊이 및 구조는 간략화된 버전으로 진행되었다. 

마무리
이 글에서는 표준 AR 트랜스포머 아키텍처를 수정 없이 활용하면서, 학습 데이터 파이프라인과 추론 로직을 변경하여 Fast-dLLM 논문의 핵심 아이디어를 구현하는 방법을 살펴보았다.

레퍼런스
스크래치 코드에서 나타나는 특징적인 현상은, 특정 에포크(epoch)까지는 손실(loss)이 안정적으로 감소하며 학습이 원활하게 진행되다가 그 이후부터 손실 값이 더 이상 수렴하지 않고 좁은 범위 내에서 불규칙하게 진동하는 훈련 불안정 상태에 진입한다는 점이다. 이는 모델의 구조적 결함이나 잘못된 학습 설정 때문이 아니라, 극심한 훈련 데이터 부족으로 인한 심각한 과적합(Overfitting)이 그 근본 원인이다.

1. 훈련 데이터 부족
예를 들어, 모델은 약 50만 개의 학습 가능한 파라미터를 가지고 있는데, 이처럼 방대한 학습 능력(capacity)을 가진 모델에게 적은 데이터셋 패턴을 학습시키는 것은 마치 대학 교수에게 알파벳만 외우게 하는 것과 같다. 
결과 특정 에포크 지점에 도달하면, 모델은 훈련 데이터에 대한 손실(loss)을 거의 0에 가깝게 최소화한다. 이 상태는 모델이 더 이상 배울 것이 없는 '포화 상태'이다. 이후에도 학습을 계속하면, 옵티마이저는 더 이상 의미 있는 방향으로 가중치를 갱신하지 못하고, 아주 작은 그래디언트 변화에 따라 기존의 최적점에서 미세하게 벗어났다가 돌아오는 과정을 반복한다. 이것이 바로 손실 값이 안정적으로 수렴하지 못하고 불규칙하게 진동(vibration)하는 현상으로 나타나는 것이다.

2. 검증 기반 제어 장치
검증 세트의 본질적인 역할은 훈련 데이터에 포함되지 않은 데이터를 통해 모델의 일반화 성능(Generalization Performance)을 측정하는 것이다. 이 과정이 없으면 모델이 훈련 데이터에 얼마나 과적합되고 있는지 객관적으로 파악할 수 없다. 또한, 과적합이 시작되는 시점에 훈련을 자동으로 중단시키는 표준적인 기법인 조기 종료(Early Stopping)를 구현할 수 없다. 과적합이 발생하여 더 이상의 학습이 무의미해진 이후에도 모델은 불필요한 훈련을 계속 진행한다. 

3. 부족한 정규화(Regularization) 기법
과적합을 억제하기 위한 장치로 드롭아웃(Dropout)이 적용되어 있기는 하다. 드롭아웃은 훈련 중에 무작위로 뉴런을 비활성화하여 모델이 특정 뉴런에 과도하게 의존하는 것을 막는 효과적인 기법이다.  모델의 가중치가 너무 커지는 것을 방지하여 과적합을 억제하는 가중치 감쇠(Weight Decay)와 같은 다른 보편적인 정규화 기법이 부재할 수 있다. 부족한 정규화는 모델이 제한된 훈련 데이터의 패턴을 더 빠르고 쉽게 암기하도록 만들어 과적합을 가속화하는 요인으로 작용한다.

이러한 문제들을 해결하고 모델을 안정적으로 훈련시키기 위한 방안은 다음과 같다.

1. 데이터셋 교체 및 증강
실제 대용량 데이터셋을 사용해야 한다. 예를 들어, 허깅페이스에 공개된 데이터셋은 수만 개 이상의 문장 쌍으로 구성되어 있어, 모델이 일반화된 언어 패턴을 학습하는 데 필수적이다.

데이터셋을 교체할 수 없는 제한된 환경이라면, 데이터 증강(Data Augmentation)을 통해 훈련 샘플을 인위적으로 늘리는 방법을 고려할 수 있다. 하지만 현재 데이터의 절대량이 너무 적어 그 효과는 매우 제한적일 가능성이 높다.

2. 검증 루프 및 조기 종료 구현
과적합을 방지하고 훈련 효율성을 높이기 위해 검증 및 조기 종료 로직을 도입 한다. 이는 가장 표준적이고 효과적인 방법이다.


    # 조기 종료 로직
    if avg_val_loss < best_val_loss:
        best_val_loss = avg_val_loss
        patience_counter = 0
        # 여기서 최고 성능 모델의 가중치를 저장하는 것이 좋음
    else:
        patience_counter += 1
        if patience_counter >= patience:
            print("Early stopping due to no improvement in validation loss.")
            break


3. 학습률 스케줄러 및 가중치 감쇠 추가
Learning Rate Scheduler: torch.optim.lr_scheduler.ReduceLROnPlateau와 같은 스케줄러를 추가하면, 검증 손실이 정체될 때 학습률(learning rate)을 동적으로 낮추어 모델이 최적점에 더 안정적으로 수렴하도록 도울 수 있다.

Weight Decay: 옵티마이저를 생성할 때 weight_decay 파라미터를 추가하여 L2 정규화를 적용한다. 이는 모델의 가중치가 너무 커지는 것을 방지하는 역할을 한다.

# 옵티마이저 생성 시 weight_decay 추가
optimizer = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=1e-5)