https://youtu.be/HTBl142x9GI?si=Jp1Sl2pisGmNxIJr

 

 


🎓 카카오 LLM 개발기로 배우는 실전 언어모델 학습 로드맵

📚 학습 주제 체계

카카오의 개발 과정을 분석하면, LLM 개발은 크게 4개의 핵심 영역으로 구성됩니다:

1. 데이터 엔지니어링 (Data Engineering)
   └─ 수집, 정제, 품질 평가, 라이선스 관리

2. 프리트레이닝 전략 (Pre-training Strategy)  
   └─ 학습 설계, 아키텍처, 최적화

3. 포스트트레이닝 (Post-training)
   └─ SFT, 선호도 학습, 얼라인먼트

4. 모델 최적화 (Model Optimization)
   └─ Pruning, Distillation, 경량화

📖 Part 1: 데이터 엔지니어링 - LLM의 숨은 90%

핵심 통찰

"모델 구조 개선보다는 데이터 품질 및 구성을 개선하는 데 집중하고 있다" - 카카오 개발팀

1.1 데이터 수집과 라이선스 관리

왜 중요한가?

  • 상용 서비스를 위해서는 법적으로 안전한 데이터만 사용 가능
  • 데이터 출처가 모델의 법적 리스크를 결정

학습 포인트:

# 라이선스 분류 체계
PERMISSIVE_LICENSES = [
    'Apache-2.0',
    'MIT', 
    'BSD-3-Clause',
    'Creative Commons (상업적 이용 허용)'
]

RESTRICTIVE_LICENSES = [
    'GPL-3.0',  # 파생 작품도 같은 라이선스
    'CC-BY-NC',  # 비상업적 이용만 허용
    '저작권 보호 콘텐츠'
]

# 실제 판단 기준
def is_safe_for_commercial(license_type):
    """
    상용 서비스에 안전한 데이터인가?
    
    카카오의 선택:
    - 나무위키 제외 (CC-BY-NC-SA)
    - 뉴스 기사 제외 (저작권)
    - The Stack v2에서 MIT 등만 필터링
    """
    return license_type in PERMISSIVE_LICENSES

실전 과제:

  1. Hugging Face의 공개 데이터셋 100개를 조사하여 라이선스 분류표 작성
  2. Common Crawl에서 특정 도메인 데이터 수집 시 고려사항 정리

1.2 데이터 품질 평가: Educational Score 시스템

카카오의 3단계 파이프라인:

[Stage 1] GPT-4로 샘플 평가
    ↓
[Stage 2] 경량 평가 모델 학습
    ↓  
[Stage 3] 전체 말뭉치 스코어링

구체적 구현 예시:

# Stage 1: GPT-4를 이용한 Educational Score 측정
EVAL_PROMPT = """
다음 문서가 언어 모델 학습에 얼마나 교육적인지 평가하세요.

평가 기준:
1. 정보의 정확성과 신뢰성
2. 문장 구조와 문법의 정확성
3. 논리적 흐름과 일관성
4. 전문성과 깊이
5. 학습 가치 (일반 상식 vs 전문 지식)

점수: 1-10 (10이 가장 교육적)
이유: [간단한 설명]

문서:
{document}
"""

# Stage 2: 경량 평가 모델 학습
from transformers import AutoModelForSequenceClassification

# FastText 기반 또는 작은 BERT 모델
model = AutoModelForSequenceClassification.from_pretrained(
    'bert-base-uncased',  # 또는 distilbert
    num_labels=10  # 1-10 점수 회귀
)

# GPT-4가 평가한 샘플로 학습
training_data = [
    {"text": doc, "score": gpt4_score}
    for doc, gpt4_score in scored_samples
]

# Stage 3: 대규모 평가
def filter_high_quality_data(corpus, threshold=7.0):
    """
    경량 모델로 전체 말뭉치 평가
    
    카카오의 결과:
    "교육 점수 기반 선별이 무작위 선별보다
     벤치마크 성능 향상에 효과적"
    """
    scores = quality_model.predict(corpus)
    return corpus[scores >= threshold]

학습 실습:

  1. FineWeb-Edu 데이터셋의 평가 방식 분석
  2. DataComp-LM의 FastText 기반 필터링 재현
  3. 한국어 문서 1000개에 대해 품질 평가 모델 직접 학습

1.3 도메인별 데이터 전처리

Case Study 1: Wikipedia 수식 복원

문제 발견:

# 기존 공개 데이터 (수식 손실)
코리올리 효과: F = -2mΩ×v
→ 실제 렌더링: "F = -2m?�v" (깨짐)

# 카카오의 해결책
원본 Wikipedia에서 직접 파싱
→ LaTeX 수식 보존
→ 올바른 렌더링: F = -2m\Omega \times v

구현 방향:

import mwparserfromhell
import wikitextparser

def parse_wikipedia_with_formulas(wiki_text):
    """
    Wikipedia 원본에서 수식을 보존하여 파싱
    
    핵심:
    - <math> 태그 감지
    - LaTeX 구문 유지
    - 주변 컨텍스트와 통합
    """
    wikicode = mwparserfromhell.parse(wiki_text)
    
    # math 태그 추출
    for tag in wikicode.filter_tags():
        if tag.tag == "math":
            latex_formula = tag.contents
            # LaTeX를 그대로 유지하거나 적절히 변환
            
    return cleaned_text_with_formulas

 

 

Case Study 2: Code 데이터 필터링

# Stage 1: 대규모 다양성 확보
sources = [
    "The Stack v2 (MIT license only)",  # 433B → 217B tokens
    "StarCoder data"
]

# Stage 2: 집중 강화
stage2_code_data = {
    "python_only": filter_by_language(stage1_data, "python"),
    "code_with_explanation": get_mixed_nl_code_data(),
    "sft_data": load_code_instruction_data()
}

# 카카오의 발견:
# → Stage 2에서 Python만 집중하는 것이 
#    코드 벤치마크 성능 향상에 효과적

학습 과제:

  1. Hugging Face의 Wikipedia 데이터 품질 직접 검증
  2. The Stack v2에서 라이선스별 토큰 수 분석
  3. 코드 + 자연어 혼합 데이터셋 3개 이상 찾기

📖 Part 2: 프리트레이닝 전략 - 효율적 학습 설계

핵심 통찰

"Llama 3.1 8B의 20% 데이터로 더 강력한 성능 달성"

2.1 Two-Stage Pre-training 철학

설계 원칙:

Stage 1 (Foundation)              Stage 2 (Specialization)
├─ 목표: Broad Knowledge          ├─ 목표: Deep Expertise
├─ 데이터: 2.7T tokens             ├─ 데이터: 0.3T tokens
├─ 전략: Diversity + Quantity     ├─ 전략: Quality + Goal
└─ 비유: "뇌 근육 키우기"            └─ 비유: "명석한 두뇌 만들기"

Data Mixing Ratios (예시):

# Stage 1 분포 (광범위한 기초)
stage1_distribution = {
    "Common Sense & General Knowledge": 40%,
    "Code": 15%,
    "Wikipedia": 10%,
    "Academic Papers": 15%,
    "Books": 10%,
    "Conversations": 10%
}

# Stage 2 분포 (목표 중심)
stage2_distribution = {
    "High-quality filtered general": 30%,
    "STEM focused": 25%,  # 성능 약점 보완
    "Code (Python)": 20%,
    "Math + SFT instruction": 15%,  # 수학 성능 향상
    "Wikipedia": 10%
}

# 카카오의 발견:
# "Stage 1의 goal별 distribution을 유지하고,
#  data quality만 개선해도 성능 향상됨"

2.2 Ablation Study: 체계적 실험 설계

Stage 1 최적화 (4개 실험, 250B tokens)

# 목표: MMLU(영어) + KMMLU/HELLM(한국어) 동시 최대화

ablation_configs = {
    "ablation_1": {
        "focus": "MMLU 최적화",
        "english_general": "높음",
        "korean_cultural": "보통"
    },
    "ablation_2": {
        "focus": "균형",
        "english_general": "보통",
        "korean_cultural": "보통"
    },
    "ablation_3": {
        "focus": "HELLM 최적화",  
        "english_general": "보통",
        "korean_cultural": "높음"
    },
    "ablation_4": {
        "focus": "저품질 영어 제거",
        "english_general": "낮음 (고품질만)",
        "korean_cultural": "보통"
    }
}

# 최종 선택: 
# ablation_1 + ablation_3 장점 결합 + 저품질 데이터 제거

Stage 2 최적화 (17개 실험!)

카카오가 발견한 3대 성능 향상 요소:

# 1. Goal별 데이터 분포 조정
key_finding_1 = """
데이터의 목표(Goal) 분포를 미세 조정하는 것이
특정 벤치마크 성능을 끌어올리는 핵심
"""

# 2. Learning Rate 최적화
key_finding_2 = """
고품질 데이터 사용 시 
상대적으로 높은 learning rate가 효과적
"""

# 3. Instruction Data 추가
key_finding_3 = """
소량의 명령어(instruction) 데이터 추가가
특히 수학 벤치마크 성능 향상에 기여
"""

# 최종 채택: Ablation 12
# → 56.56점 (가장 높은 성능)

2.3 학습 안정화 기법

Multi-step Learning Rate Scheduler

from torch.optim.lr_scheduler import MultiStepLR

# DeepSeek-LLM 연구에서 입증된 기법
optimizer = AdamW(model.parameters(), lr=3e-4)

scheduler = MultiStepLR(
    optimizer,
    milestones=[
        int(0.8 * total_steps),  # 80% 지점
        int(0.9 * total_steps)   # 90% 지점
    ],
    gamma=0.1  # 각 milestone에서 lr을 1/10로
)

# 효과: 학습 후반 안정화 + 수렴 개선

Annealing Checkpoint Averaging (SWA 변형)

def average_checkpoints(checkpoint_paths, output_path):
    """
    여러 체크포인트의 가중치를 평균
    
    카카오의 적용:
    - 'Stage 3'처럼 활용
    - 코드 벤치마크 성능 추가 향상
    - 일반화 성능 개선
    """
    averaged_state = {}
    
    for ckpt_path in checkpoint_paths:
        state_dict = torch.load(ckpt_path)
        for key, param in state_dict.items():
            if key not in averaged_state:
                averaged_state[key] = param / len(checkpoint_paths)
            else:
                averaged_state[key] += param / len(checkpoint_paths)
    
    torch.save(averaged_state, output_path)
    return averaged_state

# 사용 시점: 학습 마지막 단계의 여러 체크포인트

학습 실습:

  1. 작은 모델로 2-Stage 학습 직접 구현
  2. 4개 ablation 실험으로 데이터 분포 최적화
  3. SWA 적용 전후 벤치마크 비교

📖 Part 3: 포스트트레이닝 - 말 잘 듣는 모델 만들기

핵심 통찰

"좋은 LLM을 만들기 위해서도 서비스는 중요하다"

3.1 SFT (Supervised Fine-Tuning) 전략

Lesson 1: 실제 서비스 형태의 프롬프트

# ❌ 인위적인 데이터 (비효과적)
poor_example = {
    "prompt": "히스토그램을 그리는 파이썬 코드를 작성하시오.",
    "response": "[코드 블록]"
}

# ✅ 실제 사용자 대화 (효과적)
good_example = {
    "conversation": [
        {
            "role": "user",
            "content": "파이썬 코드"  # 간결하고 자연스러움
        },
        {
            "role": "assistant",
            "content": "[히스토그램 코드 + 간단한 설명]"
        },
        {
            "role": "user",
            "content": "주피터 노트북에서 하려면?"  # 연속적 대화
        },
        {
            "role": "assistant",
            "content": "[주피터 환경 맞춤 설명]"
        }
    ]
}

# Multi-turn의 핵심: 맥락이 이어지는 자연스러운 소통

데이터 수집 프로세스 구축:

class ServiceBasedDataCollection:
    """
    실제 서비스에서 데이터 수집
    
    카카오의 접근:
    - 사용자 동의 하에 대화 로그 수집
    - 자연스러운 multi-turn 대화 확보
    - 서비스 → 데이터 → 모델 개선의 선순환
    """
    
    def collect_conversation(self, user_session):
        # 1. 사용자 동의 확인
        # 2. 개인정보 익명화
        # 3. 대화 맥락 보존하며 저장
        pass
    
    def generate_synthetic_multiturn(self, seed_prompt):
        """
        모델이 스스로 자연스러운 대화 생성
        
        향후 방향:
        "사람이 직접 만들지 않고 모델이 스스로 
         자연스러운 멀티턴 대화 데이터를 생성"
        """
        pass

Lesson 2: 응답 품질의 중요성

# 고품질 응답 업데이트 시스템
class ResponseQualityManager:
    def __init__(self):
        self.quality_criteria = {
            "accuracy": 0.3,      # 정확성
            "helpfulness": 0.3,   # 유용성
            "naturalness": 0.2,   # 자연스러움
            "safety": 0.2         # 안전성
        }
    
    def continuous_improvement(self):
        """
        카카오의 접근:
        "'좋은 답변'의 기준이 변할 수 있으므로,
         지속적으로 업데이트할 수 있는 프로세스 구축"
        """
        # 1. 최신 모델로 재생성
        # 2. 품질 평가
        # 3. 기존 데이터 교체
        pass

Lesson 3: 도메인별 독립 구축

# 도메인별 데이터셋 구성
sft_datasets = {
    "general_conversation": {
        "size": "large",
        "focus": "자연스러운 대화"
    },
    "code": {
        "size": "medium",
        "focus": "실행 가능한 코드 생성"
    },
    "math": {
        "size": "medium",
        "focus": "정확한 풀이 과정"
    },
    "creative_writing": {
        "size": "small",
        "focus": "창의성과 스타일"
    }
}

# 카카오의 발견:
# "여러 도메인 데이터를 섞어도 
#  유의미한 간섭 현상이 거의 없음"
# → 모든 도메인 통합 학습 가능

3.2 Preference-Based Learning

Lesson 1: 도메인별 선호도 기준

# 수학 도메인: 정확성 최우선
math_preference_criteria = {
    "correct_answer": 0.5,      # 최종 답의 정확성
    "correct_process": 0.4,     # 풀이 과정의 정확성  
    "explanation_quality": 0.1  # 설명의 친절함
}

# 잘못된 예시
bad_math_response = """
[매우 친절하고 자세한 설명]
하지만 계산 과정에서 실수 발생
→ 오답 도출
"""
# → 이것은 "나쁜 응답"으로 분류해야 함!

# 코드 도메인: 실행 가능성 중요
code_preference_criteria = {
    "executable": 0.4,           # 실행 가능 여부
    "correctness": 0.3,          # 의도한 기능 수행
    "code_quality": 0.2,         # 가독성, 효율성
    "explanation": 0.1
}

def evaluate_code_response(code):
    """실제로 실행해서 테스트"""
    try:
        exec(code)
        test_results = run_unit_tests(code)
        return test_results.pass_rate
    except Exception as e:
        return 0  # 실행 불가 = 나쁜 응답

Lesson 2: Online Preference Learning

class OnlinePreferenceLearning:
    """
    오프라인 vs 온라인 학습
    
    오프라인: 미리 구축된 정적 데이터셋
    온라인: 현재 모델의 출력으로 데이터셋 구성
    """
    
    def offline_learning(self):
        """
        장점: 안정적
        단점: 모델 개선에 따른 데이터 업데이트 어려움
        """
        static_dataset = load_prebuilt_preference_data()
        train(model, static_dataset)
    
    def online_learning(self, model, prompts):
        """
        카카오의 접근:
        "학습 시점의 모델이 직접 여러 응답을 생성하고,
         리워드 모델이 평가한 정보를 기반으로 학습"
        
        효과: 반복 적용 시 성능 점진적 향상
        """
        for iteration in range(num_iterations):
            # 1. 현재 모델로 응답 생성
            responses = []
            for prompt in prompts:
                response_candidates = model.generate(
                    prompt, 
                    num_return_sequences=4
                )
                responses.append(response_candidates)
            
            # 2. 리워드 모델로 평가
            scores = reward_model.score(prompts, responses)
            
            # 3. 선호도 쌍 구성
            preference_pairs = construct_pairs(responses, scores)
            
            # 4. 모델 학습 (DPO, PPO 등)
            model.update(preference_pairs)
            
            # 5. 성능 향상 확인
            evaluate(model)

리워드 모델 구축:

class RewardModelTraining:
    """
    온라인 학습의 핵심: 좋은 리워드 모델
    
    카카오의 우선순위:
    "리워드 모델 학습을 최우선으로 진행"
    """
    
    def train_reward_model(self, preference_data):
        """
        선호도 데이터로 리워드 모델 학습
        
        입력: (prompt, better_response, worse_response)
        출력: 응답 품질 점수 (0-1)
        """
        # Bradley-Terry 모델 등 사용
        pass
    
    def domain_specific_reward(self, domain):
        """
        도메인별 리워드 모델
        - 수학: 정확성 검증기
        - 코드: 실행 + 테스트
        - 일반: 종합 품질 평가
        """
        if domain == "math":
            return MathCorrectnessVerifier()
        elif domain == "code":
            return CodeExecutionTester()
        else:
            return GeneralQualityScorer()

학습 실습:

  1. 수학/코드 도메인별 선호도 기준 설계
  2. 작은 모델로 Online DPO 직접 구현
  3. 리워드 모델 학습 파이프라인 구축

📖 Part 4: 모델 최적화 - Pruning & Distillation

핵심 통찰

"처음부터 학습 대비 1/9의 데이터로 더 높은 성능 달성"

4.1 Pruning & Distillation 전략

전체 프로세스:

[Teacher Model]
Karuna Essence (Large)
    ↓
[Pruning] 모델 구조 축소
    ↓
[Student Model] (초기)
Karuna Nano (Small, pruned structure)
    ↓
[Distillation] Teacher 지식 전달 (0.3T tokens)
    ↓
[Final Model]
Karuna Nano (High Performance)

구체적 구현:

# Step 1: Structured Pruning
def prune_model(teacher_model, pruning_ratio=0.3):
    """
    구조적 가지치기
    
    카카오의 방식 (추정):
    - Attention head 제거
    - Layer 제거
    - Hidden dimension 축소
    """
    pruned_config = {
        "num_layers": int(teacher_model.num_layers * (1 - pruning_ratio)),
        "num_attention_heads": int(teacher_model.num_heads * (1 - pruning_ratio)),
        "hidden_size": teacher_model.hidden_size  # 유지 또는 축소
    }
    
    student_model = initialize_model(pruned_config)
    
    # Teacher의 가중치에서 중요한 부분만 student에 이식
    transfer_important_weights(teacher_model, student_model)
    
    return student_model

# Step 2: Knowledge Distillation
def distillation_training(teacher, student, data_0_3T, temperature=2.0):
    """
    지식 증류 학습
    
    카카오의 데이터: 0.3T tokens (Stage 2와 동일한 고품질 데이터)
    """
    for batch in data_0_3T:
        # Teacher의 출력 (soft labels)
        with torch.no_grad():
            teacher_logits = teacher(batch.input_ids)
            teacher_probs = F.softmax(teacher_logits / temperature, dim=-1)
        
        # Student 학습
        student_logits = student(batch.input_ids)
        student_probs = F.softmax(student_logits / temperature, dim=-1)
        
        # Distillation Loss
        loss_distill = KL_divergence(teacher_probs, student_probs)
        
        # Hard Label Loss (원본 데이터도 함께 학습)
        loss_hard = cross_entropy(student_logits, batch.labels)
        
        # 총 손실
        alpha = 0.5  # 균형 조정
        loss = alpha * loss_hard + (1 - alpha) * loss_distill
        
        loss.backward()
        optimizer.step()

# Step 3: 결과 비교
performance_comparison = {
    "from_scratch": {
        "data_used": "2.7T tokens",
        "kmmlu_score": 48.81,
        "training_time": "long"
    },
    "pruning_distillation": {
        "data_used": "0.3T tokens",  # 1/9 데이터!
        "kmmlu_score": 52.07,  # 더 높은 성능!
        "training_time": "short"
    }
}

4.2 효율성 극대화 전략

class EfficientModelDevelopment:
    """
    카카오의 2단계 검증 전략
    """
    
    def stage1_validation(self):
        """
        1단계: 레시피 검증
        - 작은 모델 from scratch 학습
        - 라마 대비 성능 확인
        → 학습 방식의 우수성 입증
        """
        nano_from_scratch = train_from_scratch(
            data="2.7T + 0.3T",
            model_size="small"
        )
        
        assert nano_from_scratch.performance > llama_baseline
        print("✓ 우리 레시피가 효과적임을 검증")
    
    def stage2_optimization(self):
        """
        2단계: 효율 극대화
        - 큰 모델 (Essence) 활용
        - Pruning & Distillation 적용
        → 1/3 데이터로 더 좋은 성능
        """
        nano_optimized = pruning_and_distillation(
            teacher=essence_model,
            data="0.3T only"
        )
        
        assert nano_optimized.performance > nano_from_scratch
        assert nano_optimized.data_used < nano_from_scratch.data_used / 3
        print("✓ 최대 효율 달성")

학습 실습:

  1. 작은 Transformer 모델에 직접 Pruning 적용
  2. Knowledge Distillation 코드 구현
  3. From scratch vs Distillation 성능 비교 실험

🎯 종합 학습 로드맵

단계별 실전 프로젝트

[입문] 3개월 과정
Week 1-4: 데이터 파이프라인 구축
  ├─ Hugging Face 데이터셋 100개 라이선스 분석
  ├─ Educational Score 평가 모델 학습
  └─ Wikipedia 수식 보존 파싱 구현

Week 5-8: 작은 모델로 프리트레이닝
  ├─ 2-Stage 학습 구현 (1B 모델)
  ├─ 4개 ablation 실험 수행
  └─ Learning rate schedule 최적화

Week 9-10: SFT 데이터 구축
  ├─ Multi-turn 대화 데이터 수집
  ├─ 도메인별 데이터셋 구성
  └─ 품질 평가 기준 설계

Week 11-12: 선호도 학습
  ├─ 리워드 모델 학습
  ├─ Online DPO 구현
  └─ 도메인별 선호도 기준 적용

[중급] 추가 3개월
├─ Pruning & Distillation 마스터
├─ 대규모 데이터 처리 (10B+ tokens)
├─ 멀티 GPU 학습 최적화
└─ 프로덕션 배포 파이프라인

[고급] 실전 프로젝트
├─ 특정 도메인 전문 LLM 개발
├─ 커뮤니티 기여 (오픈소스 모델 공개)
└─ 논문 작성 및 발표

💡 핵심 Takeaways

카카오 개발팀의 5대 교훈:

  1. Data Quality > Data Quantity
    • "20%의 데이터로 거인을 이기다"
    • Educational Score 기반 선별의 위력
  2. Constraints Drive Innovation
    • 라이선스 제약 → 고품질 데이터 집중
    • 제한된 자원 → 효율적 전략 개발
  3. Smart Curation Beats Brute Force
    • GPT-4 → 경량 모델 파이프라인
    • 비용 절감 + 일관성 확보
  4. Service Data is Gold
    • 실제 multi-turn 대화의 중요성
    • 서비스 ↔ 모델 선순환
  5. Efficiency Through Transfer
    • Pruning & Distillation
    • 1/9 데이터로 더 높은 성능

📚 추천 학습 자료

필수 논문:

  1. FineWeb-Edu (데이터 품질 평가)
  2. DataComp-LM (품질 필터링)
  3. DeepSeek-LLM (Multi-step LR Scheduler)
  4. DPO (Direct Preference Optimization)
  5. Model Pruning & Distillation 서베이

실습 리소스:

  1. Hugging Face Transformers 라이브러리
  2. Axolotl (파인튜닝 프레임워크)
  3. TRL (Transformer Reinforcement Learning)
  4. PEFT (Parameter-Efficient Fine-Tuning)

커뮤니티:

  1. EleutherAI (오픈소스 LLM 연구)
  2. HuggingFace Hub (모델 & 데이터)
  3. Papers with Code (최신 연구 동향)

🚀 마무리: AI 개발의 새로운 패러다임

카카오의 사례가 보여주는 것은 **'전략의 승리'**입니다:

규모의 경쟁 (Old)          전략의 경쟁 (New)
├─ 더 많은 데이터           ├─ 더 좋은 데이터 선별
├─ 더 큰 모델               ├─ 효율적인 모델 설계
├─ 더 긴 학습 시간          ├─ 스마트한 학습 전략
└─ 무한한 자본              └─ 창의적 문제 해결

 

이제 여러분의 차례입니다.

이 로드맵을 따라 하나씩 실습하며, 자신만의 언어 모델을 개발해보세요. 카카오가 3개월 만에 이룬 성과는, 올바른 방향과 체계적인 실행이 있다면 누구나 달성할 수 있다는 것을 증명합니다.

 

Happy Learning! 🎓✨

 

추가 관련 영상:

https://youtu.be/wSawG3VklFU?si=XMI9Qz-ZeWSlv3uj

 

+ Recent posts