psiddh commited on
Commit
2c717f6
·
verified ·
1 Parent(s): e3c142c

Add ExecuTorch WebGPU program for qwen2.5-0.5b-instruct

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ qwen2_5_0_5b_instruct_q4gsw.pte filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
NOTICE ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ This repository contains a modified version of Qwen/Qwen2.5-0.5B-Instruct.
2
+
3
+ Modifications by Meta Platforms, Inc.: conversion to the ExecuTorch program format (.pte) for the
4
+ ExecuTorch WebGPU backend, and quantization as described in README.md.
5
+
6
+ The original work and these modifications are distributed under the license in LICENSE.
README.md ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model:
4
+ - Qwen/Qwen2.5-0.5B-Instruct
5
+ base_model_relation: quantized
6
+ library_name: executorch
7
+ pipeline_tag: text-generation
8
+ tags:
9
+ - executorch
10
+ - webgpu
11
+ - on-device
12
+ - browser
13
+ ---
14
+ # Qwen2.5 0.5B Instruct for ExecuTorch WebGPU
15
+
16
+ This repository contains an [ExecuTorch](https://github.com/pytorch/executorch) conversion of
17
+ [Qwen/Qwen2.5-0.5B-Instruct](https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct) for the ExecuTorch **WebGPU** backend, so the model runs entirely in a web browser on
18
+ the visitor's GPU. It powers the corresponding demo in the
19
+ [ExecuTorch Playground](https://executorch.ai/playground/webgpu/).
20
+
21
+ - **Base model:** [Qwen/Qwen2.5-0.5B-Instruct](https://huggingface.co/Qwen/Qwen2.5-0.5B-Instruct)
22
+ - **Format:** ExecuTorch `.pte` programs delegated to the WebGPU backend
23
+ - **Quantization:** 4-bit groupwise weights (q4gsw), fp16 KV cache
24
+ - **Total size:** 296.5 MB
25
+
26
+ ## Files
27
+
28
+ | File | Size | SHA-256 |
29
+ |---|---|---|
30
+ | `qwen2_5_0_5b_instruct_q4gsw.pte` | 285.6 MB | `474274b934978f6c…` |
31
+ | `tokenizer/config.json` | 659 B | `18e18afcaccafade…` |
32
+ | `tokenizer/generation_config.json` | 242 B | `e558847a8b440261…` |
33
+ | `tokenizer/merges.txt` | 1.6 MB | `599bab5407508877…` |
34
+ | `tokenizer/tokenizer.json` | 6.7 MB | `c0382117ea329cdf…` |
35
+ | `tokenizer/tokenizer_config.json` | 7.1 KB | `5b5d4f65d0acd3b2…` |
36
+ | `tokenizer/vocab.json` | 2.6 MB | `ca10d7e9fb3ed185…` |
37
+
38
+ Full checksums are in `manifest.json`. The playground verifies them after download.
39
+
40
+ ## Running it
41
+
42
+ These programs need the ExecuTorch WebGPU runtime compiled to WebAssembly
43
+ ([backends/webgpu](https://github.com/pytorch/executorch/tree/main/backends/webgpu)). The easiest way to
44
+ try the model is the [ExecuTorch Playground](https://executorch.ai/playground/webgpu/), which downloads these
45
+ files once, caches them in the browser, and runs everything locally.
46
+
47
+ ## Provenance
48
+
49
+ - **Upstream:** `Qwen/Qwen2.5-0.5B-Instruct` at revision `7ae557604adf67be50417f59c2c2f167def9a775` (official)
50
+ - **Exported with:** executorch 1.5.1+cpu, torch 2.14.1+cpu, torchao 0.18.0+cpu, transformers 5.19.0 (executorch git `3b6068392324`)
51
+ - **Reproduce:** `python recipes/qwen2.5-0.5b-instruct.py --out out/qwen2.5-0.5b-instruct` (recipe in `export/`)
52
+ - Re-running the recipe produces byte-identical files.
53
+
54
+ ## License
55
+
56
+ This is a derivative of the upstream model and is distributed under the same terms
57
+ (`apache-2.0`); see `LICENSE`.
58
+ Modifications (conversion to the ExecuTorch format and quantization) by Meta Platforms, Inc.
export/_common.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared helpers for the ExecuTorch WebGPU export recipes in this directory.
2
+
3
+ Each recipe builds an eager model from a pinned upstream revision and calls
4
+ `export_vision` (or its own LLM path), which writes:
5
+
6
+ <out>/<name>_webgpu.pte program lowered with WebGPUPartitioner (delegate id "VulkanBackend")
7
+ <out>/<name>_portable.pte same exported graph without a delegate, for CPU validation only
8
+ <out>/recipe.json upstream id + revision, versions, shapes, dtype, command line
9
+ <out>/validation.json eager vs exported-program vs portable .pte (and goldens, if given)
10
+ """
11
+
12
+ import hashlib
13
+ import io
14
+ import json
15
+ import platform
16
+ import shlex
17
+ import sys
18
+ import urllib.request
19
+ import zipfile
20
+ from pathlib import Path
21
+
22
+ import numpy as np
23
+ import torch
24
+
25
+ PROXY_HINT = (
26
+ "set https_proxy if the machine needs a proxy for huggingface.co / pypi.org"
27
+ )
28
+
29
+
30
+ def versions():
31
+ import executorch
32
+ import torchao
33
+ from executorch.version import git_version
34
+
35
+ out = {
36
+ "python": platform.python_version(),
37
+ "torch": torch.__version__,
38
+ "torchao": torchao.__version__,
39
+ "executorch": getattr(executorch, "__version__", None)
40
+ or __import__("executorch.version").version.__version__,
41
+ "executorch_git": git_version,
42
+ }
43
+ try:
44
+ import transformers
45
+
46
+ out["transformers"] = transformers.__version__
47
+ except ImportError:
48
+ pass
49
+ try:
50
+ import huggingface_hub
51
+
52
+ out["huggingface_hub"] = huggingface_hub.__version__
53
+ except ImportError:
54
+ pass
55
+ return out
56
+
57
+
58
+ def sha256_file(path):
59
+ h = hashlib.sha256()
60
+ with open(path, "rb") as fh:
61
+ for chunk in iter(lambda: fh.read(1 << 20), b""):
62
+ h.update(chunk)
63
+ return h.hexdigest()
64
+
65
+
66
+ def hf_file(repo_id, filename, revision, expected_sha256=None, cache_dir=None):
67
+ from huggingface_hub import hf_hub_download
68
+
69
+ path = hf_hub_download(
70
+ repo_id=repo_id, filename=filename, revision=revision, cache_dir=cache_dir
71
+ )
72
+ digest = sha256_file(path)
73
+ if expected_sha256 and digest != expected_sha256:
74
+ sys.exit(
75
+ f"{repo_id}@{revision}:{filename} sha256 {digest} != {expected_sha256}"
76
+ )
77
+ return Path(path), digest
78
+
79
+
80
+ def pypi_wheel_member(url, expected_sha256, member):
81
+ """Read one file out of a PyPI wheel, verifying the wheel's sha256."""
82
+ with urllib.request.urlopen(url, timeout=120) as resp:
83
+ blob = resp.read()
84
+ digest = hashlib.sha256(blob).hexdigest()
85
+ if digest != expected_sha256:
86
+ sys.exit(f"{url}: sha256 {digest} != {expected_sha256} ({PROXY_HINT})")
87
+ with zipfile.ZipFile(io.BytesIO(blob)) as zf:
88
+ return zf.read(member).decode()
89
+
90
+
91
+ def scrub(text, out_dir):
92
+ """Replace machine-specific paths so recipe.json can ship with the model."""
93
+ text = text.replace(str(Path(out_dir).resolve()), "<out>")
94
+ text = text.replace(str(Path(__file__).resolve().parent), "recipes")
95
+ return text.replace(str(Path.home()), "~")
96
+
97
+
98
+ def command_line(out_dir):
99
+ argv = [f"recipes/{Path(sys.argv[0]).name}", *sys.argv[1:]]
100
+ args = []
101
+ for a in argv:
102
+ if a.startswith("/") or a.startswith("."):
103
+ a = scrub(str(Path(a).resolve()), out_dir)
104
+ args.append(a)
105
+ return scrub(" ".join(["python", *(shlex.quote(a) for a in args)]), out_dir)
106
+
107
+
108
+ def lower_webgpu(ep):
109
+ from executorch.backends.webgpu.partitioner import WebGPUPartitioner
110
+ from executorch.exir import to_edge_transform_and_lower
111
+
112
+ return to_edge_transform_and_lower(
113
+ ep, partitioner=[WebGPUPartitioner()]
114
+ ).to_executorch()
115
+
116
+
117
+ def lower_portable(ep):
118
+ from executorch.exir import to_edge_transform_and_lower
119
+
120
+ return to_edge_transform_and_lower(ep).to_executorch()
121
+
122
+
123
+ def delegate_summary(pte_bytes):
124
+ """Non-delegated kernels and per-delegate op histograms of a serialized program."""
125
+ import collections
126
+
127
+ from executorch.backends.vulkan.serialization.vulkan_graph_serialize import (
128
+ extract_vk_flatbuffer,
129
+ flatbuffer_to_vk_graph,
130
+ )
131
+ from executorch.exir._serialize._program import deserialize_pte_binary
132
+
133
+ pte = deserialize_pte_binary(pte_bytes)
134
+ prog = pte.program if hasattr(pte, "program") else pte
135
+ seg_base = int.from_bytes(pte_bytes[24:32], "little")
136
+ plan = prog.execution_plan[0]
137
+ ops = [f"{o.name}.{o.overload}" for o in plan.operators]
138
+ kernels = collections.Counter()
139
+ for chain in plan.chains:
140
+ for ins in chain.instructions:
141
+ if type(ins.instr_args).__name__ == "KernelCall":
142
+ kernels[ops[ins.instr_args.op_index]] += 1
143
+ delegates = []
144
+ for d in plan.delegates:
145
+ proc = d.processed
146
+ if proc.location.name == "INLINE":
147
+ blob = prog.backend_delegate_data[proc.index].data
148
+ else:
149
+ seg = prog.segments[proc.index]
150
+ blob = pte_bytes[seg_base + seg.offset : seg_base + seg.offset + seg.size]
151
+ g = flatbuffer_to_vk_graph(extract_vk_flatbuffer(blob))
152
+ delegates.append(
153
+ {
154
+ "id": d.id,
155
+ "ops": dict(collections.Counter(c.name for c in g.chain)),
156
+ "inputs": len(g.input_ids),
157
+ "outputs": len(g.output_ids),
158
+ }
159
+ )
160
+ return {"non_delegated_kernels": dict(kernels), "delegates": delegates}
161
+
162
+
163
+ def compare(a, b):
164
+ a = np.asarray(a, dtype=np.float64).ravel()
165
+ b = np.asarray(b, dtype=np.float64).ravel()
166
+ n = min(a.size, b.size)
167
+ a, b = a[:n], b[:n]
168
+ cos = float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-30))
169
+ return {
170
+ "max_abs": float(np.max(np.abs(a - b))),
171
+ "mean_abs": float(np.mean(np.abs(a - b))),
172
+ "cosine": cos,
173
+ }
174
+
175
+
176
+ def run_pte(pte_bytes, inputs):
177
+ from executorch.extension.pybindings.portable_lib import (
178
+ _load_for_executorch_from_buffer,
179
+ )
180
+
181
+ m = _load_for_executorch_from_buffer(pte_bytes)
182
+ return [
183
+ o.detach().numpy() if isinstance(o, torch.Tensor) else o
184
+ for o in m.forward(inputs)
185
+ ]
186
+
187
+
188
+ def try_run_webgpu_pte_on_cpu(pte_bytes, inputs):
189
+ try:
190
+ return run_pte(pte_bytes, inputs), None
191
+ except Exception as e: # noqa: BLE001 - the error text is the result
192
+ return None, f"{type(e).__name__}: {str(e).splitlines()[0][:300]}"
193
+
194
+
195
+ def export_vision(
196
+ *,
197
+ name,
198
+ model,
199
+ example_input,
200
+ out_dir,
201
+ upstream,
202
+ extra_recipe,
203
+ golden=None,
204
+ output_index=None,
205
+ ):
206
+ """Export, lower, write both .pte files, recipe.json, and validation.json.
207
+
208
+ `golden` is an optional (input ndarray, output ndarray) pair; when its input
209
+ shape matches `example_input`, all paths are also compared against it.
210
+ `output_index` picks one tensor when the eager model returns a tuple.
211
+ """
212
+ out_dir = Path(out_dir)
213
+ out_dir.mkdir(parents=True, exist_ok=True)
214
+ torch.manual_seed(0)
215
+ model = model.eval()
216
+
217
+ class Wrapped(torch.nn.Module):
218
+ def __init__(self, m):
219
+ super().__init__()
220
+ self.m = m
221
+
222
+ def forward(self, x):
223
+ y = self.m(x)
224
+ return y[output_index] if output_index is not None else y
225
+
226
+ wrapped = Wrapped(model).eval()
227
+ with torch.no_grad():
228
+ ep = torch.export.export(wrapped, (example_input,), strict=False)
229
+ webgpu = lower_webgpu(ep)
230
+ portable = lower_portable(ep)
231
+
232
+ webgpu_path = out_dir / f"{name}_webgpu.pte"
233
+ portable_path = out_dir / f"{name}_portable.pte"
234
+ webgpu_path.write_bytes(webgpu.buffer)
235
+ portable_path.write_bytes(portable.buffer)
236
+
237
+ summary = delegate_summary(webgpu.buffer)
238
+ out_shape = list(ep.module()(example_input).shape)
239
+ recipe = {
240
+ "model": name,
241
+ "upstream": upstream,
242
+ "versions": versions(),
243
+ "export": {
244
+ "api": "torch.export.export(strict=False) -> to_edge_transform_and_lower(partitioner=[WebGPUPartitioner()]) -> to_executorch()",
245
+ "partitioner": "executorch.backends.webgpu.partitioner.WebGPUPartitioner() (default compile options; wraps VulkanPartitioner)",
246
+ "inputs": [
247
+ {
248
+ "shape": list(example_input.shape),
249
+ "dtype": "float32",
250
+ "layout": "NCHW",
251
+ }
252
+ ],
253
+ "outputs": [{"shape": out_shape, "dtype": "float32"}],
254
+ "dtype": "float32",
255
+ "quantization": "none (fp32)",
256
+ **extra_recipe,
257
+ },
258
+ "files": {
259
+ webgpu_path.name: {
260
+ "bytes": webgpu_path.stat().st_size,
261
+ "sha256": sha256_file(webgpu_path),
262
+ },
263
+ portable_path.name: {
264
+ "bytes": portable_path.stat().st_size,
265
+ "sha256": sha256_file(portable_path),
266
+ "note": "CPU-only validation artifact (portable kernels, no delegate); not for release",
267
+ },
268
+ },
269
+ "graph": summary,
270
+ "command": command_line(out_dir),
271
+ }
272
+ (out_dir / "recipe.json").write_text(json.dumps(recipe, indent=2) + "\n")
273
+
274
+ # Validation on CPU.
275
+ gen = torch.Generator().manual_seed(1234)
276
+ rand_input = torch.rand(example_input.shape, generator=gen)
277
+ cases = {"random": rand_input}
278
+ if golden is not None and tuple(golden[0].shape) == tuple(example_input.shape):
279
+ cases["golden"] = torch.from_numpy(golden[0].copy())
280
+ report = {
281
+ "cpu_runtime": "executorch pybindings (portable_lib) from the pip wheel",
282
+ "cases": {},
283
+ }
284
+ webgpu_cpu_error = None
285
+ for label, x in cases.items():
286
+ with torch.no_grad():
287
+ eager = wrapped(x).numpy()
288
+ exported = ep.module()(x).numpy()
289
+ port = run_pte(portable.buffer, (x,))[0]
290
+ res = {
291
+ "exported_vs_eager": compare(exported, eager),
292
+ "portable_pte_vs_eager": compare(port, eager),
293
+ }
294
+ wg, err = try_run_webgpu_pte_on_cpu(webgpu.buffer, (x,))
295
+ if wg is not None:
296
+ res["webgpu_pte_vs_eager"] = compare(wg[0], eager)
297
+ else:
298
+ webgpu_cpu_error = err
299
+ if label == "golden":
300
+ ref = golden[1]
301
+ res["internal_golden_vs_eager"] = compare(eager, ref)
302
+ res["internal_golden_vs_portable_pte"] = compare(port, ref)
303
+ res["golden_note"] = (
304
+ "reference output from the internal goldens.manifest.json"
305
+ )
306
+ report["cases"][label] = res
307
+ report["webgpu_pte_on_cpu"] = (
308
+ "ran"
309
+ if webgpu_cpu_error is None
310
+ else f"not runnable on CPU pybindings: {webgpu_cpu_error}"
311
+ )
312
+ (out_dir / "validation.json").write_text(json.dumps(report, indent=2) + "\n")
313
+ print(
314
+ json.dumps(
315
+ {"recipe": recipe["files"], "graph": summary, "validation": report},
316
+ indent=2,
317
+ )
318
+ )
319
+ return ep, webgpu, portable
320
+
321
+
322
+ def load_golden(manifest_path, key):
323
+ man = json.loads(Path(manifest_path).read_text())[key]["tensors"]
324
+ base = Path(manifest_path).parent
325
+
326
+ def ld(t):
327
+ return np.fromfile(base / t["file"], dtype=np.float32).reshape(t["shape"])
328
+
329
+ inp = man.get("input")
330
+ return ld(inp), ld(man["output"])
export/_llm.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared export path for the Qwen LLM recipes: pinned HF snapshot -> ExecuTorch checkpoint
2
+ -> export_llm (Vulkan partitioner, which WebGPUPartitioner wraps unchanged) -> .pte.
3
+
4
+ Graph contract (matches the internal WebGPU LLM artifacts): forward(tokens[1, <=prefill_max]
5
+ int64, input_pos[1] int64) -> logits[1, vocab] float32 for the last position, KV cache inside the
6
+ program (fp32 buffers, max_context_length positions), 4-bit groupwise weight-only linears
7
+ (et_vk.linear_q4gsw) and 4-bit groupwise embedding (et_vk.embedding_q4gsw).
8
+ """
9
+
10
+ import importlib
11
+ import json
12
+ import os
13
+ import shlex
14
+ import shutil
15
+ import subprocess
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ import _common
20
+
21
+ SHIMS = Path(__file__).resolve().parent / "_shims"
22
+
23
+ TOKENIZER_FILES = [
24
+ "tokenizer.json",
25
+ "tokenizer_config.json",
26
+ "vocab.json",
27
+ "merges.txt",
28
+ "generation_config.json",
29
+ "config.json",
30
+ "LICENSE",
31
+ ]
32
+
33
+
34
+ def export(
35
+ *, name, upstream_repo, revision, model_class, params, converter, max_context, args
36
+ ):
37
+ from huggingface_hub import snapshot_download
38
+
39
+ out = Path(args.out).resolve()
40
+ work = out / "_work"
41
+ work.mkdir(parents=True, exist_ok=True)
42
+ snap = Path(
43
+ snapshot_download(
44
+ upstream_repo,
45
+ revision=revision,
46
+ allow_patterns=["*.safetensors", "*.json", "merges.txt", "LICENSE"],
47
+ )
48
+ )
49
+
50
+ sys.path.insert(0, str(SHIMS))
51
+ env = {**os.environ, "PYTHONPATH": os.pathsep.join(filter(None, [str(SHIMS), os.environ.get("PYTHONPATH")]))}
52
+ ckpt = work / "checkpoint.pth"
53
+ if not ckpt.exists():
54
+ importlib.import_module(converter).convert_weights(str(snap), str(ckpt))
55
+ params_path = work / "params.json"
56
+ params_path.write_text(json.dumps(params, indent=2) + "\n")
57
+
58
+ output_name = f"{name}.pte"
59
+ overrides = [
60
+ f"base.model_class={model_class}",
61
+ f"base.checkpoint={ckpt}",
62
+ f"base.params={params_path}",
63
+ "model.use_kv_cache=true",
64
+ "model.use_sdpa_with_kv_cache=true",
65
+ "model.enable_dynamic_shape=true",
66
+ "model.dtype_override=fp32",
67
+ f"export.max_seq_length={args.prefill_max + 1}",
68
+ f"export.max_context_length={max_context}",
69
+ f"export.output_dir={out}",
70
+ f"export.output_name={out / output_name}",
71
+ "quantization.qmode=4w",
72
+ f"quantization.group_size={args.group_size}",
73
+ f"quantization.embedding_quantize='4,{args.group_size}'",
74
+ "backend.vulkan.enabled=true",
75
+ ]
76
+ cmd = [
77
+ sys.executable,
78
+ "-m",
79
+ "executorch.extension.llm.export.export_llm",
80
+ *overrides,
81
+ ]
82
+ print("+", " ".join(shlex.quote(c) for c in cmd), flush=True)
83
+ subprocess.run(cmd, check=True, env=env)
84
+
85
+ pte = out / output_name
86
+ tok_dir = out / "tokenizer"
87
+ tok_dir.mkdir(exist_ok=True)
88
+ tokenizer_files = {}
89
+ for f in TOKENIZER_FILES:
90
+ if (snap / f).exists():
91
+ shutil.copyfile(snap / f, tok_dir / f)
92
+ tokenizer_files[f] = _common.sha256_file(tok_dir / f)
93
+ shutil.rmtree(work)
94
+
95
+ data = pte.read_bytes()
96
+ recipe = {
97
+ "model": name,
98
+ "upstream": {
99
+ "model": upstream_repo,
100
+ "revision": revision,
101
+ "official": True,
102
+ "safetensors_sha256": {
103
+ p.name: _common.sha256_file(p)
104
+ for p in sorted(snap.glob("*.safetensors"))
105
+ },
106
+ },
107
+ "versions": _common.versions(),
108
+ "export": {
109
+ "api": "python -m executorch.extension.llm.export.export_llm (hydra overrides below)",
110
+ "overrides": [_common.scrub(o, out).replace(str(work.resolve()), "<work>") for o in overrides],
111
+ "checkpoint_conversion": f"{converter}.convert_weights(<snapshot>, checkpoint.pth)",
112
+ "import_shim": "recipes/_shims/torchtune (re-exports executorch.examples.models.checkpoint.get_mapped_key; torchtune itself is not installed)",
113
+ "params": params,
114
+ "partitioner": "VulkanPartitioner via backend.vulkan.enabled (WebGPUPartitioner is a pass-through wrapper of it; delegate id VulkanBackend)",
115
+ "inputs": [
116
+ {
117
+ "name": "tokens",
118
+ "shape": [1, f"1..{args.prefill_max}"],
119
+ "dtype": "int64",
120
+ },
121
+ {"name": "input_pos", "shape": [1], "dtype": "int64"},
122
+ ],
123
+ "outputs": [
124
+ {
125
+ "name": "logits",
126
+ "shape": [1, params["vocab_size"]],
127
+ "dtype": "float32",
128
+ "note": "last position only",
129
+ }
130
+ ],
131
+ "prefill_max": args.prefill_max,
132
+ "max_seq_len_metadata": args.prefill_max + 1,
133
+ "max_context_length": max_context,
134
+ "dtype": "float32 activations",
135
+ "quantization": f"4-bit groupwise weight-only linears (torchao IntxWeightOnlyConfig int4, group {args.group_size}) and 4-bit groupwise embedding (group {args.group_size}); output head quantized too",
136
+ "kv_cache": "fp32 buffers in the program; fp16 KV is a runtime option of the WebGPU runtime (et_set_kv_f16), not an export flag",
137
+ },
138
+ "files": {
139
+ pte.name: {"bytes": len(data), "sha256": _common.sha256_file(pte)},
140
+ **{f"tokenizer/{k}": {"sha256": v} for k, v in tokenizer_files.items()},
141
+ },
142
+ "graph": _common.delegate_summary(data),
143
+ "command": _common.command_line(out),
144
+ }
145
+ (out / "recipe.json").write_text(json.dumps(recipe, indent=2) + "\n")
146
+ print(json.dumps({"files": recipe["files"], "graph": recipe["graph"]}, indent=2))
147
+
148
+
149
+ def add_args(ap, max_context):
150
+ ap.add_argument("--out", required=True, help="output directory")
151
+ ap.add_argument(
152
+ "--prefill-max", type=int, default=512, help="max tokens per forward call"
153
+ )
154
+ ap.add_argument(
155
+ "--max-context", type=int, default=max_context, help="KV cache length"
156
+ )
157
+ ap.add_argument("--group-size", type=int, default=64)
export/_shims/torchtune/__init__.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ """Import shim for `torchtune.models.convert_weights.get_mapped_key`.
2
+
3
+ executorch 1.5.1's examples/models/qwen2_5 imports that single helper from torchtune, which
4
+ ExecuTorch pins to a GitHub commit (unreachable from the export host); the PyPI and nightly
5
+ torchtune builds do not import against torchao 0.18. ExecuTorch ships an equivalent helper in
6
+ executorch.examples.models.checkpoint (already used by its qwen3 converter); this shim
7
+ re-exports it. Only on PYTHONPATH while the Qwen2.5 recipe runs.
8
+ """
export/_shims/torchtune/models/__init__.py ADDED
File without changes
export/_shims/torchtune/models/convert_weights.py ADDED
@@ -0,0 +1 @@
 
 
1
+ from executorch.examples.models.checkpoint import get_mapped_key # noqa: F401
export/qwen2.5-0.5b-instruct.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Qwen2.5 0.5B Instruct -> ExecuTorch WebGPU (4-bit groupwise weights, KV cache in program)."""
3
+
4
+ import argparse
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
9
+ import _llm # noqa: E402
10
+
11
+ # Same values as executorch examples/models/qwen2_5/config/0_5b_config.json.
12
+ PARAMS = {
13
+ "dim": 896,
14
+ "ffn_dim_multiplier": 1,
15
+ "hidden_dim": 4864,
16
+ "n_heads": 14,
17
+ "n_kv_heads": 2,
18
+ "n_layers": 24,
19
+ "norm_eps": 1e-06,
20
+ "rope_theta": 1000000.0,
21
+ "use_scaled_rope": False,
22
+ "vocab_size": 151936,
23
+ "use_hf_rope": True,
24
+ "attention_qkv_bias": True,
25
+ }
26
+
27
+ SPEC = {
28
+ "name": "qwen2_5_0_5b_instruct_q4gsw",
29
+ "upstream_repo": "Qwen/Qwen2.5-0.5B-Instruct",
30
+ "revision": "7ae557604adf67be50417f59c2c2f167def9a775",
31
+ "model_class": "qwen2_5_0_5b",
32
+ "params": PARAMS,
33
+ "converter": "executorch.examples.models.qwen2_5.convert_weights",
34
+ }
35
+
36
+
37
+ def main():
38
+ ap = argparse.ArgumentParser(description=__doc__)
39
+ _llm.add_args(ap, max_context=8192)
40
+ args = ap.parse_args()
41
+ _llm.export(**SPEC, max_context=args.max_context, args=args)
42
+
43
+
44
+ if __name__ == "__main__":
45
+ main()
export/recipe.json ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "qwen2_5_0_5b_instruct_q4gsw",
3
+ "upstream": {
4
+ "model": "Qwen/Qwen2.5-0.5B-Instruct",
5
+ "revision": "7ae557604adf67be50417f59c2c2f167def9a775",
6
+ "official": true,
7
+ "safetensors_sha256": {
8
+ "model.safetensors": "fdf756fa7fcbe7404d5c60e26bff1a0c8b8aa1f72ced49e7dd0210fe288fb7fe"
9
+ }
10
+ },
11
+ "versions": {
12
+ "python": "3.12.15+meta",
13
+ "torch": "2.14.1+cpu",
14
+ "torchao": "0.18.0+cpu",
15
+ "executorch": "1.5.1+cpu",
16
+ "executorch_git": "3b60683923245cf472b7323426920e15623ba361",
17
+ "transformers": "5.19.0",
18
+ "huggingface_hub": "2.2.0"
19
+ },
20
+ "export": {
21
+ "api": "python -m executorch.extension.llm.export.export_llm (hydra overrides below)",
22
+ "overrides": [
23
+ "base.model_class=qwen2_5_0_5b",
24
+ "base.checkpoint=<out>/_work/checkpoint.pth",
25
+ "base.params=<out>/_work/params.json",
26
+ "model.use_kv_cache=true",
27
+ "model.use_sdpa_with_kv_cache=true",
28
+ "model.enable_dynamic_shape=true",
29
+ "model.dtype_override=fp32",
30
+ "export.max_seq_length=513",
31
+ "export.max_context_length=8192",
32
+ "export.output_dir=<out>",
33
+ "export.output_name=<out>/qwen2_5_0_5b_instruct_q4gsw.pte",
34
+ "quantization.qmode=4w",
35
+ "quantization.group_size=64",
36
+ "quantization.embedding_quantize='4,64'",
37
+ "backend.vulkan.enabled=true"
38
+ ],
39
+ "checkpoint_conversion": "executorch.examples.models.qwen2_5.convert_weights.convert_weights(<snapshot>, checkpoint.pth)",
40
+ "import_shim": "recipes/_shims/torchtune (re-exports executorch.examples.models.checkpoint.get_mapped_key; torchtune itself is not installed)",
41
+ "params": {
42
+ "dim": 896,
43
+ "ffn_dim_multiplier": 1,
44
+ "hidden_dim": 4864,
45
+ "n_heads": 14,
46
+ "n_kv_heads": 2,
47
+ "n_layers": 24,
48
+ "norm_eps": 1e-06,
49
+ "rope_theta": 1000000.0,
50
+ "use_scaled_rope": false,
51
+ "vocab_size": 151936,
52
+ "use_hf_rope": true,
53
+ "attention_qkv_bias": true
54
+ },
55
+ "partitioner": "VulkanPartitioner via backend.vulkan.enabled (WebGPUPartitioner is a pass-through wrapper of it; delegate id VulkanBackend)",
56
+ "inputs": [
57
+ {
58
+ "name": "tokens",
59
+ "shape": [
60
+ 1,
61
+ "1..512"
62
+ ],
63
+ "dtype": "int64"
64
+ },
65
+ {
66
+ "name": "input_pos",
67
+ "shape": [
68
+ 1
69
+ ],
70
+ "dtype": "int64"
71
+ }
72
+ ],
73
+ "outputs": [
74
+ {
75
+ "name": "logits",
76
+ "shape": [
77
+ 1,
78
+ 151936
79
+ ],
80
+ "dtype": "float32",
81
+ "note": "last position only"
82
+ }
83
+ ],
84
+ "prefill_max": 512,
85
+ "max_seq_len_metadata": 513,
86
+ "max_context_length": 8192,
87
+ "dtype": "float32 activations",
88
+ "quantization": "4-bit groupwise weight-only linears (torchao IntxWeightOnlyConfig int4, group 64) and 4-bit groupwise embedding (group 64); output head quantized too",
89
+ "kv_cache": "fp32 buffers in the program; fp16 KV is a runtime option of the WebGPU runtime (et_set_kv_f16), not an export flag"
90
+ },
91
+ "files": {
92
+ "qwen2_5_0_5b_instruct_q4gsw.pte": {
93
+ "bytes": 299447808,
94
+ "sha256": "474274b934978f6c11a842d575a07417d6687eb4d93f6b12d30d144dce729faa"
95
+ },
96
+ "tokenizer/tokenizer.json": {
97
+ "sha256": "c0382117ea329cdf097041132f6d735924b697924d6f6fc3945713e96ce87539"
98
+ },
99
+ "tokenizer/tokenizer_config.json": {
100
+ "sha256": "5b5d4f65d0acd3b2d56a35b56d374a36cbc1c8fa5cf3b3febbbfabf22f359583"
101
+ },
102
+ "tokenizer/vocab.json": {
103
+ "sha256": "ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910"
104
+ },
105
+ "tokenizer/merges.txt": {
106
+ "sha256": "599bab54075088774b1733fde865d5bd747cbcc7a547c5bc12610e874e26f5e3"
107
+ },
108
+ "tokenizer/generation_config.json": {
109
+ "sha256": "e558847a8b4402616f1273797b015104dc266fe4b520056fca88823ba8f8ebe6"
110
+ },
111
+ "tokenizer/config.json": {
112
+ "sha256": "18e18afcaccafade98daf13a54092927904649e1dd4eba8299ab717d5d94ff45"
113
+ },
114
+ "tokenizer/LICENSE": {
115
+ "sha256": "832dd9e00a68dd83b3c3fb9f5588dad7dcf337a0db50f7d9483f310cd292e92e"
116
+ }
117
+ },
118
+ "graph": {
119
+ "non_delegated_kernels": {},
120
+ "delegates": [
121
+ {
122
+ "id": "VulkanBackend",
123
+ "ops": {
124
+ "et_vk.prepack.default": 2,
125
+ "sym_size.int": 1,
126
+ "et_vk.embedding_q4gsw.default": 1,
127
+ "et_vk.select_as_symint.default": 2,
128
+ "et_vk.rms_norm.default": 49,
129
+ "et_vk.linear_q4gsw.default": 169,
130
+ "aten.view_copy.default": 192,
131
+ "et_vk.apply_rotary_emb_hf.default": 24,
132
+ "sdpa_with_kv_cache.default": 24,
133
+ "aten.add.Tensor": 48,
134
+ "aten.sigmoid.default": 24,
135
+ "aten.mul.Tensor": 48,
136
+ "aten.select_copy.int": 1
137
+ },
138
+ "inputs": 2,
139
+ "outputs": 1
140
+ }
141
+ ]
142
+ },
143
+ "command": "python recipes/qwen2.5-0.5b-instruct.py --out out/qwen2.5-0.5b-instruct"
144
+ }
export/requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment the re-exports in REEXPORT.md were produced with (Python 3.12, Linux x86_64, CPU only).
2
+ # python -m venv venv
3
+ # venv/bin/pip install -r recipes/requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu
4
+ # executorch 1.5.1 = pytorch/executorch git 3b60683923245cf472b7323426920e15623ba361; its wheel ships
5
+ # backends/webgpu (WebGPUPartitioner) and examples/models/llama (export_llm).
6
+ executorch==1.5.1
7
+ torch==2.14.1+cpu
8
+ torchvision==0.29.1+cpu
9
+ torchao==0.18.0+cpu
10
+ transformers==5.19.0
11
+ huggingface_hub==2.2.0
12
+ safetensors==0.8.0
13
+ tokenizers==0.23.3
14
+ numpy==2.5.4
15
+ pillow==12.3.0
manifest.json ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "qwen2.5-0.5b-instruct",
3
+ "repo": "executorch-community/Qwen2.5-0.5B-Instruct-ExecuTorch-WebGPU",
4
+ "files": [
5
+ {
6
+ "path": "qwen2_5_0_5b_instruct_q4gsw.pte",
7
+ "bytes": 299447808,
8
+ "sha256": "474274b934978f6c11a842d575a07417d6687eb4d93f6b12d30d144dce729faa"
9
+ },
10
+ {
11
+ "path": "tokenizer/config.json",
12
+ "bytes": 659,
13
+ "sha256": "18e18afcaccafade98daf13a54092927904649e1dd4eba8299ab717d5d94ff45"
14
+ },
15
+ {
16
+ "path": "tokenizer/generation_config.json",
17
+ "bytes": 242,
18
+ "sha256": "e558847a8b4402616f1273797b015104dc266fe4b520056fca88823ba8f8ebe6"
19
+ },
20
+ {
21
+ "path": "tokenizer/merges.txt",
22
+ "bytes": 1671839,
23
+ "sha256": "599bab54075088774b1733fde865d5bd747cbcc7a547c5bc12610e874e26f5e3"
24
+ },
25
+ {
26
+ "path": "tokenizer/tokenizer.json",
27
+ "bytes": 7031645,
28
+ "sha256": "c0382117ea329cdf097041132f6d735924b697924d6f6fc3945713e96ce87539"
29
+ },
30
+ {
31
+ "path": "tokenizer/tokenizer_config.json",
32
+ "bytes": 7305,
33
+ "sha256": "5b5d4f65d0acd3b2d56a35b56d374a36cbc1c8fa5cf3b3febbbfabf22f359583"
34
+ },
35
+ {
36
+ "path": "tokenizer/vocab.json",
37
+ "bytes": 2776833,
38
+ "sha256": "ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910"
39
+ }
40
+ ]
41
+ }
qwen2_5_0_5b_instruct_q4gsw.pte ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:474274b934978f6c11a842d575a07417d6687eb4d93f6b12d30d144dce729faa
3
+ size 299447808
tokenizer/config.json ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Qwen2ForCausalLM"
4
+ ],
5
+ "attention_dropout": 0.0,
6
+ "bos_token_id": 151643,
7
+ "eos_token_id": 151645,
8
+ "hidden_act": "silu",
9
+ "hidden_size": 896,
10
+ "initializer_range": 0.02,
11
+ "intermediate_size": 4864,
12
+ "max_position_embeddings": 32768,
13
+ "max_window_layers": 21,
14
+ "model_type": "qwen2",
15
+ "num_attention_heads": 14,
16
+ "num_hidden_layers": 24,
17
+ "num_key_value_heads": 2,
18
+ "rms_norm_eps": 1e-06,
19
+ "rope_theta": 1000000.0,
20
+ "sliding_window": 32768,
21
+ "tie_word_embeddings": true,
22
+ "torch_dtype": "bfloat16",
23
+ "transformers_version": "4.43.1",
24
+ "use_cache": true,
25
+ "use_sliding_window": false,
26
+ "vocab_size": 151936
27
+ }
tokenizer/generation_config.json ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 151643,
3
+ "pad_token_id": 151643,
4
+ "do_sample": true,
5
+ "eos_token_id": [
6
+ 151645,
7
+ 151643
8
+ ],
9
+ "repetition_penalty": 1.1,
10
+ "temperature": 0.7,
11
+ "top_p": 0.8,
12
+ "top_k": 20,
13
+ "transformers_version": "4.37.0"
14
+ }
tokenizer/merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer/tokenizer_config.json ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_bos_token": false,
3
+ "add_prefix_space": false,
4
+ "added_tokens_decoder": {
5
+ "151643": {
6
+ "content": "<|endoftext|>",
7
+ "lstrip": false,
8
+ "normalized": false,
9
+ "rstrip": false,
10
+ "single_word": false,
11
+ "special": true
12
+ },
13
+ "151644": {
14
+ "content": "<|im_start|>",
15
+ "lstrip": false,
16
+ "normalized": false,
17
+ "rstrip": false,
18
+ "single_word": false,
19
+ "special": true
20
+ },
21
+ "151645": {
22
+ "content": "<|im_end|>",
23
+ "lstrip": false,
24
+ "normalized": false,
25
+ "rstrip": false,
26
+ "single_word": false,
27
+ "special": true
28
+ },
29
+ "151646": {
30
+ "content": "<|object_ref_start|>",
31
+ "lstrip": false,
32
+ "normalized": false,
33
+ "rstrip": false,
34
+ "single_word": false,
35
+ "special": true
36
+ },
37
+ "151647": {
38
+ "content": "<|object_ref_end|>",
39
+ "lstrip": false,
40
+ "normalized": false,
41
+ "rstrip": false,
42
+ "single_word": false,
43
+ "special": true
44
+ },
45
+ "151648": {
46
+ "content": "<|box_start|>",
47
+ "lstrip": false,
48
+ "normalized": false,
49
+ "rstrip": false,
50
+ "single_word": false,
51
+ "special": true
52
+ },
53
+ "151649": {
54
+ "content": "<|box_end|>",
55
+ "lstrip": false,
56
+ "normalized": false,
57
+ "rstrip": false,
58
+ "single_word": false,
59
+ "special": true
60
+ },
61
+ "151650": {
62
+ "content": "<|quad_start|>",
63
+ "lstrip": false,
64
+ "normalized": false,
65
+ "rstrip": false,
66
+ "single_word": false,
67
+ "special": true
68
+ },
69
+ "151651": {
70
+ "content": "<|quad_end|>",
71
+ "lstrip": false,
72
+ "normalized": false,
73
+ "rstrip": false,
74
+ "single_word": false,
75
+ "special": true
76
+ },
77
+ "151652": {
78
+ "content": "<|vision_start|>",
79
+ "lstrip": false,
80
+ "normalized": false,
81
+ "rstrip": false,
82
+ "single_word": false,
83
+ "special": true
84
+ },
85
+ "151653": {
86
+ "content": "<|vision_end|>",
87
+ "lstrip": false,
88
+ "normalized": false,
89
+ "rstrip": false,
90
+ "single_word": false,
91
+ "special": true
92
+ },
93
+ "151654": {
94
+ "content": "<|vision_pad|>",
95
+ "lstrip": false,
96
+ "normalized": false,
97
+ "rstrip": false,
98
+ "single_word": false,
99
+ "special": true
100
+ },
101
+ "151655": {
102
+ "content": "<|image_pad|>",
103
+ "lstrip": false,
104
+ "normalized": false,
105
+ "rstrip": false,
106
+ "single_word": false,
107
+ "special": true
108
+ },
109
+ "151656": {
110
+ "content": "<|video_pad|>",
111
+ "lstrip": false,
112
+ "normalized": false,
113
+ "rstrip": false,
114
+ "single_word": false,
115
+ "special": true
116
+ },
117
+ "151657": {
118
+ "content": "<tool_call>",
119
+ "lstrip": false,
120
+ "normalized": false,
121
+ "rstrip": false,
122
+ "single_word": false,
123
+ "special": false
124
+ },
125
+ "151658": {
126
+ "content": "</tool_call>",
127
+ "lstrip": false,
128
+ "normalized": false,
129
+ "rstrip": false,
130
+ "single_word": false,
131
+ "special": false
132
+ },
133
+ "151659": {
134
+ "content": "<|fim_prefix|>",
135
+ "lstrip": false,
136
+ "normalized": false,
137
+ "rstrip": false,
138
+ "single_word": false,
139
+ "special": false
140
+ },
141
+ "151660": {
142
+ "content": "<|fim_middle|>",
143
+ "lstrip": false,
144
+ "normalized": false,
145
+ "rstrip": false,
146
+ "single_word": false,
147
+ "special": false
148
+ },
149
+ "151661": {
150
+ "content": "<|fim_suffix|>",
151
+ "lstrip": false,
152
+ "normalized": false,
153
+ "rstrip": false,
154
+ "single_word": false,
155
+ "special": false
156
+ },
157
+ "151662": {
158
+ "content": "<|fim_pad|>",
159
+ "lstrip": false,
160
+ "normalized": false,
161
+ "rstrip": false,
162
+ "single_word": false,
163
+ "special": false
164
+ },
165
+ "151663": {
166
+ "content": "<|repo_name|>",
167
+ "lstrip": false,
168
+ "normalized": false,
169
+ "rstrip": false,
170
+ "single_word": false,
171
+ "special": false
172
+ },
173
+ "151664": {
174
+ "content": "<|file_sep|>",
175
+ "lstrip": false,
176
+ "normalized": false,
177
+ "rstrip": false,
178
+ "single_word": false,
179
+ "special": false
180
+ }
181
+ },
182
+ "additional_special_tokens": [
183
+ "<|im_start|>",
184
+ "<|im_end|>",
185
+ "<|object_ref_start|>",
186
+ "<|object_ref_end|>",
187
+ "<|box_start|>",
188
+ "<|box_end|>",
189
+ "<|quad_start|>",
190
+ "<|quad_end|>",
191
+ "<|vision_start|>",
192
+ "<|vision_end|>",
193
+ "<|vision_pad|>",
194
+ "<|image_pad|>",
195
+ "<|video_pad|>"
196
+ ],
197
+ "bos_token": null,
198
+ "chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n",
199
+ "clean_up_tokenization_spaces": false,
200
+ "eos_token": "<|im_end|>",
201
+ "errors": "replace",
202
+ "model_max_length": 131072,
203
+ "pad_token": "<|endoftext|>",
204
+ "split_special_tokens": false,
205
+ "tokenizer_class": "Qwen2Tokenizer",
206
+ "unk_token": null
207
+ }
tokenizer/vocab.json ADDED
The diff for this file is too large to render. See raw diff