최근 AI 모델 분야에서는 경량화와 효율성을 동시에 갖춘 오픈 웨이트(open-weight) 모델들이 각광받고 있습니다.
이 글에서는 단일 GPU 또는 TPU 환경에서도 우수한 성능을 발휘하는 두 모델, DeepSeek-R1과 Gemma 3의 성능과 품질을 비교해보고자 합니다.
DeepSeek-R1은 낮은 자원으로도 빠르고 안정적인 추론을 제공하는 경량 모델로, 실시간 응용 프로그램이나 임베디드 시스템에 적합한 솔루션으로 주목받고 있습니다. 반면, Gemma 3는 Google의 최신 연구 성과를 바탕으로 개발된 모델로, 1B부터 27B까지 다양한 크기로 제공되며, 최대 128K 토큰의 긴 컨텍스트, 멀티모달 입력 지원, 140개 이상의 언어 지원 등 뛰어난 확장성을 자랑합니다.
실제 사용 환경에서 어떤 성능 차이를 보이는지, 그리고 품질 면에서 어떤 강점을 지니고 있는지를 다양한 벤치마크와 사례를 통해 살펴봅니다.
둘이서 인사를 잘 받아주세요.
짧게 재밌는 테스트만 해보고 코드 생성 파트에서 자세하게 비교해보겠습니다.
수학 문제
GPT o3-mini
deepseek-r1 vs gemma3
둘 다 잘 맞추네요 문제가 너무 쉬웠나봅니다.
역사 문제
중국에서 만들어서 그런가, 고의성이 보입니다.
자, 이제 코드 생성으로 넘어가보겠습니다.
코드 생성
Deepseek-r1:14b
import requests
from bs4 import BeautifulSoup
import time
def naver_news_crawler():
# Base URL for Naver News
base_url = "https://news.naver.com"
# Path to the output file
output_file = "naver_news.csv"
# Initialize headers to mimic a browser request
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.128 Safari/537.3',
'Accept-Language': 'ko-KR,en-US;q=0.9'
}
# List to store news articles
articles = []
# Navigation through pages
page = 1
while True:
try:
# Construct the URL with current page number
url = f"{base_url}/main/main.naver#§ion=search&query=latestNews&tabType=general&page={page}"
# Send a GET request
response = requests.get(url, headers=headers)
# Check if the response is successful
if response.status_code == 200:
soup = BeautifulSoup(response.text, 'html.parser')
# Find all news article containers
container = soup.find('div', {'id': 'mainContent'})
articles_list = container.find_all('div', class_='article')
# Extract article information
for article in articles_list:
title = article.find('h2').text.strip()
summary = article.find('p').text.strip()
date = article.find('span', class_='date').text.strip()
articles.append({
'title': title,
'summary': summary,
'date': date
})
# Print progress
print(f"Page {page} crawled successfully. Total articles: {len(articles)}")
# Check if there are more pages to crawl
next_button = soup.find('button', {'class': 'paging_next'})
if not next_button:
break
else:
print(f"Failed to fetch page {page}. Status code: {response.status_code}")
time.sleep(5)
continue
# Implement a delay between requests
time.sleep(1)
# Move to the next page
page += 1
except Exception as e:
print(f"Error occurred on page {page}: {str(e)}")
time.sleep(2)
continue
# Save articles to CSV file
import csv
with open(output_file, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['title', 'summary', 'date'])
writer.writeheader()
for article in articles:
writer.writerow(article)
print("Scraping completed. Data saved to naver_news.csv")
if __name__ == "__main__":
naver_news_crawler()
gemma3:12b
import requests
from bs4 import BeautifulSoup
import time
import random
def crawl_naver_news(start_page=1, num_pages=1):
"""
네이버 뉴스 크롤링 함수.
Args:
start_page: 시작 페이지 (기본값: 1)
num_pages: 크롤링할 페이지 수 (기본값: 1)
Returns:
뉴스 기사 제목과 링크를 담은 리스트.
"""
news_list = []
for page in range(start_page, start_page + num_pages):
url = f"https://news.naver.com/main/read.naver?officeId=031&page={page}" # 예시: 정치 섹션
print(f"Crawling page: {page}")
try:
response = requests.get(url, headers={'User-Agent': 'Mozilla/5.0'}) # User-Agent 설정 중요
response.raise_for_status() # HTTP 에러 발생 시 예외 발생
soup = BeautifulSoup(response.content, 'html.parser')
# 기사 제목과 링크 추출 (네이버 뉴스 구조에 따라 변경 필요)
articles = soup.find_all('div', class_='news_area') # 뉴스 영역을 찾음
for article in articles:
try:
title = article.find('a', class_='news_title').text.strip()
link = "https://news.naver.com" + article.find('a', class_='news_title')['href']
news_list.append({"title": title, "link": link})
except AttributeError:
print("Error extracting title/link from article") # 에러 처리
time.sleep(random.uniform(1, 3)) # 요청 간 딜레이 (중요)
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
time.sleep(60) # 에러 발생 시 더 긴 딜레이
return news_list
# 사용 예시
if __name__ == "__main__":
news = crawl_naver_news(start_page=1, num_pages=3) # 1~3페이지 크롤링
for item in news:
print(f"Title: {item['title']}")
print(f"Link: {item['link']}")
print("-" * 20)
간단한 코드지만 비교를 해보겠습니다.
1. 크롤링 범위 제어
- gemma3
시작 페이지와 크롤링할 페이지 수를 함수 인자로 받아, 명확하게 크롤링 범위를 설정할 수 있습니다. - deepseek-r1
다음 버튼 유무를 기준으로 무한 루프를 돌기 때문에, 불필요한 페이지까지 접근할 위험이 있으며, 관리가 어렵습니다.
2. 에러 처리 및 안정성
- gemma3
response.raise_for_status()를 통해 HTTP 에러를 즉시 감지하고, 개별 뉴스 기사 추출 시에도 try-except 문으로 예외를 세밀하게 처리합니다. - deepseek-r1
전체 크롤링 과정을 하나의 큰 while 루프 안에서 처리하여, 에러 발생 시 전체 흐름에 영향을 줄 가능성이 높습니다.
3. 요청 딜레이 관리
- gemma3
페이지 요청 사이에 1~3초의 무작위 딜레이를 도입하여, 서버에 부하를 분산시키고 차단 위험을 줄입니다. - deepseek-r1
고정된 1초 딜레이를 사용하여, 상황에 따라 서버에 과도한 요청이 발생할 수 있는 위험성이 있습니다.
코드 생성: 추가 작업
크롤링한 데이터를 오픈서치에 적재하는 로직을 추가해달라고 요청하였습니다.
Deepseek-r1:14b
# 기존코드 상단에 Elasticsearch 라이브러리 임포트 추가
from elasticsearch import Elasticsearch
# OpenSearch 연결 설정
def connect_opensearch(host='localhost', port=9200):
try:
es = Elasticsearch([{
'host': host,
'port': port,
# SSL 사용 여부에 따라 commenting/uncommenting
# 'use_ssl': True,
# 'verify_certs': True,
}])
print("Connected to OpenSearch")
return es
except Exception as e:
print(f"Failed to connect to OpenSearch: {e}")
raise
# OpenSearch에 데이터 적재하기
def load_to_opensearch(es, index_name, data):
try:
# 문서를 배치로 삽입
if not es.indices.exists(index=index_name):
print(f"Index '{index_name}' does not exist. Creating it.")
es.indices.create(
index=index_name,
body={
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"properties": {
"title": {"type": "text"},
"url": {"type": "keyword"}
}
}
}
)
# 데이터 삽입
if isinstance(data, list):
batch_size = len(data)
success_count = 0
for doc in data:
es.index(
index=index_name,
body=doc
)
success_count += 1
print(f"Successfully loaded {success_count} documents to OpenSearch.")
else:
print("Data should be a list of dictionaries.")
except Exception as e:
print(f"Error loading data to OpenSearch: {e}")
raise
# 사용자 함수 및 테스트 코드
if __name__ == "__main__":
try:
# OpenSearch에 연결
es = connect_opensearch()
# 뉴스 데이터 크롤링 (기존 함수를 통해 호출)
news_data = crawl_naver_news(start_page=1, num_pages=3)
# OpenSearch에 데이터 적재
load_to_opensearch(es, "naver_news_index", news_data)
except Exception as e:
print(f"Error in main execution: {e}")
gemma3:12b
import requests
from bs4 import BeautifulSoup
import time
import random
from opensearchpy import OpenSearch, helpers
def crawl_naver_news(start_page=1, num_pages=1):
"""
네이버 뉴스 크롤링 함수.
"""
news_list = []
for page in range(start_page, start_page + num_pages):
url = f"https://news.naver.com/main/read.naver?officeId=005&slug=money" # 예시: 머니투데이
try:
response = requests.get(url)
response.raise_for_status()
soup = BeautifulSoup(response.content, 'html.parser')
# ... (크롤링 로직) ...
time.sleep(random.uniform(1, 3))
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
time.sleep(60)
return news_list
def upload_to_opensearch(news_list, host, port, index_name, auth):
"""
크롤링한 뉴스를 OpenSearch에 적재하는 함수.
"""
client = OpenSearch(
hosts = [{'host': host, 'port': port}],
http_auth = auth,
use_ssl = False,
verify_certs = False,
ssl_show_warn = False
)
actions = []
for news in news_list:
action = {
"_index": index_name,
"_source": {
"title": news['title'],
"link": news['link'],
"content": news.get('content', ''), # 내용이 없을 경우를 대비
"timestamp": time.time() # 적재 시간 기록
}
}
actions.append(action)
try:
helpers.bulk(client, actions)
print(f"{len(actions)} 개의 문서를 OpenSearch에 적재했습니다.")
except Exception as e:
print(f"OpenSearch 적재 실패: {e}")
if __name__ == "__main__":
# OpenSearch 연결 정보
host = "localhost" # OpenSearch 호스트
port = 9200 # OpenSearch 포트
index_name = "naver_news" # OpenSearch 인덱스 이름
auth = ("admin", "admin") # OpenSearch 인증 정보 (username, password)
# 크롤링 및 적재
news = crawl_naver_news(start_page=1, num_pages=2)
upload_to_opensearch(news, host, port, index_name, auth)
한 번 비교해볼까요? 이번에는 구체적으로 평가해보겠습니다.
1. 데이터 삽입(gemma 승)
위에서 보면 알겠지만, deepseek는 opensearch에 적재하라고 했음에도 불구하고 elasticsearch 라이브러리를 import했습니다. 안타깝습니다.
또한 데이터 삽입 로직을 살펴보면 확연한 차이를 볼 수 있습니다.
deepseek는 뉴스 데이터를 하나씩 인덱스에 삽입(index 메서드 호출)하는 방식을 채택했습니다.
이런 식(이딴 식)의 로직을 사용하면 문서가 많아질 경우, 매번 개별 요청을 보내게 되어 성능 면에서 비효율적일 수 있습니다.
반면에 gemma3는 bulk 를 이용하여 배치로 문서를 삽입합니다. 배치 삽입 방식은 네트워크 요청 수를 줄이고, 대용량 데이터를 빠르게 적재할 수 있기 때문에 효율적입니다.
2. 인덱스 생성 (deepseek-r1 약간 승)
deepseek-r1은 인덱스가 존재하지 않을 것을 가정하여, 인덱스 생성 로직을 포함하고 있습니다.
초기 설정이나 새로운 환경까지 고려한 모습입니다. 물론 매번 색인 작업마다 인덱스 생성 여부를 체크하게되는 오버헤드가 존재하지만,
큰 문제는 되지 않을 것 같습니다.
gemma3는 이미 인덱스 생성이 완료된 상태를 가정하고 코드를 작성한 거 같습니다.
코드 생성 : 이론
얼마나 이론을 잘 알고있는지, 물어보겠습니다.
Deepseek-r1:14b
OpenSearch에서 대용량 데이터를 색인 작업할 때 주의사항을 5가지 나열如下합니다.
충분한 하드웨어 리소스 확보OpenSearch는 CPU, 메모리, 디스크 I/O 등을 많이 사용하므로 충분한 하드웨어 리소스를 확보해야 합니다.
예를 들어, SSD를 사용하여 빠른 디스크 I/O를 보장하고, CPU와 메모리를 충분히 할앚어야 합니다.색인 설정 최적화기본 설정은 대용량 데이터에 적합하지 않을 수 있습니다.
예를 들어, index.number_of_replicas를 0으로 설정하거나, index.refresh_interval을 긴 시간(30초, 60초 등)으로 조정하여 성능을 향상시킬 수 있습니다.
또한, index.translog.durability를 async로 설정하여 성능을 높이되, 데이터 손실 위험을 고려해야 합니다.벌크 색인 활용개별 문서씩 보내는 대신 벌크(bulk) API를 사용하여 여러 문서를 한꺼번에 보내는 것이 효율적입니다.
벌크 요청 크기는 보통 5MB~15MB로 유지하며, 네트워크 오버헤드를 줄이고 성능을 향상시킬 수 있습니다.
샤딩 전략 수립데이터가 특정 샤드에 집중되지 않도록 적절한 샤딩(shard) 전략을 수립해야 합니다.
예를 들어, 데이터 분산을 고려하여 샤드 크기를 30GB~50GB로 설정하고, 작업 후에는 shard rebalance를 통해 균형을 맞춥니다.
모니터링 및 오류 대비색인 작업 중 CPU, 메모리, 디스크 사용량 등을 모니터링하여 병목 현상이 발생하지 않도록 해야 합니다.
또한, 벌크 요청 실패 시 재시도 로직을 구현하고, 오류 원인 분석 및 로그 분석을 통해 문제를 조속히 해결해야 합니다.
이러한 주의사항을 준수하면 OpenSearch에서 대용량 데이터 색인 작업을 성공적으로 수행할 수 있습니다.
gemma3:12b
OpenSearch에서 대용량 데이터를 색인하는 작업은 신중하게 접근해야 합니다.
잘못된 설정이나 방법은 클러스터 성능 저하, 데이터 손실, 심지어 클러스터 불안정까지 야기할 수 있습니다.
다음은 대용량 데이터 색인 작업 시 주의해야 할 5가지 사항입니다.
1. 충분한 하드웨어 리소스 확보
문제: 대용량 데이터 색인은 CPU, 메모리, 디스크 I/O 등 클러스터 리소스에 큰 부담을 줍니다. 리소스 부족은 색인 속도 저하 및 클러스터 전체 성능 저하로 이어집니다.
해결: 색인 작업 전에 충분한 CPU 코어, 메모리, 빠른 디스크 (SSD 권장)를 갖춘 OpenSearch 노드를 확보해야 합니다. 색인 작업 중에는 다른 작업에 필요한 리소스량을 고려하여 노드에 여유를 두어야 합니다.
- 모니터링: 색인 작업 중 CPU 사용률, 메모리 사용률, 디스크 I/O 등을 지속적으로 모니터링하여 병목 현상을 파악하고, 필요에 따라 리소스를 추가해야 합니다.
2. 색인 설정 최적화
문제: 기본 색인 설정은 대용량 데이터에 적합하지 않을 수 있습니다. 예를 들어, 너무 많은 복제본은 색인 성능을 저하시킬 수 있습니다.
해결: 색인 생성 시 적절한 설정을 적용해야 합니다.
- `index.number_of_replicas`: 초기에는 0으로 설정하고, 색인 완료 후 필요에 따라 늘리는 것을 고려합니다.
-`index.refresh_interval`: 색인 작업 중에는 긴 시간(예: `30s` 또는 `60s`)으로 설정하여 색인 성능을 향상시키고, 색인 완료 후 짧은 시간으로 줄여 검색 성능을 높입니다.
- `index.translog.durability`: `async`로 설정하여 색인 성능을 향상시키지만, 데이터 손실 위험이 증가하므로 주의해야 합니다. - `index.translog.sync_interval`: `30s` 또는 `60s`로 설정하여 디스크 동기화 빈도를 줄입니다.
- 데이터 타입: 색인할 필드의 데이터 타입을 정확하게 정의하여 색인 공간을 효율적으로 사용하고 검색 성능을 향상시킵니다.
3. 벌크 색인 활용 및 최적화
문제: 개별 색인 요청을 보내는 것은 매우 비효율적입니다.
해결: 벌크 API를 사용하여 여러 색인 요청을 하나의 HTTP 요청으로 묶어 전송합니다. 벌크 요청 크기를 적절하게 조정하여 네트워크 오버헤드를 줄이고 색인 성능을 향상시킵니다. (일반적으로 5MB ~ 15MB 사이를 권장)
- 벌크 요청 구조: 벌크 요청의 구조를 최적화하여 색인 성능을 향상시킵니다. 예를 들어, 동일한 인덱스에 대한 색인 요청을 함께 묶거나, 동일한 샤드에 할당될 가능성이 높은 문서를 함께 묶습니다.
4. 샤딩 전략 수립
문제: 샤딩이 잘못되면 특정 샤드에 데이터가 집중되어 병목 현상이 발생할 수 있습니다.
해결: 데이터 분포를 고려하여 샤드 수를 적절하게 설정합니다. 데이터가 특정 필드에 따라 분포되는 경우, 해당 필드를 샤딩 키로 사용합니다. 샤드 크기를 적절하게 유지하여 샤드 수가 너무 많거나 적지 않도록 합니다. (일반적으로 30GB ~ 50GB 사이를 권장) - 샤드 재분배: 색인 작업이 완료된 후 샤드 재분배를 통해 데이터가 균등하게 분산되도록 합니다.
5. 모니터링 및 오류 처리
문제: 색인 작업 중 오류가 발생하면 데이터 손실이나 클러스터 불안정으로 이어질 수 있습니다.
해결: 색인 작업 중에는 CPU, 메모리, 디스크 사용량, 색인 속도 등을 지속적으로 모니터링합니다. 로그를 분석하여 오류 원인을 파악하고, 필요한 경우 색인 설정을 조정합니다. 벌크 색인 작업 시 오류 발생 시 재시도 로직을 구현하여 일시적인 오류에 대비합니다.
위의 5가지 사항을 준수하면 OpenSearch에서 대용량 데이터 색인 작업을 성공적으로 수행하고, 클러스터의 안정성과 성능을 유지할 수 있습니다.
gemma3가 문제와 해결 방법, 모니터링까지 체계적으로 정리되어 있어 색인 작업 시 발생할 수 있는 위험 요소가 더 가독성있게 읽히는 것 같습니다. 또한 구체적인 해결방안을 제시해주어, 실제 검색엔진 관련 개발 과정에서 참고가 많이 될 수 있을 거 같다는 생각이 듭니다.
코드 생성 : 알고리즘 문제 해결
문제 해결 능력을 평가해보려고합니다. 위 질문부터 모두 첫번째 시도 답변을 기재하고 있습니다.
또 영어로도 물어봤습니다.
deepseek-r1:14b
import sys
from collections import deque
def main():
# Read input dimensions
rows = int(sys.stdin.readline())
cols = int(sys.stdin.readline())
# Initialize the grid
grid = []
for _ in range(rows):
line = sys.stdin.readline().strip()
grid.append(list(line))
# Find start and end positions
start = None
end = None
portals = {}
for i in range(rows):
for j in range(cols):
cell = grid[i][j]
if cell == 'S':
start = (i, j)
elif cell == 'E':
end = (i, j)
elif 'A' <= cell <= 'Z':
if cell not in portals:
portals[cell] = []
portals[cell].append((i, j))
# Verify that each portal has exactly two cells
for cell in portals:
assert len(portals[cell]) == 2, f"Portal {cell} does not appear exactly twice"
# Create a dictionary to map each portal cell to its pair
portal_pairs = {}
for cell in portals:
(i1, j1), (i2, j2) = portals[cell]
portal_pairs[(i1, j1)] = (i2, j2)
portal_pairs[(i2, j2)] = (i1, j1)
# BFS initialization
visited = set()
queue = deque()
start_i, start_j = start
queue.append((start_i, start_j, 0))
visited.add((start_i, start_j))
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
while queue:
i, j, dist = queue.popleft()
if (i, j) == end:
print(dist)
return
# Explore adjacent cells
for di, dj in directions:
ni = i + di
nj = j + dj
if 0 <= ni < rows and 0 <= nj < cols:
if (ni, nj) not in visited:
visited.add((ni, nj))
queue.append((ni, nj, dist + 1))
# Explore portal jump
if (i, j) in portal_pairs:
pi, pj = portal_pairs[(i, j)]
if (pi, pj) not in visited:
visited.add((pi, pj))
queue.append((pi, pj, dist + 1))
# If end is unreachable
print(-1)
if __name__ == "__main__":
main()
gemma3:12b
from collections import deque
def solve():
n, m = map(int, input().split())
maze = []
for _ in range(n):
maze.append(list(input()))
start = (0, 0)
end = (n - 1, m - 1)
portals = {}
for r in range(n):
for c in range(m):
if 'A' <= maze[r][c] <= 'Z':
if maze[r][c] not in portals:
portals[maze[r][c]] = []
portals[maze[r][c]].append((r, c))
q = deque([(start, 0)])
visited = set()
visited.add(start)
while q:
(row, col), dist = q.popleft()
if (row, col) == end:
print(dist)
return
# 상하좌우 이동
directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for dr, dc in directions:
new_row, new_col = row + dr, col + dc
if 0 <= new_row < n and 0 <= new_col < m and maze[new_row][new_col] != '#' and (new_row, new_col) not in visited:
q.append(((new_row, new_col), dist + 1))
visited.add((new_row, new_col))
# 포탈 이동
if 'A' <= maze[row][col] <= 'Z':
portal_char = maze[row][col]
if portal_char in portals:
portal_locations = portals[portal_char]
for portal_row, portal_col in portal_locations:
if (portal_row, portal_col) != (row, col) and (portal_row, portal_col) not in visited:
q.append(((portal_row, portal_col), dist + 1))
visited.add((portal_row, portal_col))
print(-1)
solve()
한국어 질문/ 영어 질문 결과가 동일하여 하나만 기재했습니다.
저는 코딩 테스트에 취약한 하급 개발자입니다. 평가는 GPT o3-mini에게 맡겼습니다.
또한 여러분께 맡기겠습니다.
첫번째 풀이 : deepseek-r1:14b
두번째 풀이 : gemma3:12b
gemma3가 더 우수하다네요. 저도 그렇게 생각합니다.
또한 다국어 언어 학습에서도 차이를 보이는게,
deepseek는 자주 중국어로 답변하여 retry를 해야하는 경우가 가끔 존재하여 불편하였지만,
gemma3는 한국어로 질문하면 무조건 한국어로 답변하는 일관성이 마음에 들었습니다.
다국어 학습에도 불구하고 우수한 성능을 가지고있는게 놀라운 점인것 같습니다.
여러분은 어떻게 생각하시나요?
'A.I.(인공지능) & M.L.(머신러닝)' 카테고리의 다른 글
[ComfyUI] ComfyUI 설치 및 간단 사용법 정리 (1) | 2025.02.05 |
---|---|
intel npu acceleration library - window (0) | 2025.02.03 |
[유치원과정] 트랜스포머 이론 - 인코더 | Multi-Head Attention (0) | 2025.01.05 |
Sana: Efficient High-Resolution Image Synthesis with Linear Diffusion Transformer (2) | 2024.12.02 |
런웨이 & 루마 AI API (1) | 2024.09.22 |