모델을 얹는다 — 그래픽카드를 아예 안 쓰고
Article
여기까지 모델을 한 번도 안 불렀다. 데이터를 넣고, 벡터로 바꾸고, 가까운 상품을 찾는 데까지 왔다.
- DB1·2편 · 끝
- 벡터3·4·5편 · 끝
- 후보 찾기5편 · 끝
- 말을 붙인다이번 편
1. 무엇이 말썽이었나
모델을 내 컴퓨터에서 돌리는 도구는 그래픽카드에 모델을 얹으려고 한다. 빠르기 때문이다. 그런데 여기서 노트북마다 갈린다.
- 파이썬 코드다 똑같다
- 올라마 설치여기까지는 전원 통과
- 그래픽카드드라이버가 제각각
- 모델 얹기여기서 실패한다
그래서 아예 안 쓴다. 느려지지만, 느린 것은 기다리면 되고 안 되는 것은 기다려도 안 된다.
2. 올라마를 확인한다
올라마(Ollama)는 모델을 내 컴퓨터에서 돌려주는 프로그램이다. ollama.com 에서 받아 설치한다.
설치가 끝나면 터미널에서 확인한다.
ollama --version
- ollama version is 0.32.6
어떤 모델이 있는지 본다
ollama list
- NAME ID SIZE MODIFIED
- qwen2.5:3b 357c53fb659c 1.9 GB 2 days ago
없으면 받는다. 2GB 쯤 되니 시간이 걸린다.
ollama pull qwen2.5:3b
- qwen2.5
- 모델 이름과 판 번호
- 알리바바가 공개한 것
- 3b
- 값이 30억 개
- b = billion(십억)
- 크기
- 1.9GB
- 값 하나를 반 바이트로 눌러 담았다
3. 그래픽카드를 떼어낸 판을 만든다
받아둔 모델을 그대로 쓰지 않고, 그래픽카드에 올릴 겹 수를 0 으로 못 박은 판을 하나 더 만든다.
작업 폴더에 Modelfile 이라는 파일을 만든다. 확장자가 없다.
FROM qwen2.5:3b
PARAMETER num_gpu 0
| 줄 | 무슨 뜻인가 |
|---|---|
FROM qwen2.5:3b | 받아둔 이 모델을 바탕으로 삼는다. 새로 받지 않는다 |
PARAMETER num_gpu 0 | 그래픽카드에 올릴 겹 수 = 0. 전부 CPU 로 계산한다 |
ollama create qwen-cpu -f Modelfile
- writing manifest
- success
-f Modelfile 은 「이 파일에 적힌 대로 만들어라」 는 뜻이다. f 는 file 이다.
진짜로 안 쓰는지 확인한다
만들었다는 말만 믿지 않는다. 한 번 돌려놓고 어디에서 계산 중인지 본다.
ollama run qwen-cpu "안녕"
ollama ps
- NAME ID SIZE PROCESSOR CONTEXT
- qwen-cpu:latest c04b4cf67c53 2.2 GB 100% CPU 4096
qwen2.5:3b
받아둔 그대로
- 그래픽카드에 올린다
- 빠르다 — 초당 99토큰
- 노트북마다 다르게 실패한다
qwen-cpu
지금 만든 것
PROCESSOR 100% CPU- 느리다 — 초당 20~25토큰
- 어디서든 똑같이 돈다
4. 파이썬에서 부른다
python -m pip install openai
접속 설정을 파일 하나로
앞으로 만들 파일이 여럿이다. 접속 설정을 그 파일들에 흩뿌리면 안 된다.
"""누구에게 물어볼지를 여기서 한 번만 정한다.
다른 파일은 전부 이렇게 쓴다 ─
from config import client, MODEL
"""
from openai import OpenAI
# 어디로 보낼지. localhost = 내 컴퓨터 자신, 11434 = 올라마가 쓰는 문 번호.
# 끝의 /v1 은 "표준 방식으로 말을 걸겠다"는 표시다. 빠뜨리면 404 가 난다
BASE_URL = "http://localhost:11434/v1"
# 원래는 열쇠를 넣는 자리인데, 내 컴퓨터는 검사를 안 한다.
# 그래도 비워두면 패키지가 에러를 내므로 아무 글자나 채운다
API_KEY = "ollama"
# 방금 만든 판의 이름
MODEL = "qwen-cpu"
# 이 한 줄로 "말을 걸 상대"가 만들어진다. 다른 파일들은 이걸 가져다 쓴다
client = OpenAI(base_url=BASE_URL, api_key=API_KEY)
- BASE_URL
- 어디로 보낼지
- localhost:11434/v1
- API_KEY
- 누가 보냈는지
- 여기선 안 쓴다
- MODEL
- 누가 답할지
- qwen-cpu
5. 첫 호출
# config.py 에서 client 와 MODEL 을 가져온다.
# 접속 설정이 저기 한 군데에만 있으니 여기엔 주소도 열쇠도 안 적힌다
from config import client, MODEL
res = client.chat.completions.create(
model=MODEL,
# messages 는 주고받은 말의 목록이다. 지금은 우리가 한 말 하나뿐이다.
# role="user" 내가 하는 말
# role="assistant" 모델이 한 말 (이어서 물을 때 넣는다)
# role="system" "너는 이런 역할이다" 라는 지시 (뒤에서 쓴다)
messages=[
{"role": "user", "content": "화장품 추천 서비스를 한 문장으로 소개해줘"},
{"role": "system", "content": "한국어로 답변해줘" },
],
)
# 답이 여러 개 올 수도 있는 구조라 목록이다. 보통 하나뿐이라 [0] 으로 꺼낸다.
# choices[0].message.content 가 실제 답 글자다
print(res.choices[0].message.content)
# usage 에는 이번 호출에 쓴 토큰 수가 들어 있다.
# 내 컴퓨터에서 돌리면 돈은 안 들지만, 이 숫자만큼 시간이 든다
print("입력", res.usage.prompt_tokens, "출력", res.usage.completion_tokens)
- 화장품 추천 서비스는 고객이 특정 피부 유형이나 피부 문제에 맞는 가장 적합한 제품을 찾아내주는 도우미입니다.
- 입력 43 출력 36
| 이름 | 무슨 뜻인가 | 왜 여기 나오나 |
|---|---|---|
from config import ... | 다른 파일에서 가져오기 | 접속 설정을 한 군데에만 두려고 |
messages | 주고받은 말의 목록 | 대화 형태로 보내는 방식이다 |
role | 누가 한 말인지 | user 나 · assistant 모델 · system 지시 |
choices[0] | 답 후보 중 첫 번째 | 보통 하나만 온다 |
.message.content | 실제 답 글자 | 이걸 화면에 쓴다 |
usage | 쓴 토큰 수 | 시간과 돈의 척도 |
6. 얼마나 느린지 재둔다
느린 것은 참을 수 있지만 얼마나 느린지 모르는 것은 못 참는다. 앞으로 쓸 실제 크기로 재본다.
import time # 시간을 재는 도구. 파이썬에 기본으로 들어 있다
from config import client, MODEL
prompt = """[고객] 41세 여성 건성
[이력] 병풀 링클 클렌징오일 28100(별점3) / 히알루론산 탄력 클렌징오일 25000(별점4)
[후보]
P115 비타민C 트러블 클렌징오일 20400
P140 히알루론산 시카 클렌징오일 23200
P096 레티놀 브라이트닝 클렌징오일 24400
어울릴 상품 두 개를 골라라."""
for i in range(3): # 세 번 돌린다. 한 번만 재면 못 믿는다
started = time.time() # 지금 시각 (1970년부터 흐른 초)
res = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": prompt}],
)
took = time.time() - started # 끝난 시각 - 시작 시각 = 걸린 초
n_out = res.usage.completion_tokens
print(f"{i + 1}회 {took:5.1f}초 · 출력 {n_out:3d}토큰 · 초당 {n_out / took:.1f}토큰")
- 1회 13.2초 · 출력 264토큰 · 초당 20.0토큰
- 2회 17.0초 · 출력 421토큰 · 초당 24.7토큰
- 3회 10.3초 · 출력 255토큰 · 초당 24.7토큰
7. 안 될 때
- 01
① 올라마가 떠 있나
브라우저에서
http://localhost:11434를 연다. 「Ollama is running」이 나와야 한다. 안 나오면 트레이(시계 옆) 아이콘을 확인한다. - 02
② 모델이 있나
ollama list에qwen-cpu가 보이는지 본다. 없으면ollama create를 다시 한다. - 03
③ 진짜 CPU 인가
ollama run qwen-cpu "안녕"뒤에ollama ps를 본다.100% CPU가 아니면Modelfile의num_gpu 0을 확인하고 다른 이름으로 다시 만든다. - 04
④ 404 가 난다
config.py의 주소 끝에/v1이 빠졌다. 제일 많이 나는 실수다. - 05
⑤ 답이 아예 안 온다
메모리가 모자라 못 얹는 경우다. 다른 프로그램을 닫고 다시 해본다. 그래도 안 되면 오래 붙잡지 말고 다음 편(상용 API)으로 간다 —
config.py세 줄만 바꾸면 된다.
부록 — ollama 명령이 안 먹을 때
명령어를 못 쓰는 상황이면 파이썬으로 같은 일을 할 수 있다. 올라마는 프로그램이 말을 거는 창구도 함께 열어두기 때문이다.
# urllib 은 파이썬에 기본으로 들어 있는 인터넷 도구다 (URL 을 다루는 도구 묶음).
# 설치할 것이 없어서 어떤 컴퓨터에서든 그냥 돈다
import json
import urllib.request
# Request 는 "보낼 요청서"를 만든다. 아직 보내지는 않는다
req = urllib.request.Request(
"http://localhost:11434/api/create", # 11434 = 올라마의 문 번호
# 보낼 내용. ollama create 에 준 것과 같은 내용을 형식만 바꾼 것이다.
# json.dumps 는 파이썬 값을 글자로 바꾸고,
# .encode() 는 그 글자를 바이트로 바꾼다 — 컴퓨터끼리는 바이트로 오간다
data=json.dumps({
"model": "qwen-cpu", # 만들 이름
"from": "qwen2.5:3b", # 바탕이 될 모델
"parameters": {"num_gpu": 0}, # Modelfile 의 PARAMETER 와 같다
"stream": False, # 진행 상황을 조금씩 말고 한 번에 받는다
}).encode(),
# "보내는 내용이 JSON 형식이다" 라고 알려준다. 안 적으면 상대가 못 알아본다
headers={"Content-Type": "application/json"},
)
# 이제 실제로 보낸다. timeout=180 은 "3분 기다려도 답이 없으면 포기한다"
with urllib.request.urlopen(req, timeout=180) as res:
# read() 는 바이트로 받는다 -> decode() 로 사람이 읽는 글자로 되돌린다
print(res.read().decode())
| 이름 | 무슨 뜻인가 | 왜 여기 나오나 |
|---|---|---|
urllib | 파이썬 기본 인터넷 도구 | 설치 없이 쓰려고 |
Request | 보낼 요청서 | 아직 보내는 것은 아니다 |
urlopen | 실제로 보낸다 | 여기서 통신이 일어난다 |
11434 | 올라마의 문 번호 | 올라마가 정해둔 값이라 바꿀 수 없다 |
.encode() / .decode() | 글자 ↔ 바이트 | 컴퓨터끼리는 바이트로 오간다 |
timeout=180 | 180초까지만 기다린다 | 멈춘 채 매달리지 않으려고 |
전역에 깔린 것과 지우는 법
| 무엇 | 어디에 | 크기 |
|---|---|---|
| 올라마 프로그램 | 설치 폴더 | 수백 MB |
| 받은 모델들 | C:\사용자\<내이름>\.ollama\models | 1.8GB |
openai 패키지 | C:\Python313\Lib\site-packages | 몇 MB |
ollama list # 지금 있는 모델 목록
ollama rm qwen-cpu # 만든 판을 지운다
ollama rm qwen2.5:3b # 받아둔 모델까지 지운다
python -m pip uninstall openai
이번 편에 나온 것
| 한 것 | 내용 |
|---|---|
ollama --version | 설치됐는지 확인 |
ollama list / pull | 받아둔 모델 보기 / 받기 |
Modelfile | FROM 한 줄 + 겹 수를 0으로 박는 PARAMETER num_gpu 0 한 줄 |
ollama create -f | 그래픽카드를 뗀 판을 만든다 |
ollama ps | 100% CPU 를 눈으로 확인한다 |
| 왜 새 판을 만드나 | 요청마다 끄는 방식은 앞 배치를 재사용해 조용히 틀린다 |
config.py | 접속 설정을 한 군데로. 갈아탈 때 고칠 곳이 한 파일 |
messages · role | 대화 형태로 보낸다 |
| 속도 | 초당 20~25토큰. 시간은 답 길이에 비례한다 |
| 채점 크기 | 30명 (6~7분). 300명은 한 시간이 넘는다 |
미션
- 01
[필수] 내 노트북의 초당 토큰을 적는다
mission_llm_1.py.12_measure.py를 돌려 초당 토큰을 적는다.ollama ps의100% CPU화면도 같이 남긴다. - 02
[응용] 두 판을 나란히 비교한다
mission_llm_2.py.qwen-cpu와qwen2.5:3b에 같은 질문을 보내 초당 토큰을 비교한다. 원본 판이 에러가 나면 그것도 결과다 — 무슨 에러인지 적는다. - 03
[도전] 느린 것을 견디게 만든다
mission_llm_3.py. 답이 13초 걸리면 화면이 멈춘 것처럼 보인다.stream=True로 받아 글자가 흘러나오게 해본다. 총 시간은 그대로인데 체감이 어떻게 달라지나.
[도전] 총 시간은 같은데 왜 덜 답답한가충분히 고민해본 뒤 꼭 필요한 경우에만 열어보세요
첫 글자가 나오기까지의 시간이 체감을 정한다. 통째로 받으면 13초 동안 아무것도 없다가 한 번에 나오고, 흘려받으면 1~2초 만에 첫 글자가 나온다.
총 시간은 똑같다. 바뀐 것은 기다림의 모양뿐이다. 그런데 사람은 「멈춘 것 같다」와 「일하고 있다」를 완전히 다르게 느낀다.
느린 모델을 쓸 때 이게 특히 값을 한다. CPU 로 도는 우리에게는 사실상 필수다.
다만 뒤에서 이게 발목을 잡는다. 흘려보내면서 동시에 「형식을 지켜라」를 검사하려면 답이 다 와야 하기 때문이다. 그때 다시 이야기한다.
from config import client, MODEL
stream = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": "화장품 추천 서비스를 세 문장으로 소개해줘"}],
stream=True, # 통째로 말고 오는 대로 받는다
)
# 스트림은 조각이 하나씩 도착하는 것이라 for 문으로 받는다
for chunk in stream:
# delta 는 "이번에 새로 온 부분" 이다. 빈 조각도 오므로 확인하고 찍는다
piece = chunk.choices[0].delta.content
if piece:
# end="" 는 줄바꿈을 안 넣는다, flush=True 는 모아두지 말고 바로 화면에 뿌린다
print(piece, end="", flush=True)
print() # 마지막에 줄바꿈 하나flush=True 를 빼면 파이썬이 글자를 모아뒀다 한 번에 뿌려서 흘려받는 의미가 없어진다. 자주 빠뜨리는 한 글자다.
이 코드는 다음 편의 상용 API 로 갈아타도 똑같이 돈다. stream=True 는 양쪽 다 지원하는 표준이라 config.py 만 바꾸면 그대로 동작한다.
정리하면
그래픽카드를 안 쓰기로 했다. 말썽을 부린 것이 모델도 코드도 아니고 그것이었기 때문이다. Modelfile 두 줄로 판을 하나 만들었고, ollama ps 에 100% CPU 가 찍히는 것을 눈으로 확인했다.
네 배 느려졌다. 그런데 느린 것은 계획에 넣을 수 있고 안 되는 것은 못 넣는다. 초당 2025토큰이라는 숫자를 재뒀으니, 뒤에서 채점을 30명으로 잡으면 67분이라는 것까지 지금 안다.
재면서 하나 배운 것도 있다. 걸린 시간은 10초에서 17초까지 흔들리는데 초당 토큰은 거의 일정했다. 느린 게 아니라 길게 답한 것이다. 그래서 답을 짧게 시키면 그만큼 빨라진다 — 뒤에서 형식을 고정할 때 이게 값을 한다.
그리고 config.py 를 만들었다. 접속 설정이 한 파일에만 있으니, 이 길이 끝내 안 맞으면 세 줄을 갈아끼워 다른 상대로 넘어간다. 나머지 코드는 한 글자도 안 바뀐다.
다음 편은 선택이다. 이 모델이 너무 느리거나 답이 시원찮으면 인터넷 너머의 좋은 모델을 빌려 쓸 수 있다. 대신 돈이 든다. 지금 이대로 괜찮으면 건너뛰고 8편으로 가면 된다 — 이후 편들은 전부 여기서 만든 qwen-cpu 를 그대로 쓴다.