폐쇄망 Cloudera AI Inference 에 반입한 NVIDIA NIM 임베딩 모델(llama-3.2-nv-embedqa-1b-v2)의 요청당 평균 응답 시간이 47.8초까지 늘어 클라이언트가 응답을 받기 전에 타임아웃되는 문제가 발생했다. GPU 메모리는 17%, CPU 는 21% 만 쓰고 있어 자원 부족이 아니라 구성 문제였다. 원인은 두 가지가 겹친 것으로, 범용 ONNX 프로파일이 적용된 것과 TensorRT 엔진을 MIG 슬라이스에 올린 것이다.
파드 안에서 Triton 이 어떤 백엔드로 뜨고 배치 상한이 얼마인지 확인한다.
kubectl exec -n serving-default <pod> -c kserve-container -- \
curl -s localhost:8080/v2/models/embedding_model/config | grep -A5 instance_group
kubectl exec -n serving-default <pod> -c kserve-container -- env | grep NIM_
backend: onnxruntime 으로 동작하고 있었고, 컨테이너 안 triton.yaml 에 ONNX 전용 기본값으로 배치 상한이 3 으로 고정돼 있었다. 그 결과 요청당 56.6개 항목이 19회로 분할 실행되며 큐 대기만 41.2초가 발생했다. 전체 응답 시간의 86%가 대기였다.
TensorRT 프로파일을 반입한 뒤에도 지연이 남았는데, 그 엔진이 H100 풀 GPU 1장 기준으로 빌드된 것이고 배포 대상은 MIG 슬라이스였다. MIG 프로파일이 몇 조각인지는 다음으로 본다.
nvidia-smi -L # 예: MIG 3g.20gb
nvidia-smi --query-gpu=utilization.gpu,memory.used --format=csv -l 1
3g.20gb 는 전체 SM 의 3/7 만 쓴다. 메모리뿐 아니라 연산 유닛도 분할되므로 풀 GPU 용으로 빌드된 엔진과 궁합이 맞지 않는다.
NGC 에서 H100 TensorRT 프로파일(h100x1-trt-fp16)을 폐쇄망으로 반입하고 Ozone 에 업로드한 뒤 AI Registry 로 재임포트한다. Import 화면에서 Optimization Profile 을 명시적으로 선택해야 한다. 선택을 누락하면 범용 ONNX 프로파일이 적용된다.
그다음 엔드포인트를 H100 풀 GPU 1장에 배포한다. 이 조치로 정상 속도를 확보했다.
부하 중 GPU 활용률이 판단 기준이 된다. 활용률이 낮은데 느리면 대기·재할당 오버헤드가 원인이므로 인스턴스 증설이 효과가 있고, 활용률이 100%인데 느리면 연산력 자체가 부족해 증설은 의미가 없다.
한 MIG 슬라이스 안에서 모델 인스턴스를 늘려도 연산력은 늘지 않는다. 같은 SM 을 두 인스턴스가 나눠 쓸 뿐이다. 실제로 SM 을 늘리려면 슬라이스를 키우거나, 서로 다른 슬라이스에 파드를 여러 개 띄워야 한다. 후자는 Knative 오토스케일 범위를 1-1 에서 2-2 이상으로 바꾸는 방식이다.
입력 길이를 줄이는 것은 연산력을 늘리지 않고도 처리량을 올리는 유일한 방법이다. 긴 텍스트는 배치가 2~1 로 급감해 순차 처리에 가까워지므로, 청크를 256~512 토큰으로 맞추면 실행 횟수가 크게 줄어든다.
UI 에서 NIM 튜닝용 환경변수를 넣을 수 없을 때는 ClusterServingRuntime 의 허용 목록에 추가한다. 추가 최적화 성격이며 클러스터 스코프라 다른 엔드포인트에도 영향을 준다.
kubectl get clusterservingruntime
kubectl get clusterservingruntime <name> -o yaml | grep -A20 'annotations:'
kubectl get clusterservingruntime <name> \
-o jsonpath='{.metadata.annotations.allowed-environment-variables}' | tee ~/original-allowed-env.txt
kubectl get clusterservingruntime <name> -o yaml > ~/csr-backup-$(date +%F).yaml
기존 값을 덮어쓰지 않도록 원본 뒤에 이어 붙인다.
ORIG=$(cat ~/original-allowed-env.txt)
NEW="${ORIG},NIM_NUM_MODEL_INSTANCES,NIM_TRITON_MAX_BATCH_SIZE,NIM_NUM_TOKENIZERS,NIM_MODEL_PROFILE"
kubectl patch clusterservingruntime <name> --type=json \
-p="[{\"op\":\"replace\",\"path\":\"/metadata/annotations/allowed-environment-variables\",\"value\":\"${NEW}\"}]"
패치는 허용일 뿐이므로 값은 따로 주입한다. UI 재배포 시 드롭다운에 나타나면 거기서 넣고, 안 나오면 InferenceService 에 직접 넣는다.
env:
- name: NIM_NUM_MODEL_INSTANCES
value: "2"
kubectl exec -n serving-default <new-pod> -c kserve-container -- env | grep NIM_
kubectl exec -n serving-default <new-pod> -c kserve-container -- \
curl -s localhost:8080/v2/models/embedding_model/config | grep -A5 instance_group
count 가 실제로 바뀌었는지가 확인 지점이다. 환경변수는 들어갔는데 config 가 그대로면 그 변수가 해당 NIM 에서 동작하지 않는다는 뜻이다. 롤백은 저장해 둔 원본으로 되돌린다.
kubectl patch clusterservingruntime <name> --type=json \
-p="[{\"op\":\"replace\",\"path\":\"/metadata/annotations/allowed-environment-variables\",\"value\":\"$(cat ~/original-allowed-env.txt)\"}]"
replace 가 실패한다. 그때는 add 로 바꾼다.~1 로 이스케이프한다 — /metadata/annotations/cloudera.com~1allowed-environment-variables.