Jeffrey MorganBuild Bigger With Small AI: Running Small Models Locally을 보고 정리했다. 항상 큰 모델에 대한 얘기만 강조되다 보니 작은 모델로는 무엇이 가능한가 싶었었는데 이 발표가 이해에 많은 도움이 되었다.

  • (발표자는 Docker에서 근무하다가 현재는 Ollama를 만들고 있음)
    • 다른 도메인 같지만 여러 모델을 운용한다는 점에서 컨테이너처럼 문제를 해결
  • 작은 모델
    • 대형 클라우드 모델과 유사한 아이디어와 구조로 구현
    • 0.5B - 70B 파라미터
    • 적은 용량 (몇 GB 정도)
    • 일반 하드웨어서도 충분히 구동 가능 (적은 용량)
    • 무료 & 자유롭게 사용 가능
  • 작은 모델의 장점
    • 로컬에서 구동되기 때문에 낮은 지연 달성 가능
    • 적은 파라미터로 연산이 적어져서 높은 출력량, 즉각적인 응답을 받을 수 있음
    • 데이터 프라이버시, 보안에 유리
    • 비용이 상대적으로 적음 (이미 있는 컴퓨팅 자원 활용, 통합 비용 등)
    • 다양한 선택지 (Llama, Gemma, Phi, ... 다양한 전문성을 가진 모델을 사용 가능)
  • 모델과 데이터
    • 검색 증강 생성 (Retrieval Augmented Generation, RAG)
      • 데이터를 모델이 이해할 수 있는 형태로 변환해서 모델에 전달
      • 데이터는 벡터 스토어에 저장 (키워드: 벡터 스토어, 임베딩, 도큐먼트)
    • 도구 호출 (Tool calling)
      • 모델이 직접 코드를 구동할 수 있게 함 (예시: DuckDB의 쿼리 도구)
      • 별도의 전처리 과정이 필요 없음
      • 최근 모델에서 지원
  • 적용
    • 외부에 노출되는 서비스보다 내부에서 활용하기 유리 (적은 리소스)
    • 지식 베이스, 헬프데스크, 코드 리뷰, 이슈 배정, 데이터 엔지니어링, 리포팅, 보안, 컴플라이언스 등
    • 큰 모델과 작은 모델 함께 사용도 충분히 가능
  • 데모
    • 일반 예시: 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()

색상을 바꿔요

눈에 편한 색상을 골라보세요 :)

Darkreader 플러그인으로 선택한 색상이 제대로 표시되지 않을 수 있습니다.