모종의 음모/ai 프로그램

프로그래밍도 할 줄 아는 local LLM 만들기.. part 1?

구차니 2026. 10. 16. 16:46

클로드 갈궈서 어느정도 결과가 나왔지만

검증하다 보니 다시 추가학습을 시켜야 해서 일단 part 2로 가야 할 듯 -_ㅠ

 

실험의 목적

  • sft(supervised fine tunning)이 모델의 효용을 올리는데 얼마나 중요할까 (= 모델빨 vs 대답 채찍질 빨)
  • sft의 형상에 따라 LLM이 얼마나 코딩을 잘하게 될까

 

실험설계

  • 프로그래밍 하려면 이전의 것으로는 안된다고 함
    • 프로그램을 위해서는 토크나이저가 공백, 엔터, 탭등을 날려먹으면 안되니까
  • 다국어 하려니 학습셋과 시간이 문제라 영어로만 제한
  • 프로그래밍을 위해 python과 openSCAD 문법을 중심으로 학습
  • sft 하나는 python에 특화, 다른 sft는 openSCAD에 특화 시키기
  • 이런저런 이유로 110M 정도 까지 모델 크기가 증가했고, 그래픽 카드 양쪽으로 나눠서 학습 진행

 

현재상태

  • 학습 완료. 다만 입력 포맷만 다른 두개의 모델이 되어버려서, 추가로 학습중
  • 기존은 python / openSCAD를 다루고 (대화로),
  • 새로 할 것은 openSCAD에 대한 sft만 진행하고 python을 물어봐서 품질을 볼 것임

 

으아아 내 26시간 ㅠㅠ

일단 특이하게도 학습 자체는 1 epoch만 돌리는게 표준이라고 한다. 반복해서 보는 것 보다 새로운걸 보는것이 이득이라고 하는데

sft 자체는 2~3 epoch 정도 반복으로 보는게 낫다고 한다.

● 학습 시간 요약

  장비는 GTX 1080 Ti 2장입니다.

  1. 사전학습 (base110m)

  ┌───────┬──────────────────────────────────┐
  │      항목      │                                         값                                          │
  ├───────┼──────────────────────────────────┤
  │ 규모           │ 3,814스텝, 10억 토큰, 스텝당 26.2만 토큰                                            │
  ├───────┼──────────────────────────────────┤
  │ 순수 학습 시간 │ 약 26시간 (스텝당 약 24초, 초당 1.09만 토큰, GPU 2장 병렬)                          │
  ├───────┼─────────────────────────────────────────────────┤
  │ 실제 진행      │ 10/13 12:01 시작. step 250 근처에서 사고로 중단돼 재시작했고, step 284에서 일시정지 │
  ├────────┼────────────────────────────────────────────────────┤
  │                │ 10/15 10:09 재개 → 10/16 09:57 종료 (23.8시간 연속)                                 │
  └──────┴──────────────────────────────────────────────┘

  데이터를 몇 번 봤는지(에폭)는 출처마다 다릅니다.
                                                                                                                            
  ┌────────────────────┬───────────┬────────────────┬──────┐
  │       데이터       │ 전체 토큰 │ 학습에서 본 양 │ 에폭 │
  ├────────────────────┼───────────┼────────────────┼──────┤
  │ 영어 (FineWeb-Edu) │ 7.85억    │ 5억            │ 0.64 │
  ├────────────────────┼───────────┼────────────────┼──────┤
  │ Python             │ 5.46억    │ 4억            │ 0.73 │
  ├────────────────────┼───────────┼────────────────┼──────┤
  │ OpenSCAD           │ 0.33억    │ 1억            │ 3.04 │
  └────────────────────┴───────────┴────────────────┴──────┘

  섞는 비율을 en 5 : py 4 : scad 1로 정했는데, SCAD 원본 데이터가 작아서 SCAD만 3번 넘게 반복해서 봤습니다.

  2. SFT (두 모델 동시, GPU 한 장씩)

  ┌─────────┬───────────────────┬─────────────────────┐
  │                         │           sft_chat (GPU0)           │  sft_plain (GPU1)   │
  ├─────────┼───────────────────┼─────────────────────┤
  │ 기간                    │ 10/16 10:24 → 14:26                 │ 10/16 10:24 → 14:36 │
  ├──────────┼────────────────────┼─────────────────────┤
  │ 소요 시간               │ 4시간 02분                          │ 4시간 12분          │
  ├───────────┼───────────────────┼─────────────────────┤
  │ 에폭당                  │ 약 80분 (3,185스텝)                 │ 약 84분             │
  ├───────────┼───────────────────┼─────────────────────┤
  │ 총량                    │ 3에폭, 9,555스텝, 예시 50,958개 × 3 │ 같음                │
  ├───────────┼──────────────────────┼─────────────────────┤
  │ 성능이 가장 좋았던 지점 │ step 6200 (2에폭 끝, 13:02)         │ step 6200           │
  └──────────────┴─────────────────────┴─────────────────────┘

  - 이 밖에 첫 시도가 09:58~10:20에 OOM으로 죽어서 22분을 잃었습니다.
  - 2에폭이면 충분했다는 결과를 기준으로 하면, 다음부터 SFT는 약 2시간 40분이면 됩니다.

  3. 평가와 변환

  - 벤치 9회(모델과 형식 조합): 14:36 → 15:49, 약 1시간 15분
    - 한 번에 5~26분이 걸렸습니다. 학습하지 않은 형식으로 묻는 교차 평가와 base 평가가 오래 걸렸는데, 모델이 멈추지 않고
      최대 길이까지 생성하기 때문입니다.
  - GGUF 변환 3개: 몇 분 이내

 

아래가 잘못 진행된 실험.

base 모델은 llama-completion으로 말을 주면 이어 나가도록 구성된 모델이고

/mnt/llm-scad/gguf$ /mnt/Downloads/llama-b10488/llama-completion -m base110m-step3814-f32.gguf -sm none --temp 0 --repeat-penalty 1.1 -n 200 -p $'// Make a tube with outer diameter 12 mm, inner diameter 8 mm and length 40 mm.\n'
0.00.234.065 I llama_completion: llama backend init
0.00.234.071 I llama_completion: load the model and apply lora adapter, if any
0.00.482.739 W load: control-looking token:      7 '<|end|>' was not control-type; this is probably a bug in the model. its type will be overridden
0.00.661.575 I cmn          init: llama threadpool init, n_threads = 6
0.00.674.412 I 
0.00.674.450 I system_info: n_threads = 6 (n_threads_batch = 6) / 6 | CPU : SSE3 = 1 | SSSE3 = 1 | AVX = 1 | AVX2 = 1 | F16C = 1 | FMA = 1 | BMI2 = 1 | LLAMAFILE = 1 | OPENMP = 1 | REPACK = 1 | 
0.00.674.450 I 
0.00.674.513 I sampler seed: 2978975464
0.00.674.529 I sampler params: 
repeat_last_n = 64, repeat_penalty = 1.100, frequency_penalty = 0.000, presence_penalty = 0.000
dry_multiplier = 0.000, dry_base = 1.750, dry_allowed_length = 2, dry_penalty_last_n = 64
top_k = 40, top_p = 0.950, min_p = 0.050, xtc_probability = 0.000, xtc_threshold = 0.100, typical_p = 1.000, top_n_sigma = -1.000, temp = 0.000
mirostat = 0, mirostat_lr = 0.100, mirostat_ent = 5.000, adaptive_target = -1.000, adaptive_decay = 0.900
0.00.674.534 I sampler chain: logits -> penalties -> ?dry -> ?top-n-sigma -> top-k -> ?typical -> top-p -> min-p -> ?xtc -> temp-ext -> dist 
0.00.674.535 I generate: n_ctx = 2048, n_batch = 2048, n_predict = 200, n_keep = 0
0.00.674.535 I 
// Make a tube with outer diameter 12 mm, inner diameter 8 mm and length 40 mm.
//   The tube is made of a 3mm tube with a hole in the middle.
//   The tube is made of a 6mm tube with a hole in the middle.
//   The tube is made of a 12mm tube with a hole in the middle.
//   The tube is made of a 15mm tube with a hole in the middle.
//   The tube is made of a 16mm tube with a hole in the middle.
//   The tube is made of a 18mm tube with a hole in the middle.
//   The tube is made of a 19mm tube with a hole in the middle.
//   The tube is made of a 20mm tube with a hole in the middle.
//   The tube is made of a 30mm tube with a hole in the middle.
//   The

0.01.140.346 I common_perf_print:    sampling time =      69.83 ms
0.01.140.347 I common_perf_print:    samplers time =      61.87 ms /   225 tokens
0.01.140.349 I common_perf_print:        load time =     211.82 ms
0.01.140.351 I common_perf_print: prompt eval time =      12.78 ms /    25 tokens (    0.51 ms per token,  1955.88 tokens per second)
0.01.140.352 I common_perf_print:        eval time =     381.24 ms /   199 runs   (    1.92 ms per token,   521.98 tokens per second)
0.01.140.353 I common_perf_print:       total time =     465.94 ms /   224 tokens
0.01.140.353 I common_perf_print: unaccounted time =       2.09 ms /   0.4 %      (total - sampling - prompt eval - eval) / (total)
0.01.140.354 I common_perf_print:    graphs reused =        198

 

sft_plain은 sft를 통해 openSCAD 형태의 출력을 학습시킨 모델

/mnt/llm-scad/gguf$ /mnt/Downloads/llama-b10488/llama-completion -m sft_plain-step9555-f32.gguf -sm none --temp 0 --repeat-penalty 1.1 -n 200 -p $'// Make a tube with outer diameter 12 mm, inner diameter 8 mm and length 40 mm.\n'
0.00.212.691 I llama_completion: llama backend init
0.00.212.697 I llama_completion: load the model and apply lora adapter, if any
0.00.461.915 W load: control-looking token:      7 '<|end|>' was not control-type; this is probably a bug in the model. its type will be overridden
0.00.632.982 I cmn          init: llama threadpool init, n_threads = 6
0.00.645.921 I 
0.00.645.961 I system_info: n_threads = 6 (n_threads_batch = 6) / 6 | CPU : SSE3 = 1 | SSSE3 = 1 | AVX = 1 | AVX2 = 1 | F16C = 1 | FMA = 1 | BMI2 = 1 | LLAMAFILE = 1 | OPENMP = 1 | REPACK = 1 | 
0.00.645.961 I 
0.00.646.024 I sampler seed: 3402034624
0.00.646.039 I sampler params: 
repeat_last_n = 64, repeat_penalty = 1.100, frequency_penalty = 0.000, presence_penalty = 0.000
dry_multiplier = 0.000, dry_base = 1.750, dry_allowed_length = 2, dry_penalty_last_n = 64
top_k = 40, top_p = 0.950, min_p = 0.050, xtc_probability = 0.000, xtc_threshold = 0.100, typical_p = 1.000, top_n_sigma = -1.000, temp = 0.000
mirostat = 0, mirostat_lr = 0.100, mirostat_ent = 5.000, adaptive_target = -1.000, adaptive_decay = 0.900
0.00.646.044 I sampler chain: logits -> penalties -> ?dry -> ?top-n-sigma -> top-k -> ?typical -> top-p -> min-p -> ?xtc -> temp-ext -> dist 
0.00.646.045 I generate: n_ctx = 2048, n_batch = 2048, n_predict = 200, n_keep = 0
0.00.646.045 I 
// Make a tube with outer diameter 12 mm, inner diameter 8 mm and length 40 mm.
difference() {
    cylinder(r=7, h=40, $fn=96);
    translate([0, 0, -1])
        cylinder(r=8, h=42, $fn=96);
}
 [end of text]


0.00.798.376 I common_perf_print:    sampling time =      27.36 ms
0.00.798.378 I common_perf_print:    samplers time =      24.40 ms /    82 tokens
0.00.798.382 I common_perf_print:        load time =     204.40 ms
0.00.798.383 I common_perf_print: prompt eval time =      12.79 ms /    25 tokens (    0.51 ms per token,  1955.26 tokens per second)
0.00.798.387 I common_perf_print:        eval time =     111.27 ms /    56 runs   (    1.99 ms per token,   503.28 tokens per second)
0.00.798.387 I common_perf_print:       total time =     152.46 ms /    81 tokens
0.00.798.389 I common_perf_print: unaccounted time =       1.04 ms /   0.7 %      (total - sampling - prompt eval - eval) / (total)
0.00.798.389 I common_perf_print:    graphs reused =         55

 

sft_chat은 llama-cli 를 통해서 채팅 형태로 학습된 모델

/mnt/llm-scad/gguf$ /mnt/Downloads/llama-b10488/llama-cli -m sft_chat-step9555-f32.gguf -sm none --temp 0 --repeat-penalty 1.1 -n 200 -st -p "Make a tube with outer diameter 12 mm, inner diameter 8 mm and length 40 mm."


Loading model...  

▄▄ ▄▄
██ ██
██ ██  ▀▀█▄ ███▄███▄  ▀▀█▄    ▄████ ████▄ ████▄
██ ██ ▄█▀██ ██ ██ ██ ▄█▀██    ██    ██ ██ ██ ██
██ ██ ▀█▄██ ██ ██ ██ ▀█▄██ ██ ▀████ ████▀ ████▀
                                    ██    ██
                                    ▀▀    ▀▀

build      : b10488-9d77fa172
model      : sft_chat-step9555-f32.gguf
ftype      : all F32
modalities : text

available commands:
  /exit or Ctrl+C     stop or exit
  /regen              regenerate the last response
  /clear              clear the chat history
  /read         add a text file
  /glob      add text files using globbing pattern




> Make a tube with outer diameter 12 mm, inner diameter 8 mm and length 40 mm.
difference() {
    cylinder(r=6, h=40, $fn=96);
    translate([0, 0, -1])
        cylinder(r=4, h=42, $fn=96);
}

[ Prompt: 1378.8 t/s | Generation: 418.8 t/s ]


Exiting...