[LLM] Model Parallelism and Data Parallelism

대규모 언어 모델(LLM, Large Language Model)의 학습은 수십억 개 이상의 파라미터를 다루기 때문에, 단일 GPU의 메모리나 연산 능력으로는 처리하기 어렵다. 이러한 문제를 해결하기 위해 병렬화(parallelism) 전략이 도입되며, 주로 두 가지 방식인 모델 병렬화(Model Parallelism)데이터 병렬화(Data Parallelism)가 사용된다.

Model Parallelism

모델 병렬화는 하나의 모델을 여러 GPU에 분산시켜 학습하는 방식입니다. 모델이 너무 커서 단일 GPU 메모리에 올릴 수 없을 때 사용된다. 대표적인 두 가지 방식이 있다.

  1. Pipeline Parallelism
    • 딥러닝 모델의 층(Layer) 단위로 모델을 분할하고, 각 분할을 다른 GPU에 할당한다.
    • 입력 데이터가 GPU를 순차적으로 통과하며 forward/backward 연산이 수행된다.
    • 모델 크기를 효과 적으로 분산 할 수 있지만, 각 GPU가 순차적으로 동작하게 되어서 파이프라인 버블 문제가 생길 수 있다.
    • 그림에서 머신을 1, 2와 3, 4로 나누면 파이프라인 병렬화이다.
  2. Tensor Parallelism
    • 하나의 층 내부에서 계산되는 텐서 연산을 여러 GPU에 나누어 수행한다.
    • 행렬 곱 연산에서 행 또는 열을 나눠 병렬 연산 후 결과를 합치는 연산을 하면서 수행된다.
    • 레이어 수준보다 더 미세하게 병렬화할 수 있어서 계산량을 세밀하게 분산이 가능하지만, GPU간 통신이 빈번하게 발생하게 되어 통신 오버헤드가 커질 수 있음.
    • 그림에서 머신을 1, 3과 2, 4로 나누면 텐서 병렬화이다.
Model Parallelism

Data Parallelism

모델 크기가 하나의 GPU에 올라가기 적당하고 데이터가 많을 경우 사용할 수 있는 방식으로 데 모델을 그대로 복제한 후, 서로 다른 GPU에서 다른 데이터를 학습시키는 방식이다.

그림 처럼 각 GPU는 동일한 모델을 가지고 있으며, 각기 다른 미니배치 데이터를 학습한다.

Data Parallelism

forward/backward 연산 후, 파라미터의 그레이디언트(gradient) 를 모든 GPU가 서로 동기화하여 업데이트한다.

구현이 간단하고 대부분의 프레임워크에서 지원이 되지만, 모델 크기가 GPU 메모리에 들어가야 하고, 모든 GPU 간의 gradient 통신 비용이 크다.

LLM 모델 학습은 단순히 모델을 만드는 것 이상으로 효율적인 리소스 분산이 핵심이다. 모델 크기, 하드웨어 환경, 통신 병목 등을 고려하여 파이프라인 병렬화, 텐서 병렬화, 데이터 병렬화를 적절히 조합여 사용해야 한다.

[LLM] Strategies to Reduce GPU Memory Usage During LLM Training

대규모 언어 모델(LLM)을 학습하거나 미세 조정할 때 가장 큰 제약 중 하나는 GPU 메모리 부족이다. 학습 과정에서 어떤 데이터가 메모리에 올라가는지, 이를 어떻게 줄일 수 있는지 정리해 보자.

GPU 메모리에 저장되는 주요 데이터

  1. Model Parameters
    • 모델 가중치(Weight)와 편향(Bias) 등의 학습 가능한 값.
    • 예를 들어 7B (7 Billion) 파라미터 모델의 경우 FP32 기준 약 28GB 메모리가 필요함.
  2. Gradient
    • 각 파라미터에 대해 손실 함수의 기울기 값
    • 역전파(Back propagation) 과정에서 계산되며, 파라미터와 같은 크기의 메모리를 필요로 함.
  3. Optimizer State
    • Optimizer는 모델을 학습시키기 위해 파라미터를 업데이트 하는 알고리즘임. 이때 대부분의 고급 옵티마이저는 각 파라미터마다 추가적인 정보를 저장하는데 이 정보들이 Optimizer State임.
    • 예를 들어 Adam 옵티마이저는 각 파라미터에 대해 1st moment (이전 기울기의 평균), 2nd moment (기울기 제곱의 평균) 추적 값을 저장함.
    • 파라미터 크기의 2~3배에 달하는 추가 메모리 소모가 발생할 수 있음.
  4. Forward Activation
    • 순전파(Forward pass)에서 중간 레이어의 출력값.
    • 역전파 시 Gradient를 계산하기 위해 반드시 필요한 값.
    • 가장 많은 메모리를 차지할 수 있음.

메모리 절약을 위한 전략

  1. Gradient Accumulation
    • GPU 메모리 한계를 넘지 않기 위해 작은 배치(batch) 크기로 여러 번 계산하고 그래디언트를 누적(accumulate).
    • 일정 스텝마다 누적된 그래디언트를 이용해 파라미터를 한 번 업데이트함.
    • Batch size 16에서 OOM이 발생할 경우
      -> Batch size 4 (gradient_accumulation_steps=4)로 설정.
      -> Loss를 4로 나누어 누적하면 batch size 16과 동일한 학습 효과를 얻을 수 있음.
      -> 실제 메모리 사용량은 Batch size 4로 계산 됨.
      -> 작은 Batch size로 큰 Batch size로 학습로 학습한 것과 동일한 효과를 얻을 수 있지만 누적 연산을 추가로 해야 해서 학습 시간은 증가됨.
  1. Checkpointing (Activation Checkpointing)
    • 순전파 시 전체 activation을 저장하지 않고, 마지막 출력값만 저장.
    • 역전파 시 필요한 이전 상태는 다시 계산하여 사용.
    • 순전파 상태 값을 모두 저장하지 않아서 메모리 사용량은 대폭 감소되지만, 역전파 시 순전파를 매번 반복 계산하므로 연산량 증가하여 학습 시간은 증가됨.
  2. Gradient Checkpointing
    • 순전파의 일부 중간 지점(checkpoint)만 저장하고 나머지는 역전파 시 재계산.
    • 전체를 저장하는 일반 방식과, 끝만 저장하는 기본 checkpointing의 중간 전략.
    • 메모리 효율과 연산 효율을 적절히 절충한 방식이라 약간의 오버헤드는 존재하지만 일반적인 Checkpointing 보다는 효율적임.

GPU 메모리는 LLM 학습에서 가장 중요한 자원 중 하나이다. 위에서 소개한 전략들을 적절히 활용하면 제한된 자원 내에서도 효율적으로 모델을 학습시킬 수 있다.

[LLM] Floating Point Formats

AI 모델 학습과 추론에서 부동소수점(Floating Point) 형식은 성능과 정확도에 큰 영향을 준다. 특히, 연산 속도와 메모리 사용량을 줄이기 위해 다양한 정밀도의 부동소수점 형식이 사용되는데, 대표적으로 사용되는 BF16, FP32, FP16에 대해 간단히 정리해 본다.

  1. FP32 (Single Precision Floating Point)
    • Exponent – 8 Bit
    • Mantissa – 23 Bit
    • Sign – 1 Bit
    • Total 32 Bit
    • 높은 정밀도와 넓은 표현 범위를 모두 갖춘 표준 부동소수점 형식. 대부분의 AI 학습, 고정밀 연산이 필요한 경우 사용한다.
  2. FP16 (Half Precision Floating Point)
    • Exponent – 5 Bit
    • Mantissa – 10 Bit
    • Sign – 1 Bit
    • Total 16 Bit
    • 정밀도와 표현 범위 모두 FP32보다 줄어들지만, 메모리 절약과 연산 속도는 증가 시킬 수 있다.
  3. BF16 (bfloat16, Brain Floating Point Format)
    • Exponent – 8 Bit
    • Mantissa – 7 Bit
    • Sign – 1 Bit
    • Total 16 Bit
    • FP16과 마찬가지로 16 Bit를 사용하여 표현 범위가 줄어들었지만, FP32와 동일한 지수부를 가져서 표현 가능한 수의 범위가 넓다.

[Elasticsearch] Collecting RSS information with Logstash

RSS feed는 웹사이트의 최신 콘텐츠를 효율적으로 수집하고 분석할 수 있는 강력한 도구입니다. Logstash를 사용하면 RSS 데이터를 쉽게 수집하고 처리할 수 있습니다. 이 글에서는 Google News RSS를 Logstash로 수집하는 방법을 예제로 설명하겠습니다.

Logstash config

Logstash의 설정 파일은 input, filter, output 세 가지 섹션으로 구성됩니다. 아래는 Google News의 RSS 피드를 수집하기 위한 예제 설정입니다.

Input

http_poller 플러그인을 사용해 RSS 피드를 주기적으로 요청합니다.

input {
  http_poller {
    urls => {
      google_news => {
        url => "https://news.google.com/rss"
        method => get
      }
    }
    request_timeout => 60
    schedule => { every => "10m" }
    codec => "plain"
    metadata_target => "http_metadata"
  }
}

urls: 수집할 RSS 피드 URL을 지정합니다.
schedule: 데이터를 가져올 주기를 설정합니다. every => “5m”은 5분마다 데이터를 요청합니다. cron 표현식을 사용하여 보다 세부적인 설정이 가능합니다
metadata_target: 요청 메타데이터를 저장할 필드를 지정합니다.

Fiter

수집된 RSS 데이터를 구조화된 형태로 변환합니다.

filter {
  xml {
    source => "message"
    target => "rss"
    store_xml => true
    force_array => false
  }
  split {
    field => "[rss][channel][item]"
  }
  mutate {
    add_field => {
      "title" => "%{[rss][channel][item][title]}"
      "link" => "%{[rss][channel][item][link]}"
      "source" => "%{[rss][channel][item][source][content]}"
      "pubDate" => "%{[rss][channel][item][pubDate]}"
      "type" => "%{[http_metadata][input][http_poller][request][name]}"
    }
    remove_field => ["message", "rss", "event", "http_metadata"]
  }
}

xml: RSS 데이터를 XML 형태에서 파싱하여 rss 필드에 저장합니다.
split: 각 기사(item)를 개별 이벤트로 분리합니다.
mutate: 원하는 필드(title, link, source, pubDate)를 추가하고 불필요한 데이터를 제거합니다.

Output

결과 데이터를 Elasticsearch에 저장하거 콘솔에 출력합니다.

output {
  elasticsearch {
    hosts => ["http://localhost:9200"]
    index => "rss-data-%{+YYYY.MM.dd}"
  }
  stdout { codec => rubydebug }  
}

이 예제를 사용하면 Logstash를 활용하면 RSS 데이터를 효율적으로 수집하고 처리할 수 있습니다.

[Azure Function]Ensuring Azure Function App is Running Before Deployment

프로덕션 환경에서 Azure Function App을 배포할 때, 앱의 상태를 확인하는 것은 매우 중요하다. 만약 Azure Function App의 상태가 ‘Runing’이 아니라면 배포가 되지 않는다.

Azure DevOps와 Azure CLI를 사용해 Function App의 상태를 확인하고, 앱이 실행 중이지 않다면 시작해야한다. 이 간단한 스크립트를 통해 배포 오류를 방지하고 원활한 운영을 보장할 수 있습니다.

Azure DevOps 작업 구성

- task: AzureCLI@2
  inputs:
    azureSubscription: ' Your Azure subscription details' 
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: |
      state=$(az functionapp show --resource-group $(resourceGroupName) --name $(functionAppName) --slot second --query "state" -o tsv)
      if [ "$state" != "Running" ]; then
        echo "Function App is not running. Starting the Function App..."
        az functionapp start --resource-group $(resourceGroupName) --name $(functionAppName) --slot second
      else
        echo "Function App is already running."
      fi

[Ubuntu]Getting an SSL/TLS Certification with Let’s Encrypt for free

네트워크를 사용하는 서비스를 할 때, SSL(Secure Sockets Layer)/TLS(Transport Layer Security) 인증서를 등록해서 https 통신을 할 수 있도록 해야 안전한 서비스를 할 수 있다. 그런데 일반적으로는 SSL 인증서를 발급 받으려면, SSL 발급해주는 사이트에서 일정 비용을 내고 인증서를 구입을 해야 한다.

인증서 비용이 부담이 없다면 그냥 구입해서 사용하는 것이 편하긴 하지만, 그냥 간단히 Study를 목적으로 잠깐 사용할 것이라면 비용을 지불하기 조금 아까 울 수 있다.

Let’s Encrypt라는 인증 기관에서는 무료로 사용할 수 있는 SSL 인증서를 제공한다. 유효기간이 90일 정도라 주기적으로 갱신해야 겠지만 Study가 목적이라면 충분한 기간이다.

Let’s Encrypt 인증서를 발급 받으려면, 먼저 인증서를 설치할 server와 public domain이 준비되어 있어야 한다.

준비가 다 되었다면, 먼저 letsencrypt을 설치한다.

sudo apt-get update
sudo apt-get install letsencrypt

설치가 완료되었으면 아래 명령을 실행하면, DNS에 등록할 TXT 레코드를 확인 할 수 있다.

sudo certbot certonly --manual -d [yourdomain.com] --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory

TXT 정보를 DNS에 등록하고 발급을 계속 진행하면 /etc/letsencrypt/achive 에 인증서가 발급 된 것을 확인 할 수 있다.

/etc/letsencrypt/achive/<yourdomain.com>
├── cert.pem  // your domain 인증서 public key
├── chain.pem // Intermediate 인증서
├── fullchain.pem // cert.pem + chain.pem
└── privkey.pem // cert.pem 비밀 키

총 4개의 파일이 생성되어 있다. 이 인증서 파일을 사용하여 서버에 SSL 인증서를 설정하여 사용하면 된다.

Port Foward to Windows from WSL(Windows Subsystem for Linux) Ubuntu

WSL에서 만든 서비스가 public한 외부 network에 공개되기 위해서는 Windows network interface에서 WSL network interface로 port forwarding을 설정해야 한다.

port forwarding을 설정을 하기 위해서는 먼저 windows에서 설정하려는 port number를 이미 사용 중인지 확인해야 한다.

netstat -ano | findstr $port

TCP    0.0.0.0:135            0.0.0.0:0              LISTENING       1480
TCP    0.0.0.0:19812          0.0.0.0:0              LISTENING       11280
...... 생략 ......

만약, 사용하려는 port가 이미 사용 중이라면 다른 port를 사용하도록 변경하던가, 어떤 Porcess에서 사용 중인지 확인해서 사용 해제를 해야 한다.

사용 가능한 port 라면, windows에서 서비스에 사용할 ip와 port를 wsl ip와 port에 연결해준다.

netsh interface portproxy add v4tov4 listenport={$listen port} listenaddress={$windows ip adress} connectport={$connect port} connectaddress={$wsl ip address}

연결이 되었다면, 다음과 같이 확인 할 수 있다.

netsh interface portproxy show v4tov4

Listen on ipv4:             Connect to ipv4:
Address         Port        Address         Port
--------------- ----------  --------------- ----------
0.0.0.0         80          172.30.106.233  80

[Azure SQL]Using Always Encrypted with .NET (feat. Keyvault)(2)

[Azure SQL]Using Always Encrypted with .NET (feat. Keyvault)(1)에서 SQL에 Always Encryted가를 설정하여 데이터를 암호화하고 쿼리하는 방법에 대해서 알아봤다. 이번 포스트에서는 Always Encryted가 설정된 SQL를 이용하여 .Net 서비스를 개발하는 방법에 대해서 알아보겠다.

.Net 서비스 내에서 Always Encrypted가 설정된 SQL에 접근 하기 위해서는 Connection String에 아래 옵션이 설정 되어야 한다.

  • Encryption Setting=Enabled
  • Encrypt=True

appsettings.json에서 ConnectionStrings에 다음과 같이 설정하면 된다.

"ConnectionStrings": {
  "DefaultConnection": "Server=tcp:azure sql.database.windows.net,1433;Initial Catalog=DB;Persist Security Info=False;User ID={user id};Password={password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;Column Encryption Setting=Enabled;"
}

여기까지 하면 db connection이 생성될 때 Always Encryed 설정이 활성화 된다. 하지만 CEK가 설정된 Table을 읽어 오지는 못한다. 제대로 값을 읽어 오기 위해서는 CEK Provider를 서비스 시작 시점에 설정을 해주어야 한다.

CEK Provider는 Program.cs에 다음과 같이 Provider를 구성해주면 된다.

var azureKeyVaultProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new DefaultAzureCredential());

var dics = new Dictionary<string, SqlColumnEncryptionKeyStoreProvider>();
dics.Add(SqlColumnEncryptionAzureKeyVaultProvider.ProviderName, azureKeyVaultProvider);

SqlConnection.RegisterColumnEncryptionKeyStoreProviders(dics);

이제, 아래와 같이 간단히 앞서 암호화 했던 Users 테이블을 select해 보면 복호화 된 데이터를 확인 할 수 있다.

await using var conn = (SqlConnection)_context.Database.GetDbConnection();

await conn.OpenAsync();

await using var cmd = new SqlCommand("select * from Users", conn);
await using var reader = await cmd.ExecuteReaderAsync();

await reader.ReadAsync();

var result = $"{reader[0]}, {reader[1]}, {reader[2]}";
//id, email, name, registered date
//1, abc@gmail.com, abc, 2023-01-01 00:00.000

[Azure SQL]Using Always Encrypted with .NET (feat. Keyvault)(1)

데이터를 저장 및 관리 하다 보면, 민감한 정보를 아무나 볼 수 없도록 식별 불가능하게 하도록 비식별화 작업을 해야 할 때가 있다.

데이터 비식별화를 하는 방법은 여러가지가 있지만, 그 중에서 데이터를 암호화하는 방법을 Azure SQL의 Always Encrypted 기능을 사용해서 구현해보려고 한다.

Always Encryted 기능은 Azure SQL에 저장된 민감한 데이터를 암호화 시킬 수 있고, 암호화에 사용된 암호화 키를 DB engine과 공유하지 않기 때문에 권한이 있는 클라이언트만 실제 데이터를 확인 할 수 있다. 그래서 권한에 따라서 데이터를 볼 수 있는 사람과 없는 사람을 명확히 구분할 수 가 있게 된다.

구성 방법

Always Encryted 기능을 사용하기 위해서는 2가지 키가 필요하다.

  1. Column master key(CMK)
    • Column encryption key들을 암호화하는데 사용된다.
    • HSM 모듈을 사용해서 생성하고 Azure Keyvault에 저장한다.
  2. Column encrytion key(CEK)
    • Table의 특정 Column data를 암호화 하거나 복호화 하는데 사용된다.
    • 각 Column 별로 독립적으로 관리된다.

Create CMK

keyvault서비스에서 Key menu에서 Generate/Import 메뉴를 클릭한다.

사용할 RSA 키 정보를 입력하고 키를 생성한다.

  • Name: always-encryted-hsm
  • Key Type: RSA
  • Key Size: 3072

SQL Server Management Studio(SSMS)를 실행시킨다.
Object Explore에서 DB Instance를 선택하고, Security 폴더로 이동한다.
Always Encryted Key 폴더 밑에 Column master key폴더를 우클릭하여 master key 추가 메뉴를 클릭한다.

key store를 auzre keyvault를 선택하면, AAD Login을 해야한다. 정상적으로 로그인 하게 되면, subscription과 keyvault 리소스를 선택할 수 있다. 위에서 만든 RSA 키 정보를 찾아서 추가해준다.

여기까지 하면 Column master key가 생성된 것을 확인 할 수 있다.

Create CEK

이제 암호화를 대상이 되는 테이블을 우클릭 하면 Column Encryption key를 추가할 수 있는 메뉴를 확인 할 수 있다.

암호화를 해야 하는 Column들을 선택하고 위에서 만들 Master key를 사용해서 Column들을 암호화 한다.

이제 Table을 조회해 보면 해당 Column(email, name)의 정보가 식별할 수 없도록 암호화된 것을 확인 할 수 있다.

이 정보를 다시 복호화 하기 위해서는 SQL master 계정이나 Keyvault에 Cryptographic Operations 권한이 있는 계정을 사용해야 하며 Connection 정보에 “Column Encryption Setting = Enabled” 옵션을 추가해서 접근을 하면 복호화된 정보를 얻을 수 있다.

Next: [Azure SQL]Using Always Encrypted with .NET (feat. Keyvault)(2)

[Databricks] Get configuration profile

Databricks CLI를 사용할 때, 인증을 갱신을 해야 할 때가 있다.

databricks configure --host "<databricks host https://>" --token

PAT(Personal Access Token)을 입력하라고 나오는데 Databricks workspace에서 발급한 user token을 입력해 주면 된다.

Input Personal aceess token

인증이 갱신 되었다면, Databricks CLI가 정상 동작 할 것이다.

databricks secrets list-scopes