Skip to content

Commit 7e45489

Browse files
committed
Qualcomm AI Engine Direct - HF Exporter Demo Script
1 parent 3412237 commit 7e45489

12 files changed

Lines changed: 523 additions & 525 deletions

File tree

‎backends/qualcomm/builders/utils.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ def is_constant(
182182
def deduce_dtype(
183183
tensor: torch.Tensor, quant_infos: Optional[Dict] = None
184184
) -> torch.dtype:
185+
# Please be extra careful when modifying the logic here as HF Repo uses it too.
185186
if quant_infos:
186187
quant_range = quant_infos["quant_max"] - quant_infos["quant_min"]
187188
unsigned = quant_infos["quant_min"] >= 0
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Copyright (c) Qualcomm Innovation Center, Inc.
2+
# All rights reserved
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+
import logging
8+
from typing import Optional
9+
10+
from executorch.backends.qualcomm.hf_transformers.causal_lm.hf_llm_quant_recipe import (
11+
DefaultQuantRecipe,
12+
Granite_3_3_2B_Instruct_HFQuantRecipe,
13+
Llama3_2_1B_HFQuantRecipe,
14+
LLMQuantRecipe,
15+
Qwen2_5_0_5B_HFQuantRecipe,
16+
Qwen2_5_1_5B_HFQuantRecipe,
17+
Qwen3_0_6B_HFQuantRecipe,
18+
Smollm2_HFQuantRecipe,
19+
)
20+
21+
FORMAT = "[%(levelname)s %(asctime)s %(filename)s:%(lineno)s] %(message)s"
22+
logging.basicConfig(level=logging.INFO, format=FORMAT)
23+
24+
HUGGING_FACE_QUANT_RECIPES = {
25+
"NousResearch/Llama-3.2-1B": Llama3_2_1B_HFQuantRecipe,
26+
"Qwen/Qwen2.5-0.5B": Qwen2_5_0_5B_HFQuantRecipe,
27+
"Qwen/Qwen2.5-0.5B-Instruct": Qwen2_5_0_5B_HFQuantRecipe,
28+
"Qwen/Qwen2.5-1.5B-Instruct": Qwen2_5_1_5B_HFQuantRecipe,
29+
"Qwen/Qwen3-0.6B": Qwen3_0_6B_HFQuantRecipe,
30+
"HuggingFaceTB/SmolLM2-135M": Smollm2_HFQuantRecipe,
31+
"ibm-granite/granite-3.3-2b-instruct": Granite_3_3_2B_Instruct_HFQuantRecipe,
32+
}
33+
34+
35+
def get_quant_recipe(model_id, verbose=True):
36+
"""
37+
model_id = The Hugging Face Model ID.
38+
verbose: If True, it will the quant config tagged for each node.
39+
"""
40+
recipe_cls = HUGGING_FACE_QUANT_RECIPES.get(model_id, DefaultQuantRecipe)
41+
if recipe_cls == DefaultQuantRecipe:
42+
logging.warning(
43+
f"{model_id} does not have customized quant recipe using default quant recipe."
44+
)
45+
quant_recipe: Optional[LLMQuantRecipe] = recipe_cls(verbose) if recipe_cls else None
46+
return quant_recipe

examples/qualcomm/oss_scripts/llm_utils/decoder_model_wrapper.py renamed to backends/qualcomm/hf_transformers/causal_lm/decoder_model_wrapper.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -187,24 +187,30 @@ def forward(self, x, position_ids):
187187

188188

189189
class QnnCausalLMExportableModule(torch.nn.Module):
190-
def __init__(self, model):
190+
def __init__(self, model: torch.nn.Module, max_seq_len, ar_len=1):
191191
super().__init__()
192192
self.model = model
193-
self.config = model.config
194193
self._metadata = save_config_to_constant_methods(
195194
model.config, model.generation_config
196195
)
197196
logging.info(f"Metadata to be recorded in PTE: {self._metadata}")
198197

199-
self.num_layers = self.config.num_hidden_layers
198+
self.num_layers = self.model.config.num_hidden_layers
200199
self.num_kv_heads = getattr(
201-
self.config, "num_key_value_heads", self.config.num_attention_heads
200+
self.model.config,
201+
"num_key_value_heads",
202+
self.model.config.num_attention_heads,
202203
)
203-
self.head_dim = self.config.head_dim
204-
self.max_seq_len = self.config.max_seq_len
205-
self.ar_len = self.config.ar_len
204+
self.ar_len = ar_len
205+
self.max_seq_len = max_seq_len
206206
self.past_len = self.max_seq_len - self.ar_len
207-
207+
# Some config has head_dim provided that is different from equation below(e.g., qwen2)
208+
if not hasattr(self.model.config, "head_dim"):
209+
self.head_dim = (
210+
self.model.config.hidden_size // self.model.config.num_attention_heads
211+
)
212+
else:
213+
self.head_dim = self.model.config.head_dim
208214
self._register_attention_mask_for_4_53()
209215
self._use_precomputed_rope()
210216

@@ -261,7 +267,13 @@ def get_example_inputs(self):
261267
torch.zeros(1, self.num_kv_heads, self.past_len, self.head_dim)
262268
for _ in range(self.num_layers)
263269
]
264-
return (input_tokens, atten_mask, pos_ids, past_k, past_v)
270+
return {
271+
"input_tokens": input_tokens,
272+
"atten_mask": atten_mask,
273+
"pos_ids": pos_ids,
274+
"past_k": past_k,
275+
"past_v": past_v,
276+
}
265277

266278
def forward(self, input_tokens, atten_mask, pos_ids, past_k, past_v):
267279
cache = QnnCustomStaticCache(past_k, past_v, max_cache_len=self.max_seq_len)

examples/qualcomm/oss_scripts/llm_utils/llm_quant_recipe.py renamed to backends/qualcomm/hf_transformers/causal_lm/hf_llm_quant_recipe.py

File renamed without changes.

‎backends/qualcomm/tests/test_qnn_delegate.py‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9760,11 +9760,11 @@ def test_hf_causal_lm(self):
97609760

97619761
# TODO: Robust testing framework to check accuracy and performance metrics.
97629762
golden_start_with = {
9763-
"llama3_2-1b": "Simply put, the theory of relativity states that the speed of light",
9764-
"qwen2_5-0_5b": "Simply put, the theory of relativity states that the laws of physics",
9765-
"qwen3-0_6b": "Simply put, the theory of relativity states that the laws of physics",
9766-
"smollm2_135m": "Simply put, the theory of relativity states that the speed of light",
9767-
"granite-3_3-2b": "Simply put, the theory of relativity states that the laws of physics",
9763+
"NousResearch/Llama-3.2-1B": "Simply put, the theory of relativity states that the speed of light",
9764+
"Qwen/Qwen2.5-0.5B": "Simply put, the theory of relativity states that the laws of physics",
9765+
"Qwen/Qwen3-0.6B": "Simply put, the theory of relativity states that the laws of physics",
9766+
"HuggingFaceTB/SmolLM2-135M": "Simply put, the theory of relativity states that the speed of light",
9767+
"ibm-granite/granite-3.3-2b-instruct": "Simply put, the theory of relativity states that the laws of physics",
97689768
}
97699769
assert (
97709770
self.model_name in golden_start_with
@@ -9775,7 +9775,7 @@ def test_hf_causal_lm(self):
97759775
f"{self.executorch_root}/examples/qualcomm/oss_scripts/hf_causal_lm.py",
97769776
"--prompt",
97779777
prompt,
9778-
"--decoder_model",
9778+
"--decoder_model_id",
97799779
self.model_name,
97809780
"--max_seq_len",
97819781
"128",

‎backends/qualcomm/utils/utils.py‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -504,12 +504,21 @@ def ensure_graph_specific_dict(value, graph_names):
504504
)
505505
ep = m
506506
else:
507-
ep = torch.export.export(
508-
m,
509-
inputs[graph_name],
510-
dynamic_shapes=dynamic_shapes[graph_name],
511-
strict=True,
512-
)
507+
if isinstance(inputs[graph_name], dict):
508+
ep = torch.export.export(
509+
m,
510+
args=(),
511+
kwargs=inputs[graph_name],
512+
dynamic_shapes=dynamic_shapes[graph_name],
513+
strict=True,
514+
)
515+
else:
516+
ep = torch.export.export(
517+
m,
518+
args=inputs[graph_name],
519+
dynamic_shapes=dynamic_shapes[graph_name],
520+
strict=True,
521+
)
513522
option = generate_qnn_executorch_option(compiler_specs[graph_name])
514523
python_options = flatbuffer_to_option(option)
515524
backend_type = python_options.backend_options.backend_type

‎examples/qualcomm/oss_scripts/hf_causal_lm.py‎

Lines changed: 19 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@
2020
setup_common_args_and_variables,
2121
SimpleADB,
2222
)
23-
2423
from executorch.examples.qualcomm.oss_scripts.llm_utils.qnn_decoder_model_manager import (
2524
get_qnn_llm_edge_manager,
26-
HUGGING_FACE_REPO_IDS,
2725
)
2826
from executorch.examples.qualcomm.utils import make_output_dir
2927

@@ -35,25 +33,15 @@
3533

3634
PTE_FILENAME = "hf_causal_lm_qnn"
3735

38-
# Map the HF decoder_model keys (HUGGING_FACE_REPO_IDS) to the version strings
39-
# the shared qnn_llama_runner understands (see runner.cpp Runner()).
40-
DECODER_MODEL_VERSION = {
41-
"llama3_2-1b": "llama3",
42-
"qwen2_5-0_5b": "qwen2_5",
43-
"qwen2_5-1_5b_instruct": "qwen2_5",
44-
"qwen2_5-0_5b_instruct": "qwen2_5",
45-
"qwen3-0_6b": "qwen3",
46-
"smollm2_135m": "smollm2_135m",
47-
"granite-3_3-2b": "granite",
48-
}
49-
5036

51-
def compile(args: argparse.Namespace, qnn_config: QnnConfig): # noqa: C901
37+
def compile(
38+
args: argparse.Namespace, qnn_config: QnnConfig, pte_name: str
39+
): # noqa: C901
5240

5341
# ensure the working directory exist.
5442
os.makedirs(args.artifact, exist_ok=True)
5543

56-
manager = get_qnn_llm_edge_manager(args.decoder_model, args.max_seq_len)
44+
manager = get_qnn_llm_edge_manager(args.decoder_model_id, args.max_seq_len)
5745

5846
fixed_point_type = {}
5947
if not args.use_fp16:
@@ -71,14 +59,11 @@ def compile(args: argparse.Namespace, qnn_config: QnnConfig): # noqa: C901
7159
else:
7260
raise ValueError("Only support uint16 logits output for quantized hf llm.")
7361

74-
model_id = HUGGING_FACE_REPO_IDS[args.decoder_model]
75-
tokenizer = AutoTokenizer.from_pretrained(model_id)
62+
tokenizer = AutoTokenizer.from_pretrained(args.decoder_model_id)
7663
tokenizer_json_path = tokenizer.save_pretrained(args.artifact)[-1]
7764

7865
manager.pt2e_quantize(
7966
fixed_point_type,
80-
args.calibration_tasks,
81-
args.calibration_limit,
8267
args.prompt,
8368
tokenizer_json_path,
8469
qnn_config.backend,
@@ -97,15 +82,15 @@ def compile(args: argparse.Namespace, qnn_config: QnnConfig): # noqa: C901
9782
"scale": logits_quant_attrs["scale"],
9883
"zero_point": logits_quant_attrs["zero_point"],
9984
},
100-
open(f"{args.artifact}/{PTE_FILENAME}_quant_attrs.txt", "w"),
85+
open(f"{args.artifact}/{pte_name}_quant_attrs.txt", "w"),
10186
)
10287

103-
manager.to_executorch(args.artifact, PTE_FILENAME)
88+
manager.to_executorch(args.artifact, pte_name)
10489

10590

106-
def inference(args: argparse, qnn_config: QnnConfig):
107-
workspace = f"/data/local/tmp/{getpass.getuser()}/executorch/{PTE_FILENAME}"
108-
pte_path = f"{args.artifact}/{PTE_FILENAME}.pte"
91+
def inference(args: argparse, qnn_config: QnnConfig, pte_name: str):
92+
workspace = f"/data/local/tmp/{getpass.getuser()}/executorch/{pte_name}"
93+
pte_path = f"{args.artifact}/{pte_name}.pte"
10994
# collect output data
11095
output_data_folder = f"{args.artifact}/outputs"
11196
make_output_dir(output_data_folder)
@@ -121,12 +106,10 @@ def post_process():
121106
text = text[len(prefix) :]
122107
outputs.append(args.prompt + text)
123108

124-
model_id = HUGGING_FACE_REPO_IDS[args.decoder_model]
125-
tokenizer = AutoTokenizer.from_pretrained(model_id)
109+
tokenizer = AutoTokenizer.from_pretrained(args.decoder_model_id)
126110
tokenizer_json_path = tokenizer.save_pretrained(args.artifact)[-1]
127111
seq_len = args.max_seq_len
128112
runner_bin = "examples/qualcomm/oss_scripts/llama/qnn_llama_runner"
129-
decoder_model_version = DECODER_MODEL_VERSION[args.decoder_model]
130113

131114
# The base (non-instruct) HF models were not trained on the runner's chat
132115
# template. Tokenize the raw prompt here (matching the Python calibration
@@ -146,7 +129,6 @@ def post_process():
146129
f"export LD_LIBRARY_PATH={qnn_sdk}/lib/{target}/:{args.build_folder}/lib &&",
147130
f"{args.build_folder}/{runner_bin}",
148131
f"--tokenized_prompt {tokenized_prompt_path}",
149-
f"--decoder_model_version {decoder_model_version}",
150132
"--eval_mode 0",
151133
f"--tokenizer_path {tokenizer_json_path}",
152134
f"--model_path {pte_path}",
@@ -168,10 +150,9 @@ def post_process():
168150
f"cd {workspace} &&",
169151
"./qnn_llama_runner",
170152
"--tokenized_prompt tokenized_prompt.raw",
171-
f"--decoder_model_version {decoder_model_version}",
172153
"--eval_mode 0",
173154
"--tokenizer_path tokenizer.json",
174-
f"--model_path {PTE_FILENAME}.pte",
155+
f"--model_path {pte_name}.pte",
175156
f"--seq_len {seq_len}",
176157
"--temperature 0",
177158
"--output_path outputs/result.txt",
@@ -207,12 +188,12 @@ def main(args):
207188
qnn_config = QnnConfig.load_config(args.config_file if args.config_file else args)
208189

209190
if args.compile_only:
210-
compile(args, qnn_config)
191+
compile(args, qnn_config, PTE_FILENAME)
211192
elif args.pre_gen_pte:
212-
inference(args, qnn_config)
193+
inference(args, qnn_config, PTE_FILENAME)
213194
else:
214-
compile(args, qnn_config)
215-
inference(args, qnn_config)
195+
compile(args, qnn_config, PTE_FILENAME)
196+
inference(args, qnn_config, PTE_FILENAME)
216197

217198

218199
if __name__ == "__main__":
@@ -242,10 +223,9 @@ def main(args):
242223
)
243224

244225
parser.add_argument(
245-
"--decoder_model",
246-
choices=list(HUGGING_FACE_REPO_IDS.keys()),
247-
help=f"The Hugging Face decoder model to export. Available options are: {list(HUGGING_FACE_REPO_IDS.keys())}",
248-
required=True,
226+
"--decoder_model_id",
227+
help="The Hugging Face ID to export, e.g., 'NousResearch/Llama-3.2-1B'",
228+
default="NousResearch/Llama-3.2-1B",
249229
)
250230

251231
parser.add_argument(
@@ -254,31 +234,9 @@ def main(args):
254234
default=128,
255235
type=int,
256236
)
257-
parser.add_argument(
258-
"--calibration_tasks",
259-
nargs="+",
260-
type=str,
261-
default=None,
262-
help="Tasks for GPTQ calibration from lm_eval. Currently unsupported.",
263-
)
264-
parser.add_argument(
265-
"--calibration_limit",
266-
type=int,
267-
default=None,
268-
help="number of samples used for calibration from lm_eval. Currently unsupported.",
269-
)
270237

271238
try:
272239
args = parser.parse_args()
273-
274-
if args.calibration_tasks is not None or args.calibration_limit is not None:
275-
parser.error(
276-
"--calibration_tasks/--calibration_limit are not supported yet. "
277-
"Calibration uses --prompt instead."
278-
)
279-
280-
if args.artifact is None:
281-
args.artifact = args.decoder_model
282240
main(args)
283241
except Exception as e:
284242
if args.ip and args.port != -1:

0 commit comments

Comments
 (0)