Skip to content

Commit c26c649

Browse files
xingguo01Copilot
andauthored
Arm backend: add SmolLM2 VGF generation and evaluation workflow (#19971)
- add server-mode sampling in executorch runner - add default prompt generation and Wikitext perplexity example scripts - document the end-to-end FP32, linear8a8w, and linear16a8w workflow cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani --------- Signed-off-by: Xingguo Li <xingguo.li@arm.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 4ed31d3 commit c26c649

6 files changed

Lines changed: 1546 additions & 25 deletions

File tree

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
# SmolLM2 → VGF Quickstart
2+
3+
> **Heads-up:** The current VGF PTQ flow is still experimental. Use FP32 as the baseline, expect `linear8a8w` to be accuracy-sensitive, and treat `linear16a8w` as the preferred quantized path to try first.
4+
5+
This is a host-only VGF workflow built around `executor_runner`. Run the
6+
commands from the root of an ExecuTorch source checkout.
7+
8+
## 0. Prerequisites
9+
Run all commands from the repository root.
10+
11+
Install the Arm MLSDK/VKML dependencies and generate `setup_path.sh`:
12+
13+
```bash
14+
examples/arm/setup.sh \
15+
--i-agree-to-the-contained-eula \
16+
--disable-ethos-u-deps \
17+
--enable-mlsdk-deps \
18+
--enable-emulation-layer
19+
```
20+
21+
Activate your Python environment and source the generated Arm setup:
22+
23+
```bash
24+
# Python env (example)
25+
source env/bin/activate
26+
27+
# Arm tools + VKML emulation
28+
source examples/arm/arm-scratch/setup_path.sh
29+
```
30+
31+
If you want the broader Arm backend setup flow, see
32+
`examples/arm/README.md`. This README only covers the SmolLM2 VGF host path.
33+
34+
## 1. Tokenizer (one-time)
35+
```bash
36+
mkdir -p data/tokenizers/smollm2
37+
huggingface-cli download HuggingFaceTB/SmolLM2-135M-Instruct tokenizer.json \
38+
--local-dir data/tokenizers/smollm2
39+
```
40+
The download lives at `data/tokenizers/smollm2/tokenizer.json`. Use this path in the export and sampling commands below.
41+
42+
If you see CMake complaining that your GCC is “too new” for CUDA when building
43+
the VKML runner, use a CUDA-supported host compiler, e.g.:
44+
45+
```bash
46+
export CC=/usr/bin/gcc-12
47+
export CXX=/usr/bin/g++-12
48+
export CUDAHOSTCXX=$CXX
49+
```
50+
51+
## 2. Recommended: FP32 export
52+
Produces a stable `.pte` for experimentation and sampling.
53+
```bash
54+
python -m extension.llm.export.export_llm \
55+
base.model_class=smollm2 \
56+
base.params=examples/models/smollm2/135M_config.json \
57+
base.tokenizer_path=data/tokenizers/smollm2/tokenizer.json \
58+
export.output_dir=outputs/$(date +%F)/$(date +%H-%M-%S)_fp32 \
59+
export.output_name=smollm2_vgf_fp32_full_logits.pte \
60+
export.max_seq_length=64 \
61+
export.max_context_length=64 \
62+
backend.vgf.enabled=True \
63+
backend.vgf.compile_spec=TOSA-1.0+FP \
64+
model.use_kv_cache=False \
65+
model.enable_dynamic_shape=False \
66+
debug.verbose=True \
67+
debug.generate_full_logits=True
68+
```
69+
70+
71+
## 3. Experimental: 8-bit PTQ (Linear-only)
72+
This quantizes only `torch.nn.Linear` modules using the Arm VGF PT2E quantizer.
73+
74+
Supported calibration inputs:
75+
- `quantization.calibration_data=@...` for a text corpus
76+
- `quantization.calibration_tasks=[wikitext]` for LM-Eval tasks
77+
78+
For this static non-KV-cache flow, keep `debug.generate_full_logits=True` for
79+
calibrated exports. Calibration uses padded fixed-shape prefixes, and full
80+
logits let the calibration/eval helpers select the last real-token logits row
81+
instead of accidentally using the padded tail.
82+
83+
Example (LM-Eval wikitext calibration):
84+
```bash
85+
python -m extension.llm.export.export_llm \
86+
base.model_class=smollm2 \
87+
base.params=examples/models/smollm2/135M_config.json \
88+
base.tokenizer_path=data/tokenizers/smollm2/tokenizer.json \
89+
export.output_dir=outputs/$(date +%F)/$(date +%H-%M-%S)_linear8a8w \
90+
export.output_name=smollm2_vgf_linear8a8w_wikitext_full_logits.pte \
91+
export.max_seq_length=64 \
92+
export.max_context_length=64 \
93+
quantization.pt2e_quantize=vgf_8a8w \
94+
quantization.calibration_tasks=\[wikitext\] \
95+
quantization.calibration_limit=64 \
96+
quantization.calibration_seq_length=64 \
97+
backend.vgf.enabled=True \
98+
backend.vgf.compile_spec=TOSA-1.0+FP+INT \
99+
backend.vgf.quantize_scope=linear \
100+
model.use_kv_cache=False \
101+
model.enable_dynamic_shape=False \
102+
debug.verbose=True \
103+
debug.generate_full_logits=True
104+
```
105+
106+
Example (16-bit activations, 8-bit weights, Linear-only):
107+
108+
```bash
109+
python -m extension.llm.export.export_llm \
110+
base.model_class=smollm2 \
111+
base.params=examples/models/smollm2/135M_config.json \
112+
base.tokenizer_path=data/tokenizers/smollm2/tokenizer.json \
113+
export.output_dir=outputs/$(date +%F)/$(date +%H-%M-%S)_linear16a8w \
114+
export.output_name=smollm2_vgf_linear16a8w_wikitext_full_logits.pte \
115+
export.max_seq_length=64 \
116+
export.max_context_length=64 \
117+
quantization.pt2e_quantize=vgf_16a8w \
118+
quantization.calibration_tasks=\[wikitext\] \
119+
quantization.calibration_limit=64 \
120+
quantization.calibration_seq_length=64 \
121+
backend.vgf.enabled=True \
122+
backend.vgf.compile_spec=TOSA-1.0+FP+INT+int16 \
123+
backend.vgf.quantize_scope=linear \
124+
model.use_kv_cache=False \
125+
model.enable_dynamic_shape=False \
126+
debug.verbose=True \
127+
debug.generate_full_logits=True
128+
```
129+
130+
`quantization.pt2e_quantize` selects the numeric mode.
131+
`backend.vgf.quantize_scope=linear` keeps quantization limited to
132+
`torch.nn.Linear` modules. The compile spec still includes FP because the rest
133+
of the graph remains floating point.
134+
135+
## 4. Sampling with `executor_runner`
136+
137+
### 4.0 Build `executor_runner` (VKML)
138+
```bash
139+
source examples/arm/arm-scratch/setup_path.sh
140+
141+
rm -rf cmake-out-vkml
142+
bash examples/arm/smollm2_example_vgf/build_executor_runner_vkml.sh cmake-out-vkml
143+
```
144+
145+
This example-specific wrapper enables `EXECUTORCH_BUILD_KERNELS_OPTIMIZED=ON`
146+
in addition to the VGF and quantized kernel flags. That matters for the SmolLM2
147+
FP32 path, where the generic VKML build helper may not provide enough fallback
148+
CPU kernel coverage.
149+
150+
### 4.1 Greedy and `T=0.8` sampling
151+
`examples/arm/smollm2_example_vgf/generate_sampled.py` wraps
152+
`cmake-out-vkml/executor_runner`, keeps a sliding fixed-length token window,
153+
and can print the top-5 logits each step.
154+
155+
Greedy generation (`--temperature 0`) always chooses the highest-logit next
156+
token, which is useful for deterministic comparisons. Stochastic generation
157+
(`--temperature 0.8` with `--top-p 0.9`) samples from a filtered probability
158+
distribution, so it can produce more varied text while still being reproducible
159+
with a fixed `--seed`.
160+
161+
Notes:
162+
- `--max-seq-length` must match the export `export.max_seq_length` (otherwise you will hit input size mismatch).
163+
- The exported SmolLM2 VGF input is `int32[1, max_seq_length]`; the helper writes
164+
token windows as `int32` binary inputs for `executor_runner`.
165+
- Use `--persistent-runner` for faster multi-token generation (loads the model once).
166+
- The documented examples use `--temperature 0` (greedy) and `--temperature 0.8`.
167+
- For deterministic comparisons against saved `temp0` outputs, use `--seed 0`, `--repetition-penalty 1.1`, and `--no-topk-print`. At `--temperature 0`, token selection is greedy, so `--top-p` does not affect the chosen token.
168+
169+
Greedy example (`T=0`):
170+
```bash
171+
python examples/arm/smollm2_example_vgf/generate_sampled.py \
172+
--persistent-runner \
173+
--runner cmake-out-vkml/executor_runner \
174+
--pte smollm2_vgf_fp32_full_logits.pte \
175+
--tokenizer data/tokenizers/smollm2/tokenizer.json \
176+
--prompt "Once upon a time in a small village," \
177+
--max-seq-length 64 \
178+
--max-new-tokens 10 \
179+
--seed 0 \
180+
--temperature 0 \
181+
--repetition-penalty 1.1 \
182+
--full-logits
183+
```
184+
185+
Stochastic example (`T=0.8`):
186+
```bash
187+
python examples/arm/smollm2_example_vgf/generate_sampled.py \
188+
--persistent-runner \
189+
--runner cmake-out-vkml/executor_runner \
190+
--pte smollm2_vgf_fp32_full_logits.pte \
191+
--tokenizer data/tokenizers/smollm2/tokenizer.json \
192+
--prompt "Once upon a time in a small village," \
193+
--max-seq-length 64 \
194+
--max-new-tokens 10 \
195+
--seed 0 \
196+
--temperature 0.8 \
197+
--top-p 0.9 \
198+
--repetition-penalty 1.1 \
199+
--full-logits
200+
```
201+
> Swap `--pte` to the quantized build to compare behaviour. `linear8a8w` still
202+
> tends to drift more than `linear16a8w`.
203+
204+
205+
206+
### 4.2 Batch prompts from `default_prompts.txt`
207+
208+
To generate for *all* prompts in `default_prompts.txt` and save to a file:
209+
210+
```bash
211+
python examples/arm/smollm2_example_vgf/generate_sampled.py \
212+
--persistent-runner \
213+
--runner cmake-out-vkml/executor_runner \
214+
--pte smollm2_vgf_fp32_full_logits.pte \
215+
--tokenizer data/tokenizers/smollm2/tokenizer.json \
216+
--prompt-file examples/arm/smollm2_example_vgf/default_prompts.txt \
217+
--prompt-all \
218+
--max-seq-length 64 \
219+
--max-new-tokens 64 \
220+
--temperature 0.8 \
221+
--top-p 0.9 \
222+
--repetition-penalty 1.1 \
223+
--full-logits \
224+
--save-generations outputs/$(date +%F)/$(date +%H-%M-%S)_smollm2_gen.txt
225+
```
226+
227+
## 5. Wikitext prompts and perplexity
228+
229+
Build a reusable 1000-prompt file from `wikitext-2-raw-v1` and evaluate
230+
perplexity on the first 100 prompts for FP32, `linear8a8w`, and `linear16a8w`:
231+
232+
```bash
233+
OUT_DIR=outputs/$(date +%F)/$(date +%H-%M-%S)_smollm2_vgf_eval
234+
235+
python examples/arm/smollm2_example_vgf/eval_wikitext_perplexity.py \
236+
--runner cmake-out-vkml/executor_runner \
237+
--pte-fp32 "${OUT_DIR}/smollm2_vgf_fp32_full_logits.pte" \
238+
--pte-linear8a8w "${OUT_DIR}/smollm2_vgf_linear8a8w_wikitext_full_logits.pte" \
239+
--pte-linear16a8w "${OUT_DIR}/smollm2_vgf_linear16a8w_wikitext_full_logits.pte" \
240+
--tokenizer data/tokenizers/smollm2/tokenizer.json \
241+
--prompts-file "${OUT_DIR}/wikitext_prompts_1000.txt" \
242+
--num-prompts 1000 \
243+
--ppl-prompts 100 \
244+
--max-seq-length 64 \
245+
--max-prompt-tokens 64 \
246+
--refresh-prompts
247+
```
248+
249+
Notes:
250+
- This script downloads `wikitext-2-raw-v1` via Hugging Face `datasets`.
251+
- The prompts file is reusable; omit `--refresh-prompts` on later runs.
252+
- Perplexity is computed on the first 100 prompts from that file.
253+
- Each prompt is capped to 64 tokens and scored from one full-logits
254+
`executor_runner` invocation per prompt, rather than one invocation per token.
255+
256+
## 6. Notes
257+
- This flow keeps KV cache disabled and uses a fixed token window. KV-cache
258+
support is the expected next step for faster generation, but it is outside
259+
this static VGF quickstart.
260+
- Without KV cache, the model recomputes the entire token window for each
261+
generated token.
262+
- `linear8a8w` still shows noticeably more quality loss than `linear16a8w`.
263+
- When you change `max_seq_length`, regenerate any cached prompt inputs to match the new window size.
264+
- On hosts with multiple Vulkan devices, use `vulkaninfo --summary` to check
265+
device ordering and memory before selecting a non-default physical device.
266+
267+
### Implementation details
268+
- The VKML runner is `examples/portable/executor_runner/executor_runner.cpp`,
269+
built here as `cmake-out-vkml/executor_runner`.
270+
- `generate_sampled.py` tokenizes prompts, prepares the fixed token window,
271+
invokes `executor_runner`, reads logits, and decodes sampled tokens.
272+
- The sampling and perplexity commands pass `--full-logits` to match the
273+
exported full-logits PTEs.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2026 Arm Limited and/or its affiliates.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
set -eu
8+
9+
script_dir=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
10+
et_root_dir=$(cd "${script_dir}/../../.." && pwd)
11+
et_root_dir=$(realpath "${et_root_dir}")
12+
13+
setup_path_script="${et_root_dir}/examples/arm/arm-scratch/setup_path.sh"
14+
output_folder="${1:-cmake-out-vkml}"
15+
16+
[[ -f "${setup_path_script}" ]] \
17+
|| { echo "Missing ${setup_path_script}. Run examples/arm/setup.sh first."; exit 1; }
18+
19+
source "${setup_path_script}"
20+
21+
mkdir -p "${output_folder}"
22+
output_folder=$(realpath "${output_folder}")
23+
24+
cmake \
25+
-S "${et_root_dir}" \
26+
-B "${output_folder}" \
27+
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
28+
-DEXECUTORCH_BUILD_EXTENSION_DATA_LOADER=ON \
29+
-DEXECUTORCH_BUILD_EXTENSION_MODULE=ON \
30+
-DEXECUTORCH_BUILD_EXTENSION_NAMED_DATA_MAP=ON \
31+
-DEXECUTORCH_BUILD_EXTENSION_FLAT_TENSOR=ON \
32+
-DEXECUTORCH_BUILD_EXTENSION_TENSOR=ON \
33+
-DEXECUTORCH_BUILD_XNNPACK=OFF \
34+
-DEXECUTORCH_BUILD_VULKAN=ON \
35+
-DEXECUTORCH_BUILD_VGF=ON \
36+
-DEXECUTORCH_BUILD_KERNELS_OPTIMIZED=ON \
37+
-DEXECUTORCH_BUILD_KERNELS_QUANTIZED=ON \
38+
-DEXECUTORCH_BUILD_KERNELS_QUANTIZED_AOT=ON \
39+
-DEXECUTORCH_ENABLE_LOGGING=ON \
40+
-DEXECUTORCH_BUILD_DEVTOOLS=ON \
41+
-DPYTHON_EXECUTABLE="$(which python3)"
42+
43+
cmake --build "${output_folder}" --parallel
44+
45+
echo "[built] ${output_folder}/executor_runner"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Once upon a time in a small village,
2+
The future of artificial intelligence is
3+
To solve climate change, we need to
4+
In the year 2050, humanity will
5+
The most important lesson I learned was
6+
Write a short story about a robot:
7+
Explain quantum computing in simple terms:
8+
List three benefits of renewable energy:
9+
What's the capital of France?

0 commit comments

Comments
 (0)