LLM 엔지니어링2장 · 신경망과 딥러닝의 기본 원리15 / 23← 이전목차다음 →MetaContents

2장. 신경망과 딥러닝의 기본 원리

실습 1 — 텐서 다루고 GPU 확인하기

PyTorch 의 텐서를 만들고 계산해 보고, 이 PC 의 GPU 를 확인한 뒤 CPU 와 GPU 의 행렬 곱 속도를 견줍니다. PyTorch 가 설치되어 있어야 합니다. GPU 가 없어도 실습은 끝까지 돌아가고, 속도 견주기만 건너뜁니다.

1단계. 텐서 만들기

Python
import torch

print("PyTorch 버전:", torch.__version__)

# 파이썬 리스트로 텐서를 만든다. 실수(float)는 기본으로 float32 가 된다
t = torch.tensor([[1.0, 2.0, 3.0],
                  [4.0, 5.0, 6.0]])
print(t)
print("모양:", t.shape, "자료형:", t.dtype)
출력
PyTorch 버전: 2.14.1+cu126
tensor([[1., 2., 3.],
        [4., 5., 6.]])
모양: torch.Size([2, 3]) 자료형: torch.float32

이 단계의 함수입니다.

코드쉬운 뜻
torch.__version__설치된 PyTorch 의 판 번호. +cu126 은 CUDA 12.6 용 GPU 판이라는 표시다
torch.tensor(리스트)리스트의 숫자를 텐서라는 상자에 담는다. 안쪽 리스트 하나가 한 줄(행)이 된다
t.shape상자의 모양. [2, 3] 은 2줄 3칸이다
t.dtype칸에 담긴 숫자의 종류. float32 는 소수를 32비트(4바이트)로 담는 형식이다

2단계. 계산하기

Python
# NumPy 와 같은 모양의 계산이 그대로 된다
print(t + 10)              # 모든 칸에 10 더하기
print(t * t)               # 칸끼리 곱하기
print(t.sum(), t.mean())   # 합과 평균

# 행렬 곱: (2, 3) @ (3, 2) → (2, 2). t.T 는 행과 열을 뒤집은 것
print(t @ t.T)
출력
tensor([[11., 12., 13.],
        [14., 15., 16.]])
tensor([[ 1.,  4.,  9.],
        [16., 25., 36.]])
tensor(21.) tensor(3.5000)
tensor([[14., 32.],
        [32., 77.]])

이 단계의 함수입니다. NumPy 배열에 하던 계산을 텐서에도 그대로 쓸 수 있습니다.

코드쉬운 뜻결과
t + 10모든 칸에 10 을 더한다1 은 11, 6 은 16
t * t같은 자리의 칸끼리 곱한다(행렬 곱이 아니다)2 는 4, 6 은 36
t.sum(), t.mean()모든 칸의 합과 평균. 숫자 하나지만 0차원 텐서로 나온다tensor(21.), tensor(3.5000)
t.T행과 열을 뒤집는다(전치). (2, 3) 이 (3, 2) 가 된다1, 2, 3 이 한 열이 된다
t @ t.T행렬 곱. 앞의 줄과 뒤의 열을 짝지어 곱해 더한다("인공 뉴런" 절의 np.dot)첫 칸 14 = 1×1 + 2×2 + 3×3

t * t 와 t @ t.T 는 생김새가 비슷하지만 다릅니다. * 는 칸끼리 곱해 모양이 그대로(2, 3)이고, @ 는 줄과 열을 곱해 더해 모양이 (2, 2)로 바뀝니다.

3단계. NumPy, pandas 와 오가기

Python
import numpy as np
import pandas as pd

df = pd.DataFrame({"기온": [3.5, 21.0, 28.4], "광고비만원": [50, 120, 180]})

# 표의 값을 NumPy 배열로 꺼낸 뒤 float32 텐서로 바꾼다(신경망은 보통 float32 를 쓴다)
x = torch.tensor(df.to_numpy(), dtype=torch.float32)
print(x)

# 텐서를 다시 NumPy 배열로
print(x.numpy().round(1))
출력
tensor([[  3.5000,  50.0000],
        [ 21.0000, 120.0000],
        [ 28.4000, 180.0000]])
[[  3.5  50. ]
 [ 21.  120. ]
 [ 28.4 180. ]]

이 단계의 함수입니다. 표(pandas) → 배열(NumPy) → 텐서(PyTorch)로 옮겨 담고, 다시 배열로 되돌립니다.

코드쉬운 뜻
df.to_numpy()pandas 표에서 열 이름을 떼고 숫자만 NumPy 배열로 꺼낸다
torch.tensor(배열, dtype=torch.float32)배열을 텐서로 바꾸면서 숫자 종류를 float32 로 정한다. 정하지 않으면 NumPy 의 float64 가 그대로 따라온다
x.numpy()텐서를 NumPy 배열로 되돌린다. CPU 에 있는 텐서만 바로 된다(GPU 텐서는 .cpu() 를 먼저)

출력의 3.5000 처럼 소수 넷째 자리까지 보이는 것은 텐서를 찍는 방식일 뿐, 값이 바뀐 것은 아닙니다.

4단계. GPU 확인하고 속도 견주기

Python
import time

print("GPU 사용 가능:", torch.cuda.is_available())
if torch.cuda.is_available():
    print("GPU 이름:", torch.cuda.get_device_name(0))
    # 전체 메모리를 바이트에서 GB 로 바꿔 찍는다
    print("GPU 메모리:", round(torch.cuda.get_device_properties(0).total_memory / 1024**3, 1), "GB")

# 2000×2000 행렬 둘을 곱하는 시간을 잰다(곱셈 80억 번 남짓)
a = torch.randn(2000, 2000)
b = torch.randn(2000, 2000)

start = time.perf_counter()
for _ in range(10):
    c = a @ b
cpu_sec = (time.perf_counter() - start) / 10
print(f"CPU 한 번: {cpu_sec * 1000:.0f} ms")

if torch.cuda.is_available():
    ag, bg = a.to("cuda"), b.to("cuda")   # 두 행렬을 GPU 메모리로 옮긴다
    c = ag @ bg                           # 첫 계산은 준비 시간이 섞여서 한 번 버린다
    torch.cuda.synchronize()              # GPU 가 일을 다 끝낼 때까지 기다린다
    start = time.perf_counter()
    for _ in range(10):
        c = ag @ bg
    torch.cuda.synchronize()
    gpu_sec = (time.perf_counter() - start) / 10
    print(f"GPU 한 번: {gpu_sec * 1000:.0f} ms, CPU 의 {cpu_sec / gpu_sec:.0f}배 빠르기")
출력
GPU 사용 가능: True
GPU 이름: NVIDIA GeForce GTX 1650
GPU 메모리: 4.0 GB
CPU 한 번: 79 ms
GPU 한 번: 8 ms, CPU 의 9배 빠르기

이 단계의 함수입니다. 스톱워치를 들고 요리사(CPU)와 보조들(GPU)에게 같은 일을 시켜 보는 단계입니다.

코드쉬운 뜻비유로 치면
torch.cuda.is_available()GPU 를 쓸 수 있으면 True보조들의 작업대가 있나
torch.cuda.get_device_name(0)0번 GPU 의 이름작업대의 이름표
torch.cuda.get_device_properties(0).total_memory0번 GPU 의 전체 메모리(바이트). 1024**3 으로 나눠 GB 로 바꾼다작업대의 넓이
torch.randn(2000, 2000)난수로 채운 2000×2000 텐서를 만든다. 속도를 재려는 연습용 재료다깎을 감자 한 더미
time.perf_counter()지금 시각을 아주 잘게 잰다. 끝 시각에서 시작 시각을 빼면 걸린 시간이다스톱워치
a.to("cuda")텐서를 GPU 메모리로 옮긴다감자를 보조들의 작업대로 옮긴다
torch.cuda.synchronize()GPU 가 맡은 일을 다 끝낼 때까지 기다린다보조들이 다 깎았는지 확인하고 스톱워치를 누른다

시간은 10번 곱한 시간을 10 으로 나눈 평균입니다. GPU 쪽에서 첫 계산을 한 번 버리는 까닭은, 처음 한 번은 GPU 가 일할 준비를 하는 시간이 섞여 느리게 나오기 때문입니다.

이 실습이 뜻하는 것

이 실습은 주방에서 새 그릇과 일꾼을 시험해 보는 일과 같습니다. 숫자를 담을 그릇을 하나 만들어 모양과 재질을 보고(1단계), 그 그릇에 담긴 채로 더하고 곱해 보고(2단계), 다른 그릇(표, 배열)과 옮겨 담아 보고(3단계), 마지막으로 요리사 한 사람과 보조 여럿에게 같은 일을 시켜 스톱워치로 잽니다(4단계). 여기서 그릇이 텐서, 요리사가 CPU(Central Processing Unit, 중앙 처리 장치), 보조들이 GPU(Graphics Processing Unit, 그래픽 처리 장치)입니다. 비유와 다른 점은, GPU 는 사람처럼 판단하는 일꾼이 아니라 같은 단순 계산을 한꺼번에 많이 하는 장치라는 것입니다.

단계출력뜻
1. 버전2.14.1+cu126설치된 PyTorch 가 GPU 를 쓸 수 있는 판입니다
1. 텐서torch.Size([2, 3]), torch.float322줄 3칸짜리 그릇이고, 칸마다 소수가 담겨 있습니다
2. 계산tensor(21.) tensor(3.5000)여섯 칸의 합이 21, 평균이 3.5 입니다. NumPy 와 같은 계산이 텐서에서도 됩니다
2. 행렬 곱[[14., 32.], [32., 77.]]줄과 줄을 짝지어 곱해 더한 결과입니다. 모양이 (2, 3) 에서 (2, 2) 로 바뀌었습니다
3. 오가기같은 숫자 두 번표 → 텐서 → 배열로 옮겨 담아도 값은 그대로입니다
4. GPU 확인True, GTX 1650, 4.0 GB이 PC 에는 메모리 4GB 짜리 GPU 가 있습니다
4. 속도CPU 79 ms, GPU 8 ms, 9배이 PC 에서 큰 행렬 곱 한 번이 GPU 에서 9배쯤 빨랐습니다

한 줄로 줄이면, 텐서는 NumPy 배열처럼 다루는 숫자 그릇이고, 큰 행렬 계산은 GPU 가 훨씬 빠르다는 것을 확인한 실습입니다. 3단계의 표를 텐서로 바꾸는 줄은 실습 3부터 그대로 씁니다. 79ms, 8ms, 9배는 이 PC 에서 한 번 잰 값이라 수강생의 PC 에서는 다르게 나오고, GPU 가 없으면 속도 줄은 CPU 시간만 나옵니다.

코드 설명

  • torch.tensor 에 소수를 넣으면 자료형이 torch.float32 가 됩니다. NumPy 의 기본(float64)보다 칸 하나의 크기가 절반이라, 신경망은 보통 float32 를 씁니다.
  • t @ t.T 처럼 @ 가 행렬 곱입니다. (2, 3) 과 (3, 2) 를 곱해 (2, 2) 가 나옵니다. 순전파 절의 모양 규칙 그대로입니다.
  • torch.tensor(df.to_numpy(), dtype=torch.float32) 는 실습 3부터 표를 텐서로 바꿀 때 그대로 쓰는 줄입니다.
  • 이 PC 에서는 GPU 가 GeForce GTX 1650(4GB)으로 잡혔고, 2000×2000 행렬 곱 한 번이 CPU 79ms, GPU 8ms 로 9배쯤 빨랐습니다. 시간은 PC 와 그때의 상황에 따라 달라지므로, 수강생의 숫자는 이와 다르게 나옵니다. GPU 가 없으면 "GPU 사용 가능: False"와 CPU 시간만 나옵니다.
  • GPU 는 일을 맡겨 두고 바로 다음 줄로 넘어가기 때문에, 시간을 잴 때는 torch.cuda.synchronize() 로 끝날 때까지 기다려야 바르게 잴 수 있습니다.
← 이전 절2장 · 실습 지도다음 절 →2장 · 실습 2 — 자동 미분으로 직선 맞추기