직접 한 을 에 올려 누구나 내려받게 합니다. export.py 로 를 내보내고, 를 쓴 뒤 push 해 보세요.
10
내 모델 공개하기
는 을 올리고 내려받는 곳으로, 모델을 위한 같은 사이트입니다. 모델 하나가 하나입니다. 파일·설정··실행 와 ()를 함께 올리면 다른 사람이 같은 모델을 받아 그대로 돌릴 수 있습니다.
순서는 세 가지입니다. export.py 가 체크포인트의 숫자를 파일로 내보내고, 내가 모델 카드를 쓰고, git 으로 Hugging Face 저장소에 push 합니다. 는 바꾸지 않습니다. 내려받은 사람은 export.py --restore 로 체크포인트를 되살려 같은 generate.py·runner.py·xchat.py 로 실행합니다.
왜 공개하나 — 모델을 나누는 곳, Hugging Face
Hugging Face 는 모델·데이터셋·데모를 올리고 받는 공개 저장소입니다. 큰 회사부터 학생까지 누구나 모델을 올리고, 개요에서 본 SmolLM2-135M 도 여기서 받습니다.
저장소: 모델 하나가 git 저장소 하나입니다. 주소는 huggingface.co/아이디/모델이름 입니다.
: push 할 때마다 이 남아 예전 가중치로 돌아갈 수 있습니다.
모델 카드: 저장소의 README.md 가 모델의 첫 화면입니다.
공개 범위: Public 은 누구나, Private 은 나와 내가 허락한 사람만 봅니다.
공개하면 다른 사람이 내 결과를 그대로 다시 돌려 볼 수 있습니다. 이것을 이라고 합니다. 실제 인공지능 개발에서도 을 받아 하고 다시 올리는 일이 매일 일어납니다.
safetensors — 가중치를 담는 안전한 파일
safetensors 는 Hugging Face 가 만든 가중치 파일 형식입니다. 모양은 세 부분뿐입니다.
첫 8: 뒤에 오는 머리의 길이입니다(부호 없는 정수, ).
JSON 머리: 층 이름마다 (F32)·shape(행, 열)·data_offsets(본문 안의 시작·끝 바이트)를 적습니다.
본문: 모든 숫자를 4바이트 로 빈틈없이 이어 붙인 바이트입니다.
예전에 많이 쓰던 의 .bin 파일은 pickle 이라, 파일을 여는 순간 그 안에 숨은 코드가 실행될 수 있었습니다. safetensors 에는 숫자와 JSON 만 있어 열어도 코드가 실행되지 않습니다. 그래서 남이 올린 모델을 받을 때 더 안전합니다.
의 Python 숫자는 8바이트(float64)인데 파일에는 4바이트(float32)로 줄여 씁니다. 0.1 이 0.10000000149011612 가 되는 것처럼 아주 작은 차이가 생기지만, 기본 자료로 한 모델은 생성 결과가 똑같습니다. 1,248개 × 4바이트 = 4,992바이트에 머리를 더해 파일은 약 5.7KB 입니다.
model.safetensors 는 숫자 표일 뿐입니다. 이 숫자를 다시 쓰려면 받는 사람에게 세 가지가 더 필요합니다.
config.json: 모델의 크기(width·)와 수, 생성 기본값, 학습한 걸음 수입니다.
tokenizer.json: 글을 번호로 바꾸는 어휘와 입니다. 번호가 하나만 달라도 모델은 엉뚱한 글자를 봅니다.
실행 코드: engine.py·model.py·tokenizer.py·generate.py·runner.py·xchat.py·export.py 입니다. 같은 숫자도 같은 계산으로 돌려야 같은 답이 나옵니다.
export.py 는 이 파일을 모두 hub 폴더에 모읍니다. ()와 train.py 는 올리지 않습니다. 모델을 쓰는 데는 필요 없고, 자료는 따로 권리를 따져야 하기 때문입니다. 큰 모델도 같습니다. Hugging Face 의 모델 저장소에는 보통 config.json·tokenizer.json·*.safetensors 가 함께 있습니다.
모델 카드 — README.md 에 쓰는 모델 설명서
모델 카드는 저장소의 README.md 입니다. 위아래 두 부분으로 나뉩니다.
머리(--- 두 줄 사이): (라이선스), language(언어), tags(검색용 낱말), pipeline_tag(작업 종류, 여기서는 text-generation)입니다. Hugging Face 가 읽어 모델 목록의 필터와 표시에 씁니다.
본문: 사람이 읽는 설명입니다.
본문에는 적어도 다섯 가지를 씁니다. 무엇인지, 어떤 파일이 있는지, 어떻게 실행하는지, 무슨 자료로 학습했는지, 무엇을 못 하는지(한계)입니다. 한계를 솔직히 적는 것이 중요합니다. 가 1,248개라 답이 거의 무작위라고 써 두면, 받는 사람이 잘못 쓰지 않습니다.
라이선스와 자료 — 공개하기 전에 확인할 것
Public 저장소는 전 세계 누구나 보고 내려받습니다. 올리기 전에 세 가지를 확인하세요.
라이선스: 남이 어떻게 써도 되는지 정합니다. MIT 는 저작권 표시만 남기면 거의 자유롭게 쓰게 하는 짧은 라이선스입니다. 다른 것을 원하면 ·CC-BY-4.0 처럼 Hugging Face 목록에 있는 이름을 씁니다.
학습 자료의 권리: 남의 글을 허락 없이 학습시켰다면 그 모델도 공개하지 마세요. 기본 자료는 이 과정을 위해 지은 예문입니다.
개인정보: 이름·전화번호·주소 같은 정보를 자료에 넣었다면 모델이 그 글을 그대로 내뱉을 수 있습니다. 지우고 다시 학습한 뒤 올립니다.
처음에는 Private 으로 올려 확인하고, 괜찮으면 저장소 Settings 에서 Public 으로 바꿔도 됩니다.
큰 파일과 git-lfs — 숫자 파일은 따로 올린다
git 은 소스 코드처럼 줄이 있는 글의 바뀐 줄을 기록하도록 만들어졌습니다. 가중치 같은 파일은 줄이 없어 조금만 바뀌어도 파일 전체가 다시 쌓입니다. 그래서 Hugging Face 는 바이너리 파일을 git 안에 직접 넣은 push 를 거절합니다. 파일이 작아도 마찬가지입니다.
: 파일 내용은 따로 올리고, git 에는 그 파일을 가리키는 짧은 포인터(크기와 )만 넣습니다.
.gitattributes: 어떤 파일을 LFS 로 보낼지 적은 목록입니다. Hugging Face 가 저장소를 만들 때 *.safetensors 를 미리 넣어 둡니다.
git-xet: Hugging Face 의 더 빠른 새 방식이지만 64bit 컴퓨터 전용입니다. 이 은 라 git-lfs 를 씁니다. Hugging Face 는 git-lfs 로 올린 파일도 그대로 받아 저장합니다.
git lfs install 은 git 에 LFS 를 연결하는 설정으로, 한 번만 하면 됩니다. 그 뒤로는 평소처럼 git add·commit·push 만 하면 됩니다. git lfs ls-files 로 LFS 로 간 파일을 확인할 수 있습니다.
액세스 토큰 — 비밀번호 대신 쓰는 열쇠
Hugging Face 는 git push 에 계정 비밀번호를 받지 않습니다. 대신 Settings → Access Tokens 에서 만든 (hf_ 로 시작하는 긴 글자)을 비밀번호 자리에 넣습니다.
: Read 토큰은 받기만, Write 토큰은 올리기까지 됩니다. push 에는 Write 가 필요합니다.
credential.helper : 입력한 토큰을 15분 동안 에만 기억해 git 과 git-lfs 가 두 번 묻지 않게 합니다. 파일에는 저장하지 않습니다.
지우기: 다 올렸으면 Access Tokens 에서 그 토큰을 지워도 됩니다. 저장소와 모델은 그대로 남습니다.
토큰이 새어 나갔다고 생각되면 바로 지우고 새로 만드세요. 지운 토큰으로는 아무것도 할 수 없습니다.
내려받아 다시 돌리기 — 공개 완성의 확인
공개한 모델은 다른 사람의 컴퓨터에서 돌아가야 의미가 있습니다. 내려받는 사람은 이렇게 합니다.
git clone https://huggingface.co/YOUR_ID/xslm-tiny 로 저장소를 받습니다. git-lfs 가 없으면 model.safetensors 대신 짧은 포인터 글만 받으니, 그때는 웹의 Files 탭에서 그 파일을 내려받습니다.
export.py --restore . 로 safetensors·config·tokenizer 를 checkpoints/model.json 으로 되살립니다.
python3 generate.py 로 글을 만들거나 runner.py·xchat.py 로 대화합니다. 설치할 패키지는 없습니다.
와 설정이 같으면 이 터미널과 똑같은 답이 나옵니다. 이것이 재현입니다. 큰 모델도 같은 방식입니다. 받는 쪽은 safetensors 를 읽어 자기 (PyTorch·· 등)에서 실행합니다.
직접 타이핑하며 실습하기
먼저 huggingface.co 에 가입하고, huggingface.co/new 에서 xslm-tiny 라는 Public 모델 저장소를 만드세요. Settings → Access Tokens 에서 Write 권한 토큰도 하나 만듭니다. 그다음 아래 을 차례대로 입력합니다. YOUR_ID 는 내 Hugging Face 아이디로 바꿉니다. git push 가 Username 을 물으면 아이디를, Password 를 물으면 비밀번호 대신 토큰을 붙여 넣으세요. 토큰은 화면에 보이지 않습니다.
write_safetensors 는 숫자를 float32 4바이트씩 이어 쓰고 앞에 JSON 머리를 붙입니다. read_safetensors 는 그 반대입니다. export 는 hub 폴더에 가중치·설정·토크나이저·실행 소스를 모으고, 다시 읽어 차이를 보여 줍니다. --restore 는 내려받은 파일을 checkpoints/model.json 으로 되돌립니다.
import argparse
import json
import shutil
import struct
from pathlib import Path
SOURCES = ['engine.py', 'model.py', 'tokenizer.py', 'generate.py',
'runner.py', 'xchat.py', 'export.py']
def write_safetensors(path, weights):
header, body = {'__metadata__': {'format': 'xslm'}}, b''
for name, matrix in weights.items():
rows, cols = len(matrix), len(matrix[0])
data = struct.pack('<%df' % (rows * cols),
*[v for row in matrix for v in row])
header[name] = {'dtype': 'F32', 'shape': [rows, cols],
'data_offsets': [len(body), len(body) + len(data)]}
body += data
head = json.dumps(header).encode('utf-8')
head += b' ' * (-len(head) % 8)
path.write_bytes(struct.pack('<Q', len(head)) + head + body)
def read_safetensors(path):
raw = path.read_bytes()
size = struct.unpack('<Q', raw[:8])[0]
header = json.loads(raw[8:8 + size])
body, weights = raw[8 + size:], {}
for name, info in header.items():
if name == '__metadata__':
continue
assert info['dtype'] == 'F32', 'only float32'
rows, cols = info['shape']
start, end = info['data_offsets']
flat = struct.unpack('<%df' % (rows * cols), body[start:end])
weights[name] = [list(flat[r * cols:(r + 1) * cols])
for r in range(rows)]
return weights
def export(folder):
saved = json.loads(Path('checkpoints/model.json').read_text())
folder.mkdir(exist_ok=True)
write_safetensors(folder / 'model.safetensors', saved['weights'])
config = {'model_type': 'xslm', 'vocab_size': len(saved['tokenizer']['vocab']),
'step': saved['step'], 'settings': saved['settings']}
(folder / 'config.json').write_text(json.dumps(config, indent=2))
(folder / 'tokenizer.json').write_text(
json.dumps(saved['tokenizer'], ensure_ascii=False), encoding='utf-8')
for name in SOURCES:
shutil.copy(name, folder / name)
loaded = read_safetensors(folder / 'model.safetensors')
gap = max(abs(a - b) for name in loaded
for row, old in zip(loaded[name], saved['weights'][name])
for a, b in zip(row, old))
count = sum(len(row) for matrix in loaded.values() for row in matrix)
print('parameters:', count, 'bytes:', (folder / 'model.safetensors').stat().st_size)
print('largest float32 gap:', '%.1e' % gap)
print('exported:', folder)
def restore(folder):
config = json.loads((folder / 'config.json').read_text())
state = {'settings': config['settings'],
'tokenizer': json.loads((folder / 'tokenizer.json').read_text(encoding='utf-8')),
'weights': read_safetensors(folder / 'model.safetensors')}
Path('checkpoints').mkdir(exist_ok=True)
Path('checkpoints/model.json').write_text(
json.dumps(state, ensure_ascii=False), encoding='utf-8')
print('restored: checkpoints/model.json')
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--to', default='hub')
parser.add_argument('--restore', metavar='FOLDER')
args = parser.parse_args()
if args.restore:
restore(Path(args.restore))
else:
export(Path(args.to))
hub/README.md직접 작성할 전체 소스
모델 카드입니다. 맨 위 --- 사이의 YAML 은 Hugging Face 가 읽는 정보(라이선스·언어·태그·작업 종류)이고, 아래는 사람이 읽는 설명입니다. YOUR_ID 를 내 아이디로 바꾸고, 자료를 바꿨다면 파라미터 수와 학습 자료 설명도 내 것으로 고치세요.
---
license: mit
language:
- en
tags:
- xslm
- tiny
- educational
pipeline_tag: text-generation
---
# XSLM tiny
A 1,248-parameter GPT that I typed from scratch and trained
inside my web browser, following https://sonub.com/ai/build
## Files
- model.safetensors: trained weights (float32)
- config.json: size and sampling settings
- tokenizer.json: character BPE vocabulary and merge rules
- engine.py, model.py, tokenizer.py, generate.py: the model code
- runner.py, xchat.py: HTTP Runner and terminal chat
- export.py: writes these files, and turns them back into a checkpoint
## How to run
Python 3 only. Nothing to install.
git clone https://huggingface.co/YOUR_ID/xslm-tiny
cd xslm-tiny
python3 export.py --restore .
python3 generate.py
## Training data
15 short English Q/A lines written for the course
(12 for training, 3 for validation).
## Limitations
For learning only. With 1,248 parameters and a few training
steps, the answers are mostly random. Do not use it for real work.
struct 는 숫자를 바이트로 바꾸고 되돌리는 이고, shutil 은 파일을 복사합니다. SOURCES 는 모델을 실행하는 데 필요한 소스 목록입니다. 학습에만 쓰는 prepare.py·train.py 는 넣지 않습니다.
export.py write_safetensors — 숫자를 파일로
def write_safetensors(path, weights): header, body = {'__metadata__': {'format': 'xslm'}}, b'' for name, matrix in weights.items(): rows, cols = len(matrix), len(matrix[0]) data = struct.pack('<%df' % (rows * cols), *[v for row in matrix for v in row]) header[name] = {'dtype': 'F32', 'shape': [rows, cols], 'data_offsets': [len(body), len(body) + len(data)]} body += data head = json.dumps(header).encode('utf-8') head += b' ' * (-len(head) % 8) path.write_bytes(struct.pack('<Q', len(head)) + head + body)
층마다 을 한 줄로 펼쳐 struct.pack 으로 바이트로 바꿉니다. 형식 <%df 에서 < 는 little-endian, f 는 4바이트 float32 이고, %d 자리에 숫자 개수가 들어갑니다. 머리에는 이 층의 dtype·shape 와 본문 안의 시작·끝 위치를 적습니다. JSON 머리 길이를 8의 배수로 맞추려고 빈칸을 붙이고, 맨 앞에 그 길이를 8바이트(<Q)로 씁니다.
export.py read_safetensors — 파일을 숫자로
def read_safetensors(path): raw = path.read_bytes() size = struct.unpack('<Q', raw[:8])[0] header = json.loads(raw[8:8 + size]) body, weights = raw[8 + size:], {} for name, info in header.items(): if name == '__metadata__': continue assert info['dtype'] == 'F32', 'only float32' rows, cols = info['shape'] start, end = info['data_offsets'] flat = struct.unpack('<%df' % (rows * cols), body[start:end]) weights[name] = [list(flat[r * cols:(r + 1) * cols]) for r in range(rows)] return weights
쓰기의 거꾸로입니다. 처음 8바이트로 머리 길이를 읽고, 그만큼을 JSON 으로 읽습니다. __metadata__ 는 층이 아니므로 건너뜁니다. data_offsets 로 본문에서 그 층의 바이트만 잘라 struct.unpack 으로 숫자를 꺼내고, cols 개씩 끊어 행렬로 되돌립니다.
학습이 저장한 checkpoints/model.json 을 읽습니다. 가중치는 safetensors 로, 크기·설정·학습 걸음 수는 config.json 으로, 어휘와 병합 규칙은 tokenizer.json 으로 씁니다. ensure_ascii=False 라 한글 같은 글자도 그대로 저장됩니다. 마지막으로 SOURCES 를 hub 로 복사합니다.
export.py 확인 — 다시 읽어 차이 재기
loaded = read_safetensors(folder / 'model.safetensors') gap = max(abs(a - b) for name in loaded for row, old in zip(loaded[name], saved['weights'][name]) for a, b in zip(row, old)) count = sum(len(row) for matrix in loaded.values() for row in matrix) print('parameters:', count, 'bytes:', (folder / 'model.safetensors').stat().st_size) print('largest float32 gap:', '%.1e' % gap) print('exported:', folder)
방금 쓴 파일을 read_safetensors 로 다시 읽어, 원래 숫자와 가장 크게 다른 값을 찾습니다. float32 로 줄인 차이라 1e-8 근처면 정상입니다. 파라미터 수와 파일 크기도 찍어 hub 가 제대로 만들어졌는지 봅니다.
config.json 의 settings, tokenizer.json, safetensors 의 가중치를 모아 generate.py 의 load 가 읽는 모양 그대로 checkpoints/model.json 을 만듭니다. 그래서 generate.py·runner.py·xchat.py 를 한 줄도 고치지 않고 내려받은 모델을 돌립니다.
export.py 명령줄 — 내보내기와 되살리기
if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--to', default='hub') parser.add_argument('--restore', metavar='FOLDER') args = parser.parse_args() if args.restore: restore(Path(args.restore)) else: export(Path(args.to))
인자가 없으면 hub 로 내보내고, --to 로 다른 폴더를 줄 수 있습니다. --restore 에 폴더를 주면 그 폴더의 파일을 체크포인트로 되살립니다. 내려받은 저장소 안에서는 python3 export.py --restore . 처럼 점()을 줍니다.
여기까지 이해했나요?
huggingface.co/YOUR_ID/xslm-tiny 를 열어 보세요. 모델 카드가 첫 화면에 보이고, Files 탭의 model.safetensors 에 LFS 표시가 있나요? 다른 폴더에 git clone 한 뒤 python3 export.py --restore . 와 python3 generate.py 를 실행해 이 터미널과 같은 답이 나오면 공개 완성입니다.
막혔을 때 확인하기
git push 가 binary files 때문에 거절되면 git lfs install 을 빠뜨린 것입니다. hub 로 지우고 git lfs install 부터 다시 하세요. failed 는 비밀번호를 넣었거나 Read 권한 토큰일 때 납니다. 새로 고침하면 hub 의 .git 이 사라지므로, 그때도 rm -rf hub 후 git clone 과 python3 export.py 를 다시 실행합니다. 다 올렸으면 Access Tokens 에서 토큰을 지워도 됩니다.
자주 묻는 질문
저장소를 비공개(Private)로 만들어도 되나요?
됩니다. Private 저장소는 나와 내가 허락한 사람만 봅니다. 올리는 과정은 똑같고, 내려받을 때도 토큰이 필요하다는 점만 다릅니다. 확인이 끝나면 저장소 Settings 에서 Public 으로 바꿀 수 있습니다.
올린 모델을 고치거나 지우려면 어떻게 하나요?
고치려면 다시 학습하고 python3 export.py 를 실행한 뒤 hub 에서 git add·commit·push 합니다. 새 커밋이 쌓이고 예전 버전도 기록에 남습니다. 지우려면 저장소 Settings 맨 아래의 삭제(Delete) 단추를 누릅니다. 지운 저장소는 되돌릴 수 없습니다.
transformers 라이브러리로 불러올 수 있나요?
그대로는 안 됩니다. 는 · 처럼 등록된 구조만 읽는데, XSLM 은 층 구성과 문자 기반 토크나이저가 다릅니다. 그래서 실행 코드를 함께 올리고 export.py --restore 로 우리 코드에서 돌립니다. safetensors 파일 자체는 표준 형식이라 safetensors 로 열어 배열로 읽을 수 있습니다.