Skip to content

Commit 80de749

Browse files
committed
Arm backend: add SmolLM2 VGF generation and evaluation workflow
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 Signed-off-by: Xingguo Li <xingguo.li@arm.com> Change-Id: I14472f987c739ea80457acc543e89b0e5a5f4a4c
1 parent b86fc8b commit 80de749

5 files changed

Lines changed: 33 additions & 21 deletions

File tree

examples/arm/smollm2_example_vgf/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ If you want the broader Arm backend setup flow, see
3434
## 1. Tokenizer (one-time)
3535
```bash
3636
mkdir -p data/tokenizers/smollm2
37-
hf download HuggingFaceTB/SmolLM2-135M-Instruct tokenizer.json \
37+
huggingface-cli download HuggingFaceTB/SmolLM2-135M-Instruct tokenizer.json \
3838
--local-dir data/tokenizers/smollm2
3939
```
4040
The download lives at `data/tokenizers/smollm2/tokenizer.json`. Use this path in the export and sampling commands below.

examples/arm/smollm2_example_vgf/build_executor_runner_vkml.sh

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ output_folder=$(realpath "${output_folder}")
2424
cmake \
2525
-S "${et_root_dir}" \
2626
-B "${output_folder}" \
27-
-Wall \
28-
-Werror \
2927
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
3028
-DEXECUTORCH_BUILD_EXTENSION_DATA_LOADER=ON \
3129
-DEXECUTORCH_BUILD_EXTENSION_MODULE=ON \

examples/arm/smollm2_example_vgf/eval_wikitext_perplexity.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99

1010
import argparse
1111
import math
12+
import sys
1213
from pathlib import Path
1314
from typing import Iterable, List, Tuple
1415

1516
import numpy as np
1617

18+
sys.path.insert(0, str(Path(__file__).resolve().parent))
1719
from generate_sampled import ( # type: ignore[import-not-found]
1820
prepare_input,
1921
RunnerSession,
@@ -103,13 +105,6 @@ def read_prompts(path: Path, limit: int) -> List[str]:
103105
return prompts[:limit]
104106

105107

106-
def token_nll(logits: np.ndarray, target_id: int) -> float:
107-
max_logit = float(np.max(logits))
108-
shifted = logits - max_logit
109-
log_denom = max_logit + math.log(float(np.exp(shifted).sum()))
110-
return log_denom - float(logits[target_id])
111-
112-
113108
def reshape_full_logits(*, logits: np.ndarray, window: int) -> np.ndarray:
114109
if window <= 0:
115110
raise ValueError("window must be > 0")
@@ -153,13 +148,18 @@ def eval_prompt_nll(
153148
logits = runner.run(window_tokens)
154149
logits_2d = reshape_full_logits(logits=logits, window=window)
155150

156-
total_nll = 0.0
157-
for pos, target_id in enumerate(target_ids[-valid_len:]):
158-
if target_id >= logits_2d.shape[1]:
159-
raise RuntimeError(
160-
f"Target token id {target_id} out of inferred vocab size {logits_2d.shape[1]}."
161-
)
162-
total_nll += token_nll(logits_2d[pos], target_id)
151+
# prepare_input keeps the scored tokens at positions [0, valid_len).
152+
rows = logits_2d[:valid_len]
153+
targets = np.asarray(target_ids[-valid_len:], dtype=np.int64)
154+
if targets.size and (targets.min() < 0 or targets.max() >= rows.shape[1]):
155+
raise RuntimeError(
156+
f"Target token id out of inferred vocab size {rows.shape[1]}."
157+
)
158+
159+
max_logits = rows.max(axis=1)
160+
shifted = rows - max_logits[:, None]
161+
log_denom = max_logits + np.log(np.exp(shifted).sum(axis=1))
162+
total_nll = float((log_denom - rows[np.arange(valid_len), targets]).sum())
163163

164164
return total_nll, valid_len
165165

examples/arm/smollm2_example_vgf/generate_sampled.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import subprocess # nosec B404
1111
import tempfile
1212
from pathlib import Path
13-
from typing import List, Optional
13+
from typing import List, Optional, Tuple
1414

1515
import numpy as np
1616
from pytorch_tokenizers import get_tokenizer # type: ignore[import-untyped]
@@ -130,14 +130,15 @@ def run(self, tokens: np.ndarray) -> np.ndarray:
130130

131131
assert self._input_path is not None
132132
tokens.tofile(self._input_path)
133+
output_path = self._output_path()
134+
output_path.unlink(missing_ok=True)
133135

134136
if not self._persistent:
135137
cmd = self._base_cmd()
136138
proc = subprocess.run( # nosec B603
137139
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
138140
)
139141
out = proc.stdout.decode(errors="replace")
140-
output_path = self._output_path()
141142

142143
# Prefer checking for the output file instead of relying on stdout.
143144
if output_path.exists() and output_path.stat().st_size > 0:
@@ -173,6 +174,8 @@ def run(self, tokens: np.ndarray) -> np.ndarray:
173174
self._recent_stdout = self._recent_stdout[-400:]
174175
if "SERVER MODE DONE" in line:
175176
break
177+
if not output_path.exists() or output_path.stat().st_size == 0:
178+
raise RuntimeError("executor_runner did not write logits output")
176179
return self._read_logits()
177180

178181

@@ -383,7 +386,7 @@ def select_last_token_logits(
383386
window: int,
384387
use_full_logits: bool,
385388
valid_len: int,
386-
) -> tuple[np.ndarray, Optional[int]]:
389+
) -> Tuple[np.ndarray, Optional[int]]:
387390
"""Select the logits row used for sampling and infer vocab size.
388391
389392
Args:

examples/portable/executor_runner/executor_runner.cpp

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ Error load_input_files(
150150
return Error::Internal;
151151
}
152152

153-
// Reserve memory for actual file contents.
154153
// Reserve memory for actual file contents.
155154
inputs_storage.emplace_back(static_cast<size_t>(file_size), '\0');
156155

@@ -161,6 +160,7 @@ Error load_input_files(
161160

162161
input_buffers.emplace_back(
163162
inputs_storage.back().data(), static_cast<size_t>(file_size));
163+
}
164164

165165
return Error::Ok;
166166
}
@@ -506,6 +506,9 @@ int main(int argc, char** argv) {
506506
}
507507

508508
if (FLAGS_server_mode) {
509+
ET_CHECK_MSG(
510+
!FLAGS_output_file.empty(),
511+
"--output_file must be set in --server_mode so outputs can be retrieved.");
509512
ET_LOG(Info, "Running in server mode; waiting for stdin triggers.");
510513
std::vector<EValue> outputs(method->outputs_size());
511514
ET_LOG(Info, "%zu outputs: ", outputs.size());
@@ -568,6 +571,10 @@ int main(int argc, char** argv) {
568571
out_filename, 255, "%s-%d.bin", FLAGS_output_file.c_str(), i);
569572
ET_LOG(Info, "Writing output to file: %s", out_filename);
570573
FILE* out_file = fopen(out_filename, "wb");
574+
ET_CHECK_MSG(
575+
out_file != nullptr,
576+
"Failed to open output file: %s",
577+
out_filename);
571578
fwrite(tensor.const_data_ptr<char>(), 1, tensor.nbytes(), out_file);
572579
fclose(out_file);
573580
}
@@ -662,6 +669,10 @@ int main(int argc, char** argv) {
662669
snprintf(out_filename, 255, "%s-%d.bin", FLAGS_output_file.c_str(), i);
663670
ET_LOG(Info, "Writing output to file: %s", out_filename);
664671
FILE* out_file = fopen(out_filename, "wb");
672+
ET_CHECK_MSG(
673+
out_file != nullptr,
674+
"Failed to open output file: %s",
675+
out_filename);
665676
fwrite(tensor.const_data_ptr<char>(), 1, tensor.nbytes(), out_file);
666677
fclose(out_file);
667678
}

0 commit comments

Comments
 (0)