Jeffrey Morgan의 Build Bigger With Small AI: Running Small Models Locally을 보고 정리했다. 항상 큰 모델에 대한 얘기만 강조되다 보니 작은 모델로는 무엇이 가능한가 싶었었는데 이 발표가 이해에 많은 도움이 되었다.
- (발표자는 Docker에서 근무하다가 현재는 Ollama를 만들고 있음)
- 다른 도메인 같지만 여러 모델을 운용한다는 점에서 컨테이너처럼 문제를 해결
- 작은 모델
- 대형 클라우드 모델과 유사한 아이디어와 구조로 구현
- 0.5B - 70B 파라미터
- 적은 용량 (몇 GB 정도)
- 일반 하드웨어서도 충분히 구동 가능 (적은 용량)
- 무료 & 자유롭게 사용 가능
- 작은 모델의 장점
- 로컬에서 구동되기 때문에 낮은 지연 달성 가능
- 적은 파라미터로 연산이 적어져서 높은 출력량, 즉각적인 응답을 받을 수 있음
- 데이터 프라이버시, 보안에 유리
- 비용이 상대적으로 적음 (이미 있는 컴퓨팅 자원 활용, 통합 비용 등)
- 다양한 선택지 (Llama, Gemma, Phi, ... 다양한 전문성을 가진 모델을 사용 가능)
- 모델과 데이터
- 검색 증강 생성 (Retrieval Augmented Generation, RAG)
- 데이터를 모델이 이해할 수 있는 형태로 변환해서 모델에 전달
- 데이터는 벡터 스토어에 저장 (키워드: 벡터 스토어, 임베딩, 도큐먼트)
- 도구 호출 (Tool calling)
- 모델이 직접 코드를 구동할 수 있게 함 (예시: DuckDB의 쿼리 도구)
- 별도의 전처리 과정이 필요 없음
- 최근 모델에서 지원
- 검색 증강 생성 (Retrieval Augmented Generation, RAG)
- 적용
- 외부에 노출되는 서비스보다 내부에서 활용하기 유리 (적은 리소스)
- 지식 베이스, 헬프데스크, 코드 리뷰, 이슈 배정, 데이터 엔지니어링, 리포팅, 보안, 컴플라이언스 등
- 큰 모델과 작은 모델 함께 사용도 충분히 가능
- 데모
- 일반 예시:
gemma2:2b
, 간단한 대화 프롬프트 시연 - RAG 예시:
gemma2:2b
,llama_index
로 텍스트 파일을 documents로 변환한 후 DuckDB VectorStore를 활용 - Tool Calling 예시:
qwen2.5-coder
, 질문에 대해 SQL를 생성한 후 duckDB에서 답을 찾아 반환
- 일반 예시:
- 레퍼런스
RAG 예시 코드
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, Settings
from llama_index.core.node_parser import TokenTextSplitter
from llama_index.vector_stores.duckdb import DuckDBVectorStore
from llama_index.core import StorageContext
from llama_index.llms.ollama import Ollama
from llama_index.embeddings.ollama import OllamaEmbedding
# models
Settings.embed_model = OllamaEmbedding(model_name="all-minilm")
Settings.llm = Olama (model="gemma2:2b", temperature=0, request_timeout=360.0)
# load documents into a vector store (DuckDB)
documents = SimpleDirectoryReader(input_files=["facts.txt"]).\
load_data(show_progress=True)
splitter = TokenTextSplitter(separator="\n", chunk_size=64, chunk_overlap=0)
vector_store = DuckDBVectorStore()
storage_context = StorageContext.from_defaults(vector_store=vector_store)
index = VectorStoreIndex(splitter.get_nodes_from_documents(documents), \
storage_context=storage_context, show_progress=True)
query_engine = index.as_query_engine()
try:
while True:
user_query = input (">>> ")
response = query_ engine. query (user_query)
print (response)
except KeyboardInterrupt:
exit()
Tool Calling 예시 코드
import duckdb
from langchain_core.tools import tool
from langchain_ollama import ChatOllama
from langchain_core.messages import HumanMessage, ToolMessage
con = duckdb.connect(database="ducks.duckdb")
schema = con.execute(f"DESCRIBE ducks"). fetchdf()
schema_str = schema.to_string(index=False)
@tool
def query(query: str) -> str:
"""Queries the database for information and returns the result.
Args:
query: The query to run against the database.
"""
return str(con.execute(query).fetchone()[0])
llm = ChatOllama (model="qwen2.5-coder") .bind_tools([query])
try:
while True:
user_query = input(">>> ")
messages = [HumanMessage(f"You are provided You are given a DuckDB \\
schema for table 'ducks': \n\n{schema_strschema_str}\n\n.\n\nAnswer \\
the user query: '{user_query}' in a single sentence.")]
ai_msg = llm.invoke(messages)
messages.append(ai_msg)
for tool_call in ai_msg.tool_calls:
print('>>> tool_call:', tool_call)
selected_tool = {"query": query}[tool_call["name"].lower()]
tool_output = selected_tool.invoke(tool_call["args"])
print('>>> tool_output:', tool_output)
messages.append(ToolMessage(tool_output, tool_call_id=tool_call["id"]))
response = llm.invoke(messages)
print(response.content)
except KeyboardInterrupt:
exit()