CS, Python, 그리고 AI

YoungCoder Diary·2025년 2월 4일


Deepseek는 적어도 명절 시즌 동안 대왕고래였다. 언급량도 많고 정보도 많고, 컴퓨터 관련 지식을 얘기하며 한 번 짚고 넘어가면 좋을 듯 하다.

컴퓨터 구성 요소


컴퓨터 구매하는 방법

  • 실제 예산과 컴퓨터 사용처를 준비한다.
  • '내맘대로견적'으로 검색하고 총합 가격대만 맞춰서 어떻게든 구성한다.
  • 구성한 결과를 스샷떠서 조립컴 커뮤니티에 "20만원대로 싸게 맞췄다 ㅋㅋ" 고 글을 올린다.
  • 완성!
    AI를 공부하기 위해선 프로그램이 돌아가기 위한 컴퓨터에 대해 알아야 한다.

하드웨어

물리적인 실체가 있는 컴퓨터의 구성 요소이다.

CPU(중앙처리장치)

연산을 수행하는 핵심 장치이다. 인간의 두뇌에 보통 비견되는데, 데이터의 변환과 분석 속도에 직접적인 영향을 미친다. CPU는 데이터를 순서대로 직렬 방식으로 처리한다. 그런데 AI는 빠르게 여러 데이터를 처리해야 하기 때문에, AI의 성능에 관해서는 CPU보단 GPU가 보통 더 많이 거론된다.

GPU

CPU와 달리 병렬 방식으로 연산을 수행한다. 한꺼번에 여러 데이터를 처리하기 때문에, 대량 데이터 분석, AI 학습 등의 분야에 많이 사용된다. 그런데 고사양 GPU는 엄청 비싸기 때문에(H100의 경우 한화 5000만원이라고 한다.) 이 비용을 줄였다고 대대로 홍보하는 것이 바로 지금 핫한 DeepSeek 이다.

메모리(RAM)

프로그램을 실행할 때 필요한 데이터를 일시적으로 저장하는 공간이다. 빠르게, 자주 접근해야 되는 데이터를 담는 곳이다. 컴퓨터가 꺼지면 안의 내용이 사라진다.

저장장치(SSD/HDD)

컴퓨터가 꺼져도 사라지면 안되는, 장기적으로 데이터를 저장하는 공간이다. 성능은 메모리보다 조금 느릴지라도, DB를 관리하는 데에 있어서 중요한 역할을 한다.

소프트웨어

컴퓨터의 구성 요소 중 물리적 실체가 없는 프로그램이다.

OS


컴퓨터 하드웨어와 소프트웨어를 관리하기 위한 시스템이다. 데이터와 프로그램이 돌아가는 기반 환경이다.

프로세스

실행중인 프로그램을 의미한다. 우리가 흔히 사용하게 될 코드, 데이터, 스택 등이 포함된다.

스케쥴링

운영체제가 다중 프로세스를 어떻게 실행할지 결정할 방식이다. 프로세스를 효율적으로 실행하기 위해 CPU 시간을 할당하는 방법인데, 선점형과 비선점형 방식으로 나뉜다. 데이터 처리 작업의 성능 최적화를 할 때 중요한 역할을 한다.

스레드와 멀티 스레딩

스레드는 프로세스 내의 작은 작업 단위이다. 하나의 프로세스는 여러 개의 스레드로 구성될 수 있고, 하나의 프로세스 내에 있는 스레드들은 병렬적으로 처리된다. 이렇게 하나의 프로세스 내부에서 여러 작업을 동시에 수행하여 성능을 향상시키는 걸 멀티 스래딩 이라고 한다.

AI 모델 최적화


행동주의 심리학에서의 조건형성 이론, 이 중 조작적 조건형성은 AI의 강화학습과 거의 똑같다.

컴퓨터 지식에 대해 습득했으니, AI를 학습시키는 방법에 대해서도 알아볼 필요가 있겠다.

input(질문)과 output(정답)이 정해져있는 상태에서 학습을 시키는 지도학습과, 정답을 주지 않고 input(데이터)만을 가지고 output을 추론해내도록 하는 비지도학습이 있는데, 최근의 AI 모델은 보상 기반 학습, 강화학습을 중요하게 다룬다고 한다.

강화학습

AI에게 적절한 행동을 가르치기 위해 보상을 제공하는 방식이다. 위에서의 조작적 조건형성 중 정적 강화와 가장 비슷하다. 특히 RLHF(인간 피드백을 통한 강화학습)을 최근에는 많이 사용해왔다.

하지만 Deepseek는 직접 강화학습을 선택했다. 인간 비평자 모델 (critic model) 을 사용하는 대신 그룹 점수에서 기준선을 추정하는 GRPO (Group Relative Policy Optimization) 를 사용했다. 보상 시스템은 규칙 기반이며, 정답과의 근접성을 체크하는 정확도 보상과, 답변의 형식이 올바른지를 체크하는 형식 보상 두 가지를 사용했다.

https://x.com/i/status/1882459540681298161
개인적으로 딥시크의 추론 과정을 잘 보여주는 글이라고 생각한다.

특히 Deepseek가 화제가 된 건 자체 검증, 숙고(reflection), 긴 CoT(Chain-of-Thought)이다. 초기 데이터가 부족한 콜드 스타트 학습으로, 지도자 없이 강화학습에 성공했다는 사실이 ai 개발 방향에 있어서 많은 시사점이 된 것으로 보인다.

그 외 성능을 최적화하기 위해 Deepseekr가 취한 기술적 접근 또한 가볍게 짚고 넘어가자.

  • 양자화 기법(Quantization)
    모델의 연산 정밀도를 줄여 속도를 향상시키는 기법. 아래의 다이나믹 비트 할당을 포함한다.

  • 다이나믹 비트 할당
    연산의 정확도를 유지하면서도, 메모리 사용량을 줄이기 위해 동적으로 비트를 조정하는 기법이다. 데이터의 용량을 압축하기 위해 사용되었다고 한다.

  • MOE Architecture
    언어 모델을 더 작고 특화된 하위 전문가 모델들로 분해하여, 전체적인 계산 효율과 자원 할당을 개선하는 방식이다.
    수천 개의 파라미터를 효과적으로 활용하여 성능을 극대화한다고 한다.

  • 온디바이스 AI
    모델의 경량화를 통해 스마트폰이나 소형 디바이스에서도 직접 AI를 실행 가능하게 만든 것이다. chatGPT 등 기존의 ai모델들은 웹에서 호출하거나, 앱을 깔거나 했어야 하는데, 모델이 가볍기 때문에 이러한 작업을 하지 않고도 ai를 사용할 수 있다. 수학적 추론 능력 등이 특히 좋은 R1 모델이 깃허브에 오픈되어있다. 보안에 대한 걱정이 없는 환경에서라면 직접 import해서 구동해봐도 좋을 것 같다.

Python으로 실습해보기

연산 속도 최적화 실험

import numpy as np
import time
from collections import deque

size = 1000000

# 리스트 연산 속도 측정
lst = list(range(size))
start = time.time()
sum(lst)
print("리스트 연산 시간:", time.time() - start)

# 리스트 연산 시간: 0.021947145462036133

# NumPy 배열 연산 속도 측정
arr = np.array(lst)
start = time.time()
np.sum(arr)
print("NumPy 배열 연산 시간:", time.time() - start)

# NumPy 배열 연산 시간: 0.0009999275207519531

# 셋 연산 속도 측정
st = set(lst)
start = time.time()
sum(st)
print("셋 연산 시간:", time.time() - start)

# 셋 연산 시간: 0.02391195297241211

# 스택 연산 속도 측정 (리스트 사용)
stack = lst.copy()
start = time.time()
while stack:
    stack.pop()
print("스택 연산 시간:", time.time() - start)

# 스택 연산 시간: 0.08080935478210449

# 큐 연산 속도 측정 (deque 사용)
queue = deque(lst)
start = time.time()
while queue:
    queue.popleft()
print("큐 연산 시간:", time.time() - start)
# 큐 연산 시간: 0.0658261775970459

연산 결과를 보면 자체적으로 C언어 최적화된 NumPy 배열이 압도적으로 빠르고, 리스트와 셋은 유사한 속도를 보이며, 스택과 큐 연산은 상대적으로 느린 것을 확인할 수 있다.

따라서 대규모 데이터 연산 시 NumPy 배열을 사용하는 것이 매우 효율적이며,
리스트와 셋은 유사한 속도를 보이며, 가장 느린 스택과 큐는 합산 작업에서는 큰 차이가 없으나 pop() 또는 popleft()로 제거하는 작업에서는 시간이 더 걸릴 수 있을 거라고 확인할 수 있다.

다이나믹 비트 할당 실험

import numpy as np
import time

# 데이터 크기와 반복 횟수 설정
size = 1000000
repeat = 100

# 정수 데이터의 비트 크기 줄이기
arr = np.array([255, 128, 64, 32, 16, 255, 128, 64, 32, 16] * (size // 10), dtype=np.uint8)
print("기본 데이터:", arr[:5])

# 비트 마스킹 처리 전 시간 측정
start = time.time()
for _ in range(repeat):
    arr_reduced = arr & 0b11111111  # 실제로는 아무 변화 없음
print("비트 마스킹 처리 전 시간:", time.time() - start)

# 4비트로 줄이기 (비트 마스킹)
start = time.time()
for _ in range(repeat):
    arr_reduced = arr & 0b00001111
print("비트 마스킹 처리 시간:", time.time() - start)

print("비트 줄인 데이터:", arr_reduced[:5])

### 연산결과 ###
# 기본 데이터: [255 128  64  32  16]
# 비트 마스킹 처리 전 시간: 0.054852962493896484
# 비트 마스킹 처리 시간: 0.042885780334472656
# 비트 줄인 데이터: [15  0  0  0  0]

처리 전과 후, 0.1초 이상 연산 시간이 줄어든 것을 확인할 수 있다. 원하는 특정 값만을 다이나믹하게 처리할 수 있어서 좋으나, 데이터의 비트 손실이 발생할 수 있다. 복원이 필요하며 손실이 없는 것이 중요한 데이터라면 권장하지 않는다.

profile
제로베이스 비전공자의 개발자 성장 일지

0개의 댓글