- Published on
Transformers 로컬 LLM RoPE 스케일링 오류 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버 없이 로컬에서 LLM을 돌리는 흐름이 보편화되면서, transformers 기반으로 모델을 로드해 추론하는 경우가 많아졌습니다. 그런데 Llama 계열을 포함한 다수의 최신 모델은 RoPE(Rotary Position Embedding)를 쓰고, 긴 컨텍스트를 위해 RoPE 스케일링(rope scaling)을 적용하는 경우가 많다 보니 모델 로딩 단계에서 바로 터지거나, 추론 중 위치 임베딩 관련 에러가 발생하는 일이 잦습니다.
이 글에서는 로컬 LLM에서 흔히 발생하는 RoPE 스케일링 오류를 “증상별”로 분류하고, transformers에서의 올바른 설정 방식과 버전 호환, 그리고 안전한 디버깅 순서를 정리합니다.
RoPE 스케일링이 왜 문제를 일으키나
RoPE는 토큰의 위치 정보를 회전 행렬 형태로 주입하는 방식입니다. 기본적으로 모델은 학습 시점에 정해진 max_position_embeddings 범위 내에서 안정적으로 동작하도록 학습됩니다.
긴 컨텍스트(예: 8k, 16k, 32k)를 지원하려면 다음 중 하나가 필요합니다.
- 애초에 긴 컨텍스트로 학습된 체크포인트 사용
- 학습은 짧게 했지만 추론에서 RoPE 스케일링으로 “위치 주파수”를 재매핑해 확장
문제는 이 확장 로직이 모델 패밀리/버전마다 다르고, transformers가 이를 config.json의 rope_scaling 필드로 처리하는 과정에서 형식 불일치, 지원하지 않는 타입, 키 누락, 라이브러리 버전 미지원이 쉽게 발생한다는 점입니다.
대표 증상 1: 모델 로딩 시 rope_scaling 관련 ValueError
가장 흔한 케이스는 from_pretrained() 시점에 아래 계열의 오류가 나는 것입니다.
ValueError: rope_scaling must be a dictionary with keys ...ValueError: 'type' must be one of ...KeyError: rope_scalingUnexpected keyword argument 'rope_scaling'
원인 A: 모델 config.json의 rope_scaling 스키마가 Transformers 버전과 불일치
모델 허브에 올라온 config.json은 최신 스키마를 쓰는데, 로컬 환경의 transformers가 구버전이면 해당 필드를 파싱하지 못합니다. 반대로, 구형 스키마를 가진 모델을 최신 transformers에서 로드할 때도 경고/에러가 발생할 수 있습니다.
해결 1) 우선 버전 확인 및 업데이트
로컬 LLM은 대체로 CUDA, torch, transformers, accelerate 조합이 맞아야 합니다. 먼저 아래처럼 버전을 확인합니다.
python -c "import transformers, torch; print('transformers', transformers.__version__); print('torch', torch.__version__)"
그 다음 transformers를 업데이트합니다.
pip install -U transformers accelerate
환경 고정이 필요한 경우라면, 최소한 모델이 요구하는 transformers 버전을 릴리즈 노트 또는 모델 카드에서 확인하고 그에 맞춰 핀ning 하세요.
원인 B: rope_scaling 딕셔너리 키/값이 잘못됨
모델에 따라 rope_scaling은 보통 아래 형태 중 하나입니다.
{"type": "linear", "factor": 2.0}{"type": "dynamic", "factor": 4.0}
그런데 일부 체크포인트는 커스텀 키(예: rope_type, original_max_position_embeddings)를 포함하거나, factor를 문자열로 저장하는 등 비정상 값이 섞여 있습니다.
해결 2) 로드 전에 config를 강제로 정규화
AutoConfig로 먼저 읽고, rope_scaling을 정리한 뒤 모델을 로드합니다.
from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer
import torch
model_id = "your-local-or-hf-model"
config = AutoConfig.from_pretrained(model_id, trust_remote_code=True)
# rope_scaling이 존재하지만 형식이 이상한 경우를 방어
rope_scaling = getattr(config, "rope_scaling", None)
if rope_scaling is not None:
# 예: factor가 문자열이면 float로 변환
if "factor" in rope_scaling and isinstance(rope_scaling["factor"], str):
rope_scaling["factor"] = float(rope_scaling["factor"])
# type 키가 없고 rope_type 같은 키만 있는 경우를 보정(모델별로 다름)
if "type" not in rope_scaling and "rope_type" in rope_scaling:
rope_scaling["type"] = rope_scaling.pop("rope_type")
config.rope_scaling = rope_scaling
# 토크나이저/모델 로드
tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_id,
config=config,
torch_dtype=torch.float16,
device_map="auto",
trust_remote_code=True,
)
핵심은 “모델이 제공한 config.json을 그대로 믿지 말고, 로컬에서 파싱 가능한 형태로 정규화”하는 것입니다.
대표 증상 2: 컨텍스트 길이를 늘렸더니 품질 급락 또는 반복 출력
에러가 아니라 더 난감한 케이스로, RoPE 스케일링을 적용했더니 다음 증상이 나타납니다.
- 답변이 같은 문장을 반복
- 중반 이후 문맥이 붕괴
- 특정 길이 이상에서 급격히 헛소리
원인: “긴 컨텍스트 지원”과 “RoPE 스케일링 적용”은 동일하지 않음
RoPE 스케일링은 어디까지나 추론 시점 보정입니다. 모델이 긴 컨텍스트에서 안정적으로 동작하려면 학습/파인튜닝 단계에서 해당 컨텍스트에 대한 적응이 되어 있거나, 최소한 모델이 의도한 방식의 스케일링을 써야 합니다.
예를 들어,
- 모델 카드에 8k 지원이라고 되어 있는데 임의로
factor=4로 32k를 만들면 품질이 급락할 수 있습니다. linear와dynamic은 동작이 다르고, 모델이 기대한 타입과 다르면 결과가 크게 흔들립니다.
해결: 모델 카드의 권장 설정을 우선 적용
가능하면 모델 카드에 명시된 rope_scaling 혹은 max_position_embeddings 권장값을 그대로 사용하세요. 임의로 확장하려면 다음 순서로 검증하는 것을 추천합니다.
- 기본 설정(스케일링 없음)으로 정상 출력 확인
- 모델이 공식 지원하는 컨텍스트 길이까지 확장
- 그 이후 확장은 샘플링 파라미터(temperature, top_p)까지 포함해 회귀 테스트
이 접근은 운영 장애에서 “원인 격리”를 하는 방식과 유사합니다. 재현 가능한 단위로 좁혀가는 디버깅이 중요합니다. 같은 맥락에서 장애 원인을 체계적으로 좁히는 방법론은 Nginx에서 JWT 401 간헐 발생 - 시계오차 해결 같은 글의 트러블슈팅 흐름도 참고가 됩니다.
대표 증상 3: max_position_embeddings를 늘렸는데도 길이 제한이 그대로
간혹 config에서 max_position_embeddings를 늘려도, 실제로는 입력이 잘리거나 경고가 뜹니다.
원인: 토크나이저/서빙 코드에서 model_max_length가 제한
tokenizer.model_max_length가 보수적으로 잡혀 있으면, 토크나이저가 알아서 잘라버리거나 경고를 냅니다.
해결: 토크나이저의 최대 길이를 명시적으로 맞추기
from transformers import AutoTokenizer
model_id = "your-local-or-hf-model"
tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True)
# 모델이 실제로 감당 가능한 길이로 설정(무작정 크게 잡지 말 것)
tokenizer.model_max_length = 8192
tokens = tokenizer(
"긴 입력...",
return_tensors="pt",
truncation=True,
max_length=8192,
)
이때 max_length만 늘려도 모델이 감당 못 하면 OOM이 나거나 품질이 깨지므로, VRAM과 배치 전략을 함께 봐야 합니다.
대표 증상 4: 로컬에서만 터지는 경우(서버/다른 PC에서는 정상)
같은 모델이 다른 환경에서는 잘 도는데, 내 PC에서만 RoPE 관련 오류가 나는 경우가 있습니다.
원인: 캐시된 모델 파일/구 config가 섞임
transformers는 모델을 캐시합니다. 모델이 업데이트되었거나, 이전에 받아둔 config.json이 남아 있으면 로컬에서만 스키마 불일치가 발생할 수 있습니다.
해결: 캐시 삭제 후 재다운로드
# 리눅스/맥 예시
rm -rf ~/.cache/huggingface/hub
또는 모델 단위로 캐시 폴더를 찾아 지우고 다시 받는 방식도 가능합니다.
실전: RoPE 스케일링 오류를 빠르게 잡는 체크리스트
아래 순서대로 보면 대부분의 케이스가 해결됩니다.
transformers버전 확인 후 업데이트- 캐시 삭제 후 재다운로드
AutoConfig를 먼저 로드해서config.rope_scaling값을 출력rope_scaling이 dict인지,type과factor가 유효한지 확인- 토크나이저의
model_max_length와 실제 입력max_length정합성 점검 - 긴 컨텍스트 확장 시 “품질 회귀 테스트”를 짧은 프롬프트 세트로 자동화
운영에서 재시도/백오프/큐로 장애를 완화하듯, LLM 로컬 추론도 “실패를 전제로 한 설계”가 도움이 됩니다. 예를 들어 모델 로드 실패 시 다른 체크포인트로 폴백하거나, 요청을 큐잉해 재처리하는 패턴은 OpenAI API 429 RateLimit 재시도와 큐 설계에서 설명한 방식과 유사한 면이 있습니다.
재현 가능한 최소 코드: RoPE 스케일링 설정을 안전하게 적용
아래 코드는 (1) config를 먼저 읽고, (2) rope_scaling을 정규화한 뒤, (3) 추론까지 수행하는 최소 예시입니다.
import torch
from transformers import AutoConfig, AutoTokenizer, AutoModelForCausalLM
model_id = "your-local-or-hf-model"
def normalize_rope_scaling(cfg):
rope_scaling = getattr(cfg, "rope_scaling", None)
if rope_scaling is None:
return cfg
if not isinstance(rope_scaling, dict):
raise ValueError("config.rope_scaling must be a dict")
# 필수 키 보정
if "type" not in rope_scaling:
if "rope_type" in rope_scaling:
rope_scaling["type"] = rope_scaling.pop("rope_type")
# 값 보정
if "factor" in rope_scaling and isinstance(rope_scaling["factor"], str):
rope_scaling["factor"] = float(rope_scaling["factor"])
cfg.rope_scaling = rope_scaling
return cfg
config = AutoConfig.from_pretrained(model_id, trust_remote_code=True)
config = normalize_rope_scaling(config)
print("rope_scaling:", getattr(config, "rope_scaling", None))
print("max_position_embeddings:", getattr(config, "max_position_embeddings", None))
tokenizer = AutoTokenizer.from_pretrained(model_id, use_fast=True, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_id,
config=config,
device_map="auto",
torch_dtype=torch.float16,
trust_remote_code=True,
)
prompt = "RoPE scaling 설정이 올바르면 이 문장을 자연스럽게 이어서 설명해줘."
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
with torch.no_grad():
out = model.generate(
**inputs,
max_new_tokens=128,
do_sample=True,
temperature=0.7,
top_p=0.9,
)
print(tokenizer.decode(out[0], skip_special_tokens=True))
이 코드는 “무조건 rope_scaling을 덮어쓴다”가 아니라, 존재할 때만 최소한의 스키마 정리를 합니다. 모델마다 의도한 스케일링 방식이 다르므로, 임의로 type이나 factor를 바꾸는 것은 마지막 수단으로 남겨두는 편이 안전합니다.
trust_remote_code=True를 켰을 때의 주의점
RoPE 관련 커스텀 로직이 modeling_*.py에 구현된 모델은 trust_remote_code=True 없이는 정상 로드가 안 되기도 합니다. 다만 이는 원격 코드를 실행하는 옵션이므로, 다음을 권장합니다.
- 가능한 한 신뢰할 수 있는 출처의 모델만 사용
- 모델 파일을 로컬에 고정(핀)하고 해시 기반으로 검증
- 운영 환경에서는 별도 컨테이너/권한 격리
마무리
Transformers 로컬 LLM에서 RoPE 스케일링 오류는 대부분 “모델 config 스키마”와 “라이브러리 버전/캐시”의 엇갈림에서 시작합니다. 먼저 버전과 캐시를 정리하고, 그 다음 AutoConfig를 선로딩해 rope_scaling을 눈으로 확인한 뒤, 필요한 최소한의 정규화만 적용하는 흐름이 가장 빠르고 안전합니다.
긴 컨텍스트 확장은 숫자만 키운다고 해결되지 않습니다. 모델이 의도한 스케일링 타입과 지원 길이를 존중하면서, 짧은 회귀 테스트 세트를 만들어 단계적으로 늘려가면 “에러는 없는데 품질이 망가지는” 구간도 훨씬 빨리 잡아낼 수 있습니다.