AWS Trainium & Inferentia 文件
引言
並獲得增強的文件體驗
開始使用
簡介
在當今世界,幾乎每位 AI 工程師都熟悉透過簡單的 API 呼叫來執行推理,但後端是如何以最優方式處理這些請求的呢?您所使用的模型提供商或服務是如何確保滿足延遲和吞吐量要求的呢?
在這篇部落格中,我們將介紹如何使用 Optimum Neuron 在 AWS Inferentia2 上透過 HuggingFace TGI 容器來部署模型。我還會深入探討如何最佳化延遲和吞吐量,以及我們可以做出哪些決策來影響我們的優先順序。
瞭解工具
- Inferentia2 晶片:Inferentia2 是 AWS 專為機器學習推理打造的第二代加速器。
- Optimum Neuron:連線 🤗 Transformers 庫與 AWS 加速器(包括 AWS Trainium 和 AWS Inferentia)的介面。
- 文字生成推理 (TGI) 容器:文字生成推理 (TGI) 是一個用於部署和提供大型語言模型 (LLM) 服務的工具包。
- GuideLLM:一個用於評估和最佳化大型語言模型 (LLM) 部署的工具。
本次實驗我使用的例項是 inf2.48xlarge
。我可以執行 neuron-ls
來檢查例項型別並檢視每個裝置,該命令會給出以下輸出:
instance-type: inf2.48xlarge
+--------+--------+--------+-----------+---------+
| NEURON | NEURON | NEURON | CONNECTED | PCI |
| DEVICE | CORES | MEMORY | DEVICES | BDF |
+--------+--------+--------+-----------+---------+
| 0 | 2 | 32 GB | 11, 1 | 80:1e.0 |
| 1 | 2 | 32 GB | 0, 2 | 90:1e.0 |
| 2 | 2 | 32 GB | 1, 3 | 80:1d.0 |
| 3 | 2 | 32 GB | 2, 4 | 90:1f.0 |
| 4 | 2 | 32 GB | 3, 5 | 80:1f.0 |
| 5 | 2 | 32 GB | 4, 6 | 90:1d.0 |
| 6 | 2 | 32 GB | 5, 7 | 20:1e.0 |
| 7 | 2 | 32 GB | 6, 8 | 20:1f.0 |
| 8 | 2 | 32 GB | 7, 9 | 10:1e.0 |
| 9 | 2 | 32 GB | 8, 10 | 10:1f.0 |
| 10 | 2 | 32 GB | 9, 11 | 10:1d.0 |
| 11 | 2 | 32 GB | 10, 0 | 20:1d.0 |
+--------+--------+--------+-----------+---------+
設定和安裝
首先,我執行以下命令來安裝必要的依賴項,並拉取編譯模型以及為基準測試提供已編譯模型服務所需的容器。
!pip install hftransfer guidellm==0.1.0
!git clone https://github.com/huggingface/optimum-neuron.git
!docker pull ghcr.io/huggingface/text-generation-inference:latest-neuron
根據模型的不同,可選擇性地配置你的 HF_TOKEN,如下所示:
!export HF_TOKEN=你的HF_TOKEN
模型編譯與部署
在我的用例中,我需要用獨特的特定引數來編譯模型。值得一提的是,編譯並非總是必需的。例如,如果已經快取的配置對我有效,optimum 會預設使用它。
摘自文件:“Neuron 模型快取是一個遠端快取,用於儲存 neff
格式的已編譯 Neuron 模型。它已整合到 NeuronTrainer
和 NeuronModelForCausalLM
類中,以便從快取載入預訓練模型,而不是在本地進行編譯。”
現在我使用以下命令編譯我選擇的模型 meta-llama-3.1-8b-instruct
:
!docker run -p 8080:80 -e HF_TOKEN=YOUR_TOKEN \
-v $(pwd):/data \
--device=/dev/neuron0 \
--device=/dev/neuron1 \
--device=/dev/neuron2 \
--device=/dev/neuron3 \
--device=/dev/neuron4 \
--device=/dev/neuron5 \
--device=/dev/neuron6 \
--device=/dev/neuron7 \
--device=/dev/neuron8 \
--device=/dev/neuron9 \
--device=/dev/neuron10 \
--device=/dev/neuron11 \
-ti \
--entrypoint "optimum-cli" ghcr.io/huggingface/text-generation-inference:latest-neuron \
export neuron --model "meta-llama/Meta-Llama-3.1-8B-Instruct" \
--sequence_length 16512 \
--batch_size 8 \
--num_cores 8 \
/data/exportedmodel/
請注意,在我的用例中,我決定使用大小為 8 的批次大小(batch size)和 8 的張量並行度(tensor parallel degree)。由於一個 inf2.48xlarge 例項有 24 個核心,我可以使用 3 的資料並行度(data parallel),這意味著我將在該例項上擁有 3 個模型的副本。`
最佳化批次大小以實現最大吞吐量
當為了成本效益而最佳化硬體利用率時,特別是對於按需每小時 12.98 美元的 inf2.48xlarge 例項,屋頂線模型(roofline model)是一個有價值的框架。
屋頂線模型定義了理論效能的邊界。在一個極端,受記憶體限制的工作負載受限於記憶體容量,需要頻繁的讀/寫操作。在另一個極端,受計算限制的工作負載充分利用加速器的計算能力,最大化裝置上的資料處理。批次大小是控制這種平衡的關鍵槓桿。較大的批次大小傾向於將工作負載推向計算密集型,而較小的批次大小可能導致更多的記憶體密集型操作。儘管如此,最大化批次大小並不總是可行的。在指定的延遲預算(我們希望返回響應所花費的時間)內,牢記最大批次大小至關重要。這最直接地由批次大小控制。有關此主題的更多資訊,請檢視此資源: https://awsdocs-neuron.readthedocs-hosted.com/en/latest/general/arch/neuron-features/neuroncore-batching.html
建立用於服務的檔案
需要幾個檔案來確保我們的配置設定正確,並確保使用的是我編譯的模型而不是快取的配置。
首先,我需要建立我的 .env 檔案,其中指定了我的批次大小、精度等。需要注意的是,由於我編譯了我的模型,我需要將 model_id 從通常的 Hugging Face 倉庫識別符號更改為我在編譯命令中指定的容器卷位置。
MODEL_ID='/data/exportedmodel'
HF_AUTO_CAST_TYPE='bf16'
MAX_BATCH_SIZE=8
MAX_INPUT_TOKENS=16000
MAX_TOTAL_TOKENS=16512
接下來,我根據我期望的設定建立 benchmark.sh 指令碼。
#!/bin/bash
model=${1:-meta-llama/Meta-Llama-3.1-8B-Instruct}
date_str=$(date '+%Y-%m-%d-%H-%M-%S')
output_path="${model//\//_}#${date_str}_guidellm_report.json"
export HF_TOKEN=YOUR_TOKEN
export GUIDELLM__NUM_SWEEP_PROFILES=1
export GUIDELLM__MAX_CONCURRENCY=128
export GUIDELLM__REQUEST_TIMEOUT=60
guidellm \
--target "https://:8080/v1" \
--model ${model} \
--data-type emulated \
--data "prompt_tokens=15900,prompt_tokens_variance=100,generated_tokens=450,generated_tokens_variance=50" \
--output-path ${output_path} \
請注意透過 --data
標誌傳遞的引數。由於我的用例是長提示和長生成,我已經相應地設定了 `prompt_tokens` 和 `generated_tokens`。請記住根據你的用例和你預期的輸入/輸出詞元負載來設定這些值。根據這些數字,GuideLLM 將生成大小在 15900 個詞元左右的正態分佈中的隨機提示,並要求生成在 450 個詞元左右的正態分佈中的隨機數量的詞元。
docker-compose 檔案對於定義你的資料並行非常重要,它透過指定我希望分配給每個容器的裝置數量來實現。這也是我指定負載均衡器的地方。
version: '3.7'
services:
tgi-1:
image: ghcr.io/huggingface/text-generation-inference:latest-neuron
ports:
- "8081:8081"
volumes:
- $PWD:/data
environment:
- PORT=8081
- MODEL_ID=${MODEL_ID}
- HF_AUTO_CAST_TYPE=${HF_AUTO_CAST_TYPE}
- HF_NUM_CORES=8
- MAX_BATCH_SIZE=${MAX_BATCH_SIZE}
- HF_TOKEN=YOUR_TOKEN
- MAX_INPUT_TOKENS=${MAX_INPUT_TOKENS}
- MAX_TOTAL_TOKENS=${MAX_TOTAL_TOKENS}
- MAX_CONCURRENT_REQUESTS=512
devices:
- "/dev/neuron0"
- "/dev/neuron1"
- "/dev/neuron2"
- "/dev/neuron3"
tgi-2:
image: ghcr.io/huggingface/text-generation-inference:latest-neuron
ports:
- "8082:8082"
volumes:
- $PWD:/data
environment:
- PORT=8082
- MODEL_ID=${MODEL_ID}
- HF_AUTO_CAST_TYPE=${HF_AUTO_CAST_TYPE}
- HF_NUM_CORES=8
- MAX_BATCH_SIZE=${MAX_BATCH_SIZE}
- HF_TOKEN=YOUR_TOKEN
- MAX_INPUT_TOKENS=${MAX_INPUT_TOKENS}
- MAX_TOTAL_TOKENS=${MAX_TOTAL_TOKENS}
- MAX_CONCURRENT_REQUESTS=512
devices:
- "/dev/neuron4"
- "/dev/neuron5"
- "/dev/neuron6"
- "/dev/neuron7"
tgi-3:
image: ghcr.io/huggingface/text-generation-inference:latest-neuron
ports:
- "8083:8083"
volumes:
- $PWD:/data
environment:
- PORT=8083
- MODEL_ID=${MODEL_ID}
- HF_AUTO_CAST_TYPE=${HF_AUTO_CAST_TYPE}
- HF_NUM_CORES=8
- MAX_BATCH_SIZE=${MAX_BATCH_SIZE}
- HF_TOKEN=YOUR_TOKEN
- MAX_INPUT_TOKENS=${MAX_INPUT_TOKENS}
- MAX_TOTAL_TOKENS=${MAX_TOTAL_TOKENS}
- MAX_CONCURRENT_REQUESTS=512
devices:
- "/dev/neuron8"
- "/dev/neuron9"
- "/dev/neuron10"
- "/dev/neuron11"
loadbalancer:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- tgi-1
- tgi-2
- tgi-3
deploy:
placement:
constraints: [node.role == manager]
最後,我為負載均衡器定義 nginx.conf 檔案
### Nginx TGI Load Balancer
events {}
http {
upstream tgicluster {
server tgi-1:8081;
server tgi-2:8082;
server tgi-3:8083;
}
server {
listen 80;
location / {
proxy_pass http://tgicluster;
}
}
}
使用 GuideLLM 進行基準測試
現在我已經定義了必要的檔案,我開始用 TGI 後端來為我的 optimum-neuron 模型提供服務。
!docker compose -f docker-compose.yaml --env-file .env up
為了確保一切正常,我可以觀察上述命令的輸出來確認每個容器以及負載均衡器是否都正確啟動。一旦我成功啟動了容器,我就可以使用先前定義的基準測試指令碼開始進行基準測試。
!benchmark.sh "meta-llama/Meta-Llama-3.1-8B-Instruct"
當 guidellm 開始測試你的模型服務設定時,終端將開始填充彩色的標準輸出。
效能分析
大約 15-20 分鐘後,基準測試完成,並在終端顯示以下詳細分解:
╭─ Benchmarks ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ [15:02:17] 100% synchronous (0.10 req/sec avg)│
│ [15:04:17] 100% throughput (0.85 req/sec avg)│
│ [15:05:25] 100% constant@0.85 req/s (0.77 req/sec avg) │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Generating report... ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ (3/3) [ 0:05:04 < 0:00:00 ]
╭─ GuideLLM Benchmarks Report (meta-llama_Meta-Llama-3.1-8B-Instruct#2025-05-27-15-02-11_guidellm_report.json) ──────────────────────────────────────────────────────────────────────────────────╮
│ ╭─ Benchmark Report 1 ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │
│ │ Backend(type=openai_server, target=http://:8080/v1, model=meta-llama/Meta-Llama-3.1-8B-Instruct) │ │
│ │ Data(type=emulated, source=prompt_tokens=15900,prompt_tokens_variance=100,generated_tokens=450,generated_tokens_variance=50, tokenizer=meta-llama/Meta-Llama-3.1-8B-Instruct) │ │
│ │ Rate(type=sweep, rate=None) │ │
│ │ Limits(max_number=None requests, max_duration=120 sec) │ │
│ │ │ │
│ │ │ │
│ │ Requests Data by Benchmark │ │
│ │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━┓ │ │
│ │ ┃ Benchmark ┃ Requests Completed ┃ Request Failed ┃ Duration ┃ Start Time ┃ End Time ┃ │ │
│ │ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━┩ │ │
│ │ │ synchronous │ 11/11 │ 0/11 │ 113.56 sec │ 15:02:17 │ 15:04:11 │ │ │
│ │ │ asynchronous@0.85 req/sec │ 88/88 │ 0/88 │ 114.59 sec │ 15:05:25 │ 15:07:19 │ │ │
│ │ │ throughput │ 55/55 │ 0/55 │ 64.83 sec │ 15:04:17 │ 15:05:22 │ │ │
│ │ └───────────────────────────┴────────────────────┴────────────────┴────────────┴────────────┴──────────┘ │ │
│ │ │ │
│ │ Tokens Data by Benchmark │ │
│ │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ │ │
│ │ ┃ Benchmark ┃ Prompt ┃ Prompt (1%, 5%, 50%, 95%, 99%) ┃ Output ┃ Output (1%, 5%, 50%, 95%, 99%) ┃ │ │
│ │ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ │
│ │ │ synchronous │ 15902.82 │ 15896.0, 15896.0, 15902.0, 15913.0, 15914.6 │ 293.09 │ 70.3, 119.5, 315.0, 423.5, 443.1 │ │ │
│ │ │ asynchronous@0.85 req/sec │ 15899.06 │ 15877.4, 15879.4, 15898.5, 15918.0, 15919.8 │ 288.75 │ 24.6, 74.1, 298.5, 452.6, 459.1 │ │ │
│ │ │ throughput │ 15899.22 │ 15879.5, 15883.7, 15898.0, 15914.6, 15920.5 │ 294.24 │ 59.1, 114.9, 285.0, 452.9, 456.4 │ │ │
│ │ └───────────────────────────┴──────────┴─────────────────────────────────────────────┴────────┴──────────────────────────────────┘ │ │
│ │ │ │
│ │ Performance Stats by Benchmark │ │
│ │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ │ │
│ │ ┃ ┃ Request Latency [1%, 5%, 10%, 50%, 90%, 95%, 99%] ┃ Time to First Token [1%, 5%, 10%, 50%, 90%, 95%, ┃ Inter Token Latency [1%, 5%, 10%, 50%, 90% 95%, ┃ │ │
│ │ ┃ Benchmark ┃ (sec) ┃ 99%] (ms) ┃ 99%] (ms) ┃ │ │
│ │ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ │
│ │ │ synchronous │ 3.68, 5.13, 6.94, 10.91, 13.51, 14.26, 14.87 │ 1563.3, 1569.2, 1576.5, 1589.4, 1594.0, 1595.3, │ 23.2, 28.2, 29.4, 29.8, 30.3, 31.7, 36.5 │ │ │
│ │ │ │ │ 1596.4 │ │ │ │
│ │ │ asynchronous@0.85 req/sec │ 2.62, 6.55, 9.40, 20.66, 30.60, 32.78, 35.07 │ 1594.1, 1602.5, 1605.7, 1629.7, 4650.1, 4924.1, │ 0.2, 0.2, 0.2, 34.3, 44.9, 54.5, 1613.9 │ │ │
│ │ │ │ │ 5345.6 │ │ │ │
│ │ │ throughput │ 18.29, 21.24, 23.81, 44.60, 61.50, 62.80, 63.72 │ 2157.6, 9185.1, 12220.5, 23333.5, 44214.1, │ 28.2, 31.5, 33.1, 39.1, 59.0, 65.2, 1604.6 │ │ │
│ │ │ │ │ 45329.8, 51276.9 │ │ │ │
│ │ └───────────────────────────┴───────────────────────────────────────────────────┴───────────────────────────────────────────────────┴────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Performance Summary by Benchmark │ │
│ │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━┓ │ │
│ │ ┃ Benchmark ┃ Requests per Second ┃ Request Latency ┃ Time to First Token ┃ Inter Token Latency ┃ Output Token Throughput ┃ │ │
│ │ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━┩ │ │
│ │ │ synchronous │ 0.10 req/sec │ 10.32 sec │ 1585.08 ms │ 29.81 ms │ 28.39 tokens/sec │ │ │
│ │ │ asynchronous@0.85 req/sec │ 0.77 req/sec │ 20.77 sec │ 2401.32 ms │ 63.69 ms │ 221.75 tokens/sec │ │ │
│ │ │ throughput │ 0.85 req/sec │ 43.78 sec │ 24624.46 ms │ 65.18 ms │ 249.64 tokens/sec │ │ │
│ │ └───────────────────────────┴─────────────────────┴─────────────────┴─────────────────────┴─────────────────────┴─────────────────────────┘ │ │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
解開結果,我們得到了相當多有用的資料點供我們使用。在底層,guidellm 執行三個獨立的“負載”來對系統進行基準測試。
- 同步 - 一次只處理一個請求
- 非同步 - 以固定的每秒請求數(本例中為 0.85)同時處理多個請求
- 吞吐量 - 服務系統能夠承受的最大請求數
透過這些測試,我們得到了每個測試的多個指標,例如成功執行的請求數與失敗的請求數、首個 token 的生成時間、提示輸入和輸出的大小等。在我的實驗中,我可以看到在最大負載下,我每秒最多可以服務 0.85 個請求,每個請求的最大延遲略低於 44 秒。根據我的延遲預算,下一步是如果我能容忍更長的響應時間並期望更高的吞吐量,就增加我的批次大小。或者,我可以降低我的批次大小以減少延遲,但代價是可能降低吞吐量。
最後,我的工作負載所需的大量輸入和輸出詞元直接影響了基準測試結果,特別是編碼輸入上下文所需的時間,這構成了大部分基準測試時間。
結論
在這篇部落格文章中,我帶您瞭解瞭如何編譯和載入 Optimum Neuron 模型,如何使用 HuggingFace 文字生成推理容器來提供服務,以及如何對您的設定進行基準測試,以便為您的工作負載進行最佳化。
參考文獻
https://huggingface.co/docs/optimum-neuron/en/guides/cache_system https://github.com/huggingface/optimum-neuron/tree/main/benchmark/text-generation-inference/performance https://github.com/vllm-project/guidellm