BitNet에 LoRA로 이메일 톤 입히기BitNet에 LoRA로 이메일 톤 입히기

BitNet에 LoRA로 이메일 톤 입히기

태그
python
linux
wsl
AI
생성 일시
Jun 8, 2026 03:51 PM
최종 편집 일시
Last updated June 9, 2026
Date

개요

이 글은 1.58비트로 양자화된 BitNet b1.58 모델 위에, GPU 없이 CPU만으로, 수백 건도 안 되는 작은 데이터로 LoRA 어댑터를 학습시켜 "캐주얼한 개인 이메일 톤"이라는 문체를 주입할 수 있는지 검증한 기록이다. 제약은 세 가지였다. 첫째, 베이스 모델이 ternary 양자화(약 1.58비트)라 표현력 자체가 낮다. 둘째, 가속기 없이 CPU만 쓴다. 셋째, 학습 데이터가 200건, 그마저 컨텍스트 필터링을 거치면 실제로는 108건뿐이다. 이 세 조건의 조합은 학습 안정성에 명백히 불리하다. 스택은 표준 Hugging Face 경로 대신 Tether의 QVAC Fabric BitNet 스택(qvac-fabric-llm.cpp, llama.cpp 계열 fork)을 썼다. 이 fork는 BitNet b1.58의 ternary 추론과 LoRA 학습을 함께 지원한다.

실험 환경

항목
플랫폼
WSL2 Ubuntu 22.04 (kernel 6.6, microsoft-standard-WSL2)
GPU
NVIDIA RTX 2060 6GB (driver 591.86 / CUDA 13.1) (미사용)
CPU
x86-64 12 logical threads, AVX2/FMA (ggml haswell 백엔드)
RAM
15GB
베이스 모델
1bitLLM bitnet_b1_58-xl, 1.27B, 24 layers, vocab 32002
양자화
TQ2_0 (ternary, ~2.06 bpw)

스택과 모델 준비

이용한 레포

qvac-rnd-fabric-llm-bitnet
tethertoUpdated Aug 9, 2026
qvac-fabric-llm.cpp
tethertoUpdated Aug 13, 2026

prebuilt 받기

gh release view --repo tetherto/qvac-fabric-llm.cpp # 최신 태그 확인 (당시 b7349) gh release download b7349 --repo tetherto/qvac-fabric-llm.cpp \\ --pattern "llama-b7349-bin-ubuntu-vulkan-x64.tar.gz" --dir bin tar xzf bin/llama-b7349-bin-ubuntu-vulkan-x64.tar.gz -C bin

모델 받기(양자화)

mkdir -p models curl -L -f -o models/bitnet-xl.tq2_0.gguf \\ "<https://huggingface.co/qvac/fabric-llm-finetune-bitnet/resolve/main/1bitLLM-bitnet_b1_58-xl-tq2_0.gguf>"
이 스택의 커널과 호환되는 포맷은 TQ2_0(file_type 37) 또는 TQ1_0(36)이다.
general.architecture = bitnet general.file_type = 37 # 37 = TQ2_0 (36 = TQ1_0) print_info: file type = TQ2_0 - 2.06 bpw ternary model params = 1.27 B

학습 데이터

import json src = "qvac-rnd-fabric-llm-bitnet/evaluations/email_style_transfer/email_dataset.jsonl" with open(src) as f, open("train.jsonl", "w") as w: for line in f: d = json.loads(line) w.write(json.dumps({"text": d["text"]}, ensure_ascii=False) + "\\n")
데이터는 역할 태그가 붙은 대화 포맷이 아니라 Subject: …\\n\\n<body> 형태의 plain text다. 따라서 instruction/chat 학습이 아니라 순수 next-token prediction이며, --assistant-loss-only는 쓰지 않는다.

학습

학습률 1e-4에서 발산

데이터셋 README가 권장하는 이메일 설정값(rank 16 / alpha 32 / lr 1e-4 / cosine / warmup 0.1)을 그대로 5에폭(510 step) 돌렸다. 초반에는 loss가 정상적으로 떨어졌지만, 약 80 step을 변곡점으로 다시 단조 증가하는 전형적인 발산이 나타났다.
step
loss
acc
16
2.35
69.0%
최저점 부근
80
2.47
67.4%
변곡점
100
3.74
54.7%
173
7.17
31.4%
수동 중단
LR만 2e-5로 낮추자, 앞선 실행이 발산하던 구간을 이번에는 평탄하게 통과했다.
loss @ step100: 1e-4 = 3.74 (발산중) vs 2e-5 = 2.35 (수렴중) acc @ step100: 1e-4 = 54.7% vs 2e-5 = 69.9%

사용 명령어

cd bin LD_LIBRARY_PATH=. ./llama-finetune-lora \\ -m ../models/bitnet-xl.tq2_0.gguf \\ -f ../train.jsonl \\ --output-adapter ../email-tone-adapter.gguf \\ -ngl 0 -c 256 -b 64 -ub 64 --flash-attn off \\ --lora-rank 16 --lora-alpha 32 \\ --learning-rate 2e-5 --lr-scheduler constant --warmup-ratio 0.1 \\ --num-epochs 2 \\ --checkpoint-save-steps 200 --checkpoint-save-dir ../lora_ckpt

체크포인트

스케줄러 크래시로 학습이 중간에 멈췄지만, 다행히 200 step 체크포인트가 남아 있었다. 이 시점의 loss 2.22 / acc 71.5%는 최종값(loss 2.216)과 사실상 같았다. 그래서 이 체크포인트를 최종 어댑터로 채택해 결과를 분석했다.
cp lora_ckpt/checkpoint_step_00000200/model.gguf email-tone-adapter.gguf

추론과 정성 평가

notion imagenotion image

반복 퇴화

base와 어댑터 모두 기본 디코딩(repeat-penalty 1.0)에서는 같은 구절을 끝없이 되풀이하는 퇴화 루프에 빠졌다.
i want to send an email to my professional email address on my iphone. i want to send an email to my professional email address on my iphone. ...
원인은 두 가지다. repetition penalty를 걸지 않은 것, 그리고 소형 저정밀 모델이 본래 가진 반복 경향이다. 아래 디코딩 설정으로 이 반복을 눌렀다.
# base LD_LIBRARY_PATH=. ./llama-cli -m ../models/bitnet-xl.tq2_0.gguf \\ -ngl 0 -c 256 --flash-attn off -s 42 \\ --temp 0.8 --top-p 0.9 --min-p 0.05 --repeat-penalty 1.3 --repeat-last-n 128 \\ -n 160 -p $'Subject: can you cover my shift saturday??\\n\\n' # 어댑터 LD_LIBRARY_PATH=. ./llama-cli -m ../models/bitnet-xl.tq2_0.gguf \\ --lora ../email-tone-adapter.gguf \\ -ngl 0 -c 256 --flash-attn off -s 42 \\ --temp 0.8 --top-p 0.9 --min-p 0.05 --repeat-penalty 1.3 --repeat-last-n 128 \\ -n 160 -p $'Subject: can you cover my shift saturday??\\n\\n'

결론

어댑터는 톤을 옮겼다. 같은 프롬프트를 주어도 base 모델은 서사·논평조로 이탈하는데, 이를테면 출생과 가족사를 줄줄이 늘어놓는 문학적 자서전 쪽으로 빠지는 식이다. 반면 어댑터는 1인칭, 관계 중심의 구어체로 흐른다. "개인적·비격식 1인칭"이라는 학습 방향과 일치하는 레지스터 변화가 또렷하게 관찰되는 셈이다.
다만 이 변화는 강하다기보다 은은했다. 데이터포인트가 108건으로 적은 데다, base가 1.27B ternary 모델이라 표현력 자체에 천장이 있기 때문으로 추정된다.
여기에 더해, 톤은 옮겨가도 장르까지 강제되지는 않는다는 점도 함께 관찰되었다. 이메일 앵커(Subject:) 없이 hello만 입력하면, 어댑터 출력은 이메일이 아니라 포럼 게시글("I'm new to this forum…")로 새어 나간다. 톤(비격식 1인칭)은 학습 분포를 따라가지만, 108건이라는 규모로는 장르(이메일 포맷)까지 잡아내기에는 부족하기 때문이다. 따라서 어댑터의 효과를 또렷하게 확인하려면, 추론 프롬프트를 학습 분포와 동일한 Subject: … + 인사말 템플릿으로 고정해 두어야 할 것으로 보인다.
이번 시도로 분명해진 것은, QVAC의 시스템을 이용하면 저정밀 ternary 모델 + CPU + 100여 건의 데이터라는 극한 조건에서도 LoRA 튜닝을 시도할 수 있다는 사실이다. 기존 모델의 천장을 넘어서는 모델을 확보하고 좀 더 건강한 데이터셋을 갖춘다면, 도메인에 따라 의미 있는 결과를 이끌어낼 수 있으리라 본다.