MyLittleButler

Edge AI Lab — Raspberry Pi 5 × Ollama × OpenClaw

01통찰 훈련법

하루 20-30분 | 7단계 순환 | AI 없이 먼저 완결

통찰은 많이 아는 것이 아니다. 구조를 압축해서 다른 분야에 이식할 수 있는 형태로 만드는 능력이다. 아래 7단계는 그 압축 과정을 반복 가능한 루틴으로 만든 것이다.

이 훈련법이 Pi 5의 Ollama 튜닝과 무슨 관계인가. 튜닝 실험은 7단계의 실전 적용 대상이다. 뉴스나 논문 대신 자기 시스템의 측정 데이터를 소재로 쓰면 훈련이 곧 운영 개선이 된다. 관찰한 숫자에서 해석을 분리하고, 지배 변수를 찾고, 반증 조건을 세우는 과정이 튜닝 실험 설계 자체다.

Step 1

사실 분리 (Fact Extraction)

뉴스, 논문, 글 하나를 읽고 해석을 전부 제거한다. 관측된 사실 3개만 적는다. 의견 금지.

실행: 글 하나를 골라서 종이에 사실 3개를 손으로 쓴다. "~인 것 같다", "~때문이다" 같은 문장이 나오면 해석이 섞인 것이다. 삭제.
예시: "qwen2.5:1.5b의 콜드스타트 21.8초, 웜 5.8초, 출력 토큰 12개" -- 이것은 사실. "느리다" -- 이것은 해석.
벤치마크 로그에서 tok/s, elapsed_sec, load_sec만 추출하고 "빠르다/느리다" 판단은 다음 단계로 미루는 것과 동일한 구조.
Step 2

지배 변수 찾기 (Dominant Variable)

추출한 사실을 움직이는 핵심 변수 1-2개를 지정한다.

실행: "이 숫자가 바뀌려면 무엇이 바뀌어야 하는가?"를 반복 질문한다. 답이 수렴하는 지점이 지배 변수.
예시 변수 후보: 규제, 인센티브, 기술 비용, 권력 구조, 유동성, 병목 자원, 피드백 지연.
Pi 5에서 tok/s를 지배하는 변수는 무엇인가? 메모리 대역폭인가, CPU clock인가, 모델 양자화 수준인가. 이 질문이 튜닝 실험의 설계 기준이 된다.
Step 3

구조 압축 (Structural Compression)

현상을 한 문장 모델로 만든다.

형식: "이 현상은 A 변수 때문에 B 구조로 움직인다."
예시: "Pi 5의 추론 속도는 메모리 대역폭 때문에 모델 크기에 선형 반비례한다."
이 한 문장이 틀릴 수 있다. 그래서 Step 4가 필요하다. flash attention이 속도를 바꾸면 지배 변수가 메모리 대역폭이 아니라 연산 패턴이었다는 뜻.
Step 4

역가설 생성 (Inverse Hypothesis)

정반대 설명을 하나 만든다. "내 설명이 틀렸다면 실제 원인은 무엇인가?"

실행: Step 3의 문장을 부정하는 대안 모델을 하나 만든다. 불편해도 그럴듯한 것을 만들어야 한다.
역가설: "속도 병목은 메모리 대역폭이 아니라 ollama의 스레드 스케줄링 오버헤드다."
num_thread를 1, 2, 4로 바꿔보면 이 역가설을 검증할 수 있다. num_thread=2가 4보다 빠르면 스케줄링 오버헤드 가설이 지지된다.
Step 5

반증 조건 (Falsifiability)

내 모델이 틀릴 조건을 명시한다.

형식: "X가 발생하면 이 모델은 무효다."
예시: "OLLAMA_KV_CACHE_TYPE=q8_0 적용 후에도 tok/s 변화가 5% 미만이면, KV cache 크기는 이 모델 규모에서 병목이 아니다."
모든 튜닝 파라미터에 대해 이 조건을 사전에 쓰면, 실험 결과가 나온 뒤 사후적 해석 함정을 피한다.
Step 6

타 영역 적용 (Transfer Test)

같은 구조가 다른 분야에서도 설명되는지 확인한다. 통찰은 분야 간 재사용 가능성이 높다.

실행: Step 3에서 만든 문장의 A, B를 다른 분야의 용어로 바꿔본다. 여전히 성립하면 구조적 통찰에 가깝다.
예시: "작은 팀의 생산성은 커뮤니케이션 오버헤드 때문에 인원수에 선형 반비례한다" -- Brooks의 법칙과 Pi 5의 스레드 모델이 같은 구조.
이 관찰이 성립하면 향후 multi-Pi 클러스터 구성 시에도 "노드 수를 늘리면 통신 오버헤드가 이득을 상쇄한다"는 예측이 가능해진다.
Step 7

초압축 요약 (Insight Sentence)

15단어 이내 문장으로 요약한다. 설명력이 유지되면 통찰에 가까워진다.

실행: Step 3의 문장을 더 짧게 만든다. 단어를 빼도 의미가 보존되는지 확인한다.
"ARM 소형 LLM 속도는 메모리 대역폭이 결정한다." (11단어)
이 문장이 registry.json에 들어간다. 6개월 뒤 새 모델 평가 시 여전히 성립하는지 확인하면 된다.
핵심 원칙: AI에게 먼저 물어보지 마라. 7단계를 손으로 먼저 완결한 뒤 AI 결과와 비교하라. 순서가 바뀌면 훈련이 아니라 확인 작업이 된다.

02Ollama 튜닝 실험 계획서

Pi 5 (Cortex-A76 4C / 8GB) | Ollama + qwen2.5:1.5b | on/off 비교 방식

각 파라미터를 하나씩 켜고 끄면서 비교한다. 한 번에 두 개 이상 바꾸면 어떤 것이 원인인지 알 수 없다. 실험 순서는 기대 효과가 큰 것부터.

실험 항목 목록

순위 파라미터 설정값 측정 방법 기대 효과
1 OLLAMA_FLASH_ATTENTION =1 동일 프롬프트 tok/s 3회 중앙값 5~15% tok/s 향상. ARM NEON 최적화. 메모리 사용량 감소 가능.
2 OLLAMA_KV_CACHE_TYPE =q8_0 / =q4_0 긴 프롬프트(500자+)에서 RSS + tok/s KV cache 메모리 30~50% 절감. 긴 컨텍스트 OOM 방지.
3 num_thread 1, 2, 4 각 값에서 tok/s + CPU 사용률(mpstat) 2~3에서 피크 가능. 스케줄링 오버헤드 확인용.
4 CPU governor performance 고정 governor 변경 전후 tok/s + CPU freq 5~20% 향상. 발열 증가 트레이드오프.
5 OLLAMA_NUM_PARALLEL =1 명시 동시 2 vs 1 요청 tok/s 단일 요청 최적화 확인용.
6 num_ctx 512, 1024, 2048 각 값 RSS + 응답 품질 윈도우 축소 시 메모리 절감. 품질 임계점 확인.
7 OLLAMA_KEEP_ALIVE =10m / =30m / =0 언로드 후 재요청 콜드스타트 시간 메모리 회수 vs 재로드 비용.

측정 프로토콜

모든 실험은 아래 스크립트로 통일한다. 프롬프트, 측정 방법, 기록 형식이 바뀌면 이전 결과와 비교할 수 없다.

# bench.sh -- 표준 벤치마크 스크립트
# 사용법: ./bench.sh [변수설명메모]
#!/bin/bash
set -euo pipefail

PROMPT='파이썬에서 리스트와 튜플의 차이를 두 줄로 설명해주세요.'
MODEL='qwen2.5:1.5b'
MEMO="${1:-baseline}"
OUTFILE="/mnt/data/openclaw/state/bench_results.jsonl"

# 워밍업
curl -s http://localhost:11434/api/generate \
  -d "{\"model\":\"$MODEL\",\"prompt\":\"hi\",\"stream\":false}" > /dev/null
sleep 2

# 3회 측정
for i in 1 2 3; do
  RESULT=$(curl -s http://localhost:11434/api/generate \
    -d "{\"model\":\"$MODEL\",\"prompt\":\"$PROMPT\",\"stream\":false}")

  echo "$RESULT" | python3 -c "
import sys, json, datetime
r = json.load(sys.stdin)
tok = r.get('eval_count', 0)
ns = r.get('eval_duration', 1)
total = r.get('total_duration', 1)
load = r.get('load_duration', 0)
out = {
    'ts': datetime.datetime.now().isoformat(),
    'memo': '$MEMO',
    'run': $i,
    'model': '$MODEL',
    'tok': tok,
    'tok_s': round(tok / (ns / 1e9), 2) if ns > 0 else 0,
    'total_s': round(total / 1e9, 2),
    'load_s': round(load / 1e9, 2),
    'response_preview': r.get('response','')[:80]
}
print(json.dumps(out, ensure_ascii=False))
" >> "$OUTFILE"
  sleep 1
done

# 시스템 상태 스냅샷
echo "{\"ts\":\"$(date -Iseconds)\",\"memo\":\"$MEMO\",\"type\":\"sysinfo\",\
\"mem_free_mb\":$(free -m | awk '/Mem:/{print $4}'),\
\"cpu_temp\":$(cat /sys/class/thermal/thermal_zone0/temp),\
\"governor\":\"$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)\"}" \
  >> "$OUTFILE"

echo "Done: $MEMO (3 runs) -> $OUTFILE"

파라미터별 적용 커맨드

# --- 실험 1: OLLAMA_FLASH_ATTENTION ---

# baseline (off)
sudo systemctl restart ollama
./bench.sh "flash_attn=off"

# 켜기
sudo mkdir -p /etc/systemd/system/ollama.service.d
echo '[Service]
Environment="OLLAMA_FLASH_ATTENTION=1"' \
  | sudo tee /etc/systemd/system/ollama.service.d/flash.conf
sudo systemctl daemon-reload
sudo systemctl restart ollama
./bench.sh "flash_attn=on"

# 원복
sudo rm /etc/systemd/system/ollama.service.d/flash.conf
sudo systemctl daemon-reload


# --- 실험 2: OLLAMA_KV_CACHE_TYPE ---

echo '[Service]
Environment="OLLAMA_KV_CACHE_TYPE=q8_0"' \
  | sudo tee /etc/systemd/system/ollama.service.d/kvcache.conf
sudo systemctl daemon-reload && sudo systemctl restart ollama
./bench.sh "kv_cache=q8_0"

sudo sed -i 's/q8_0/q4_0/' /etc/systemd/system/ollama.service.d/kvcache.conf
sudo systemctl daemon-reload && sudo systemctl restart ollama
./bench.sh "kv_cache=q4_0"

# 원복
sudo rm /etc/systemd/system/ollama.service.d/kvcache.conf
sudo systemctl daemon-reload


# --- 실험 3: num_thread ---

PROMPT='파이썬에서 리스트와 튜플의 차이를 두 줄로 설명해주세요.'
for T in 1 2 3 4; do
  echo "=== num_thread=$T ==="
  curl -s http://localhost:11434/api/generate \
    -d "{\"model\":\"qwen2.5:1.5b\",\"prompt\":\"hi\",\"stream\":false,\
\"options\":{\"num_thread\":$T}}" > /dev/null
  sleep 2
  for i in 1 2 3; do
    curl -s http://localhost:11434/api/generate \
      -d "{\"model\":\"qwen2.5:1.5b\",\"prompt\":\"$PROMPT\",\"stream\":false,\
\"options\":{\"num_thread\":$T}}" | python3 -c "
import sys,json
r=json.load(sys.stdin)
tok=r.get('eval_count',0); ns=r.get('eval_duration',1)
print(f'thread=$T run=$i tok/s={tok/(ns/1e9):.2f}')
"
    sleep 1
  done
done


# --- 실험 4: CPU governor ---

cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
./bench.sh "governor=ondemand"

echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
./bench.sh "governor=performance"

# 원복
echo ondemand | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor

결과 해석 기준

baseline 대비 변화율로 판단한다. 5% 미만은 "효과 없음"으로 분류, 해당 파라미터는 기본값 원복. 5~15%면 "유의미", 15% 초과면 "상시 적용 후보". 최종적으로 유의미한 것들만 조합해서 통합 벤치를 한 번 더 돌린다.

한 번에 하나만 바꾼다. 실험 N의 결과가 나오면 원복한 뒤 실험 N+1로 넘어간다. 최종 통합 테스트에서만 복수 파라미터를 동시 적용한다.

03현재 Baseline

2026-04-18 측정 | qwen2.5:1.5b | 튜닝 전 기준점
실측 데이터 (local-assistant 파이프라인 경유)
model: qwen2.5:1.5b (package: general-ko v0.1.0)
cold start: 21.795s (load_sec: 3.579s)
warm response: 5.772s (load_sec: 0.366s)
tokens_in: ~151-155 | tokens_out: 12-16
status: success
pipeline: stdin JSON -> run.sh -> ollama API -> stdout JSON
하드웨어
SoC: Broadcom BCM2712 (Cortex-A76 x4, 2.4GHz)
RAM: 8GB LPDDR4X
Storage: 2TB USB SSD (/mnt/data) + SD boot
OS: Debian 13 (trixie) aarch64
Kernel: 6.12.x rpt-rpi-v8

이 숫자가 모든 튜닝 실험의 비교 기준이다. 튜닝 후 이 baseline보다 나빠지면 해당 파라미터는 롤백한다.

04추가 가능성 키워드

현재 실험 범위 밖 | 향후 탐색 대상

아래 키워드들은 현재 실험 계획에는 포함되지 않았지만, 1~7번 실험이 끝난 뒤 다음 단계로 검토할 항목이다. 각각이 통찰 훈련의 소재가 된다.

llama.cpp 직접 빌드 speculative decoding PCIe NVMe 부착 MLC-LLM 대안 GGUF 양자화 레벨 비교 mmap vs mlock ARM NEON SIMD 프로파일링 swap 비활성화 zram 압축 메모리 batch size 조절 multi-Pi 분산 추론 systemd MemoryMax thermal throttling 모니터링 bench-skill 자동화 registry.json 누적 모델별 timeout_sec 자동 책정 watchdog healthcheck continuous profiling

키워드와 통찰 훈련의 교차점

llama.cpp 직접 빌드를 예시로 7단계를 돌리면 이렇게 된다. (1) 사실: "ollama는 llama.cpp 위에 구축된 래퍼다." (2) 지배 변수: "래퍼의 오버헤드 크기." (3) 구조 압축: "추상화 계층이 두꺼울수록 제어 가능성과 성능이 반비례한다." (4) 역가설: "ollama 오버헤드는 무시 가능 수준이고 실제 병목은 llama.cpp 자체 설정이다." (5) 반증 조건: "llama.cpp 직접 실행이 ollama 대비 3% 미만 차이면 래퍼 오버헤드 가설 기각." (6) 타 영역: Docker vs bare-metal 성능 논쟁과 동일 구조. (7) 압축: "래퍼 오버헤드는 측정 없이 논할 수 없다."

18개 키워드 각각을 이 방식으로 돌리면, 그 자체가 판단력 훈련이면서 동시에 다음 실험의 우선순위를 정하는 근거가 된다.