26O07f

QK·5일 전

현재 구조라면 AIStor를 GPU Pod에 filesystem처럼 직접 mount해서 모델을 실행하기보다, AIStor = 원본 저장소, GPU node NVMe = 실행용 local cache로 명확히 분리하는 것을 가장 추천합니다.

핵심 data path는 이것입니다.

Storage Cluster                         GPU Cluster
┌──────────────────┐                  ┌─────────────────────────────┐
│ MinIO AIStor     │   S3 / 400GbE   │ GPU Node                   │
│                  │ =================>│                             │
│ Model Repository │                  │ Local NVMe RAID0 + XFS     │
│ Dataset          │                  │ /model-cache               │
│ Checkpoint       │                  │       │                     │
│ Artifact         │                  │       ▼                     │
└──────────────────┘                  │     RAM                     │
                                      │       │                     │
                                      │       ▼                     │
                                      │  B300 HBM / vLLM           │
                                      └─────────────────────────────┘

          AIStor = Source of Truth
          NVMe   = Disposable Local Cache

왜 이 방식이 좋은가

vLLM이 뜰 때마다 AIStor에서 직접 weight를 읽는 구조라면 재시작/전환 때마다 Storage network 성능과 AIStor 상태가 model startup의 critical path가 됩니다. 반대로 local NVMe에 모델을 캐시해 두면 주/야간 전환 시 대부분 NVMe → RAM → HBM만 거치면 됩니다.

또한 GPU node 장애 시 local NVMe 데이터가 사라져도 괜찮습니다. 새 node에서 AIStor로부터 다시 구성하면 됩니다.

따라서 AIStor는 durable tier, NVMe는 performance tier입니다.


1. 연결은 S3 API를 기본으로

GPU cluster에서 AIStor까지는 S3 endpoint를 하나 고정해서 사용하는 게 좋습니다.

GPU Cluster
    │
    │ S3 HTTPS
    │ high-speed Ethernet
    ▼
s3.aistor.internal
    │
    ▼
AIStor Service
    │
    ├── model-prod
    ├── datasets
    ├── checkpoints
    └── artifacts

예를 들어 bucket을 단순히 하나 크게 쓰기보다 역할별로 나눕니다.

model-prod/

  glm-5.3/
    v1/
      manifest.json
      ...
    v2/
      manifest.json
      ...

  glm-5.3-flash/
  deepseek-v4.1-flash/
  qwen3.6-35b-a3b/
  gpt-oss-20b/


datasets/

checkpoints/

artifacts/

모델은 특히 mutable한 latest에 의존하지 않고 version/hash를 고정하는 것을 추천합니다.


2. GPU Node에는 /model-cache

앞서 이야기한 8개 NVMe라면 우선:

NVMe0 ─┐
NVMe1 ─┤
NVMe2 ─┤
NVMe3 ─┤
NVMe4 ─┼── RAID0 ── XFS ── /model-cache
NVMe5 ─┤
NVMe6 ─┤
NVMe7 ─┘

로 구성합니다.

그리고 Kubernetes에서 해당 디렉터리를 사용할 수 있게 합니다.

가장 단순하게는:

volumes:
  - name: model-cache
    hostPath:
      path: /model-cache
      type: Directory

containers:
  - name: vllm
    volumeMounts:
      - name: model-cache
        mountPath: /models
        readOnly: true

vLLM에서는:

/models/glm-5.3/<version>/

을 읽습니다.

vLLM Pod 자체가 S3에서 매번 다운로드하도록 만들지 않는 것이 포인트입니다.


3. 별도의 Model Cache/Prefetch Job을 둠

구조를 이렇게 분리합니다.

                    AIStor
                       │
                       │ S3
                       ▼
              Model Prefetch Job
                       │
                  download
                       │
                  hash verify
                       │
                       ▼
              /model-cache
                       │
                  read-only
                       │
                       ▼
                     vLLM
                       │
                       ▼
                     B300

Prefetch Job 역할은 딱 네 가지면 충분합니다.

1. AIStor manifest 조회
2. local version/hash 확인
3. 필요한 파일만 download
4. checksum 검증 후 READY 표시

예를 들어:

/model-cache/

  glm-5.3/
    sha256-abc123/
       config.json
       tokenizer...
       *.safetensors
       .READY

  glm-5.3-flash/
    sha256-def456/
       ...
       .READY

.READY는 모든 파일의 hash 검증이 끝난 뒤 마지막에 생성합니다.

그러면 vLLM startup script는 .READY 없는 모델은 절대로 사용하지 않게 하면 됩니다.


4. 주/야간 전환과 아주 잘 맞음

현재 요구사항에서는 이 방식의 효과가 큽니다.

예를 들어 야간 전환이 22시라면:

20:30

AIStor
   │
   │ S3
   ▼
GPU Node NVMe

GLM-5.3        download/check
Qwen           download/check
gpt-oss        download/check

       ↓

21:30

/model-cache
 DAY   ✓
 NIGHT ✓

DAY vLLM은 계속 서비스 중

22시가 되면:

DAY
vLLM
 │
 ├─ drain
 └─ stop
      │
      ▼
GPU free
      │
      ▼
NIGHT vLLM
      │
      │ /model-cache에서 load
      ▼
HBM
      │
   warm-up
      │
      ▼
Service

가 됩니다.

따라서 AIStor→NVMe 전송시간은 서비스 중단시간에서 빠집니다.


5. GPU node가 여러 개면 "어느 node에 cache할 것인가"가 중요

예를 들어:

AIStor
   │
   ├────────── GPU01 /model-cache
   ├────────── GPU02 /model-cache
   ├────────── GPU03 /model-cache
   └────────── GPU04 /model-cache

모든 모델을 모든 node에 복제할 필요는 없습니다.

ServingProfile과 연계해서:

GPU01
  DeepSeek
  GLM-Flash
  GLM

GPU02
  DeepSeek
  GLM-Flash
  Qwen

GPU03
  GLM
  Qwen
  gpt-oss

처럼 배치할 수도 있습니다.

다만 현재처럼 Day/Night workload가 명확하고 NVMe 용량이 충분하다면 저는 가능한 한 각 serving node에 DAY+NIGHT 모델을 모두 캐시하는 쪽을 먼저 검토하겠습니다.

운영이 훨씬 단순해집니다.


6. AIStor credential은 Pod YAML에 직접 넣지 않음

예를 들어 이런 건 피합니다.

env:
  - name: AWS_ACCESS_KEY_ID
    value: "AKIA...."
  - name: AWS_SECRET_ACCESS_KEY
    value: "xxxx"

최소한 Kubernetes Secret을 사용합니다.

Secret
  │
  ├─ S3 endpoint
  ├─ access key
  ├─ secret key
  └─ CA
       │
       ▼
Model Prefetch Job

그리고 credential 권한도 분리합니다.

vLLM
    S3 credential 없음

Model Prefetch
    model-prod : READ

Training/Checkpoint writer
    checkpoints : READ/WRITE

이 구조가 좋습니다.

즉 serving vLLM 자체에는 가능하면 AIStor credential조차 주지 않습니다.


7. NetworkPolicy도 이 역할에 맞춰 분리

예를 들어:

Model Prefetch Pod
       │
       │ TCP/443
       ▼
AIStor
       ✓


vLLM Pod ───────────X────> AIStor

Other Pod ──────────X────> AIStor

처럼 만들 수 있습니다.

Training Pod은 별도 policy로 checkpoint bucket 접근을 허용합니다.

                  AIStor

model-prod        datasets       checkpoints
    ▲                ▲               ▲
    │ R              │ R             │ R/W
    │                │               │
Prefetch         Training         Training

이렇게 하면 credential과 network authorization이 같이 맞물립니다.


8. ClusterMesh와 AIStor data path는 구분해서 생각

현재 Compute/GPU/Storage를 ClusterMesh로 연결한다 하더라도 저는 다음처럼 생각하는 것을 추천합니다.

Control / Service Connectivity
──────────────────────────────
Cilium
ClusterMesh
Service discovery
NetworkPolicy


Bulk Data Path
──────────────────────────────
GPU node
   │
   │ high-speed Ethernet
   │ S3/HTTPS
   ▼
AIStor

즉 ClusterMesh가 모델 데이터를 운반하는 storage 기술인 것은 아닙니다.

물론 실제 packet은 Cilium networking/routing을 이용할 수 있지만, AIStor 모델 전송 성능을 설계할 때는 별도의:

GPU NIC
   ↓
Leaf
   ↓
ECMP routed fabric
   ↓
Storage Leaf
   ↓
AIStor NIC

경로의 bandwidth/MTU/ECMP/failure를 검증해야 합니다.

NDR InfiniBand는 기본적으로 이 S3 모델 전송 경로에 넣지 않고:

B300 ↔ B300
NCCL / RDMA

용도로 분리하는 것을 우선 권합니다.


9. 특히 여러 GPU node가 동시에 AIStor를 때리는 상황을 테스트

평상시보다 아침/저녁 prefetch 시점이 더 위험합니다.

예를 들어 GPU node 8대가 동시에:

             AIStor
               ▲
        ┌──────┼──────┐
        │      │      │
      GPU01  GPU02  GPU03 ...
        │      │      │
      100GB  100GB  100GB

를 받으면 AIStor와 Ethernet에 순간적인 부하가 몰립니다.

따라서 production에서는 약간 stagger할 수도 있습니다.

20:20 GPU01 prefetch
20:22 GPU02
20:24 GPU03
20:26 GPU04
...

혹은 concurrency/bandwidth를 제한합니다.

Stage7 후반부에 단일 node download 성능뿐 아니라 N개 GPU node 동시 prefetch 성능도 측정하는 게 좋습니다.


10. Checkpoint는 반대 방향

Serving model은:

AIStor
   ↓
NVMe
   ↓
HBM

이지만 학습/튜닝 checkpoint는 반대입니다.

GPU/HBM
   ↓
Local NVMe
   ↓
async upload
   ↓
AIStor

가능하면 checkpoint를 매번 synchronous하게 AIStor에 쓰면서 GPU를 기다리게 하지 않고, local NVMe에 먼저 안전하게 생성한 후 uploader가 AIStor로 올리는 구조를 검토합니다.

완료 후:

checkpoint-001/
checkpoint-002/
checkpoint-003/
manifest.json

처럼 version 관리합니다.


11. CSI mount는?

AIStor를 CSI/POSIX 형태로:

vLLM
  │
  ▼
/models
  │
 CSI
  │
  ▼
AIStor

처럼 직접 mount하고 싶을 수 있는데, 현재 vLLM model serving의 기본 경로로는 제가 우선 선택하지 않겠습니다.

현재 요구사항에는:

AIStor → Local NVMe → vLLM

이 더 단순하고, 성능 특성이 예측 가능하며, AIStor/network 장애가 이미 running 중인 vLLM에 미치는 영향도 작습니다.

CSI/POSIX mount가 유용한 workload가 별도로 있다면 그때 사용하면 됩니다.


결국 가장 추천하는 구조

              STORAGE CLUSTER
┌───────────────────────────────────┐
│             AIStor                │
│                                   │
│ models        Source of Truth     │
│ datasets                          │
│ checkpoints                       │
│ artifacts                         │
└───────────────┬───────────────────┘
                │
                │ S3 / HTTPS
                │ High-speed Ethernet
                │
════════════════╪════════════════════════
                │
                ▼
              GPU CLUSTER

┌───────────────────────────────────────┐
│ GPU Node                              │
│                                       │
│ Model Prefetch Job                    │
│       │                               │
│       ├─ version check                │
│       ├─ download                     │
│       └─ SHA256 verify                │
│              │                        │
│              ▼                        │
│     8 × NVMe RAID0 / XFS              │
│          /model-cache                 │
│              │                        │
│              │ read-only              │
│              ▼                        │
│            vLLM                       │
│              │                        │
│              ▼                        │
│          B300 HBM                     │
└───────────────────────────────────────┘

그리고 운영 자동화는:

AIStor Model Repository
          │
          ▼
   Prefetch CronJob
          │
          ▼
   /model-cache READY
          │
          ▼
    Switch Workflow
          │
     ┌────┴─────┐
     │          │
 old drain    scale 0
                │
             GPU free
                │
             scale 1
                │
          NVMe → HBM
                │
             warmup
                │
              OPEN

으로 연결하면 됩니다.

현재 환경에서는 이 방식이 AIStor와 B300 GPU cluster 사이의 가장 단순하면서도 production으로 확장하기 좋은 출발점이라고 봅니다. 특히 다음 Stage7에 AIStor→NVMe cold prefetch, NVMe→vLLM Ready, 동시 N-node prefetch, cache hit/miss, AIStor/network 장애 상태에서 기존 vLLM 서비스 영향까지 넣어두면, 앞에서 이야기한 주/야간 자동전환 시간을 꽤 정확하게 산출할 수 있습니다.

===

네. 추론에서는 model-cache 외 사용처가 많지 않은 반면, 학습에서는 local NVMe의 역할이 훨씬 커집니다. 그래서 8개 NVMe를 전부 하나의 /model-cache라고 보기보다는 물리적으로는 하나의 고속 local pool을 만들고 그 안을 용도별 디렉터리로 나누는 것을 추천합니다.

8 × NVMe
   │
   └── RAID0 + XFS
          │
          ├── /local/model-cache
          ├── /local/dataset-cache
          ├── /local/checkpoints
          ├── /local/scratch
          └── /local/tmp

중요한 원칙은 여기에 있는 데이터는 모두 node가 죽으면 없어져도 복구 가능해야 한다는 것입니다. 영구 보관은 AIStor가 담당합니다.

추론(Inference)에서는

가장 큰 용도는 역시 ① model weight cache입니다.

AIStor
  ↓
/local/model-cache
  ↓
RAM
  ↓
GPU HBM

여기에 model weight뿐 아니라 같은 버전의 실행 artifact를 묶어두는 게 좋습니다.

/local/model-cache/
└── glm-5.3/
    └── <model-hash>/
        ├── *.safetensors
        ├── config.json
        ├── tokenizer.*
        ├── generation_config.json
        └── .READY

두 번째는 ② 모델 변환/컴파일 cache입니다. 사용하는 runtime과 기능에 따라 CUDA/Triton/torch compile 계열의 cache가 생길 수 있습니다.

/local/runtime-cache/
├── torch/
├── triton/
└── vllm/

이걸 container filesystem에만 두면 Pod 재생성 때 다시 만들어질 수 있으므로, 실제 테스트에서 startup 시간이 의미 있게 줄어든다면 local NVMe에 persist시키는 것이 좋습니다. 단, runtime/driver/GPU architecture가 바뀌었을 때 stale cache를 재사용하지 않도록 versioning이 필요합니다.

세 번째 ③ /tmp / scratch도 있습니다. 모델 load/변환, preprocessing 등의 임시파일이 container root filesystem을 채우지 않게:

/local/tmp/<pod-id>/

같은 node-local 영역을 줄 수 있습니다.

반면 일반적인 vLLM의 KV cache를 local NVMe에 기본적으로 두는 것은 권하지 않습니다.

             Inference

Model       AIStor → NVMe → HBM

KV cache             HBM     ← 기본
                       │
                 필요시 MemKV

KV cache             NVMe     ← 기본 설계로는 X

KV는 latency에 매우 민감하기 때문입니다. NVMe KV/offload는 HBM 부족이나 특수한 long-context 요구가 있고 실제 benchmark에서 이득이 확인된 경우에만 별도 선택지로 보는 게 좋습니다.

따라서 추론 node는 대략:

/local/
├── model-cache/       ★★★★★
├── runtime-cache/     ★★~★★★
├── scratch/           ★
└── tmp/               ★

KV cache               → HBM
Persistent data        → AIStor

정도로 생각하면 됩니다.


학습에서는 훨씬 많습니다

학습 시 가장 중요한 추가 용도는 ① Dataset cache/staging입니다.

AIStor
   │
   │ training 시작 전
   ▼
Local NVMe
/local/dataset-cache
   │
   ▼
CPU/RAM
   │
   ▼
GPU

매 epoch마다 AIStor에서 dataset을 반복해서 읽는 것보다 학습 시작 전에 필요한 shard를 local NVMe로 staging하는 것이 좋습니다.

예를 들어:

/local/dataset-cache/
├── dataset-A/
│   └── version-202610/
│       ├── shard-00001
│       ├── shard-00002
│       └── ...
└── dataset-B/

대규모 dataset이면 모든 node가 전체 dataset을 복제할 필요도 없습니다. Distributed training에서 node별 shard를 나눠 staging할 수 있습니다.

AIStor Dataset

shard 000~099 → GPU01 NVMe
shard 100~199 → GPU02 NVMe
shard 200~299 → GPU03 NVMe
shard 300~399 → GPU04 NVMe

다만 training framework의 data sharding 방식과 맞춰야 합니다.


② Checkpoint staging

학습에서는 매우 중요합니다.

나쁜 형태는 매 checkpoint마다 GPU 학습 경로가 AIStor upload 완료를 기다리는 것입니다.

가능하면:

Training
   │
   ▼
Local NVMe
/local/checkpoints
   │
   │ async
   ▼
AIStor

로 가져갑니다.

예를 들어:

step 10000
    │
    ▼
local checkpoint write
    │
    ├─────────── Training 계속
    │
    ▼
background uploader
    │
    ▼
AIStor

입니다.

단, 여기서 주의할 게 있습니다.

local write가 끝났다고 checkpoint가 안전한 것은 아닙니다.

GPU node 자체가 죽으면 NVMe checkpoint도 사라지기 때문입니다.

그래서 상태을 구분합니다.

WRITING
   ↓
LOCAL_COMPLETE
   ↓
UPLOADING
   ↓
AISTOR_COMPLETE
   ↓
VERIFIED

AISTOR_COMPLETE/VERIFIED가 되어야 durable checkpoint로 취급합니다.


③ Training scratch

학습 중에는 preprocessing이나 데이터 변환으로 임시 공간이 상당히 필요할 수 있습니다.

/local/scratch/

에:

dataset preprocessing
tokenization output
shuffle files
temporary shards
intermediate artifacts
packing output

등을 둘 수 있습니다.

특히 대규모 dataset을:

Raw data
   ↓
tokenization
   ↓
packing
   ↓
training shards

로 변환한다면 local NVMe가 상당히 유용합니다.


④ Model/cache

학습에서도 base model을 당연히 cache할 수 있습니다.

AIStor
   ↓
/local/model-cache
   ↓
training

LoRA/QLoRA/fine-tuning이라면 base model을 매 작업마다 AIStor에서 다시 가져올 필요가 없습니다.

따라서 동일 node에서 반복적으로:

GLM base
  +
Dataset A
    ↓
LoRA #1

GLM base
  +
Dataset B
    ↓
LoRA #2

를 한다면 base model은 계속 local cache에 남겨둡니다.


⑤ 결과 artifact staging

학습 결과도 바로 AIStor에 넣기 전에 local에서 생성/검증할 수 있습니다.

/local/artifacts/
    │
    ├── adapter
    ├── final weights
    ├── tokenizer
    ├── config
    └── manifest
          │
          ▼
       validation
          │
          ▼
        AIStor

다만 이것 역시 최종 source of truth는 AIStor입니다.


그래서 추론과 학습을 비교하면

Local NVMe 용도추론학습
Model weight cache필수급필수급
Dataset cache거의 없음매우 중요
Checkpoint staging없음매우 중요
Training scratch없음중요
Preprocessing/temp소량중요
Runtime/compile cache유용유용
Final artifact staging거의 없음유용
KV cache기본 HBM해당 없음
영구 저장XX

그러면 8개 NVMe를 4+4 등으로 물리적으로 나눠야 하나?

현재 단계에서는 그렇게 하지 않는 것을 추천합니다.

예를 들어:

NVMe 0~3 → model
NVMe 4~5 → dataset
NVMe 6~7 → checkpoint

처럼 고정하면 workload가 바뀔 때 capacity가 낭비됩니다.

예를 들어 추론 시:

model       5 TB 필요
dataset     0 TB
checkpoint  0 TB

인데 model partition이 4 TB면 전체 NVMe 여유 공간이 있어도 못 씁니다.

반대로 학습에서는:

model       1 TB
dataset     8 TB
checkpoint  3 TB
scratch     2 TB

일 수도 있습니다.

그래서 저는 우선:

NVMe × 8
    │
    ▼
하나의 고속 pool
RAID0 + XFS
    │
    ▼
/local
 ├── model-cache
 ├── dataset-cache
 ├── checkpoints
 ├── artifacts
 ├── runtime-cache
 ├── scratch
 └── tmp

로 가져가겠습니다.

그 위에서 directory별 quota/cleanup policy를 관리하는 편이 훨씬 유연합니다.


대신 eviction 우선순위를 정해야 합니다

용량이 부족할 때 아무거나 지우면 안 됩니다.

예를 들어:

높은 보존 우선순위

1. 현재 실행 중인 model
2. 다음 시간대에 사용할 model
3. 진행 중 training checkpoint
4. 현재 training dataset
5. 자주 사용하는 model
6. runtime cache
7. 오래된 dataset
8. scratch/tmp

낮은 보존 우선순위

같은 정책을 둡니다.

특히 주/야간 전환이 있으므로:

20:00

/local/model-cache

DAY models       PINNED
NIGHT models     PINNED

old model v1     evictable
old dataset      evictable
scratch          evictable

처럼 현재 + 다음 ServingSet은 eviction 금지시키는 게 좋습니다.


학습 checkpoint에는 한 가지 예외가 있습니다

8개를 전부 RAID0으로 만들면 NVMe 하나만 장애가 나도 local pool 전체가 깨질 수 있습니다.

추론 model-cache는 문제 없습니다.

RAID0 장애
   ↓
cache 유실
   ↓
AIStor에서 재구성

하면 됩니다.

Dataset cache도 마찬가지입니다.

하지만 아직 AIStor에 upload되지 않은 checkpoint는 손실될 수 있습니다.

따라서 학습에서는:

Local NVMe
     │
     │ 최대한 빠르게 async upload
     ▼
AIStor

를 강하게 적용해야 합니다.

checkpoint durability가 특히 중요하고 checkpoint가 매우 크다면 나중에는 checkpoint용 storage 전략을 별도로 benchmark할 가치가 있습니다. 단순히 RAID1을 하면 용량과 write bandwidth를 희생하므로 처음부터 분리할 필요는 없습니다.


Kubernetes 쪽도 하나의 /local을 제공하면 관리하기 편합니다

Node:

/local

Pod에는 필요한 것만 mount합니다.

Inference Pod:

/model-cache     ← /local/model-cache
/runtime-cache   ← /local/runtime-cache
/tmp             ← /local/tmp

Training Pod:

/model-cache     ← /local/model-cache
/dataset-cache   ← /local/dataset-cache
/checkpoints     ← /local/checkpoints
/scratch         ← /local/scratch

그리고 vLLM Pod는 /model-cache를 가능하면 read-only로, 다운로드/삭제는 별도의 cache manager/prefetch Job이 담당하게 하는 게 좋습니다.


현재 B300 노드라면 제가 잡을 baseline

                    AIStor
          Source of Truth / Durable
                       │
        ┌──────────────┼──────────────┐
        │              │              │
      Models        Datasets      Checkpoints
        │              │              ▲
        ▼              ▼              │ async
────────────────────────────────────────────────
                  GPU NODE
────────────────────────────────────────────────

            8 × Local NVMe
                  RAID0
                    │
                   XFS
                    │
                 /local
                    │
       ┌────────────┼─────────────┐
       │            │             │
 model-cache   dataset-cache  checkpoints
       │            │             │
       │         Training      Training
       │
       ├──── Inference
       └──── Training

       + scratch
       + runtime-cache
       + tmp

────────────────────────────────────────────────

Inference:
NVMe model-cache → RAM → B300 HBM
                              │
                           KV cache

Training:
NVMe model/dataset → RAM → B300
                              │
                        checkpoint
                              ↓
                         Local NVMe
                              ↓
                        async AIStor

따라서 8 NVMe를 “model-cache disk”라고 정의하기보다 GPU Node Local Data Tier라고 정의하는 게 더 정확합니다. 추론에서는 대부분 model cache가 차지하지만, 학습이 시작되면 dataset-cache + checkpoint staging + scratch가 상당한 비중을 차지하게 됩니다.

그리고 Stage7 다음에 학습 테스트까지 계획한다면, NVMe 용량을 얼마나 model/dataset/checkpoint에 할당할지는 미리 고정하지 말고 실제 모델 크기 × DAY/NIGHT 보관 세트 + 학습 dataset working set + checkpoint 크기 × local 보관 개수 + 20~30% 여유를 측정한 뒤 quota를 정하는 것이 가장 좋습니다.

profile
engineer

0개의 댓글