인터넷이 차단된 다크사이트에 LLM을 반입하려면 모델 폴더, 실행 패키지, 무결성 확인 자료를 함께 준비하는 편이 좋아요. 인터넷이 되는 준비 서버에서 파일을 내려받고 압축한 뒤, 내부 서버에서는 체크섬을 확인하고 로컬 경로로 모델을 읽습니다.
이 글은 Hugging Face의 Qwen/Qwen2.5-0.5B-Instruct를 예로 들어 Linux에서 safetensors 모델을 옮기는 과정을 설명합니다. 기존 Ollama 모델 저장 위치·오프라인 실행 글과 달리, Transformers가 모델 폴더를 직접 읽는 방식입니다.
모델 반입 구조 한눈에 보기
인터넷에서 준비하고, 파일로 옮겨 내부에서 실행합니다.
01 · 인터넷 구간
다운로드·실행 확인
모델 가중치 · 설정 · 토크나이저
Python 패키지와 버전 기록
02 · 파일 반입
압축·체크섬 전달
bundle/ → tar.gz
압축파일 + .sha256
03 · 다크사이트
검증·해제·실행
체크섬 확인 → 로컬 패키지 설치
로컬 경로 + 오프라인 모드
압축파일 안의 bundle/ 구성
model/
가중치 · 설정 · 토크나이저
wheelhouse/
오프라인 설치용 Python 패키지
run_local.py
로컬 모델을 읽는 실행 코드
requirements.lock · SHA256SUMS · records/
패키지 버전 · 파일 체크섬 · 모델 기록
내부 서버의 Python·OS 환경은 별도로 준비합니다. 가상환경 폴더는 복사하지 않습니다.
이 글의 목차
반입할 파일과 실행 환경
.safetensors는 모델의 가중치를 저장하는 형식입니다. 일반적인 Transformers 텍스트 모델은 가중치와 함께 모델 구조 설정, 문장을 토큰으로 바꾸는 토크나이저가 필요해요. 분할된 가중치는 인덱스 파일이 가리키는 조각을 모두 가져와야 합니다. Transformers의 로컬 모델 로딩 문서와 분할 체크포인트 설명에서 이 구조를 확인할 수 있습니다.
| 파일·디렉터리 | 반입 이유 |
|---|---|
model.safetensors 또는 분할된 모든 *.safetensors | 실제 모델 가중치 |
config.json | 모델 구조와 설정 |
model.safetensors.index.json | 분할 모델에서 가중치와 파일의 대응 관계. 단일 파일 모델에는 없을 수 있음 |
| 토크나이저·채팅 템플릿 파일 | tokenizer.json, tokenizer_config.json, 어휘 파일 등 모델별 구성 |
generation_config.json, README·라이선스 | 제공된 생성 설정, 모델 설명과 사용 조건 |
wheelhouse/, requirements.lock, 실행 코드 | 내부망에서 Python 패키지를 설치하고 같은 방식으로 실행 |
아래 명령은 Bash, GNU tar·coreutils, Python 3.11과 venv·pip가 준비된 Linux x86_64를 기준으로 합니다. Python 자체와 OS 패키지는 모델 압축파일에 포함하지 않습니다. 준비 서버와 내부 서버의 OS 계열·CPU 아키텍처·Python 버전을 맞추고, 모델 적재에 필요한 RAM과 작업 디스크 여유를 확인합니다. Windows에서는 WSL2 안의 Linux 환경에서 진행할 수 있습니다.
예제는 반입 절차를 확인하기 위한 작은 텍스트 모델과 CPU 실행을 사용합니다. Qwen 모델 카드는 이 저장소의 라이선스를 Apache-2.0으로 표시합니다. 다른 모델을 선택한다면 해당 저장소의 모델 카드와 라이선스, 사용하는 Transformers 버전의 지원 여부부터 확인합니다. Qwen2.5-0.5B-Instruct 모델 카드가 기준 자료입니다.
인터넷 구간에서 패키지와 모델 받기
작업 폴더와 전용 가상환경
준비 서버에서 새 작업 폴더를 만들고 그 안에서 진행합니다. 아래 버전은 이 글의 재현용 조합이며 최신 버전이라는 의미는 아닙니다. CPU용 PyTorch의 설치 경로는 PyTorch 공식 버전별 설치 안내를 따릅니다.
mkdir -p ~/darksite-llm/bundle/{model,wheelhouse,records}
cd ~/darksite-llm
python3.11 -m venv .prepare-venv
source .prepare-venv/bin/activate
python -m pip install "torch==2.8.0+cpu" \
--index-url https://download.pytorch.org/whl/cpu
python -m pip install \
"transformers==4.57.1" \
"huggingface_hub==0.36.0" \
"safetensors==0.6.2"
python -m pip check
설치가 실패하면 모델 다운로드로 넘어가기 전에 원인을 해결합니다. 내부망에 실행 환경이 이미 있어도 그 환경의 패키지 버전과 모델 지원 여부를 확인해야 합니다. 가상환경 폴더 자체는 경로와 운영체제에 의존하므로 복사하지 않고 내부 서버에서 다시 만듭니다.
커밋을 고정하고 모델 폴더 다운로드
다운로드할 시점의 커밋 ID를 구한 뒤 그 값을 --revision에 전달합니다. 브랜치 이름만 기록하는 것보다 어떤 파일을 반입했는지 추적하기 쉽습니다. hf download에 개별 파일명을 생략하면 저장소 파일을 함께 받을 수 있고, --local-dir로 반입용 폴더를 지정할 수 있습니다. Hugging Face CLI 문서를 참고합니다.
export MODEL_ID="Qwen/Qwen2.5-0.5B-Instruct"
export MODEL_DIR="$PWD/bundle/model"
MODEL_REVISION=$(python -c \
'import os; from huggingface_hub import HfApi; print(HfApi().model_info(os.environ["MODEL_ID"]).sha)')
export MODEL_REVISION
hf download "$MODEL_ID" \
--revision "$MODEL_REVISION" \
--local-dir "$MODEL_DIR"
printf '%s\n' "$MODEL_ID" > bundle/records/model-id.txt
printf '%s\n' "$MODEL_REVISION" > bundle/records/model-revision.txt
date -u +%FT%TZ > bundle/records/downloaded-at-utc.txt
python -VV > bundle/records/python-version.txt
uname -m > bundle/records/architecture.txt
cat /etc/os-release > bundle/records/os-release.txt
예제 공개 모델은 별도 로그인이 필요하지 않습니다. 접근 제한 모델은 준비 서버에서 이용 조건에 동의한 계정으로 hf auth login을 실행한 뒤 받습니다. 토큰 저장소나 사용자 홈 전체를 반입 압축파일에 넣지 않습니다.
이 예제는 필요한 설정 파일을 빠뜨리지 않도록 저장소 전체를 받습니다. 다른 저장소에 여러 정밀도·다른 포맷의 대용량 파일이 함께 있으면 목록과 모델 카드를 먼저 확인하세요. 파일을 선별하더라도 같은 커밋의 가중치·설정·토크나이저를 한 세트로 유지합니다. 이미지 생성 모델, 멀티모달 모델, LoRA 어댑터는 이 텍스트 모델과 필요한 구성품이 다릅니다.
find bundle/model -maxdepth 2 -type f -printf '%P\n' | sort
find bundle/model -type l -print
du -sh bundle/model
파일 목록에서 config.json, 토크나이저 파일, model.safetensors를 확인합니다. 분할 모델이라면 인덱스와 모든 조각을 확인합니다. 두 번째 명령에서 심볼릭 링크가 나오면 압축을 멈추고 실제 파일이 반입 폴더 안에 있는지 확인합니다. Hub 캐시의 스냅샷 폴더만 복사하면 링크가 가리키는 파일이 빠질 수 있어요. Hub 다운로드·캐시 안내에 설명된 로컬 폴더 방식을 사용합니다.
로컬 경로로 읽는 실행 코드 만들기
다음 내용을 bundle/run_local.py로 저장합니다. 모델 경로는 실행 파일의 위치를 기준으로 계산하므로 압축을 다른 디렉터리에 풀어도 동작합니다. 예제는 CPU와 float32를 사용하며, 가중치는 safetensors로만 읽도록 지정합니다.
from pathlib import Path
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_dir = Path(__file__).resolve().parent / "model"
tokenizer = AutoTokenizer.from_pretrained(
str(model_dir),
local_files_only=True,
trust_remote_code=False,
)
model = AutoModelForCausalLM.from_pretrained(
str(model_dir),
local_files_only=True,
trust_remote_code=False,
use_safetensors=True,
dtype=torch.float32,
attn_implementation="eager",
)
model.eval()
messages = [
{"role": "user", "content": "Explain what an offline language model is in one sentence."}
]
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
inputs = tokenizer(text, return_tensors="pt")
with torch.inference_mode():
output = model.generate(
**inputs,
max_new_tokens=48,
do_sample=False,
pad_token_id=tokenizer.eos_token_id,
)
answer = output[0, inputs["input_ids"].shape[1]:]
print(tokenizer.decode(answer, skip_special_tokens=True))
local_files_only=True는 지정된 로컬 파일만 사용하게 합니다. trust_remote_code=False는 저장소의 사용자 정의 Python 코드를 허용하지 않는 설정입니다. 이 예제 모델은 Transformers에 구현된 구조를 사용합니다. 다른 모델이 사용자 정의 코드를 요구한다면 파일을 추가하는 것과 별개로 해당 코드와 실행 환경을 검토해야 합니다.
준비 서버에서도 아래처럼 오프라인 모드를 설정하고 한 번 실행합니다. HF_HUB_OFFLINE은 라이브러리를 불러오기 전에 설정해야 합니다. 이 변수의 적용 범위는 Hugging Face Hub 통신이며, 서버 전체의 네트워크를 차단하는 기능은 아닙니다. 공식 환경 변수 설명을 참고합니다.
HF_HUB_OFFLINE=1 HF_HUB_DISABLE_TELEMETRY=1 \
python bundle/run_local.py
응답이 출력되면 모델 적재와 짧은 생성 요청을 확인한 것입니다. 원격 저장소 ID를 넣은 다른 프로그램이나 기존 캐시에 의존하지 않도록, 실제 반입할 bundle/model을 읽는지 확인합니다. 영어 질문은 재현용이며, 한국어 질문으로 바꿔도 반입 절차는 같습니다.
내부망 설치용 wheel 파일 묶기
모델 폴더를 옮겨도 내부 서버에서 pip install transformers가 인터넷에 연결되면 설치가 멈춥니다. 준비 서버의 전용 가상환경에서 패키지 버전을 기록하고, 해당 버전의 wheel 파일과 의존성을 함께 받습니다. wheel은 Python 패키지의 설치용 배포 파일입니다.
python -m pip freeze > bundle/requirements.lock
python -m pip download \
--only-binary=:all: \
--dest bundle/wheelhouse \
--extra-index-url https://download.pytorch.org/whl/cpu \
-r bundle/requirements.lock
python -m pip check
ls bundle/wheelhouse
--only-binary=:all:은 소스 빌드가 필요한 파일 대신 wheel만 받도록 합니다. 이 단계가 실패하면 대상에 맞는 wheel이 있는지 확인하거나, 호환되는 준비 환경에서 빌드·검증한 배포 파일을 마련합니다. 다른 OS나 Python 버전의 wheel은 그대로 설치할 수 없습니다. pip download 공식 문서에 대상 플랫폼 옵션과 제약이 정리되어 있습니다.
CPU 예제이므로 CUDA·NVIDIA 드라이버는 포함하지 않습니다. GPU로 사용할 때는 대상 GPU와 드라이버에 맞는 PyTorch 빌드, 추가 커널·양자화 라이브러리를 별도로 준비하고 다시 테스트해야 합니다. 모델 파일의 압축을 푸는 것만으로 GPU 실행 환경이 설치되지는 않습니다.
체크섬을 만들고 압축하기
모델 파일과 wheel, 실행 코드를 모두 준비한 뒤 작업 폴더에서 실행합니다. 내부 파일용 체크섬과 압축파일 자체의 체크섬을 따로 만들면 전송 중 손상과 압축 해제 후 누락을 각각 확인할 수 있어요. 다운로드 재개용 model/.cache 메타데이터는 묶음에서 제외합니다.
(
cd bundle
find . -type f \
! -path './model/.cache/*' \
! -path './SHA256SUMS' -print0 \
| sort -z \
| xargs -0 sha256sum > SHA256SUMS
sha256sum -c SHA256SUMS
)
tar --exclude='bundle/model/.cache' \
-czf qwen25-darksite-bundle.tar.gz bundle
sha256sum qwen25-darksite-bundle.tar.gz \
> qwen25-darksite-bundle.tar.gz.sha256
du -sh bundle qwen25-darksite-bundle.tar.gz
출력에 FAILED가 있거나 명령이 실패하면 반입하지 않고 원인을 해결합니다. 압축은 여러 파일을 전달하기 위한 방법으로 사용하며, 가중치가 크게 줄어든다고 가정하지 않습니다. 준비 구간에는 원본과 압축파일을 함께 둘 공간이, 내부 구간에는 압축파일과 해제한 파일을 함께 둘 공간이 필요합니다.
qwen25-darksite-bundle.tar.gz와 qwen25-darksite-bundle.tar.gz.sha256을 승인된 매체나 반입 경로로 함께 옮깁니다. 체크섬은 동일성 확인 수단이므로, 신뢰할 수 있는 반입 기록에도 원본 해시를 남깁니다. 모델 이용 조건과 조직의 파일 검사·반입 절차도 함께 적용합니다.
다크사이트에서 검증하고 압축 풀기
내부 서버에 두 파일을 /data/import로 반입했다고 가정합니다. 경로는 실제 위치로 바꾸고, 압축 해제 대상은 실행 계정이 쓸 수 있는 새 디렉터리로 준비합니다. 기존 모델 위에 바로 덮어쓰지 않으면 문제가 생겼을 때 이전 모델을 계속 사용할 수 있습니다.
cd /data/import
sha256sum -c qwen25-darksite-bundle.tar.gz.sha256
tar -tzf qwen25-darksite-bundle.tar.gz
mkdir -p /data/llm/releases/qwen25-offline-01
tar --no-same-owner \
-xzf qwen25-darksite-bundle.tar.gz \
-C /data/llm/releases/qwen25-offline-01
cd /data/llm/releases/qwen25-offline-01/bundle
sha256sum -c SHA256SUMS
첫 번째 검증이 OK인지 확인한 뒤 다음 명령으로 진행합니다. tar -tzf로 파일이 bundle/ 아래에 있는지도 확인합니다. 마지막 검증에서 누락이나 불일치가 하나라도 나오면 실행을 멈추고 전송본과 해제 경로를 점검합니다.
외부 저장소를 조회하지 않고 설치·실행
압축을 푼 bundle 디렉터리에서 실행합니다. 내부 서버에도 Python 3.11과 venv·pip가 있어야 합니다. --no-index와 --find-links를 함께 사용해 반입한 wheel 디렉터리만 설치 대상으로 지정합니다. pip의 로컬 패키지 설치 안내에 나온 방식입니다.
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install \
--no-index \
--find-links ./wheelhouse \
-r requirements.lock
python -m pip check
HF_HUB_OFFLINE=1 HF_HUB_DISABLE_TELEMETRY=1 \
python run_local.py
준비 구간과 내부 구간에서 같은 코드로 답변이 나오는지 확인합니다. 외부 통신이 차단된 테스트 환경에서 설치와 모델 실행을 모두 통과하면, 기존 인터넷 캐시나 원격 다운로드에 의존하는 부분을 찾기 쉽습니다. 별도의 웹 UI·RAG·검색 도구를 붙인 경우에는 그 구성요소의 네트워크 의존성도 따로 확인합니다.
파일은 있는데 실행되지 않을 때
| 증상 | 먼저 확인할 항목 | 조치 |
|---|---|---|
config.json을 찾지 못함 | 전달한 경로 안의 파일 구조 | 저장소 ID나 safetensors 파일 한 개 대신 config.json이 있는 디렉터리를 전달 |
| 토크나이저·채팅 템플릿 오류 | 토크나이저 파일과 모델 종류 | 같은 커밋의 토크나이저를 반입하고 모델 카드의 대화 형식 확인 |
| 가중치 조각을 찾지 못함 | 인덱스가 가리키는 모든 shard | 누락된 조각을 같은 커밋에서 다시 받고 체크섬 재작성 |
| Hub 연결 시도·오프라인 파일 오류 | 로컬 경로, 오프라인 변수 설정 시점 | 변수를 프로세스 시작 전에 지정하고 필요한 파일 누락 확인 |
No matching distribution·wheel 설치 오류 | Python·OS·아키텍처·의존성 | 대상과 호환되는 준비 서버에서 wheel 묶음 다시 구성 |
| RAM·VRAM 부족 | 모델 정밀도, 컨텍스트, 동시 요청 | 실행 크기를 줄이거나 적합한 모델·메모리 구성 선택 |
| 지원하지 않는 모델·양자화 오류 | 런타임 버전과 quantization_config | 지원 버전과 필요한 패키지를 준비 구간에서 검증 후 함께 반입 |
파일 확장자가 같아도 AWQ·GPTQ·FP8 모델이나 LoRA 어댑터는 필요한 실행 조건이 다릅니다. 어댑터만 받은 경우에는 기반 모델도 있어야 합니다. 이 글의 CPU 스크립트를 다른 종류의 safetensors 파일에 그대로 적용할 수 있다고 가정하지 않습니다.
Ollama나 llama.cpp에 연결하려면 해당 런타임이 지원하는 모델 구조와 가져오기 절차를 따릅니다. 확장자를 .gguf로 바꾸는 것만으로 포맷이 변환되지는 않습니다. GGUF를 사용하는 과정은 llama.cpp의 GGUF·CPU·GPU 옵션 글에서 별도로 다룹니다.
확인한 범위와 공식 자료
공식 문서와 실행 절차는 2026년 10월 22일 기준으로 확인했습니다. Debian 13 x86_64 컨테이너의 Python 3.11.17, PyTorch 2.8.0+cpu, Transformers 4.57.1에서 예제 모델을 내려받아 압축했습니다. 외부 네트워크를 끈 별도 컨테이너에서 압축파일·내부 파일 체크섬 검증, wheel만 사용한 새 가상환경 설치, pip check, 짧은 답변 생성까지 확인했습니다. GPU 실행과 다른 OS·대형 모델은 이 검증에 포함하지 않았습니다.
- Hugging Face CLI 0.36.0: 모델 다운로드와 revision
- Transformers 4.57.1: 로컬 모델 로딩 옵션
- Hugging Face Hub: 오프라인 모드 환경 변수
- Qwen2.5-0.5B-Instruct 모델 카드와 파일
- pip download: 오프라인 설치용 패키지 준비
- pip: 로컬 패키지 설치
내부에서 사용할 모델로 바꿀 때에는 준비 구간에서 다운로드·실행·재설치를 먼저 끝내고 새 묶음으로 반입합니다. 모델 커밋, 패키지 목록, 체크섬을 함께 보관하면 이후에도 같은 파일 세트를 다시 확인할 수 있습니다.



