|
1 | | -# MLLM LLM Benchmark Tool |
| 1 | +# CPU Benchmark Tool |
2 | 2 |
|
3 | | -## Overview |
| 3 | +Added a benchmark tool for measuring prefill/decode latency across different context lengths on CPU. |
4 | 4 |
|
5 | | -This is a benchmark tool for measuring MLLM model performance, including: |
6 | | -- **TTFT (Time To First Token)**: Time to first token latency |
7 | | -- **Prefill Speed**: Prefill speed (tokens/s) |
8 | | -- **Decode Speed**: Decode generation speed (tokens/s) |
| 5 | +## Why |
9 | 6 |
|
10 | | -## Build |
| 7 | +There wasn't a unified way to benchmark mllm performance with varying context lengths. The existing benchmark tools had prefill length and decode length settings, but no automated sweep across contexts. So I put together this tool + a bash script to run sweeps automatically. |
| 8 | + |
| 9 | +## Test Setup |
11 | 10 |
|
12 | | -Build from the mllm_v2 project root directory: |
| 11 | +Ran on my machine: |
| 12 | +- **System**: WSL2 Ubuntu 24.04 |
| 13 | +- **CPU**: AMD Ryzen 7 6800H with Radeon Graphics |
| 14 | +- **Arch**: x86_64 |
| 15 | +- **Threads**: 8 |
| 16 | +- **Model**: TinyLLaMA fp32 (not quantized) |
13 | 17 |
|
| 18 | +## Build |
14 | 19 | ```bash |
15 | | -mkdir -p build && cd build |
16 | | -cmake .. |
17 | | -make mllm-llm-benchmark |
| 20 | +cmake -S . -B build -G Ninja \ |
| 21 | + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ |
| 22 | + -DBUILD_TESTING=OFF \ |
| 23 | + -DMLLM_ENABLE_TOOLS=ON \ |
| 24 | + -DCMAKE_CXX_FLAGS="-march=native" |
| 25 | + |
| 26 | +cmake --build build -j$(nproc) |
18 | 27 | ``` |
19 | 28 |
|
20 | 29 | ## Usage |
21 | 30 |
|
22 | | -### Basic Usage |
23 | | - |
| 31 | +### Single run |
24 | 32 | ```bash |
25 | | -./mllm-llm-benchmark \ |
26 | | - -n qwen3-w4a32-kai \ |
27 | | - -m /path/to/model.mllm \ |
28 | | - -c /path/to/config.json \ |
29 | | - -t 4 \ |
30 | | - -pp 64,128,256 \ |
31 | | - -tg 100,200,300 \ |
32 | | - -cl 2048 |
| 33 | +./build/bin/mllm-llm-benchmark \ |
| 34 | + -n tiny_llama \ |
| 35 | + -m /path/to/tinyllama-fp32.mllm \ |
| 36 | + -c /path/to/config_tiny_llama.json \ |
| 37 | + -pp 254 -tg 2 -cl 256 -t 8 |
33 | 38 | ``` |
34 | 39 |
|
35 | | -### Parameters |
36 | | - |
37 | | -| Parameter | Long Format | Description | Example | |
38 | | -|-----------|-------------|-------------|---------| |
39 | | -| `-n` | `--model_name` | Model name (used to select the correct benchmark implementation) | `qwen3-w4a32-kai` | |
40 | | -| `-m` | `--model_path` | Model weight file path | `/path/to/model.mllm` | |
41 | | -| `-c` | `--config_path` | Model configuration file path | `/path/to/config.json` | |
42 | | -| `-t` | `--threads` | Number of CPU threads | `4` | |
43 | | -| `-pp` | `--prompt_length` | Prompt length list (comma-separated) | `64,128,256` | |
44 | | -| `-tg` | `--test_generation_length` | Generation length list (comma-separated, must match pp count) | `100,200,300` | |
45 | | -| `-cl` | `--cache_length` | Maximum KV cache length | `2048` | |
| 40 | +### Context sweep |
46 | 41 |
|
47 | | -### Examples |
| 42 | +The script uses environment variables for configuration: |
| 43 | +```bash |
| 44 | +cd tools/mllm-llm-benchmark |
| 45 | +chmod +x scripts/sweep_context_v2.sh |
48 | 46 |
|
49 | | -#### Testing Qwen3-0.6B Model |
| 47 | +export BIN=../../build/bin/mllm-llm-benchmark |
| 48 | +export MODEL=/path/to/tinyllama-fp32.mllm |
| 49 | +export CFG=../../examples/llama/config_tiny_llama.json |
50 | 50 |
|
51 | | -```bash |
52 | | -./mllm-llm-benchmark \ |
53 | | - -n qwen3-w4a32-kai \ |
54 | | - -m ../models/Qwen3-0.6B-w4a32kai/model.mllm \ |
55 | | - -c ../models/Qwen3-0.6B-w4a32kai/config.json \ |
56 | | - -t 4 \ |
57 | | - -pp 64,128,256 \ |
58 | | - -tg 100,100,100 \ |
59 | | - -cl 2048 |
| 51 | +./scripts/sweep_context_v2.sh |
60 | 52 | ``` |
61 | 53 |
|
62 | | -#### Quick Test (Single Configuration) |
| 54 | +Output goes to `bench_context/context_sweep_v2.csv`. You can tweak other params like `THREADS`, `RUNS`, `COOLDOWN` if needed - they have sensible defaults. |
63 | 55 |
|
| 56 | +### Plot results |
64 | 57 | ```bash |
65 | | -./mllm-llm-benchmark \ |
66 | | - -n qwen3-w4a32-kai \ |
67 | | - -m ../models/Qwen3-0.6B-w4a32kai/model.mllm \ |
68 | | - -c ../models/Qwen3-0.6B-w4a32kai/config.json \ |
69 | | - -t 8 \ |
70 | | - -pp 128 \ |
71 | | - -tg 128 \ |
72 | | - -cl 2048 |
| 58 | +python3 scripts/plot_sweep.py bench_context/context_sweep_v2.csv snapshots/ |
73 | 59 | ``` |
74 | 60 |
|
75 | | -## Output Example |
| 61 | +## Benchmark Results |
76 | 62 |
|
77 | | -``` |
78 | | -MLLM Build Version : abc123def456 |
79 | | -ARCH : ARM64 |
80 | | -FP16 : true |
81 | | -... |
82 | | -
|
83 | | -Create Benchmark: qwen3-w4a32-kai |
84 | | -Model Info |
85 | | -========== Model Information ========== |
86 | | -Model Type : Qwen3 W4A32 KAI |
87 | | -Hidden Size : 1024 |
88 | | -Num Layers : 28 |
89 | | -... |
90 | | -======================================= |
91 | | -
|
92 | | -Warmup Run |
93 | | -Warming up with 8 tokens prefill and 4 tokens generation... |
94 | | -Warmup completed |
95 | | -
|
96 | | -======================================== |
97 | | -Starting Benchmark Tests |
98 | | -======================================== |
99 | | -
|
100 | | ----------------------------------------- |
101 | | -Test Configuration: |
102 | | - Prompt Length (PP) : 128 |
103 | | - Generation Length (TG): 128 |
104 | | ----------------------------------------- |
105 | | - Run 1 of 3... |
106 | | - TTFT : 902.38605 ms |
107 | | - Prefill Speed: 141.84618 tokens/s |
108 | | - Decode Speed : 78.11022 tokens/s |
109 | | - Cooling down for 5 seconds... |
110 | | - Run 2 of 3... |
111 | | - TTFT : 911.94403 ms |
112 | | - Prefill Speed: 140.3595 tokens/s |
113 | | - Decode Speed : 77.60929 tokens/s |
114 | | - Cooling down for 5 seconds... |
115 | | - Run 3 of 3... |
116 | | - TTFT : 923.905 ms |
117 | | - Prefill Speed: 138.54239 tokens/s |
118 | | - Decode Speed : 76.48289 tokens/s |
119 | | -
|
120 | | -========== Average Results ========== |
121 | | -Configuration: PP= 128 TG= 128 |
122 | | -Average TTFT : 912.74506 ms |
123 | | -Average Prefill Speed: 140.24936 tokens/s |
124 | | -Average Decode Speed : 77.4008 tokens/s |
125 | | -===================================== |
126 | | -
|
127 | | -
|
128 | | -======================================== |
129 | | -Benchmark Tests Completed |
130 | | -======================================== |
131 | | -``` |
| 63 | +Tested TinyLLaMA fp32 on Ryzen 7 6800H (WSL2): |
132 | 64 |
|
133 | | -## Test Workflow |
134 | | - |
135 | | -Each test configuration executes the following steps: |
136 | | - |
137 | | -1. **Clear Cache** - Ensures each test starts from a clean state |
138 | | -2. **Run 3 Test Rounds** - 5-second sleep between rounds to avoid overheating |
139 | | -3. **Calculate Average** - Average results from 3 rounds |
140 | | -4. **Output Results** - Display TTFT, Prefill Speed, Decode Speed |
141 | | - |
142 | | -## Adding New Model Support |
143 | | - |
144 | | -### 1. Create New Benchmark Class |
145 | | - |
146 | | -Create `YourModel_Benchmark.hpp` in the `models/` directory: |
147 | | - |
148 | | -```cpp |
149 | | -#include "BenchmarkTemplate.hpp" |
150 | | -#include <mllm/models/yourmodel/modeling_yourmodel.hpp> |
151 | | - |
152 | | -class YourModel_Benchmark final : public BenchmarkTemplate { |
153 | | - public: |
154 | | - void init(const std::string& cfg_path, const std::string& model_path, int32_t cache_length) override { |
155 | | - // Initialize your model |
156 | | - } |
157 | | - |
158 | | - void printModelInfo() override { |
159 | | - // Print model information |
160 | | - } |
161 | | - |
162 | | - void warmup() override { |
163 | | - // Warmup run |
164 | | - } |
165 | | - |
166 | | - void clear() override { |
167 | | - // Clear KV cache |
168 | | - } |
169 | | - |
170 | | - BenchmarkTemplateResult run(int32_t pp, int32_t tg) override { |
171 | | - // Run test and return results |
172 | | - } |
173 | | - |
174 | | - private: |
175 | | - std::unique_ptr<YourModelConfig> config_; |
176 | | - std::unique_ptr<YourModel> model_; |
177 | | -}; |
178 | | -``` |
| 65 | +| Context Length | TTFT (s) | Decode Latency (ms/token) | Peak RSS (GB) | |
| 66 | +|----------------|----------|---------------------------|---------------| |
| 67 | +| 256 | 8.5 | 0.68 | 4.0 | |
| 68 | +| 512 | 18.2 | 0.68 | 4.2 | |
| 69 | +| 1024 | 50.5 | 0.67 | 4.6 | |
| 70 | +| 2048 | 149.8 | 0.73 | 5.9 | |
| 71 | +| 4096 | 523.1 | 0.88 | 7.0 | |
179 | 72 |
|
180 | | -### 2. Register in All.hpp |
181 | | -
|
182 | | -```cpp |
183 | | -#include "YourModel_Benchmark.hpp" |
184 | | -
|
185 | | -std::shared_ptr<BenchmarkTemplate> createBenchmark(const std::string& model_name) { |
186 | | - auto normalized_model_name = tolower(model_name); |
187 | | - |
188 | | - // Add your model check |
189 | | - if (normalized_model_name.find("yourmodel") != std::string::npos) { |
190 | | - return std::make_shared<YourModel_Benchmark>(); |
191 | | - } |
192 | | - |
193 | | - // ... other models |
194 | | -} |
195 | | -``` |
| 73 | +Prefill time scales roughly quadratically with context length (as expected). Decode latency per token stays pretty stable around 0.7ms. Memory usage grows linearly with context due to KV cache. |
196 | 74 |
|
197 | | -## Notes |
| 75 | +Note: This is running in WSL2 so performance is slightly worse than native Linux. Also using unquantized fp32 model which is slower than q4/q8 would be. |
198 | 76 |
|
199 | | -- Ensure sufficient memory for model loading and inference |
200 | | -- 5-second sleep between test rounds to avoid device overheating affecting results |
201 | | -- TTFT is in milliseconds, speed is in tokens/s |
202 | | -- Each configuration runs 3 rounds and averages results for more stable measurements |
| 77 | +## Test Modes |
203 | 78 |
|
204 | | -## Troubleshooting |
| 79 | +The sweep script runs two modes for each context length: |
| 80 | +- `prefill_ttft`: Measures time to first token (prompt length = CL-2, generates 2 tokens) |
| 81 | +- `decode_heavy`: Measures decode throughput (prompt length = CL-256, generates 256 tokens) |
205 | 82 |
|
206 | | -### Error: Model not initialized |
207 | | -- Check if model path is correct |
208 | | -- Verify configuration file format is correct |
| 83 | +## Configuration |
| 84 | + |
| 85 | +Environment variables for `sweep_context_v2.sh`: |
| 86 | +- `BIN`: Path to benchmark binary (required) |
| 87 | +- `MODEL`: Path to model file (required) |
| 88 | +- `CFG`: Path to config json (default: ./examples/llama/config_tiny_llama.json) |
| 89 | +- `THREADS`: Number of threads (default: 8) |
| 90 | +- `RUNS`: How many runs to average (default: 1) |
| 91 | +- `COOLDOWN`: Seconds to wait between runs (default: 0) |
| 92 | +- `CLS`: Context lengths to test (default: "256 512 1024 2048 4096") |
| 93 | +- `TG_DH`: Generate length for decode_heavy mode (default: 256) |
| 94 | +- `TG_TTFT`: Generate length for prefill_ttft mode (default: 2) |
| 95 | +- `OUTDIR`: Output directory (default: bench_context) |
| 96 | + |
| 97 | +## Code Structure |
| 98 | +``` |
| 99 | +tools/mllm-llm-benchmark/ |
| 100 | +├── main.cpp # CLI entry point |
| 101 | +├── models/ |
| 102 | +│ ├── BenchmarkTemplate.hpp # Base interface |
| 103 | +│ ├── All.hpp # Model factory |
| 104 | +│ └── Llama.hpp # LLaMA/TinyLLaMA implementation |
| 105 | +├── scripts/ |
| 106 | +│ ├── sweep_context_v2.sh # Context sweep script |
| 107 | +│ └── plot_sweep.py # Plotting script |
| 108 | +``` |
209 | 109 |
|
210 | | -### Error: Benchmark not found |
211 | | -- Check if model name is correct (using `-n` parameter) |
212 | | -- Confirm corresponding model registration exists in `All.hpp` |
| 110 | +The benchmark tool supports multiple models through the `BenchmarkTemplate` interface. Currently implemented: |
| 111 | +- LLaMA/TinyLLaMA |
| 112 | +- Qwen3 (w4a32 with KAI) |
213 | 113 |
|
214 | | -### Performance Anomalies |
215 | | -- Check CPU thread count setting (`-t` parameter) |
216 | | -- Confirm no other programs are consuming system resources |
217 | | -- Check if correct quantization scheme is enabled |
| 114 | +Adding new models just requires implementing the template interface. |
0 commit comments