해당 포스팅은 스터디를 진행하시는 최영락님의 GenAI with Inferentia & FSx Workshop 환경을 제공받아 실습한 내용을 정리하였습니다.
[기존의 ML 사용법]
베어메탈 서버에 직접 GPU 드라이버와 라이브러리를 설치하여 작업을 했다.
이런 작업 방식에는 몇 가지 문제점이 있었는데,
이러한 복잡성을 해결하기 위해 컨테이너 기술을 사용하고자 했지만, GPU와 같은 특수 하드웨어 장치에 대해서는 몇 가지 근본적인 제약이 있었다.
왜냐하면 컨테이너 기술은 주로 두 가지 핵심 Linux 커널 기능에 의존하고 있는데,
이러한 기술들은 CPU와 메모리 같은 리소스를 효과적으로 관리할 수 있게 해주지만, CPU 코어나 메모리와 달리, 초기 GPU는 물리적으로 분할하여 여러 컨테이너에 할당하기 어려웠기 때문이다 (지금은 사용 가능!)
[컨테이너 환경에서의 GPU 리소스 사용 진화 (단일 GPU)]
docker run --device=/dev/nvidia0:/dev/nvidia0 \
--device=/dev/nvidiactl:/dev/nvidiactl \
-v /usr/local/cuda:/usr/local/cuda \
tensorflow/tensorflow:latest-gpu
# Docker 19.03 이전 버전 사용
docker run --runtime=nvidia nvidia/cuda:11.0-base nvidia-smi
# Docker 19.03 이후부터는 더 간단하게 **--gpus** 플래그를 사용
docker run --gpus '"device=0,1"' nvidia/cuda:11.0-base nvidia-smi
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: gpu-container
image: nvidia/cuda:11.0-base
command: ["nvidia-smi"]
resources:
limits:
nvidia.com/gpu: 2 # 2개의 GPU 요청
[컨테이너 환경에서의 GPU 리소스 사용 진화 (멀티 GPU)]
[분산 학습에서 이루어지는 주요 통신 패턴]
[AWS EFA (Elastic Fabric Adapter)]
GPU 네트워크 병목 문제를 해결하기 위해 설계된 혁신적인 네트워크 인터페이스
(EFA를 지원하는 인스턴스 유형 https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/efa.html 참고)
[워크숍에서 배우는 내용]
[생성형 AI와 기계 학습]
[대규모 언어 모델(LLM)]
[vLLM(Virtual Large Language Model)]
[Amazon EKS에 Mistral-7B-Instruct 배포]
[추론 서비스 활용]
[모델 및 학습 데이터 저장/접근]
[컴퓨팅 가속화]
[실습]
Cloud9에서 genaifsxworkshoponeks를 클릭한다.
확인이 되었으면 아래 명령을 실행하여 랩 지역 이름을 설정한다.
TOKEN=`curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"`
export AWS_REGION=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region)
export CLUSTER_NAME=eksworkshop
kube-config 파일을 업데이트 해준 후 node를 조회해본다.
aws eks update-kubeconfig --name $CLUSTER_NAME --region $AWS_REGION
kubectl get nodes
[Explore workshop environment]
kubectl -n karpenter get deploy/karpenter -o yaml
주요 환경 변수
카펜터가 잘 설치되어 있는지 확인하기 위해 아래 커맨드를 실행한다.
kubectl get pods --namespace karpenter
[Configure storage - Host model data on Amazon FSx for Lustre]
모델 스토리지 구조
인프라 구성
[Amazon FSx for Lustre 개요]
[쿠버네티스 스토리지 개념]
1. CSI 드라이버(Container Storage Interface):
[스토리지 프로비저닝 유형]
ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
아래 명령을 복사하여 실행하여 fsx-csi-driver.json 파일을 만든다.
cat << EOF > fsx-csi-driver.json
{
"Version":"2012-10-17",
"Statement":[
{
"Effect":"Allow",
"Action":[
"iam:CreateServiceLinkedRole",
"iam:AttachRolePolicy",
"iam:PutRolePolicy"
],
"Resource":"arn:aws:iam::*:role/aws-service-role/s3.data-source.lustre.fsx.amazonaws.com/*"
},
{
"Action":"iam:CreateServiceLinkedRole",
"Effect":"Allow",
"Resource":"*",
"Condition":{
"StringLike":{
"iam:AWSServiceName":[
"fsx.amazonaws.com"
]
}
}
},
{
"Effect":"Allow",
"Action":[
"s3:ListBucket",
"fsx:CreateFileSystem",
"fsx:DeleteFileSystem",
"fsx:DescribeFileSystems",
"fsx:TagResource"
],
"Resource":[
"*"
]
}
]
}
EOF
그 이후에는 설정한 내용을 기반으로 iam policy를 생성한다.
aws iam create-policy \
--policy-name Amazon_FSx_Lustre_CSI_Driver \
--policy-document file://fsx-csi-driver.json
이후에는 아래 명령을 복사하여 실행하여 서비스 계정을 생성하고 3단계에서 생성한 IAM 정책을 첨부한다.
eksctl create iamserviceaccount \
--region $AWS_REGION \
--name fsx-csi-controller-sa \
--namespace kube-system \
--cluster $CLUSTER_NAME \
--attach-policy-arn arn:aws:iam::$ACCOUNT_ID:policy/Amazon_FSx_Lustre_CSI_Driver \
--approve
export ROLE_ARN=$(aws cloudformation describe-stacks --stack-name "eksctl-${CLUSTER_NAME}-addon-iamserviceaccount-kube-system-fsx-csi-controller-sa" --query "Stacks[0].Outputs[0].OutputValue" --region $AWS_REGION --output text)
이제 FSx for Lustre용 CSI 드라이버를 배포한다.
kubectl apply -k "github.com/kubernetes-sigs/aws-fsx-csi-driver/deploy/kubernetes/overlays/stable/?ref=release-1.2"
kubectl get pods -n kube-system -l app.kubernetes.io/name=aws-fsx-csi-driver
kubectl annotate serviceaccount -n kube-system fsx-csi-controller-sa \
eks.amazonaws.com/role-arn=$ROLE_ARN --overwrite=true
Cloud9 터미널에서 아래 명령을 실행하여 FSx Lustre 인스턴스 세부 정보(사전 생성됨)로 변수를 채운다.
cd /home/ec2-user/environment/eks/FSxL
FSXL_VOLUME_ID=$(aws fsx describe-file-systems --query 'FileSystems[].FileSystemId' --output text)
DNS_NAME=$(aws fsx describe-file-systems --query 'FileSystems[].DNSName' --output text)
MOUNT_NAME=$(aws fsx describe-file-systems --query 'FileSystems[].LustreConfiguration.MountName' --output text)
그 이후에는 PV를 만들어준다.
sed -i'' -e "s/FSXL_VOLUME_ID/$FSXL_VOLUME_ID/g" fsxL-persistent-volume.yaml
sed -i'' -e "s/DNS_NAME/$DNS_NAME/g" fsxL-persistent-volume.yaml
sed -i'' -e "s/MOUNT_NAME/$MOUNT_NAME/g" fsxL-persistent-volume.yaml
cat fsxL-persistent-volume.yaml
kubectl apply -f fsxL-persistent-volume.yaml
kubectl get pv
이어서 PVC도 만든다.
kubectl apply -f fsxL-claim.yaml
kubectl get pv,pvc
=> PersistentVolume을 성공적으로 구성하고 vLLM이 Mistral-7B 애플리케이션에 액세스하는 데 사용할 PersistentVolumeClaim을 생성을 완료했다.
콘솔에서도 생성된 File system을 확인할 수 있다.
[Deploy Generative AI Chat application]
Karpenter 구성은 NodePool 커스텀 리소스(CR)를 통해 이루어진다. 이 NodePool은 Karpenter가 생성할 수 있는 노드와 해당 노드에서 실행할 Pod에 대한 제약 조건을 설정한다. NodePool은 특정 컴퓨터 아키텍처로 노드 생성을 제한하거나 여러 아키텍처를 유연하게 사용하도록 구성할 수 있다. 단일 Karpenter NodePool은 다양한 Pod 형태를 처리할 수 있으며, Pod의 라벨이나 어피니티와 같은 속성에 기반하여 스케줄링 및 프로비저닝 결정을 내린다. 하나의 클러스터에 여러 NodePool을 구성할 수 있으며, 현재 상황에서는 추가적으로 inferentia NodePool을 선언할 예정이다. 이를 통해 Karpenter는 다양한 워크로드 요구사항에 맞게 노드를 효율적으로 관리할 수 있다.
cd /home/ec2-user/environment/eks/genai
cat inferentia_nodepool.yaml
kubectl apply -f inferentia_nodepool.yaml
kubectl get nodepool,ec2nodeclass inferentia
이번에는 Neuron device plugin & scheduler를 설치한다.
# Neuron Device plugin
kubectl apply -f https://raw.githubusercontent.com/aws-neuron/aws-neuron-sdk/master/src/k8/k8s-neuron-device-plugin-rbac.yml
kubectl apply -f https://raw.githubusercontent.com/aws-neuron/aws-neuron-sdk/master/src/k8/k8s-neuron-device-plugin.yml
# Neuron Scheduler
kubectl apply -f https://raw.githubusercontent.com/aws-neuron/aws-neuron-sdk/master/src/k8/k8s-neuron-scheduler-eks.yml
kubectl apply -f https://raw.githubusercontent.com/aws-neuron/aws-neuron-sdk/master/src/k8/my-scheduler.yml
여기까지 성공했다면 이제 모델 서빙 기능과 추론 엔드포인트를 제공할 vLLM 포드를 배포하게 된다. vLLM 포드가 온라인 상태가 되면, FSx for Lustre 기반 영구 볼륨에서 Mistral-7B 모델(29GB)을 메모리에 로드할 것이며, 그 후에 사용할 준비가 완료된다. 이 과정이 완료되면 모델 서빙을 통해 추론 서비스를 이용할 수 있게 된다.
kubectl apply -f mistral-fsxl.yaml
cat mistral-fsxl.yaml
kubectl get pod
정상적으로 배포된 것을 확인할 수 있다.
콘솔에서도 AWS Inferentia inf2.xlarge compute node가 생성된 것을 확인할 수 있다.
이제 실제로 ui에 접근하기 위해 ingress를 배포한다.
kubectl apply -f open-webui.yaml
# 1-2분 뒤에 실행하기
kubectl get ing
Mistral-7B이 보인다.
결과도 잘 나오는 것을 확인할 수 있다.
[Inspect Mistral-7B data, and share & replicate generated data assets]
이 과정에서 vLLM 포드에 로그인하여 영구 볼륨에 저장된 Mistral 모델 데이터 구조를 확인하게 된다. vLLM 포드에 접속함으로써 FSx Lustre 파일 시스템이 지원하는 영구 볼륨을 통해 S3 버킷에 저장된 모든 데이터에 접근하고 확인하는 방법을 볼 수 있으며, 이는 다른 리전의 EKS 클러스터와 모델이나 학습 데이터를 공유해야 하는 시나리오(재해 복구 또는 다른 팀의 분산 접근)에 유용하다. 실습에서는 영구 볼륨에 테스트 파일을 생성하고, 이 파일이 자동으로 S3 버킷으로 내보내지는 과정을 관찰합니다. 또한 설정할 S3 복제를 통해 테스트 파일이 다른 대상 AWS 리전(us-east-2)의 S3 버킷으로 자동 복제되는 과정도 확인하게 된다.
S3 Bucket 중에 fsx-lustre-suxxx 버킷에 ReplicationRule을 추가한다.
두 개의 S3 버킷 간에 S3 크로스 리전 복제 규칙을 성공적으로 생성했다. 다음 섹션에서는 포드에 마운트된 영구 볼륨에 테스트 데이터를 생성하고, 생성된 데이터가 원활하게 대상 S3 버킷으로 복사되는 과정을 확인한다.
cd /home/ec2-user/environment/eks/FSxL
kubectl get pods
vllm이라는 이름으로 시작하는 새로운 파드가 생겼다.
kubectl exec -it <YOUR-vLLM-POD-NAME> -- bash
cd /work-dir/
ls -ll
cd Mistral-7B-Instruct-v0.2/
ls -ll
cd /work-dir
mkdir test
cd test
cp /work-dir/Mistral-7B-Instruct-v0.2/README.md /work-dir/test/testfile
ls -ll /work-dir/test
버킷에 방금 생성한 파일이 똑같이 생성된 것을 확인할 수 있다.
다른 리전에 있는 버킷에도 자동으로 동기화가 된 것을 확인할 수 있다.
[Use Dynamic Provisioning to deploy a new PV and FSx Lustre instance for testing]
이전 모듈에서는 기존 스토리지 엔티티(관리자가 생성한)를 사용하여 정적 프로비저닝으로 영구 볼륨과 클레임을 생성하는 방법을 배웠다. 이번 섹션에서는 사용자가 CSI 드라이버와 동적 프로비저닝 기능을 사용하여 주문형 영구 볼륨과 클레임을 배포하는 방법을 배우게 된다. 이 과정에서는 백엔드에서 연결된 FSx Lustre 인스턴스도 자동으로 생성되며, 관리자의 사전 프로비저닝이 필요하지 않다. 정적 프로비저닝과 동적 프로비저닝 간의 차이점을 강조하기 위해 StorageClass, PersistentVolume 및 PersistentVolumeClaim에 대한 정의를 생성한다. 이렇게 생성된 영구 볼륨은 이번 실습 섹션에서 테스트 용도로 사용하게 된다.
VPC_ID=$(aws eks describe-cluster --name $CLUSTER_NAME --region $AWS_REGION --query "cluster.resourcesVpcConfig.vpcId" --output text)
SUBNET_ID=$(aws eks describe-cluster --name $CLUSTER_NAME --region $AWS_REGION --query "cluster.resourcesVpcConfig.subnetIds[0]" --output text)
SECURITY_GROUP_ID=$(aws ec2 describe-security-groups --filters Name=vpc-id,Values=${VPC_ID} Name=group-name,Values="FSxLSecurityGroup01" --query "SecurityGroups[*].GroupId" --output text)
echo $SUBNET_ID
echo $SECURITY_GROUP_ID
cd /home/ec2-user/environment/eks/FSxL
sed -i'' -e "s/SUBNET_ID/$SUBNET_ID/g" fsxL-storage-class.yaml
sed -i'' -e "s/SECURITY_GROUP_ID/$SECURITY_GROUP_ID/g" fsxL-storage-class.yaml
cat fsxL-storage-class.yaml
kubectl apply -f fsxL-storage-class.yaml
kubectl get sc
그 뒤에는 앞서 정의한 스토리지 클래스에 대한 영구 볼륨 클레임을 생성한다.
kubectl apply -f fsxL-dynamic-claim.yaml
kubectl describe pvc/fsx-lustre-dynamic-claim
kubectl get pvc
15분 정도 기다리면 "Pending" 상태에서 "Bound" 상태로 변경된다.
새로운 PV와 Amazon S3 버킷에 연결된 관련 FSx for Lustre 파일을 생성했다. 영구 볼륨을 위해 FSx for Lustre를 사용하는 StorageClass 정의를 생성하고, Pod가 생성된 영구 볼륨에 접근할 수 있도록 영구 볼륨 클레임을 생성했다.
[Performance testing]
aws ec2 describe-subnets --subnet-id $SUBNET_ID --region $AWS_REGION | jq .Subnets[0].AvailabilityZone
cd /home/ec2-user/environment/eks/FSxL
vi pod_performance.yaml
아래 두줄 주석을 해제한다.
kubectl apply -f pod_performance.yaml
kubectl get pods
FSx for Lustre 파일 시스템의 지연 시간을 테스트하려면 아래 IOping 명령을 실행해본다.
kubectl exec -it fsxl-performance -- bash
apt-get update
apt-get install fio ioping -y
ioping -c 20 .
mkdir -p /data/performance
cd /data/performance
fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=fiotest --filename=testfio8gb --bs=1MB --iodepth=64 --size=8G --readwrite=randrw --rwmixread=50 --numjobs=8 --group_reporting --runtime=10
FIO와 IOping 도구를 사용하여 Amazon FSx for Lustre 파일시스템의 성능 테스트를 성공적으로 완료했다.