Furiosa-LLM 프로파일링 방법

:warning: 이 튜토리얼은 2025.3 릴리즈를 기준으로 작성되었으며, 개발이 아직 이루어지고 있어 이후 릴리즈에서 기능이 변경될 수 있습니다.

History

  • 2025-09-26: 2025.3 릴리즈 기준으로 Furiosa-LLM 프로파일링 방법 최초 버전 포스트

furiosa-llm은 모델 서빙의 성능을 분석할 수 있는 프로파일러를 제공합니다. 프로파일링을 활성화한 상태에서 furiosa-llm serve를 실행하면 지정한 경로에 Chrome trace event format 형태를 가진 프로파일 데이터 기록이 시작됩니다. furiosa-llm serve 인스턴스가 종료되면 프로파일 이벤트가 파일에 기록되고, 기록된 파일은 Perfetto 또는 Chrome에 내장된 The Trace Event Profiling Tool 등 Chrome trace event format을 지원하는 도구를 이용해 시각화할 수 있습니다.

프로파일링 활성화 설정

프로파일링 활성화는 환경 변수를 사용해 이루어집니다. 우선 실행 예를 하나 보겠습니다.

TUC_PROFILE_LEVEL=info \
FURIOSA_TELEMETRY_TARGET="telemetry_core,telemetry_sprinter" \
FURIOSA_PROFILER_OUTPUT_PATH=./profile.json \
furiosa-llm serve furiosa-ai/Llama-3.1-8B-Instruct-FP8

위 예제는 Llama-3.1-8B-Instruct-FP8 에 대한 furiosa-llm 을 시작하고 core, sprinter (runtime) 를 대상으로 프로파일링을 수행하고 그 결과를 ./profile.json에 저장합니다.

사용된 환경 변수에 대한 설명은 다음과 같습니다.

  • FURIOSA_PROFILER_OUTPUT_PATH: (필수): 프로파일된 데이터를 저장할 파일의 경로를 지정합니다. 이 환경 변수의 지정 여부가 프로파일링 활성화를 결정합니다. 지정할 경우에만 프로파일링이 활성화 됩니다.
  • FURIOSA_TELEMETRY_TARGET (옵션): 프로파일링 할 대상 컴포넌트를 지정할 수 있습니다. 다음과 같은 카테고리를 지원합니다:
    • telemetry_core : furiosa-llm의 실행 양상을 파악하는데 도움이 되는 필수적인 프로파일 데이터입니다. FURIOSA_TELEMETRY_TARGET 환경 변수를 설정하지 않고 프로파일링을 진행할 경우 기본값으로 수집되는 프로파일링 카테고리입니다.
    • telemetry_sprinter: 장치에서 실행되는 테스크의 실행 시간, IO, P2P communication을 타겟으로 합니다.
    • telemetry_scheduler: 추론 요청 스케쥴러의 실행 성능을 프로파일링 대상으로 지정합니다.
    • telemetry_frontend: Python frontend 의 실행 성능을 프로파일링 대상으로 지정합니다.
  • TUC_PROFILE_LEVEL (옵션): 각 디바이스 내에서 실행되는 NPU 태스크 실행 내에 발생하는 세부적인 실행에 대한 프로파일 정보의 수집 여부를 결정하고 수집 레벨을 지정합니다. TUC_PROFILE_LEVEL을 설정하지 않은 경우 이 데이터는 수집되지 않습니다.
    • warn, info 로 수집하는 정보의 상세함을 지정할 수 있습니다.

프로파일링 결과 분석 및 시각화

이 예시에서는 다음과 같은 명령어를 사용해 furiosa-llm 을 통해 inference를 진행할 때 발생하는 프로파일 데이터를 Google Chrome 브라우져의 tracing viewer 기능을 사용하여 이용해 분석하는 방법을 소개합니다.

# Launch a furiosa-llm server instance
TUC_PROFILE_LEVEL=info \
FURIOSA_TELEMETRY_TARGET="telemetry_core,telemetry_sprinter" \
FURIOSA_PROFILER_OUTPUT_PATH=./profile.json \
furiosa-llm serve furiosa-ai/EXAONE-3.5-7.8B-Instruct

# Run a vLLM benchmark
# (git clone -b furiosa-benchmark-custom https://github.com/furiosa-ai/vllm)
python benchmarks/benchmark_serving.py --backend vllm \
              --model furiosa-ai/EXAONE-3.5-7.8B-Instruct \
              --dataset-name fixed \
              --random-input-len 10000 \
              --random-output-len 1500 \
              --num-prompts 8

위 커맨드 실행 후에 로컬 경로에 ./profile.json 파일이 저장됩니다. Google Chrome 브라우져에서 chrome://tracing 주소를 통해 The Trace Event Profiling Tool를 열고 프로파일 데이터를 로드하면 다음과 같은 화면이 나타납니다.

프로파일 화면에 나타나는 항목들은 크게 호스트와 디바이스 실행 정보로 분류할 수 있습니다.

  • 호스트: 입력을 받아 스케쥴링을 진행하고, decoding strategy 실행 등 호스트에서 실행된 프로파일링 결과를 표현합니다.
  • 디바이스: 호스트에서 스케쥴링한 inference task를 실행한 디바이스와 task를 실행하는데 걸린 시간을 나타냅니다. 프로파일 데이터에서 디바이스는 npuXpeY-Z 와 같은 형식으로 표현됩니다.
    • X: Chip ID
    • Y-Z: Fusion이 이루어진 PE ID 범위

Device Profile 분석

다음은 Device Profile을 더 자세히 보기 위해 위 프로파일 데이터 중 일부를 확대한 화면입니다.

Device 프로파일링 결과는 NPU program 단위로 기록되며 각 항목은 다음과 같은 이름을 갖습니다.

(Phase) [(Batch Size), (Attention Size)] (Layer Index):(Sequence Index)

각 요소가 의미하는 바는 다음과 같습니다.

  • Phase: Prefill 또는 Decode 중 해당 span이 어떤 단계를 실행하면서 측정된 것인지 나타냅니다.
  • Batch size: 실행된 task가 속한 bucket의 batch size를 나타냅니다.
  • Attention size: 실행된 task가 속한 bucket의 KV-cache 크기와 최대 input length의 합을 나타냅니다.
  • Layer Index: 해당 span이 어떤 유형의 NPU program block의 실행을 나타내는지 표기합니다.
    • 0: Input embedding과 첫 번째 Transformer block으로 구성된 NPU program
    • 1: 실행한 모델의 두 번째 ~ N-1번째 Transformer block으로 구성된 NPU program
    • 2: 마지막 (N번째) Transformer block과 LMHead가 포함된 NPU program
    • Sequence Index: 한 layer에서 실행되는 EDF의 sequence number를 의미합니다.
      • 선택된 NPU program이 여러 개의 분리된 NPU task로 구성된 경우 여러 일련의 NPU program이 실행되고 이 경우 sequence가 부여됩니다.

Bucket 별 실행 시간 프로파일링

NPU program의 실행 시간만 측정하려면 진행하려면 EDF 프로파일러를 사용하는 것이 좋습니다. EDF 프로파일러는 furiosa-llm serve 의 실행 시간동안 발생한 디바이스 span의 실행 정보만 취합해 CSV 형태로 저장합니다. 이 방법은 특정 batch size + sequence length 조합에 대한 실행 시간을 분석하는데 용이합니다.
다음 명령어는 EDF 프로파일러를 활성화해 furiosa-llm을 실행하는 예시입니다:

TUC_PROFILE_LEVEL=info \
EDF_PROFILER_OUTPUT_PATH=./edf_profile.csv \
furiosa-llm serve furiosa-ai/EXAONE-3.5-7.8B-Instruct

:warning: FURIOSA_PROFILER_OUTPUT_PATHEDF_PROFILER_OUTPUT_PATH 는 동시에 사용할 수 없습니다. 상황에 따라 의도에 맞는 것을 선택하여 사용하시길 바랍니다.)

EDF 프로파일러 또한 환경 변수를 통해 프로파일링에 필요한 설정을 진행할 수 있습니다:

  • EDF_PROFILER_OUTPUT_PATH (필수): 프로파일된 데이터를 저장할 파일의 이름입니다. 해당 환경 변수를 설정하지 않으면 EDF 프로파일링 데이터를 수집하지 않습니다.
  • TUC_PROFILE_LEVEL (필수): 호스트로부터 전달 받은 추론 태스크를 각 디바이스에서 실행할 때 발생하는 프로파일 정보의 수집 여부를 결정합니다. EDF 프로파일러는 디바이서에서 각 task가 실행된 정보를 수집하기 때문에 이 환경변수가 info , 혹은 그보다 더 verbose하게 설정되어 있어야 정상적인 데이터 수집이 이루어집니다.

Bucket별 실행 시간 분석

이 예시에서는 다음과 같은 명령어를 사용해 furiosa-llm 을 통해 inference를 진행할 때 발생하는 EDF 프로파일 데이터를 분석하는 방법을 소개합니다.

# Launch a furiosa-llm server with EDF_PROFILER_OUTPUT_PATH
TUC_PROFILE_LEVEL=info \
EDF_PROFILER_OUTPUT_PATH=./edf_profile.csv \
furiosa-llm serve furiosa-ai/Llama-3.1-8B-Instruct-FP8

# (git clone -b furiosa-benchmark-custom https://github.com/furiosa-ai/vllm)
python benchmarks/benchmark_serving.py --backend vllm \
              --model furiosa-ai/EXAONE-3.5-7.8B-Instruct \
              --dataset-name fixed \
              --random-input-len 10000 \
              --random-output-len 1500 \
              --num-prompts 8

EDF 프로파일러는 CSV 형태로 다음 컬럼을 저장해 기록합니다.

  • device : 해당 Task를 실행한 디바이스의 index입니다. 해당 index는 다음과 같은 방식으로 계산됩니다:
    • 100_000_000 + (Chip ID) * 100 + (Leader PE ID)
    • e.g., 100000000 → Chip ID 0, Leader PE 0
  • name: 실행된 NPU Program의 정보를 나타냅니다.
    • e.g., (Phase) [(Batch Size), (Attention Size)] (Layer Index):(Sequence Index)
  • cycle: 해당 task를 실행하는데 소요된 device cycle입니다. RNGD의 Performance Governor를 사용할 경우 2GHz cycle로 동작합니다.

생성된 CSV 파일은 Python Pands 등 CSV 파일을 입력으로 받아드리는 분석 도구를 이용해 분석할 수 있습니다. 다음 예제는 쉘에서 xan 이라는 커맨드 도구를 사용해 Cycle count의 평균과 최대를 계산하는 간단히 예시 스크립트입니다:

#!/bin/bash

# This script shows an example of getting statistics on EDF run data.
# You can use `xan` as shown below, or you can use `pandas` and so on.

if [[ $# -lt 1 ]]; then
  echo "Usage: $0 <edf_profile_file>"
  exit 1
fi

if ! command -v xan >/dev/null 2>&1; then
  cargo install xan
fi

xan groupby name,prompt_sp,decode_sp 'count(cycle) as cnt, mean(cycle) as avg, max(cycle) as max' $1 \
    | xan sort -s name,prompt_sp,decode_sp \
    | xan view -I -A -M

해당 스크립트를 실행한 결과는 다음과 같습니다.

┌────────────────────────┬───────────┬───────────┬──────┬────────────────────┬──────────┐
│ name                   │ prompt_sp │ decode_sp │ cnt  │ avg                │ max      │
├────────────────────────┼───────────┼───────────┼──────┼────────────────────┼──────────┤
│ Decode [1, 16384] 0:0  │ 0.00      │ 0.61      │ 152  │ 898361.1447368418  │ 941055   │
│ Decode [1, 16384] 0:0  │ 0.00      │ 0.62      │ 328  │ 898584.2835365854  │ 971744   │
│ Decode [1, 16384] 0:0  │ 0.00      │ 0.63      │ 328  │ 898972.6859756102  │ 959864   │
│ Decode [1, 16384] 0:0  │ 0.00      │ 0.64      │ 328  │ 899285.4939024391  │ 983935   │
│ Decode [1, 16384] 0:0  │ 0.00      │ 0.65      │ 326  │ 900632.9631901842  │ 1021701  │
│ Decode [1, 16384] 0:0  │ 0.00      │ 0.66      │ 328  │ 901386.3810975607  │ 943417   │
│ Decode [1, 16384] 0:0  │ 0.00      │ 0.67      │ 328  │ 902787.4786585369  │ 1001846  │
│ Decode [1, 16384] 0:0  │ 0.00      │ 0.68      │ 328  │ 903258.3536585371  │ 932557   │
│ Decode [1, 16384] 0:0  │ 0.00      │ 0.69      │ 328  │ 904406.3140243904  │ 964146   │
│ Decode [1, 16384] 0:0  │ 0.00      │ 0.70      │ 224  │ 904981.267857143   │ 937159   │
│ Decode [1, 16384] 1:0  │ 0.00      │ 0.61      │ 4560 │ 897147.6054824557  │ 916562   │
│ Decode [1, 16384] 1:0  │ 0.00      │ 0.62      │ 9840 │ 896993.1955284568  │ 915991   │
│ Decode [1, 16384] 1:0  │ 0.00      │ 0.63      │ 9840 │ 897856.3871951244  │ 917612   │
│ Decode [1, 16384] 1:0  │ 0.00      │ 0.64      │ 9840 │ 898423.4407520319  │ 917887   │
│ Decode [1, 16384] 1:0  │ 0.00      │ 0.65      │ 9780 │ 898777.0583844597  │ 917950   │
│ Decode [1, 16384] 1:0  │ 0.00      │ 0.66      │ 9840 │ 899444.7690040682  │ 919592   │
│ Decode [1, 16384] 1:0  │ 0.00      │ 0.67      │ 9840 │ 900215.6548780447  │ 917908   │
│ Decode [1, 16384] 1:0  │ 0.00      │ 0.68      │ 9840 │ 900993.0184959286  │ 920694   │
│ Decode [1, 16384] 1:0  │ 0.00      │ 0.69      │ 9840 │ 901851.6648374     │ 919151   │
│ Decode [1, 16384] 1:0  │ 0.00      │ 0.70      │ 6720 │ 902478.2671130981  │ 919235   │
│ Decode [1, 16384] 2:0  │ 0.00      │ 0.61      │ 152  │ 2248608.3618421033 │ 2278420  │
│ Decode [1, 16384] 2:0  │ 0.00      │ 0.62      │ 328  │ 2250213.332317073  │ 2279018  │
│ Decode [1, 16384] 2:0  │ 0.00      │ 0.63      │ 328  │ 2251546.268292683  │ 2276505  │
│ Decode [1, 16384] 2:0  │ 0.00      │ 0.64      │ 328  │ 2252229.914634147  │ 2280433  │
│ Decode [1, 16384] 2:0  │ 0.00      │ 0.65      │ 326  │ 2249870.14417178   │ 2279897  │
│ Decode [1, 16384] 2:0  │ 0.00      │ 0.66      │ 328  │ 2252790.996951219  │ 2281114  │
│ Decode [1, 16384] 2:0  │ 0.00      │ 0.67      │ 328  │ 2253029.545731709  │ 2279892  │
│ Decode [1, 16384] 2:0  │ 0.00      │ 0.68      │ 328  │ 2253913.18902439   │ 2284363  │
│ Decode [1, 16384] 2:0  │ 0.00      │ 0.69      │ 328  │ 2253957.076219513  │ 2283831  │
│ Decode [1, 16384] 2:0  │ 0.00      │ 0.70      │ 224  │ 2254564.7276785737 │ 2283008  │
│ Prefill [1, 16384] 0:0 │ 0.00      │ 1.00      │ 2    │ 20124129           │ 20145776 │
│ Prefill [1, 16384] 0:1 │ 0.00      │ 1.00      │ 2    │ 20392593           │ 20393343 │
│ Prefill [1, 16384] 0:2 │ 0.00      │ 1.00      │ 2    │ 29480613           │ 29510547 │
│ Prefill [1, 16384] 1:0 │ 0.00      │ 1.00      │ 60   │ 18365659.333333336 │ 18382028 │
│ Prefill [1, 16384] 1:1 │ 0.00      │ 1.00      │ 60   │ 20926287.116666663 │ 20938677 │
│ Prefill [1, 16384] 1:2 │ 0.00      │ 1.00      │ 60   │ 28895398.233333334 │ 28963754 │
│ Prefill [1, 16384] 2:0 │ 0.00      │ 1.00      │ 2    │ 3839080.5          │ 3839789  │
│ Prefill [1, 8192] 0:0  │ 0.00      │ 0.00      │ 2    │ 28873106.5         │ 28881649 │
│ Prefill [1, 8192] 0:1  │ 0.00      │ 0.00      │ 2    │ 21103218           │ 21103803 │
│ Prefill [1, 8192] 1:0  │ 0.00      │ 0.00      │ 60   │ 26978629.500000007 │ 27065857 │
│ Prefill [1, 8192] 1:1  │ 0.00      │ 0.00      │ 60   │ 21765353.133333333 │ 21771134 │
│ Prefill [1, 8192] 2:0  │ 0.00      │ 0.00      │ 2    │ 3364823            │ 3392578  │
├────────────────────────┼───────────┼───────────┼──────┼────────────────────┼──────────┤
│ name                   │ prompt_sp │ decode_sp │ cnt  │ avg                │ max      │
└────────────────────────┴───────────┴───────────┴──────┴────────────────────┴──────────┘
2 Likes

기다리고 있었습니다!
성능 최적화에 큰 도음이 될 것 같습니다.

1 Like

혹시 prompt_sp와 decode_sp column은 무엇을 의미하는지 알 수 있을까요?

안녕하세요? 답장이 늦었습니다.

decode_sp 는 위와 같은 정의를 가지고 있습니다. furiosa-llm에서는 증가하는 sequence length나 다양한 batch 를 다루기 위해 bucketization 을 사용합니다. 다시 말해, 메뉴얼한 설정 또는 자동으로 설정되는 batch, sequence 축에 대한 shape들을 기반으로 컴파일 해서 동작합니다. 이런 경우 shape 보다 실제 입력이 작은 경우가 생길 수 있는데요. decode에서 batch, kv cache index 축을 2차원 공간으로 보았을 때 얼마나 많은 past_kv_cache index가 채워진채 실행되었는지를 그 비율을 표현합니다.

prefill_sp는 현재는 사용되지 않는 값이라 무시하셔도 좋을 것 같습니다.