CPU
CPU (Central Processing Unit)
이 페이지는 워크로드를 배치하고 실행하기 위해 Nomad가 노드의 CPU 리소스를 어떻게 발견하고 사용하는지에 대한 개념 정보를 제공해요.
출처: 문서
본문
모든 Nomad 노드에는 운영체제 프로세스를 실행하는 데 필요한 컴퓨팅 성능을 제공하는 중앙 처리 장치(CPU)가 있어요. Nomad는 Nomad 작업 제출자가 정의한 태스크를 실행하기 위해 CPU를 사용해요. 어느 노드가 주어진 태스크를 실행할 충분한 용량을 가졌는지 Nomad가 알기 위해, 클러스터의 각 노드는 CPU의 성능 특성에 대한 정보를 수집하도록 지문화(fingerprint)돼요. CPU 성능과 관련해 각 Nomad 노드와 연관된 두 가지 메트릭은 대역폭(얼마나 계산할 수 있는지)과 코어 수예요.
현대 CPU는 이기종(heterogeneous) 코어 유형을 포함할 수 있어요. Apple은 2020년에 성능(P-Core)과 효율(E-Core) 유형을 모두 포함하는 M1 CPU를 출시했어요. 각 코어 유형은 서로 다른 기본 주파수에서 작동해요. Intel은 2022년 Raptor Lake 칩에서 유사한 토폴로지를 도입했어요. CPU 특성을 지문화할 때 Nomad는 이러한 고급 CPU 토폴로지를 고려할 수 있어요.
CPU 리소스 계산 (Calculating CPU resources)
Nomad 노드의 총 CPU 대역폭은 각 코어 유형의 주파수와 CPU에서 해당 유형의 총 코어 수의 곱의 합이에요.
bandwidth = (p_cores * p_frequency) + (e_cores * e_frequency)
총 코어 수는 P-Core 수와 E-Core 수를 더해 계산돼요.
cores = p_cores + e_cores
Nomad는 논리 코어와 물리 코어를 구분하지 않아요. P-Core와 E-Core 유형을 구분하는 결정적 차이 중 하나는 E-Core는 하이퍼스레딩을 지원하지 않는 반면 P-Core는 지원한다는 점이에요. 따라서 단일 물리 P-Core는 2개의 논리 코어로 표시되고, 단일 E-Core는 1개의 논리 코어로 표시돼요.
아래 예시는 Intel i9-13900 CPU가 있는 Nomad 노드에서 가져온 것이에요. P-Core 기본 주파수 2 GHz와 E-Core 기본 주파수 1.5 GHz의 혼합 코어 유형으로 구성돼 있어요.
이러한 특성은 각각 cpu.frequency.performance와 cpu.frequency.efficiency 노드 속성에 반영돼요.
cpu.arch = amd64
cpu.frequency.efficiency = 1500
cpu.frequency.performance = 2000
cpu.modelname = 13th Gen Intel(R) Core(TM) i9-13900
cpu.numcores = 32
cpu.numcores.efficiency = 16
cpu.numcores.performance = 16
cpu.reservablecores = 32
cpu.totalcompute = 56000
cpu.usablecompute = 56000
CPU 리소스 예약 (Reserving CPU resources)
지문화된 노드 속성에서 cpu.totalcompute는 프로세서가 제공할 수 있는 총 CPU 대역폭을 나타내요. 어떤 경우에는 운영체제와 다른 비-Nomad 프로세스가 사용하도록 노드의 CPU 리소스 일부를 예약하는 것이 유용할 수 있어요. 이것은 클라이언트 구성에서 수행할 수 있어요.
예약된 CPU 양은 cpu를 통해 대역폭으로 지정할 수 있어요.
client {
reserved {
cpu = 3000 # mhz
}
}
또는 Nomad 태스크의 스케줄링을 허용하지 않을 특정 코어 집합으로 지정할 수 있어요. 이 기능은 Linux 시스템에서만 사용할 수 있어요.
client {
reserved {
cores = "0-3"
}
}
CPU가 위 구성 중 하나로 제약되면 노드 속성 cpu.usablecompute는 Nomad 태스크 스케줄링에 사용할 수 있는 총 CPU 대역폭을 나타내요.
CPU 리소스 할당 (Allocating CPU Resources)
작업을 스케줄링할 때 태스크는 자신을 위해 얼마나 많은 CPU 리소스를 할당해야 하는지 지정해야 해요. 이것은 cpu 속성을 사용해 MHz 단위의 대역폭으로 할 수 있어요. cgroups v1 아래의 Linux에서 Nomad는 이 MHz 값을 cpu.share로 직접 매핑해요. cgroups v2 아래의 Linux에서 Nomad는 MHz 값을 cgroups v1 cpu.share와 같은 비율의 cpu.weight로 변환해요.
task {
resources {
cpu = 2000 # mhz
}
}
CPU 리소스 주변의 격리 메커니즘은 각 태스크 드라이버와 그 구성에 달려 있다는 점을 참고해 주세요. 표준 동작은 Nomad가 태스크에 할당된 CPU 대역폭 중 최소한 그만큼에 접근할 수 있도록 보장하는 것이에요. 이 경우 노드에 유휴 CPU 용량이 있으면 태스크가 추가 CPU 리소스를 사용할 수 있어요. 일부 태스크 드라이버는 아래의 "CPU 하드 리미트" 섹션에서 설명하는 대로 태스크가 할당된 대역폭만 사용하도록 제한하는 것을 활성화해요.
Linux에서의 상대적 CPU 공유/가중치 (Relative CPU shares/weights on Linux)
Linux cgroups는 계층적이며, cpu.share / cpu.weight 값은 주어진 하위 트리 내의 상대적 가중치를 반영해요. Nomad는 시작 시 자체 cgroup 하위 트리( nomad.slice )를 만들고, Nomad가 기록하는 모든 cpu.share / cpu.weight 값은 해당 슬라이스 내 프로세스 간의 상대적 값이에요. nomad.slice 하위 트리 자체는 호스트의 다른 하위 트리를 기준으로 해요. 예를 들어 systemd를 실행하는 호스트에는 다음 슬라이스가 있을 수 있어요.
/sys/fs/cgroup
├── nomad.slice
│ ├── reserve.slice
│ └── share.slice
│ ├── 912dcc05-61e1-53cb-5489-a976a1231960.task.scope
│ ├── 247e706a-6df8-4123-89b3-1bcf2846b503.task.scope
│ └── 586c0c58-3d50-4730-b4ad-022076d3c6a4.task.scope
├── system.slice
│ ├── journald.service
│ └── (various system services, etc.)
└── user-1000.slice
├── session-1.scope
└── (various user services, etc.)
태스크 912dcc05가 resources.cpu = 1024이고 태스크 247e706a와 586c0c58이 resources.cpu = 512라면, 912dcc05는 nomad.slice에 사용 가능한 CPU 리소스의 50%를, 247e706a와 586c0c58은 각각 25%를 얻어요. (여기서 reserve.slice와 share.slice는 cpu share용 통과(passthrough)예요.) 하지만 nomad.slice, system.slice, user-1000.slice가 기본 1024 공유가 아닌 다른 값을 갖지 않는 한, 함께 호스트 총 CPU 리소스의 33%를 얻어요. 1024 값은 Nomad 슬라이스의 맥락에서만 의미가 있어요.
코어 할당 (Allocating cores)
Linux 시스템에서 Nomad는 태스크를 위해 전체 CPU 코어를 예약하는 것을 지원해요. 다른 태스크를 위해 예약된 CPU 코어에서는 어떤 태스크도 실행될 수 없어요.
task {
resources {
cores = 4
}
}
높은 CPU 성능을 요구하는 태스크에 대해서는 resources.cores를 사용해 그 태스크가 CPU 대역폭에 독점적으로 접근하도록 하는 것을 권장해요. 같은 할당의 사이드카 태스크는 resources.cpu를 사용해 노드의 나머지 CPU를 비례적으로 공유할 수 있어요.
Nomad Enterprise는 NUMA 인지 스케줄링을 지원하며, 이를 통해 운영자가 태스크에 예약될 수 있는 CPU 코어를 더 세밀하게 제어할 수 있어요.
CPU 하드 리미트 (CPU hard limits)
일부 태스크 드라이버는 cpu_hard_limit 구성 옵션을 지원해요. 활성화되면 이 옵션은 노드에 유휴 용량이 있더라도 태스크가 CPU 한도를 넘어 버스트(burst)하는 것을 제한해요. 트레이드오프는 일관성 대 활용도예요. CPU 리소스가 너무 적은 태스크는 다른 태스크가 노드에 배치되어 사용 가능한 CPU 대역폭이 줄어들 때까지는 정상 작동할 수 있으며, 이는 과소 프로비저닝된 태스크에 중단을 일으킬 수 있어요.
CPU 환경 변수 (CPU environment variables)
태스크가 사용 가능한 리소스를 이해하도록 돕기 위해, Nomad는 런타임 환경에 다음 환경 변수를 설정해요.
NOMAD_CPU_LIMIT— 태스크를 대신해 할당된 CPU 대역폭의 양.NOMAD_CPU_CORES— 태스크를 위해 예약된 cpuset 표기법의 코어 집합. 이 값은 resources.cores가 구성된 경우에만 설정돼요.
NOMAD_CPU_CORES=3-5
NOMAD_CPU_LIMIT=9000
NUMA
Nomad 클라이언트는 일반적으로 온프레미스 환경의 실제 하드웨어나 클라우드의 대형 .metal 인스턴스 유형에 프로비저닝돼요. 어느 경우든 기본 서버는 비균일 메모리 접근(NUMA) 토폴로지를 중심으로 설계되어 있을 가능성이 높아요. 여러 CPU 소켓이나 CPU 소켓당 여러 RAM 뱅크를 포함하는 서버는 시스템 메모리 접근에 관련된 비균일 접근 시간이 특징이에요.
위의 단순화된 예시 머신은 다음 토폴로지를 가져요.
- 2개의 물리 CPU 소켓
- 4개의 시스템 메모리 뱅크 (소켓당 2개)
- 8개의 물리 CPU 코어 (소켓당 4개)
- 물리 코어당 2개의 논리 CPU 코어
- 4개의 PCI 장치 (메모리 뱅크당 1개)
성능 최적화 (Optimizing performance)
운영체제 프로세스는 NUMA 경계를 넘어 메모리에 접근하는 데 더 오래 걸려요.
위 예시를 사용하면 태스크가 Core 0에 스케줄링된 경우 Mem 1의 메모리에 접근하는 것이 Mem 0의 메모리에 접근하는 것보다 20% 더 오래 걸리고, Mem 2의 메모리에 접근하는 것은 300% 더 오래 걸릴 수 있어요.
극단적인 차이는 다양한 물리 하드웨어 제한 때문이에요. 자체 NUMA 노드의 메모리에 접근하는 코어가 최적이에요. 시스템 메모리에 대한 높은 처리량의 읽기 또는 쓰기를 수행하는 프로그램은 시스템의 NUMA 토폴로지에 대한 공간적 지역성(spatial locality)을 최적화하지 않으면 성능이 상당히 저하돼요.
SLIT 테이블 (SLIT tables)
현대 머신은 펌웨어에 System Locality Distance Information (SLIT) 테이블을 정의해요. Linux 커널이 이 테이블을 이해하고 참조할 수 있게 해줘요. SLIT 테이블이 제공하는 두 가지 핵심 정보가 있어요.
- 어떤 CPU 코어가 어떤 NUMA 노드에 속하는지
- 코어가 다른 모든 NUMA 노드의 각 NUMA 노드에 접근할 때 발생하는 패널티
lscpu 명령어를 사용해 머신의 코어 연관성을 설명할 수 있어요. 예를 들어 r6a.metal EC2 인스턴스에서:
$ lscpu | grep NUMA
NUMA node(s): 4
NUMA node0 CPU(s): 0-23,96-119
NUMA node1 CPU(s): 24-47,120-143
NUMA node2 CPU(s): 48-71,144-167
NUMA node3 CPU(s): 72-95,168-191
그리고 관련 성능 저하는 numactl을 통해 확인할 수 있어요:
$ numactl -H
available: 4 nodes (0-3)
...
node distances:
node 0 1 2 3
0: 10 12 32 32
1: 12 10 32 32
2: 32 32 10 12
3: 32 32 12 10
이 SLIT 테이블 노드 거리 값은 대략적인 상대 비율로 표시돼요. 값 10은 같은 NUMA 노드의 일부인 CPU에서 메모리 접근이 발생하는 최적 상황을 나타내요. 값 20이면 200% 성능 저하, 30이면 300% 등을 나타내요.
노드 속성 (Node Attributes)
Nomad 클라이언트는 머신의 NUMA 토폴로지를 지문화하고 코어 연관성을 노드 속성으로 내보내요. 이 데이터는 Nomad 운영자에게 특정 워크로드에 NUMA 인지 스케줄링을 사용하는 것이 언제 유용한지 더 잘 이해할 수 있게 해줘요.
numa.node.count = 4
numa.node0.cores = 0-23,96-119
numa.node1.cores = 24-47,120-143
numa.node2.cores = 48-71,144-167
numa.node3.cores = 72-95,168-191
NUMA 인지 스케줄링 (NUMA aware scheduling) — Enterprise
Nomad Enterprise는 클라이언트 노드의 NUMA 토폴로지에 최적화된 방식으로 태스크를 스케줄링할 수 있어요. Nomad는 CPU 코어를 메모리 노드와 연관시키고 태스크가 특정 CPU 코어에서 실행되도록 할당해 교차 메모리 노드 접근 패턴을 최소화할 수 있어요. 또한 Nomad는 장치를 메모리 노드와 연관시키고 스케줄링 결정을 내릴 때 장치 연관성을 고려하도록 NUMA 인지 스케줄링을 활성화할 수 있어요.
태스크는 NUMA 최적화 선호도를 나타내는 numa 블록을 지정할 수 있어요. 이 예시는 1080ti GPU를 할당하고 태스크를 위해 예약된 4개의 CPU 코어와 같은 NUMA 노드에 있도록 보장해요.
task {
resources {
cores = 4
memory = 2048
device "nvidia/gpu/1080ti" {
count = 1
}
numa {
affinity = "require"
devices = [
"nvidia/gpu/1080ti"
]
}
}
}
affinity 옵션 (affinity options)
이것은 필수 필드예요. 지원되는 세 가지 affinity 옵션인 none, prefer, require가 있으며, 각각 고유한 장점과 트레이드오프가 있어요.
none 옵션 (option none)
none 모드에서 Nomad 스케줄러는 NUMA affinity 선호도가 없는 작업의 무관심(apathy)을 활용해 NUMA 노드 내 코어 단편화를 줄이는 데 도움을 줘요. 이러한 작업의 코어 요청을 사용 가능한 미사용 코어가 가장 적은 NUMA 노드에 빈 패킹함으로써 수행해요.
none 모드는 numa 블록이 지정되지 않았을 때의 기본 모드예요.
resources {
cores = 4
numa {
affinity = "none"
}
}
prefer 옵션 (option prefer)
prefer 모드에서 Nomad 스케줄러는 노드의 하드웨어 토폴로지를 사용해 사용 가능한 코어의 최적화된 선택을 계산하지만, 해당 코어가 단일 NUMA 노드에서 나와야 한다고 제한하지는 않아요.
resources {
cores = 4
numa {
affinity = "prefer"
}
}
require 옵션 (option require)
require 모드에서 Nomad 스케줄러는 각 잠재적 클라이언트의 토폴로지를 사용해 같은 NUMA 노드에 속하는 사용 가능한 CPU 코어 집합을 찾아요. 그러한 코어 집합을 찾을 수 없으면 해당 노드는 numa-cores 리소스에 대해 고갈된 것으로 표시돼요.
resources {
cores = 4
numa {
affinity = "require"
}
}
devices 옵션 (devices options)
devices는 할당된 CPU 코어와 함께 NUMA 노드에 함께 배치되어야 하는 선택적 장치 목록이에요.
다음 다이어그램은 장치 집합이 CPU와 메모리와 어떻게 연관될 수 있는지 보여줘요.
이 예시는 세 개의 장치를 선언하고 numa 블록에 두 개를 구성해요.
task {
resources {
cores = 8
memory = 16384
device "nvidia/gpu/H100" {
count = 2
}
device "intel/net/XXVDA2" {
count = 1
}
device "xilinx/fpga/X7" {
count = 1
}
numa {
affinity = "require"
devices = [
"nvidia/gpu/H100",
"intel/net/XXVDA2"
]
}
}
}
가상 CPU 지문화 (Virtual CPU fingerprinting)
Amazon EC2 같은 가상화된 호스트에서 실행할 때 Nomad는 dmidecode 도구를 사용해 CPU 성능 데이터를 감지해요. 일부 Linux 배포판에서는 dmidecode 패키지를 수동으로 설치해야 할 수 있어요.