TheKoguryo's 기술 블로그

 Version 2026-10-14

Compute VM에서 CPU 기반 AI 추론 모델 테스트

CPU 추론은 하드웨어 예산이 제한적이거나, 높은 처리량의 실시간 예측이 필요하지 않은 작은 규모 애플리케이션에서 시도해 볼 수 있는 방법입니다. 문서 검색 및 요약, 고객 지원 챗봇, 엣지 디바이스 분석 등에 시도해 볼 수 있는 방법입니다.

당연히 작은 사이즈 모델을 사용해야 겠지만, 그래도 어느 정도의 성능, 감내할 만한 응답 속도, 동시 접속 유저수 등을 확인하는 작업이 필요해 보입니다. 여기서는 x86기반 CPU에서 제공하는 AVX2/AVX-512와 같은 기술을 이용하여, OCI에서 CPU 기반 AI 추론 성능이 어느 정도 나오는 지를 확인해 보고자 합니다.

테스트할 Compute VM 준비

비교를 위해 OCI에서 8 OCPU, 64 GB 기준, Intel/AMD 기반 CPU를 사용하여 VM을 생성하였습니다.

  1. OS: Oracle Linux 9.8
  2. Shape:
    1. VM.Standard3.Flex으로 시작
    2. 성능 비교를 위해 Intel 최신 CPU(VM.Standard4.Ax.Flex), AMD CPU(VM.Standard.E6.Flex, VM.Standard.E6.Ax.Flex)는 추가로 테스트
    3. 용량: 8 OCPU(16vCPU), 64 GB
      1. m8i.4xlarge, m7i.4xlarge의 16 vCPU, 64 GiB에 맞춘 구성
      2. 테스트 후, 메모리 사용량은 확인후 비용을 고려하여 최적화할 수 있음
  3. Boot Volume: 200 GB
vLLM 설치

vLLM 설치가이드에 따라 설치합니다.

  1. 설치가이드에 따라 Python: 3.10 – 3.13 버전을 설치합니다.

    sudo dnf install python3.12
    
  2. uv 설치

    curl -LsSf https://astral.sh/uv/install.sh | sh
    
  3. Python 환경 생성

    uv venv --python 3.12 --seed --managed-python
    source .venv/bin/activate
    
  4. OS 업데이트 및 필요한 컴파일러 g++를 설치합니다.

    sudo dnf update
    sudo dnf install -y gcc-c++ numactl-devel
    g++ --version
    
  5. 설치된 glibc 버전이 2.34입니다. vllm 0.29를 설치합니다. 최신버전인 vllm 0.30은 glibc 버전 문제로 설치시 오류가 발생하였습니다.

    $ getconf GNU_LIBC_VERSION
    glibc 2.34
    
  6. 설치

    # use pip
    pip install \
      "https://github.com/vllm-project/vllm/releases/download/v0.29.0/vllm-0.29.0+cpu-cp38-abi3-manylinux_2_34_x86_64.whl" \
      --extra-index-url https://download.pytorch.org/whl/cpu
    
  7. LD_PRELOAD에 Intel OpenMP 추가: sudo find / -iname *libiomp5.so 결과값이 없어, 아래와 같이 수동 설치

    # vLLM을 설치한 동일한 venv를 활성화한 상태에서
    pip install intel-openmp
    
    IOMP_PATH=$(find "$VIRTUAL_ENV" -type f -name 'libiomp5.so' -print -quit)
    echo "$IOMP_PATH"
    
    export LD_PRELOAD="${IOMP_PATH}${LD_PRELOAD:+:$LD_PRELOAD}"
    
  8. 설치 확인

    $ vllm --version
    WARNING 09-22 06:16:52 [importing.py:45] Triton is installed, but doesn't include CPU backend. Disabling Triton.
    INFO 09-22 06:16:52 [importing.py:74] Triton is installed but 0 active driver(s) found (expected 1). Disabling Triton to prevent runtime errors.
    INFO 09-22 06:16:52 [importing.py:98] Triton not installed or not compatible; certain GPU-related functions will not be available.
    0.29.0+cpu
    
    $ python -c "import vllm, torch; print(vllm.__version__); print(torch.__version__)"
    0.29.0
    2.13.0+cpu
    
vLLM 서버 시작
  1. Hugging Face에서 Read 권한 토큰을 만든 뒤 로그인 합니다.

    hf auth login
    
  2. 추가적으로 대상 모델은 사용하기 LICENSE AGREEMENT에 대해 동의가 필요합니다. 사이트에 접속해 관련 내용을 수행합니다.

    https://huggingface.co/google/gemma-3-1b-it

  3. google/gemma-3-1b-itt 모델로 서버를 시작합니다.

    export VLLM_CPU_KVCACHE_SPACE=8
    export VLLM_CPU_NUM_OF_RESERVED_CPU=1
    
    vllm serve google/gemma-3-1b-it \
      --dtype bfloat16 \
      --max-model-len 4096
    
  4. 모델 리스트를 조회합니다.

    curl -s http://localhost:8000/v1/models | jq .
    
    • 결과 예시

      {
        "object": "list",
        "data": [
          {
            "id": "google/gemma-3-1b-it",
            "object": "model",
            "created": 1791438278,
            "owned_by": "vllm",
            "root": "google/gemma-3-1b-it",
            "parent": null,
            "max_model_len": 4096,
            "permission": [
              ...
            ]
          }
        ]
      }
      
  5. 새 터미널에서 호출이 되는 지 확인합니다.

    curl -s http://localhost:8000/v1/chat/completions \
      -H "Content-Type: application/json" \
      -d '{
        "model": "google/gemma-3-1b-it",
        "messages": [
          {"role": "user", "content": "What is the capital of South Korea?"}
        ]
      }' | jq .
    
    • 결과 예시

      {
        ...
        "model": "google/gemma-3-1b-it",
        "choices": [
          {
            "index": 0,
            "message": {
              "role": "assistant",
              "content": "The capital of South Korea is **Seoul**.\n",
              ...
            },
            ...
          }
        ],
        ...
      }
      
Advanced Vector Extensions (AVX), Advanced Matrix Extensions (AMX) 지원 여부 및 사용 여부 확인
  1. CPU flag 체크

    lscpu | grep -Ei 'avx512f|avx2|amx'
    
  2. vllm 서버 로그에서 Worker의 pid를 확인합니다. 예, 82911

    (Worker pid=82911) INFO 09-22 09:03:57 [cpu_worker.py:255] Explicitly set (8.0/62.27) GiB for KV cache on node 0.
    
  3. 새 터미널에서 다음 명령을 실행합니다.

    sudo perf stat -p <Worker_PID> \
      -e fp_arith_inst_retired.vector,\
    fp_arith_inst_retired.256b_packed_single,\
    fp_arith_inst_retired.512b_packed_single,\
    fp_arith_inst_retired.512b_packed_double \
      -- sleep 30
    
  4. 새 터미널에서 지속적으로 요청합니다.

    while true; do
      curl http://localhost:8000/v1/chat/completions \
        -H "Content-Type: application/json" \
        -d '{
          "model": "google/gemma-3-1b-it",
          "messages": [
            {"role": "user", "content": "What is the capital of South Korea?"}
          ]
        }' > /dev/null
    done  
    
  5. perf stat 다음과 같은 결과를 볼 수 있습니다.

    1. Intel CPU - AVX 지원 CPU 기준, 예, VM.Standard3.Flex(OCI X9 with Intel Xeon 3)

      $ sudo perf stat -p 10210 \
        -e fp_arith_inst_retired.vector,\
      fp_arith_inst_retired.256b_packed_single,\
      fp_arith_inst_retired.512b_packed_single,\
      fp_arith_inst_retired.512b_packed_double \
        -- sleep 30
      
       Performance counter stats for process id '10210':
      
           3,299,170,832      fp_arith_inst_retired.vector
              21,528,426      fp_arith_inst_retired.256b_packed_single
           3,277,423,632      fp_arith_inst_retired.512b_packed_single
                       0      fp_arith_inst_retired.512b_packed_double
      
            30.004479368 seconds time elapsed
      
      • 결과에서 AVX를 사용하는 것을 확인할 수 있습니다.
        카운터 0보다 크면 의미
        fp_arith_inst_retired.vector 벡터 FP 연산 사용
        fp_arith_inst_retired.256b_packed_single AVX/AVX2 256-bit FP 연산 사용
        fp_arith_inst_retired.512b_packed_single AVX-512 512-bit FP 연산 사용
        fp_arith_inst_retired.512b_packed_double AVX-512 512-bit double FP 연산 사용
    2. Intel CPU - AMX 지원 CPU 기준, 예, VM.Standard4.Ax.Flex(OCI X12 with Intel Xeon 6)

      sudo perf stat -p <Worker_PID> \
        -e exe.amx_busy,cycles,instructions \
        -- sleep 30
      
      • 결과에서 AMX를 사용하는 것을 확인할 수 있습니다.
        카운터 0보다 크면 의미
        exe.amx_busy AMX 타일 연산 장치가 실제 연산 중이었던 CPU 사이클 수
    3. AMD CPU 기준 - AVX만 지원함, 예, VM.Standard.E6.Flex, VM.Standard.E6.Ax.Flex

      $ sudo perf stat -p 6061 \
        -e fp_ops_retired_by_width.pack_128_uops_retired,\
      fp_ops_retired_by_width.pack_256_uops_retired,\
      fp_ops_retired_by_width.pack_512_uops_retired,\
      fp_ops_retired_by_width.scalar_uops_retired \
        -- sleep 30
      
       Performance counter stats for process id '6061':
      
          17,907,052,184      fp_ops_retired_by_width.pack_128_uops_retired
             571,635,321      fp_ops_retired_by_width.pack_256_uops_retired
          74,804,451,060      fp_ops_retired_by_width.pack_512_uops_retired
           7,676,075,731      fp_ops_retired_by_width.scalar_uops_retired
      
            30.002445660 seconds time elapsed
      
LLM 서빙 성능 평가

여기서는 vllm 프로젝트에서 제공하는 LLM 성능 평가 도구인 GuildeLLM을 사용하여, 성능을 평가합니다.

  1. GuideLLM을 설치합니다.

    pip install guidellm
    
  2. 벤치마크를 위해 다음 명령을 실행합니다.

    • max_length=100 조건에 맞춰 token 수를 가정
    • streams로 동시 요청수, 배치 사이즈 지정
    guidellm run \
      --backend kind=openai_http,target=http://localhost:8000 \
      --profile kind=concurrent,streams=1,warmup=5,cooldown=5 \
      --constraint kind=max_duration,seconds=120 \
      --data kind=synthetic_text,prompt_tokens=16,output_tokens=84
    
  3. 전체 결과 중 일부 항목입니다.

    • 요청에 대한 결과까지 시간(Request Latency), 첫 글자가 보이기까지 시간(TTFT), 이후 생성되는 토큰 간 간격(ITL), 출력 처리량(Output Tokens Per Sec)
    • 전체 응답(Request Latency)까지 5초 걸리며, UI가 스트리밍 모드라면, 첫 응답토큰(TTFT)을 받는 데 0.3초가 걸렸습니다.
    • VM의 고정비용이 나가지만, 일반적인 LLM 서비스에서는 비용 계산시 사용되는 항목으로 보면 Output Tokens Per Sec로 비용을 산정해 볼 수 있습니다.
    ℹ Request Latency Statistics (Completed Requests)
    |============|=========|========|=======|=======|=======|=======|======|======|======|======|
    | Benchmark  | Request Latency || TTFT         || TTFOT        || ITL        || TPOT       ||
    | Strategy   | Sec             || ms           || ms           || ms         || ms         ||
    |            | Mdn     | p95    | Mdn   | p95   | Mdn   | p95   | Mdn  | p95  | Mdn  | p95  |
    |------------|---------|--------|-------|-------|-------|-------|------|------|------|------|
    | concurrent | 5.0     | 5.0    | 305.4 | 309.9 | 305.4 | 309.9 | 56.5 | 56.9 | 59.5 | 59.9 |
    |============|=========|========|=======|=======|=======|=======|======|======|======|======|
    
    
    ℹ Server Throughput Statistics (All Requests)
    |============|=======|======|=========|==============|===============|==============|
    | Benchmark  | Requests             ||| Input Tokens | Output Tokens | Total Tokens |
    | Strategy   | Concurrency || Per Sec | Per Sec      | Per Sec       | Per Sec      |
    |            | Mdn   | Mean | Mean                                               ||||
    |------------|-------|------|---------|--------------|---------------|--------------|
    | concurrent | 1.0   | 1.0  | 0.2     | 4.8          | 16.8          | 21.6         |
    |============|=======|======|=========|==============|===============|==============|
    
LLM 서빙 성능 평가 결과

테스트 환경에 따라 결과치는 달라질수 있습니다. 단순 참조용입니다.

google/gemma-3-1b-it

  • 실요청 Input 토큰: 24
  • 실응답 Output 토큰: 84
shape streams dtype p95 Request Latency (초)
(답변 전체 시간)
p95 TTFT (ms)
(첫 글자가 보이기까지)
p95 ITL (ms)
(생성 중 토큰 간 간격)
Output Tokens / 초
(출력 처리량)
VM.Standard3.Flex 1 bfloat16(AVX) 5.0 309.9 56.9 16.8
8 17.6 1551.5 205.5 38.5
32 62.9 5272.5 744.8 44.3
VM.Standard4.Ax.Flex 1 bfloat16(AMX) 3.6 88.6 42.2 23.6
8 5.2 180.1 60.4 131.1
32 10.6 406.7 124.6 254.4
VM.Standard.E6.Flex 1 bfloat16(AVX) 2.9 50.0 34.5 29.1
8 4.7 180.8 55.6 143.1
32 10.4 599.0 122.1 258.0
VM.Standard.E6.Ax.Flex 1 bfloat16(AVX) 3.9 101.6 46.4 21.4
8 5.6 202.7 65.8 120.2
32 12.9 633.6 151.1 221.6
shape streams Output Tokens / 초
(출력 처리량)
OCPU
시간당 단가 ($)
메모리 GB
시간당 단가 ($)
8 OCPU / 64 GB
시간당 단가 ($)
$ per 1M token
1백만 토큰 당 비용($)
VM.Standard3.Flex 1 16.8 0.0400 0.0015 0.4160 6.8783
8 38.5 0.0400 0.0015 0.4160 3.0014
32 44.3 0.0400 0.0015 0.4160 2.6085
VM.Standard4.Ax.Flex 1 23.6 0.0119 0.0114 0.8248 9.7081
8 131.1 0.0119 0.0114 0.8248 1.7476
32 254.4 0.0119 0.0114 0.8248 0.9006
VM.Standard.E6.Flex 1 29.1 0.0300 0.0020 0.3680 3.5128
8 143.1 0.0300 0.0020 0.3680 0.7143
32 258.0 0.0300 0.0020 0.3680 0.3962
VM.Standard.E6.Ax.Flex 1 21.4 0.0138 0.0108 0.8016 10.4050
8 120.2 0.0138 0.0108 0.8016 1.8525
32 221.6 0.0138 0.0108 0.8016 1.0048
  • 비용 계산 - $ / 1M output tokens = (8 OCPU / 64 GB 시간당 단가) / (Output Tokens/초 * 3600초) * 1,000,000
    • 예, VM.Standard.E6.Flex, stream=32 비용 계산하면 (0.3680) / (258.0 * 3600) * 1,000,000=0.3962
Open WebUI VM 구성

https://github.com/open-webui/open-webui?tab=readme-ov-file#how-to-install-

  1. public subnet에 Compute VM을 생성합니다.

    • 이름: openwebui
    • OS: Oracle Linux 9
    • Shape: VM.Standard.E4.Flex, 1 OCPU, 16 GB
  2. Docker Engine을 설치합니다.

    sudo yum install -y yum-utils
    sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    
    sudo dnf install -y docker-ce docker-ce-cli containerd.io
    
    sudo systemctl start docker
    
    sudo usermod -aG docker $USER
    newgrp docker
    
    docker run hello-world
    
  3. vLLM이 호출되는 지 확인합니다. 네트워크에 문제가 없는 지 체크합니다.

    • 예, vLLM VM의 Private IP: 10.0.0.139
    [opc@openwebui ~]$ curl -s http://10.0.0.139:8000/v1/models | jq .data[].id
    "google/gemma-3-1b-it"
    
  4. OPENAI_API_BASE_URL에 vLLM VM의 Private IP로 업데이트하여, Open WebUI 컨테이너를 실행합니다.

    docker run -d \
      -p 3000:8080 \
      -e OPENAI_API_BASE_URL=http://10.0.0.139:8000/v1 \
      -e OPENAI_API_KEY=EMPTY \
      -v open-webui:/app/backend/data \
      --name open-webui \
      --restart always \
      ghcr.io/open-webui/open-webui:main
    
  5. OS 방화벽을 개방합니다.

    # OS 방화벽에서 Open WebUI용 포트, 3000 포트 개방
    sudo firewall-cmd --permanent --add-port=3000/tcp
    # 방화벽 변경정보 다시 반영
    sudo firewall-cmd --reload
    
  6. 속한 서브넷의 보안 규칙에서 Ingress Rule에 3000 포트를 추가합니다.

  7. 웹브라우저에서 서버로 접속합니다.

    http://{openwebui-VM-Public-IP}:3000
    
  8. 첫 접속시 화면을 따라 Admin 유저를 생성합니다.

  9. 기본 모델로 google/gemma-3-1b-it가 선택된 상태로 보입니다.

  10. 첫 질문을 하면 아래와 같이 잘 답변합니다. CPU 추론을 사용하는 것과 1b 사이즈 모델임에도 처음 말한 “문서 검색 및 요약, 고객 지원 챗봇, 엣지 디바이스 분석” 용도에 사용할 만한지 질의응답을 통해 확인해 봅니다.

    image-20261007003403911

참고



이 글은 개인으로서, 개인의 시간을 할애하여 작성된 글입니다. 글의 내용에 오류가 있을 수 있으며, 글 속의 의견은 개인적인 의견입니다.